Socket
Socket
Sign inDemoInstall

serve-index

Package Overview
Dependencies
12
Maintainers
6
Versions
33
Alerts
File Explorer

Advanced tools

Install Socket

Detect and block malicious and high-risk dependencies

Install

Comparing version 1.6.4 to 1.7.0

20

HISTORY.md

@@ -0,1 +1,21 @@

1.7.0 / 2015-06-15
==================
* Accept `function` value for `template` option
* Send non-chunked response for `OPTIONS`
* Stat parent directory when necessary
* Use `Date.prototype.toLocaleDateString` to format date
* deps: accepts@~1.2.9
- deps: mime-types@~2.1.1
- deps: negotiator@0.5.3
- perf: avoid argument reassignment & argument slice
- perf: avoid negotiator recursive construction
- perf: enable strict mode
- perf: remove unnecessary bitwise operator
* deps: escape-html@1.0.2
* deps: mime-types@~2.1.1
- Add new mime types
* perf: enable strict mode
* perf: remove argument reassignment
1.6.4 / 2015-05-12

@@ -2,0 +22,0 @@ ==================

253

index.js

@@ -9,4 +9,3 @@ /*!

// TODO: arrow key navigation
// TODO: make icons extensible
'use strict';

@@ -33,2 +32,9 @@ /**

/**
* Module exports.
* @public
*/
module.exports = serveIndex;
/*!

@@ -73,31 +79,31 @@ * Icon cache.

*
* @param {String} path
* @param {String} root
* @param {Object} options
* @return {Function} middleware
* @api public
* @public
*/
exports = module.exports = function serveIndex(root, options){
options = options || {};
function serveIndex(root, options) {
var opts = options || {};
// root required
if (!root) throw new TypeError('serveIndex() root path required');
if (!root) {
throw new TypeError('serveIndex() root path required');
}
// resolve root to absolute and normalize
root = resolve(root);
root = normalize(root + sep);
var rootPath = normalize(resolve(root) + sep);
var hidden = options.hidden
, icons = options.icons
, view = options.view || 'tiles'
, filter = options.filter
, template = options.template || defaultTemplate
, stylesheet = options.stylesheet || defaultStylesheet;
var filter = opts.filter;
var hidden = opts.hidden;
var icons = opts.icons;
var stylesheet = opts.stylesheet || defaultStylesheet;
var template = opts.template || defaultTemplate;
var view = opts.view || 'tiles';
return function serveIndex(req, res, next) {
return function (req, res, next) {
if (req.method !== 'GET' && req.method !== 'HEAD') {
res.statusCode = 'OPTIONS' === req.method
? 200
: 405;
res.statusCode = 'OPTIONS' === req.method ? 200 : 405;
res.setHeader('Allow', 'GET, HEAD, OPTIONS');
res.setHeader('Content-Length', '0');
res.end();

@@ -114,3 +120,3 @@ return;

// join / normalize from root dir
var path = normalize(join(root, dir));
var path = normalize(join(rootPath, dir));

@@ -121,3 +127,3 @@ // null byte(s), bad request

// malicious path
if ((path + sep).substr(0, root.length) !== root) {
if ((path + sep).substr(0, rootPath.length) !== rootPath) {
debug('malicious path "%s"', path);

@@ -128,3 +134,3 @@ return next(createError(403));

// determine ".." display
var showUp = normalize(resolve(path) + sep) !== root;
var showUp = normalize(resolve(path) + sep) !== rootPath;

@@ -163,3 +169,3 @@ // check if we have a directory

if (!type) return next(createError(406));
exports[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
serveIndex[mediaType[type]](req, res, files, next, originalDir, showUp, icons, path, view, template, stylesheet);
});

@@ -174,19 +180,42 @@ });

exports.html = function(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet){
fs.readFile(template, 'utf8', function(err, str){
serveIndex.html = function _html(req, res, files, next, dir, showUp, icons, path, view, template, stylesheet) {
var render = typeof template !== 'function'
? createHtmlRender(template)
: template
if (showUp) {
files.unshift('..');
}
// stat all files
stat(path, files, function (err, stats) {
if (err) return next(err);
fs.readFile(stylesheet, 'utf8', function(err, style){
// combine the stats into the file list
var fileList = files.map(function (file, i) {
return { name: file, stat: stats[i] };
});
// sort file list
fileList.sort(fileSort);
// read stylesheet
fs.readFile(stylesheet, 'utf8', function (err, style) {
if (err) return next(err);
stat(path, files, function(err, stats){
// create locals for rendering
var locals = {
directory: dir,
displayIcons: Boolean(icons),
fileList: fileList,
path: path,
style: style,
viewName: view
};
// render html
render(locals, function (err, body) {
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\}/g, style.concat(iconStyle(files, icons)))
.replace(/\{files\}/g, html(files, dir, icons, view))
.replace(/\{directory\}/g, escapeHtml(dir))
.replace(/\{linked-path\}/g, htmlPath(dir));
var buf = new Buffer(str, 'utf8');
var buf = new Buffer(body, 'utf8');
res.setHeader('Content-Type', 'text/html; charset=utf-8');

@@ -204,3 +233,3 @@ res.setHeader('Content-Length', buf.length);

exports.json = function(req, res, files){
serveIndex.json = function _json(req, res, files) {
var body = JSON.stringify(files);

@@ -218,3 +247,3 @@ var buf = new Buffer(body, 'utf8');

exports.plain = function(req, res, files){
serveIndex.plain = function _plain(req, res, files) {
var body = files.join('\n') + '\n';

@@ -229,2 +258,84 @@ var buf = new Buffer(body, 'utf8');

/**
* Map html `files`, returning an html unordered list.
* @private
*/
function createHtmlFileList(files, dir, useIcons, view) {
var html = '<ul id="files" class="view-' + escapeHtml(view) + '">'
+ (view == 'details' ? (
'<li class="header">'
+ '<span class="name">Name</span>'
+ '<span class="size">Size</span>'
+ '<span class="date">Modified</span>'
+ '</li>') : '');
html += files.map(function (file) {
var classes = [];
var isDir = file.stat && file.stat.isDirectory();
var path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
if (useIcons) {
classes.push('icon');
if (isDir) {
classes.push('icon-directory');
} else {
var ext = extname(file.name);
var icon = iconLookup(file.name);
classes.push('icon');
classes.push('icon-' + ext.substring(1));
if (classes.indexOf(icon.className) === -1) {
classes.push(icon.className);
}
}
}
path.push(encodeURIComponent(file.name));
var date = file.stat && file.name !== '..'
? file.stat.mtime.toLocaleDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
: '';
var size = file.stat && !isDir
? file.stat.size
: '';
return '<li><a href="'
+ escapeHtml(normalizeSlashes(normalize(path.join('/'))))
+ '" class="' + escapeHtml(classes.join(' ')) + '"'
+ ' title="' + escapeHtml(file.name) + '">'
+ '<span class="name">' + escapeHtml(file.name) + '</span>'
+ '<span class="size">' + escapeHtml(size) + '</span>'
+ '<span class="date">' + escapeHtml(date) + '</span>'
+ '</a></li>';
}).join('\n');
html += '</ul>';
return html;
}
/**
* Create function to render html.
*/
function createHtmlRender(template) {
return function render(locals, callback) {
// read template
fs.readFile(template, 'utf8', function (err, str) {
if (err) return callback(err);
var body = str
.replace(/\{style\}/g, locals.style.concat(iconStyle(locals.fileList, locals.displayIcons)))
.replace(/\{files\}/g, createHtmlFileList(locals.fileList, locals.directory, locals.displayIcons, locals.viewName))
.replace(/\{directory\}/g, escapeHtml(locals.directory))
.replace(/\{linked-path\}/g, htmlPath(locals.directory));
callback(null, body);
});
};
}
/**
* Sort function for with directories first.

@@ -234,2 +345,8 @@ */

function fileSort(a, b) {
// sort ".." to the top
if (a.name === '..' || b.name === '..') {
return a.name === b.name ? 0
: a.name === '..' ? -1 : 1;
}
return Number(b.stat && b.stat.isDirectory()) - Number(a.stat && a.stat.isDirectory()) ||

@@ -321,3 +438,3 @@ String(a.name).toLocaleLowerCase().localeCompare(String(b.name).toLocaleLowerCase());

function iconStyle (files, useIcons) {
function iconStyle(files, useIcons) {
if (!useIcons) return '';

@@ -336,3 +453,3 @@ var className;

var isDir = '..' == file.name || (file.stat && file.stat.isDirectory());
var isDir = file.stat && file.stat.isDirectory();
var icon = isDir

@@ -365,58 +482,2 @@ ? { className: 'icon-directory', fileName: icons.folder }

/**
* Map html `files`, returning an html unordered list.
*/
function html(files, dir, useIcons, view) {
return '<ul id="files" class="view-' + escapeHtml(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 = '..' == file.name || (file.stat && file.stat.isDirectory())
, classes = []
, path = dir.split('/').map(function (c) { return encodeURIComponent(c); });
if (useIcons) {
classes.push('icon');
if (isDir) {
classes.push('icon-directory');
} else {
var ext = extname(file.name);
var icon = iconLookup(file.name);
classes.push('icon');
classes.push('icon-' + ext.substring(1));
if (classes.indexOf(icon.className) === -1) {
classes.push(icon.className);
}
}
}
path.push(encodeURIComponent(file.name));
var date = file.stat && file.name !== '..'
? file.stat.mtime.toDateString() + ' ' + file.stat.mtime.toLocaleTimeString()
: '';
var size = file.stat && !isDir
? file.stat.size
: '';
return '<li><a href="'
+ escapeHtml(normalizeSlashes(normalize(path.join('/'))))
+ '" class="' + escapeHtml(classes.join(' ')) + '"'
+ ' title="' + escapeHtml(file.name) + '">'
+ '<span class="name">' + escapeHtml(file.name) + '</span>'
+ '<span class="size">' + escapeHtml(size) + '</span>'
+ '<span class="date">' + escapeHtml(date) + '</span>'
+ '</a></li>';
}).join('\n') + '</ul>';
}
/**
* Load and cache the given `icon`.

@@ -423,0 +484,0 @@ *

{
"name": "serve-index",
"description": "Serve directory listings",
"version": "1.6.4",
"version": "1.7.0",
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",

@@ -9,8 +9,8 @@ "license": "MIT",

"dependencies": {
"accepts": "~1.2.7",
"accepts": "~1.2.9",
"batch": "0.5.2",
"debug": "~2.2.0",
"escape-html": "1.0.1",
"escape-html": "1.0.2",
"http-errors": "~1.3.1",
"mime-types": "~2.0.11",
"mime-types": "~2.1.1",
"parseurl": "~1.3.0"

@@ -21,4 +21,4 @@ },

"istanbul": "0.3.9",
"mocha": "~2.2.4",
"supertest": "~0.15.0"
"mocha": "2.2.5",
"supertest": "1.0.1"
},

@@ -25,0 +25,0 @@ "files": [

@@ -59,5 +59,7 @@ # serve-index

Optional path to an HTML template. Defaults to a built-in template.
Optional path to an HTML template or a function that will render a HTML
string. Defaults to a built-in template.
The following tokens are replaced in templates:
When given a string, the string is used as a file path to load and then the
following tokens are replaced in templates:

@@ -69,2 +71,16 @@ * `{directory}` with the name of the directory.

When given as a function, the function is called as `template(locals, callback)`
and it needs to invoke `callback(error, htmlString)`. The following are the
provided locals:
* `directory` is the directory being displayed (where `/` is the root).
* `displayIcons` is a Boolean for if icons should be rendered or not.
* `fileList` is a sorted array of files in the directory. The array contains
objects with the following properties:
- `name` is the relative name for the file.
- `stat` is a `fs.Stats` object for the file.
* `path` is the full filesystem path to `directory`.
* `style` is the default stylesheet or the contents of the `stylesheet` option.
* `viewName` is the view name provided by the `view` option.
##### view

@@ -71,0 +87,0 @@

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc