Compare commits
22 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9a77df8c77 | ||
|
|
856450da8f | ||
|
|
1bda9f95e4 | ||
|
|
f0652de409 | ||
|
|
f0579f7093 | ||
|
|
970b567f48 | ||
|
|
47b3884bbe | ||
|
|
5b2ff74554 | ||
|
|
7dc0ff4d97 | ||
|
|
1bdd53c05d | ||
|
|
dce12186f8 | ||
|
|
838ee4cc02 | ||
|
|
f332e6e576 | ||
|
|
b866f9690a | ||
|
|
ac94303941 | ||
|
|
cececc8c2e | ||
|
|
5e050a7d23 | ||
|
|
5731ebee6b | ||
|
|
c6b9d3bdbf | ||
|
|
4686c18e1d | ||
|
|
8a06bb7e19 | ||
|
|
effbe1a4b0 |
39
HISTORY.md
39
HISTORY.md
@@ -1,3 +1,42 @@
|
||||
1.5.1 / 2014-11-22
|
||||
==================
|
||||
|
||||
* deps: accepts@~1.1.3
|
||||
- deps: mime-types@~2.0.3
|
||||
* deps: mime-types@~2.0.3
|
||||
- Add new mime types
|
||||
- deps: mime-db@~1.2.0
|
||||
|
||||
1.5.0 / 2014-10-16
|
||||
==================
|
||||
|
||||
* Create errors with `http-errors`
|
||||
* deps: debug@~2.1.0
|
||||
- Implement `DEBUG_FD` env variable support
|
||||
* deps: mime-types@~2.0.2
|
||||
- deps: mime-db@~1.1.0
|
||||
|
||||
1.4.1 / 2014-10-15
|
||||
==================
|
||||
|
||||
* deps: accepts@~1.1.2
|
||||
- Fix error when media type has invalid parameter
|
||||
- deps: negotiator@0.4.9
|
||||
|
||||
1.4.0 / 2014-10-03
|
||||
==================
|
||||
|
||||
* Add `dir` argument to `filter` function
|
||||
* Support using tokens multiple times
|
||||
|
||||
1.3.1 / 2014-10-01
|
||||
==================
|
||||
|
||||
* Fix incorrect 403 on Windows and Node.js 0.11
|
||||
* deps: accepts@~1.1.1
|
||||
- deps: mime-types@~2.0.2
|
||||
- deps: negotiator@0.4.8
|
||||
|
||||
1.3.0 / 2014-09-20
|
||||
==================
|
||||
|
||||
|
||||
14
README.md
14
README.md
@@ -4,7 +4,7 @@
|
||||
[![NPM Downloads][downloads-image]][downloads-url]
|
||||
[![Build Status][travis-image]][travis-url]
|
||||
[![Test Coverage][coveralls-image]][coveralls-url]
|
||||
[![Gittip][gittip-image]][gittip-url]
|
||||
[![Gratipay][gratipay-image]][gratipay-url]
|
||||
|
||||
Serves pages that contain directory listings for a given path.
|
||||
|
||||
@@ -35,7 +35,11 @@ Serve index accepts these properties in the options object.
|
||||
|
||||
##### filter
|
||||
|
||||
Apply this filter function to files. Defaults to `false`.
|
||||
Apply this filter function to files. Defaults to `false`. The `filter` function
|
||||
is called for each file, with the signature `filter(filename, index, files, dir)`
|
||||
where `filename` is the name of the file, `index` is the array index, `files` is
|
||||
the array of files and `dir` is the absolute path the file is located (and thus,
|
||||
the directory the listing is for).
|
||||
|
||||
##### hidden
|
||||
|
||||
@@ -117,7 +121,7 @@ are created by/copyright of [FAMFAMFAM](http://www.famfamfam.com/).
|
||||
[travis-url]: https://travis-ci.org/expressjs/serve-index
|
||||
[coveralls-image]: https://img.shields.io/coveralls/expressjs/serve-index.svg?style=flat
|
||||
[coveralls-url]: https://coveralls.io/r/expressjs/serve-index?branch=master
|
||||
[downloads-image]: http://img.shields.io/npm/dm/serve-index.svg?style=flat
|
||||
[downloads-image]: https://img.shields.io/npm/dm/serve-index.svg?style=flat
|
||||
[downloads-url]: https://npmjs.org/package/serve-index
|
||||
[gittip-image]: https://img.shields.io/gittip/dougwilson.svg?style=flat
|
||||
[gittip-url]: https://www.gittip.com/dougwilson/
|
||||
[gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg?style=flat
|
||||
[gratipay-url]: https://www.gratipay.com/dougwilson/
|
||||
|
||||
49
index.js
49
index.js
@@ -1,4 +1,3 @@
|
||||
|
||||
/*!
|
||||
* serve-index
|
||||
* Copyright(c) 2011 Sencha Inc.
|
||||
@@ -15,9 +14,9 @@
|
||||
*/
|
||||
|
||||
var accepts = require('accepts');
|
||||
var createError = require('http-errors');
|
||||
var debug = require('debug')('serve-index');
|
||||
var http = require('http')
|
||||
, fs = require('fs')
|
||||
var fs = require('fs')
|
||||
, path = require('path')
|
||||
, normalize = path.normalize
|
||||
, sep = path.sep
|
||||
@@ -79,8 +78,9 @@ exports = module.exports = function serveIndex(root, options){
|
||||
// root required
|
||||
if (!root) throw new TypeError('serveIndex() root path required');
|
||||
|
||||
// resolve root to absolute
|
||||
// resolve root to absolute and normalize
|
||||
root = resolve(root);
|
||||
root = normalize(root + sep);
|
||||
|
||||
var hidden = options.hidden
|
||||
, icons = options.icons
|
||||
@@ -102,21 +102,24 @@ exports = module.exports = function serveIndex(root, options){
|
||||
// parse URLs
|
||||
var url = parseUrl(req);
|
||||
var originalUrl = parseUrl.original(req);
|
||||
var dir = decodeURIComponent(url.pathname);
|
||||
var originalDir = decodeURIComponent(originalUrl.pathname);
|
||||
|
||||
var dir = decodeURIComponent(url.pathname)
|
||||
, path = normalize(join(root, dir))
|
||||
, originalDir = decodeURIComponent(originalUrl.pathname)
|
||||
var showUp = resolve(path) !== root;
|
||||
// join / normalize from root dir
|
||||
var path = normalize(join(root, dir));
|
||||
|
||||
// null byte(s), bad request
|
||||
if (~path.indexOf('\0')) return next(createError(400));
|
||||
|
||||
// malicious path
|
||||
if (path.substr(0, root.length) !== root) {
|
||||
if ((path + sep).substr(0, root.length) !== root) {
|
||||
debug('malicious path "%s"', path);
|
||||
return next(createError(403));
|
||||
}
|
||||
|
||||
// determine ".." display
|
||||
var showUp = normalize(resolve(path) + sep) !== root;
|
||||
|
||||
// check if we have a directory
|
||||
debug('stat "%s"', path);
|
||||
fs.stat(path, function(err, stat){
|
||||
@@ -138,7 +141,9 @@ exports = module.exports = function serveIndex(root, options){
|
||||
fs.readdir(path, function(err, files){
|
||||
if (err) return next(err);
|
||||
if (!hidden) files = removeHidden(files);
|
||||
if (filter) files = files.filter(filter);
|
||||
if (filter) files = files.filter(function(filename, index, list) {
|
||||
return filter(filename, index, list, path);
|
||||
});
|
||||
files.sort();
|
||||
|
||||
// content-negotiation
|
||||
@@ -168,10 +173,10 @@ exports.html = function(req, res, files, next, dir, showUp, icons, path, view, t
|
||||
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));
|
||||
.replace(/\{style\}/g, style.concat(iconStyle(files, icons)))
|
||||
.replace(/\{files\}/g, html(files, dir, icons, view))
|
||||
.replace(/\{directory\}/g, dir)
|
||||
.replace(/\{linked-path\}/g, htmlPath(dir));
|
||||
|
||||
var buf = new Buffer(str, 'utf8');
|
||||
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
||||
@@ -208,22 +213,6 @@ exports.plain = function(req, res, files){
|
||||
res.end(buf);
|
||||
};
|
||||
|
||||
/**
|
||||
* Generate an `Error` from the given status `code`
|
||||
* and optional `msg`.
|
||||
*
|
||||
* @param {Number} code
|
||||
* @param {String} msg
|
||||
* @return {Error}
|
||||
* @api private
|
||||
*/
|
||||
|
||||
function createError(code, msg) {
|
||||
var err = new Error(msg || http.STATUS_CODES[code]);
|
||||
err.status = code;
|
||||
return err;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sort function for with directories first.
|
||||
*/
|
||||
|
||||
15
package.json
15
package.json
@@ -1,22 +1,23 @@
|
||||
{
|
||||
"name": "serve-index",
|
||||
"description": "Serve directory listings",
|
||||
"version": "1.3.0",
|
||||
"version": "1.5.1",
|
||||
"author": "Douglas Christopher Wilson <doug@somethingdoug.com>",
|
||||
"license": "MIT",
|
||||
"repository": "expressjs/serve-index",
|
||||
"dependencies": {
|
||||
"accepts": "~1.1.0",
|
||||
"accepts": "~1.1.3",
|
||||
"batch": "0.5.1",
|
||||
"debug": "~2.0.0",
|
||||
"mime-types": "~2.0.1",
|
||||
"debug": "~2.1.0",
|
||||
"http-errors": "~1.2.7",
|
||||
"mime-types": "~2.0.3",
|
||||
"parseurl": "~1.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"after": "0.8.1",
|
||||
"istanbul": "0.3.2",
|
||||
"mocha": "~1.21.1",
|
||||
"should": "~4.0.0",
|
||||
"supertest": "~0.13.0"
|
||||
"mocha": "~2.0.1",
|
||||
"supertest": "~0.15.0"
|
||||
},
|
||||
"files": [
|
||||
"public/",
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
</head>
|
||||
<body class="directory">
|
||||
<h1>This is the test template</h1>
|
||||
<h2>directory {directory}</h2>
|
||||
<div id="wrapper">
|
||||
<h1>{linked-path}</h1>
|
||||
{files}
|
||||
|
||||
106
test/test.js
106
test/test.js
@@ -1,9 +1,10 @@
|
||||
|
||||
var after = require('after');
|
||||
var assert = require('assert');
|
||||
var http = require('http');
|
||||
var fs = require('fs');
|
||||
var path = require('path');
|
||||
var request = require('supertest');
|
||||
var should = require('should');
|
||||
var serveIndex = require('..');
|
||||
|
||||
var fixtures = path.join(__dirname, '/fixtures');
|
||||
@@ -13,7 +14,7 @@ var skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relativ
|
||||
|
||||
describe('serveIndex(root)', function () {
|
||||
it('should require root', function () {
|
||||
serveIndex.should.throw(/root path required/)
|
||||
assert.throws(serveIndex, /root path required/)
|
||||
})
|
||||
|
||||
it('should serve text/html without Accept header', function (done) {
|
||||
@@ -146,7 +147,7 @@ describe('serveIndex(root)', function () {
|
||||
.end(function (err, res) {
|
||||
if (err) throw err;
|
||||
var urls = res.text.split(/<a href="([^"]*)"/).filter(function(s, i){ return i%2; });
|
||||
urls.should.eql([
|
||||
assert.deepEqual(urls, [
|
||||
'/%23directory',
|
||||
'/collect',
|
||||
'/g%23%20%253%20o%20%252525%20%2537%20dir',
|
||||
@@ -198,11 +199,8 @@ describe('serveIndex(root)', function () {
|
||||
|
||||
request(server)
|
||||
.get('/')
|
||||
.expect(200, function (err, res) {
|
||||
if (err) return done(err)
|
||||
res.text.should.not.containEql('.hidden')
|
||||
done()
|
||||
});
|
||||
.expect(bodyDoesNotContain('.hidden'))
|
||||
.expect(200, done)
|
||||
});
|
||||
|
||||
it('should filter hidden files', function (done) {
|
||||
@@ -210,11 +208,8 @@ describe('serveIndex(root)', function () {
|
||||
|
||||
request(server)
|
||||
.get('/')
|
||||
.expect(200, function (err, res) {
|
||||
if (err) return done(err)
|
||||
res.text.should.not.containEql('.hidden')
|
||||
done()
|
||||
});
|
||||
.expect(bodyDoesNotContain('.hidden'))
|
||||
.expect(200, done)
|
||||
});
|
||||
|
||||
it('should not filter hidden files', function (done) {
|
||||
@@ -228,41 +223,51 @@ describe('serveIndex(root)', function () {
|
||||
|
||||
describe('with "filter" option', function () {
|
||||
it('should custom filter files', function (done) {
|
||||
var seen = false
|
||||
var cb = after(2, done)
|
||||
var server = createServer(fixtures, {'filter': filter})
|
||||
|
||||
function filter(name) {
|
||||
if (name.indexOf('foo') === -1) return true
|
||||
seen = true
|
||||
cb()
|
||||
return false
|
||||
}
|
||||
|
||||
request(server)
|
||||
.get('/')
|
||||
.expect(200, function (err, res) {
|
||||
if (err) return done(err)
|
||||
seen.should.be.true
|
||||
res.text.should.not.containEql('foo')
|
||||
done()
|
||||
});
|
||||
.expect(bodyDoesNotContain('foo'))
|
||||
.expect(200, cb)
|
||||
});
|
||||
|
||||
it('should filter after hidden filter', function (done) {
|
||||
var seen = false
|
||||
var server = createServer(fixtures, {'filter': filter, 'hidden': false})
|
||||
|
||||
function filter(name) {
|
||||
seen = seen || name.indexOf('.') === 0
|
||||
if (name.indexOf('.') === 0) {
|
||||
done(new Error('unexpected hidden file'))
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
request(server)
|
||||
.get('/')
|
||||
.expect(200, function (err, res) {
|
||||
if (err) return done(err)
|
||||
seen.should.be.false
|
||||
done()
|
||||
});
|
||||
.expect(200, done)
|
||||
});
|
||||
|
||||
it('should filter directory paths', function (done) {
|
||||
var cb = after(3, done)
|
||||
var server = createServer(fixtures, {'filter': filter})
|
||||
|
||||
function filter(name, index, list, dir) {
|
||||
if (path.normalize(dir) === path.normalize(path.join(fixtures, '/users'))) {
|
||||
cb()
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
request(server)
|
||||
.get('/users')
|
||||
.expect(200, cb)
|
||||
});
|
||||
});
|
||||
|
||||
@@ -286,10 +291,7 @@ describe('serveIndex(root)', function () {
|
||||
|
||||
describe('when using custom handler', function () {
|
||||
describe('exports.html', function () {
|
||||
var orig = serveIndex.html
|
||||
after(function () {
|
||||
serveIndex.html = orig
|
||||
})
|
||||
alterProperty(serveIndex, 'html', serveIndex.html)
|
||||
|
||||
it('should get called with Accept: text/html', function (done) {
|
||||
var server = createServer()
|
||||
@@ -384,10 +386,7 @@ describe('serveIndex(root)', function () {
|
||||
});
|
||||
|
||||
describe('exports.plain', function () {
|
||||
var orig = serveIndex.plain
|
||||
after(function () {
|
||||
serveIndex.plain = orig
|
||||
})
|
||||
alterProperty(serveIndex, 'plain', serveIndex.plain)
|
||||
|
||||
it('should get called with Accept: text/plain', function (done) {
|
||||
var server = createServer()
|
||||
@@ -405,10 +404,7 @@ describe('serveIndex(root)', function () {
|
||||
});
|
||||
|
||||
describe('exports.json', function () {
|
||||
var orig = serveIndex.json
|
||||
after(function () {
|
||||
serveIndex.json = orig
|
||||
})
|
||||
alterProperty(serveIndex, 'json', serveIndex.json)
|
||||
|
||||
it('should get called with Accept: application/json', function (done) {
|
||||
var server = createServer()
|
||||
@@ -506,6 +502,18 @@ describe('serveIndex(root)', function () {
|
||||
.set('Accept', 'text/html')
|
||||
.expect(200, /ul#files/, done)
|
||||
});
|
||||
|
||||
it('should list directory twice', function (done) {
|
||||
request(server)
|
||||
.get('/users/')
|
||||
.set('Accept', 'text/html')
|
||||
.expect(function (res) {
|
||||
var occurances = res.text.match(/directory \/users\//g)
|
||||
if (occurances && occurances.length === 2) return
|
||||
throw new Error('directory not listed twice')
|
||||
})
|
||||
.expect(200, done)
|
||||
});
|
||||
});
|
||||
|
||||
describe('when setting a custom stylesheet', function () {
|
||||
@@ -572,6 +580,18 @@ describe('serveIndex(root)', function () {
|
||||
});
|
||||
});
|
||||
|
||||
function alterProperty(obj, prop, val) {
|
||||
var prev
|
||||
|
||||
beforeEach(function () {
|
||||
prev = obj[prop]
|
||||
obj[prop] = val
|
||||
})
|
||||
afterEach(function () {
|
||||
obj[prop] = prev
|
||||
})
|
||||
}
|
||||
|
||||
function createServer(dir, opts) {
|
||||
dir = dir || fixtures
|
||||
|
||||
@@ -584,3 +604,9 @@ function createServer(dir, opts) {
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function bodyDoesNotContain(text) {
|
||||
return function (res) {
|
||||
assert.equal(res.text.indexOf(text), -1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user