Permalink
Browse files

directory: use serve-index

  • Loading branch information...
dougwilson committed Mar 6, 2014
1 parent a1f0b01 commit 6d5dd30075d2bc4ee97afdbbe3d9d98d8d52d74b
Showing with 5 additions and 667 deletions.
  1. +1 −0 History.md
  2. +1 −1 lib/index.js
  3. +2 −325 lib/middleware/directory.js
  4. +0 −82 lib/public/directory.html
  5. BIN lib/public/favicon.ico
  6. BIN lib/public/icons/folder.png
  7. BIN lib/public/icons/page.png
  8. BIN lib/public/icons/page_add.png
  9. BIN lib/public/icons/page_attach.png
  10. BIN lib/public/icons/page_code.png
  11. BIN lib/public/icons/page_copy.png
  12. BIN lib/public/icons/page_delete.png
  13. BIN lib/public/icons/page_edit.png
  14. BIN lib/public/icons/page_error.png
  15. BIN lib/public/icons/page_excel.png
  16. BIN lib/public/icons/page_find.png
  17. BIN lib/public/icons/page_gear.png
  18. BIN lib/public/icons/page_go.png
  19. BIN lib/public/icons/page_green.png
  20. BIN lib/public/icons/page_key.png
  21. BIN lib/public/icons/page_lightning.png
  22. BIN lib/public/icons/page_link.png
  23. BIN lib/public/icons/page_paintbrush.png
  24. BIN lib/public/icons/page_paste.png
  25. BIN lib/public/icons/page_red.png
  26. BIN lib/public/icons/page_refresh.png
  27. BIN lib/public/icons/page_save.png
  28. BIN lib/public/icons/page_white.png
  29. BIN lib/public/icons/page_white_acrobat.png
  30. BIN lib/public/icons/page_white_actionscript.png
  31. BIN lib/public/icons/page_white_add.png
  32. BIN lib/public/icons/page_white_c.png
  33. BIN lib/public/icons/page_white_camera.png
  34. BIN lib/public/icons/page_white_cd.png
  35. BIN lib/public/icons/page_white_code.png
  36. BIN lib/public/icons/page_white_code_red.png
  37. BIN lib/public/icons/page_white_coldfusion.png
  38. BIN lib/public/icons/page_white_compressed.png
  39. BIN lib/public/icons/page_white_copy.png
  40. BIN lib/public/icons/page_white_cplusplus.png
  41. BIN lib/public/icons/page_white_csharp.png
  42. BIN lib/public/icons/page_white_cup.png
  43. BIN lib/public/icons/page_white_database.png
  44. BIN lib/public/icons/page_white_delete.png
  45. BIN lib/public/icons/page_white_dvd.png
  46. BIN lib/public/icons/page_white_edit.png
  47. BIN lib/public/icons/page_white_error.png
  48. BIN lib/public/icons/page_white_excel.png
  49. BIN lib/public/icons/page_white_find.png
  50. BIN lib/public/icons/page_white_flash.png
  51. BIN lib/public/icons/page_white_freehand.png
  52. BIN lib/public/icons/page_white_gear.png
  53. BIN lib/public/icons/page_white_get.png
  54. BIN lib/public/icons/page_white_go.png
  55. BIN lib/public/icons/page_white_h.png
  56. BIN lib/public/icons/page_white_horizontal.png
  57. BIN lib/public/icons/page_white_key.png
  58. BIN lib/public/icons/page_white_lightning.png
  59. BIN lib/public/icons/page_white_link.png
  60. BIN lib/public/icons/page_white_magnify.png
  61. BIN lib/public/icons/page_white_medal.png
  62. BIN lib/public/icons/page_white_office.png
  63. BIN lib/public/icons/page_white_paint.png
  64. BIN lib/public/icons/page_white_paintbrush.png
  65. BIN lib/public/icons/page_white_paste.png
  66. BIN lib/public/icons/page_white_php.png
  67. BIN lib/public/icons/page_white_picture.png
  68. BIN lib/public/icons/page_white_powerpoint.png
  69. BIN lib/public/icons/page_white_put.png
  70. BIN lib/public/icons/page_white_ruby.png
  71. BIN lib/public/icons/page_white_stack.png
  72. BIN lib/public/icons/page_white_star.png
  73. BIN lib/public/icons/page_white_swoosh.png
  74. BIN lib/public/icons/page_white_text.png
  75. BIN lib/public/icons/page_white_text_width.png
  76. BIN lib/public/icons/page_white_tux.png
  77. BIN lib/public/icons/page_white_vector.png
  78. BIN lib/public/icons/page_white_visualstudio.png
  79. BIN lib/public/icons/page_white_width.png
  80. BIN lib/public/icons/page_white_word.png
  81. BIN lib/public/icons/page_white_world.png
  82. BIN lib/public/icons/page_white_wrench.png
  83. BIN lib/public/icons/page_white_zip.png
  84. BIN lib/public/icons/page_word.png
  85. BIN lib/public/icons/page_world.png
  86. +0 −257 lib/public/style.css
  87. +1 −2 package.json
