Connect
Directory:
Serve directory listings with the given root
path.
Options:
hidden
display hidden (dot) files. Defaults to false.icons
display icons. Defaults to false.filter
Apply this filter function to files. Defaults to false.
Source
exports = module.exports = function directory(root, options){
options = options || {};
// root required
if (!root) throw new Error('directory() root path required');
var hidden = options.hidden
, icons = options.icons
, filter = options.filter
, root = normalize(root + sep);
return function directory(req, res, next) {
if ('GET' != req.method && 'HEAD' != req.method) return next();
var url = parse(req.url)
, dir = decodeURIComponent(url.pathname)
, path = normalize(join(root, dir))
, originalUrl = parse(req.originalUrl)
, originalDir = decodeURIComponent(originalUrl.pathname)
, showUp = path != root;
// null byte(s), bad request
if (~path.indexOf('\0')) return next(utils.error(400));
// malicious path, forbidden
if (0 != path.indexOf(root)) return next(utils.error(403));
// check if we have a directory
fs.stat(path, function(err, stat){
if (err) return 'ENOENT' == err.code
? next()
: next(err);
if (!stat.isDirectory()) return next();
// fetch files
fs.readdir(path, function(err, files){
if (err) return next(err);
if (!hidden) files = removeHidden(files);
if (filter) files = files.filter(filter);
files.sort();
// content-negotiation
var type = new Negotiator(req).preferredMediaType(mediaTypes);
// not acceptable
if (!type) return next(utils.error(406));
exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path);
});
});
};
};
exports.html()
Respond with text/html.
Source
exports.html = function(req, res, files, next, dir, showUp, icons, path){
fs.readFile(__dirname + '/../public/directory.html', 'utf8', function(err, str){
if (err) return next(err);
fs.readFile(__dirname + '/../public/style.css', 'utf8', function(err, style){
if (err) return next(err);
stat(path, files, function(err, stats){
if (err) return next(err);
files = files.map(function(file, i){ return { name: file, stat: stats[i] }; });
files.sort(fileSort);
if (showUp) files.unshift({ name: '..' });
str = str
.replace('{style}', style)
.replace('{files}', html(files, dir, icons))
.replace('{directory}', dir)
.replace('{linked-path}', htmlPath(dir));
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', str.length);
res.end(str);
});
});
});
};
exports.json()
Respond with application/json.
Source
exports.json = function(req, res, files){
files = JSON.stringify(files);
res.setHeader('Content-Type', 'application/json');
res.setHeader('Content-Length', files.length);
res.end(files);
};
exports.plain()
Respond with text/plain.
Source
exports.plain = function(req, res, files){
files = files.join('\n') + '\n';
res.setHeader('Content-Type', 'text/plain');
res.setHeader('Content-Length', files.length);
res.end(files);
};
fileSort()
Sort function for with directories first.
Source
function fileSort(a, b) {
return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||
String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());
}
htmlPath()
Map html dir
, returning a linked path.
Source
function htmlPath(dir) {
var curr = [];
return dir.split('/').map(function(part){
curr.push(encodeURIComponent(part));
return part ? '' + part + '' : '';
}).join(' / ');
}
html()
Map html files
, returning an html unordered list.
Source
function html(files, dir, useIcons) {
return '' + files.map(function(file){
var icon = ''
, isDir
, classes = []
, path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
if (useIcons) {
isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
icon = isDir ? icons.folder : icons[extname(file.name)] || icons.default;
icon = '';
classes.push('icon');
}
path.push(encodeURIComponent(file.name));
return '- '
+ icon + file.name + '
';
}).join('\n') + '
';
}
load()
Load and cache the given icon
.
Source
function load(icon) {
if (cache[icon]) return cache[icon];
return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64');
}
removeHidden()
Filter "hidden" files
, aka files
beginning with a .
.
Source
function removeHidden(files) {
return files.filter(function(file){
return '.' != file[0];
});
}
stat()
Stat all files and return array of stat
in same order.
Source
function stat(dir, files, cb) {
var batch = new Batch();
batch.concurrency(10);
files.forEach(function(file, i){
batch.push(function(done){
fs.stat(join(dir, file), done);
});
});
batch.end(cb);
}
icons
Icon map.
Source
var icons = {
'.js': 'page_white_code_red.png'
, '.c': 'page_white_c.png'
, '.h': 'page_white_h.png'
, '.cc': 'page_white_cplusplus.png'
, '.php': 'page_white_php.png'
, '.rb': 'page_white_ruby.png'
, '.cpp': 'page_white_cplusplus.png'
, '.swf': 'page_white_flash.png'
, '.pdf': 'page_white_acrobat.png'
, 'folder': 'folder.png'
, 'default': 'page_white.png'
};
mediaTypes
Media types and the map for content negotiation.
Source