View
@@ -6,6 +6,7 @@ HEAD
* compress: use compression
* csrf: use csurf
* dep: cookie-signature@1.0.3
* directory: use serve-index
* errorHandler: use errorhandler
* favicon: use static-favicon
* logger: use morgan
View
@@ -23,6 +23,7 @@
* - [cookieParser](https://github.com/expressjs/cookie-parser) cookie parser
* - [compress](https://github.com/expressjs/compression) Gzip compression middleware
* - [csrf](https://github.com/expressjs/csurf) Cross-site request forgery protection
* - [directory](https://github.com/expressjs/serve-index) directory listing middleware
* - [errorHandler](https://github.com/expressjs/errorhandler) flexible error handler
* - [favicon](https://github.com/expressjs/favicon) efficient favicon server (with default icon)
* - [logger](https://github.com/expressjs/morgan) request logger with custom format support
@@ -38,7 +39,6 @@
* - [cookieSession](cookieSession.html) cookie-based session support
* - [staticCache](staticCache.html) memory cache layer for the static() middleware
* - [static](static.html) streaming static file server supporting `Range` and more
* - [directory](directory.html) directory listing middleware
* - [limit](limit.html) limit the bytesize of request bodies
* - [query](query.html) automatic querystring parser, populating `req.query`
*
View
@@ -6,338 +6,15 @@
* MIT Licensed
*/
// TODO: arrow key navigation
// TODO: make icons extensible
/**
* Module dependencies.
*/
var fs = require('fs')
, parse = require('url').parse
, utils = require('../utils')
, path = require('path')
, normalize = path.normalize
, sep = path.sep
, extname = path.extname
, join = path.join;
var Batch = require('batch');
var Negotiator = require('negotiator');
/*!
* Icon cache.
*/
var cache = {};
/*!
* Default template.
*/
var defaultTemplate = join(__dirname, '..', 'public', 'directory.html');
/**
* Media types and the map for content negotiation.
*/
var mediaTypes = [
'text/html',
'text/plain',
'application/json'
];
var mediaType = {
'text/html': 'html',
'text/plain': 'plain',
'application/json': 'json'
};
/**
* 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.
* - `template` Optional path to html template. Defaults to a built-in template.
* The following tokens are replaced:
* - `{directory}` with the name of the directory.
* - `{files}` with the HTML of an unordered list of file links.
* - `{linked-path}` with the HTML of a link to the directory.
* - `{style}` with the built-in CSS and embedded images.
* See [serve-index](https://github.com/expressjs/serve-index)
*
* @param {String} root
* @param {Object} options
* @return {Function}
* @api public
*/
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
, view = options.view || 'tiles'
, filter = options.filter
, root = normalize(root + sep)
, template = options.template || defaultTemplate;
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, view, template);
});
});
};
};
/**
* Respond with text/html.
*/
exports.html = function(req, res, files, next, dir, showUp, icons, path, view, template){
fs.readFile(template, '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.concat(iconStyle(files, icons)))
.replace('{files}', html(files, dir, icons, view))
.replace('{directory}', dir)
.replace('{linked-path}', htmlPath(dir));
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', str.length);
res.end(str);
});
});
});
};
/**
* Respond with application/json.
*/
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);
};
/**
* Respond with text/plain.
*/
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);
};
/**
* Sort function for with directories first.
*/
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());
}
/**
* Map html `dir`, returning a linked path.
*/
function htmlPath(dir) {
var curr = [];
return dir.split('/').map(function(part){
curr.push(encodeURIComponent(part));
return part ? '<a href="' + curr.join('/') + '">' + part + '</a>' : '';
}).join(' / ');
}
/**
* Load icon images, return css string.
*/
function iconStyle (files, useIcons) {
if (!useIcons) return '';
var data = {};
var views = { tiles: [], details: [], mobile: [] };
for (var i=0; i < files.length; i++) {
var file = files[i];
if (file.name == '..') continue;
var isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
var icon = isDir ? icons.folder : icons[extname(file.name)] || icons.default;
var ext = extname(file.name);
ext = isDir ? '.directory' : (icons[ext] ? ext : '.default');
if (data[icon]) continue;
data[icon] = ext + ' .name{background-image: url(data:image/png;base64,' + load(icon)+');}';
views.tiles.push('.view-tiles ' + data[icon]);
views.details.push('.view-details ' + data[icon]);
views.mobile.push('#files ' + data[icon]);
}
var style = views.tiles.join('\n')
+ '\n'+views.details.join('\n')
+ '\n@media (max-width: 768px) {\n\t'
+ views.mobile.join('\n\t')
+ '\n}';
return style;
}
/**
* Map html `files`, returning an html unordered list.
*/
function html(files, dir, useIcons, view) {
return '<ul id="files" class="view-'+view+'">'
+ (view == 'details' ? (
'<li class="header">'
+ '<span class="name">Name</span>'
+ '<span class="size">Size</span>'
+ '<span class="date">Modified</span>'
+ '</li>') : '')
+ files.map(function(file){
var isDir
, classes = []
, path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
if (useIcons) {
var ext = extname(file.name);
isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
ext = isDir ? '.directory' : (icons[ext] ? ext : '.default');
classes.push('icon');
classes.push(ext.replace('.',''));
}
path.push(encodeURIComponent(file.name));
var date = file.name == '..' ? ''
: file.stat.mtime.toDateString()+' '+file.stat.mtime.toLocaleTimeString();
var size = file.name == '..' ? '' : file.stat.size;
return '<li><a href="'
+ utils.normalizeSlashes(normalize(path.join('/')))
+ '" class="'
+ classes.join(' ') + '"'
+ ' title="' + file.name + '">'
+ '<span class="name">'+file.name+'</span>'
+ '<span class="size">'+size+'</span>'
+ '<span class="date">'+date+'</span>'
+ '</a></li>';
}).join('\n') + '</ul>';
}
/**
* Load and cache the given `icon`.
*
* @param {String} icon
* @return {String}
* @api private
*/
function load(icon) {
if (cache[icon]) return cache[icon];
return cache[icon] = fs.readFileSync(__dirname + '/../public/icons/' + icon, 'base64');
}
/**
* Filter "hidden" `files`, aka files
* beginning with a `.`.
*
* @param {Array} files
* @return {Array}
* @api private
*/
function removeHidden(files) {
return files.filter(function(file){
return '.' != file[0];
});
}
/**
* Stat all files and return array of stat
* in same order.
*/
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);
}
/**
* Icon map.
*/
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'
};
module.exports = require('serve-index');
Oops, something went wrong.

0 comments on commit 6d5dd30

Please sign in to comment.