pax_global_header00006660000000000000000000000064124152611570014516gustar00rootroot0000000000000052 comment=f1308134d21aeaf5849a3d41c3a04b1779819f3c serve-static-1.6.4/000077500000000000000000000000001241526115700141375ustar00rootroot00000000000000serve-static-1.6.4/.gitignore000066400000000000000000000000461241526115700161270ustar00rootroot00000000000000coverage/ node_modules/ npm-debug.log serve-static-1.6.4/.travis.yml000066400000000000000000000003711241526115700162510ustar00rootroot00000000000000language: node_js node_js: - "0.8" - "0.10" - "0.11" matrix: allow_failures: - node_js: "0.11" fast_finish: true script: "npm run-script test-travis" after_script: "npm install coveralls@2.10.0 && cat ./coverage/lcov.info | coveralls" serve-static-1.6.4/HISTORY.md000066400000000000000000000070431241526115700156260ustar00rootroot000000000000001.6.4 / 2014-10-08 ================== * Fix redirect loop when index file serving disabled 1.6.3 / 2014-09-24 ================== * deps: send@0.9.3 - deps: etag@~1.4.0 1.6.2 / 2014-09-15 ================== * deps: send@0.9.2 - deps: depd@0.4.5 - deps: etag@~1.3.1 - deps: range-parser@~1.0.2 1.6.1 / 2014-09-07 ================== * deps: send@0.9.1 - deps: fresh@0.2.4 1.6.0 / 2014-09-07 ================== * deps: send@0.9.0 - Add `lastModified` option - Use `etag` to generate `ETag` header - deps: debug@~2.0.0 1.5.4 / 2014-09-04 ================== * deps: send@0.8.5 - Fix a path traversal issue when using `root` - Fix malicious path detection for empty string path 1.5.3 / 2014-08-17 ================== * deps: send@0.8.3 1.5.2 / 2014-08-14 ================== * deps: send@0.8.2 - Work around `fd` leak in Node.js 0.10 for `fs.ReadStream` 1.5.1 / 2014-08-09 ================== * Fix parsing of weird `req.originalUrl` values * deps: parseurl@~1.3.0 * deps: utils-merge@1.0.0 1.5.0 / 2014-08-05 ================== * deps: send@0.8.1 - Add `extensions` option 1.4.4 / 2014-08-04 ================== * deps: send@0.7.4 - Fix serving index files without root dir 1.4.3 / 2014-07-29 ================== * deps: send@0.7.3 - Fix incorrect 403 on Windows and Node.js 0.11 1.4.2 / 2014-07-27 ================== * deps: send@0.7.2 - deps: depd@0.4.4 1.4.1 / 2014-07-26 ================== * deps: send@0.7.1 - deps: depd@0.4.3 1.4.0 / 2014-07-21 ================== * deps: parseurl@~1.2.0 - Cache URLs based on original value - Remove no-longer-needed URL mis-parse work-around - Simplify the "fast-path" `RegExp` * deps: send@0.7.0 - Add `dotfiles` option - deps: debug@1.0.4 - deps: depd@0.4.2 1.3.2 / 2014-07-11 ================== * deps: send@0.6.0 - Cap `maxAge` value to 1 year - deps: debug@1.0.3 1.3.1 / 2014-07-09 ================== * deps: parseurl@~1.1.3 - faster parsing of href-only URLs 1.3.0 / 2014-06-28 ================== * Add `setHeaders` option * Include HTML link in redirect response * deps: send@0.5.0 - Accept string for `maxAge` (converted by `ms`) 1.2.3 / 2014-06-11 ================== * deps: send@0.4.3 - Do not throw un-catchable error on file open race condition - Use `escape-html` for HTML escaping - deps: debug@1.0.2 - deps: finished@1.2.2 - deps: fresh@0.2.2 1.2.2 / 2014-06-09 ================== * deps: send@0.4.2 - fix "event emitter leak" warnings - deps: debug@1.0.1 - deps: finished@1.2.1 1.2.1 / 2014-06-02 ================== * use `escape-html` for escaping * deps: send@0.4.1 - Send `max-age` in `Cache-Control` in correct format 1.2.0 / 2014-05-29 ================== * deps: send@0.4.0 - Calculate ETag with md5 for reduced collisions - Fix wrong behavior when index file matches directory - Ignore stream errors after request ends - Skip directories in index file search - deps: debug@0.8.1 1.1.0 / 2014-04-24 ================== * Accept options directly to `send` module * deps: send@0.3.0 1.0.4 / 2014-04-07 ================== * Resolve relative paths at middleware setup * Use parseurl to parse the URL from request 1.0.3 / 2014-03-20 ================== * Do not rely on connect-like environments 1.0.2 / 2014-03-06 ================== * deps: send@0.2.0 1.0.1 / 2014-03-05 ================== * Add mime export for back-compat 1.0.0 / 2014-03-05 ================== * Genesis from `connect` serve-static-1.6.4/LICENSE000066400000000000000000000022401241526115700151420ustar00rootroot00000000000000(The MIT License) Copyright (c) 2010 Sencha Inc. Copyright (c) 2011 LearnBoost Copyright (c) 2011 TJ Holowaychuk Copyright (c) 2014 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. serve-static-1.6.4/README.md000066400000000000000000000103521241526115700154170ustar00rootroot00000000000000# serve-static [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] [![Gratipay][gratipay-image]][gratipay-url] ## Install ```sh $ npm install serve-static ``` ## API ```js var serveStatic = require('serve-static') ``` ### serveStatic(root, options) Create a new middleware function to serve files from within a given root directory. The file to serve will be determined by combining `req.url` with the provided root directory. When a file is not found, instead of sending a 404 response, this module will instead call `next()` to move on to the next middleware, allowing for stacking and fall-backs. #### Options ##### dotfiles Set how "dotfiles" are treated when encountered. A dotfile is a file or directory that begins with a dot ("."). Note this check is done on the path itself without checking if the path actually exists on the disk. If `root` is specified, only the dotfiles above the root are checked (i.e. the root itself can be within a dotfile when when set to "deny"). The default value is `'ignore'`. - `'allow'` No special treatment for dotfiles. - `'deny'` Send a 403 for any request for a dotfile. - `'ignore'` Pretend like the dotfile does not exist and call `next()`. ##### etag Enable or disable etag generation, defaults to true. ##### extensions Set file extension fallbacks. When set, if a file is not found, the given extensions will be added to the file name and search for. The first that exists will be served. Example: `['html', 'htm']`. The default value is `false`. ##### index By default this module will send "index.html" files in response to a request on a directory. To disable this set `false` or to supply a new index pass a string or an array in preferred order. ##### lastModified Enable or disable `Last-Modified` header, defaults to true. Uses the file system's last modified value. ##### maxAge Provide a max-age in milliseconds for http caching, defaults to 0. This can also be a string accepted by the [ms](https://www.npmjs.org/package/ms#readme) module. ##### redirect Redirect to trailing "/" when the pathname is a dir. Defaults to `true`. ##### setHeaders Function to set custom headers on response. ## Examples ### Serve files with vanilla node.js http server ```js var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') // Serve up public/ftp folder var serve = serveStatic('public/ftp', {'index': ['index.html', 'index.htm']}) // Create server var server = http.createServer(function(req, res){ var done = finalhandler(req, res) serve(req, res, done) }) // Listen server.listen(3000) ``` ### Serve all files as downloads ```js var contentDisposition = require('content-disposition') var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') // Serve up public/ftp folder app.use(serveStatic('public/ftp', { 'index': false, 'setHeaders': setHeaders })) // Set header to force download function setHeaders(res, path) { res.setHeader('Content-Disposition', contentDisposition(path)) } // Create server var server = http.createServer(function(req, res){ var done = finalhandler(req, res) serve(req, res, done) }) // Listen server.listen(3000) ``` ### Serving using express ```js var connect = require('connect') var serveStatic = require('serve-static') var app = connect() app.use(serveStatic('public/ftp', {'index': ['default.html', 'default.htm']})) app.listen(3000) ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/serve-static.svg?style=flat [npm-url]: https://npmjs.org/package/serve-static [travis-image]: https://img.shields.io/travis/expressjs/serve-static.svg?style=flat [travis-url]: https://travis-ci.org/expressjs/serve-static [coveralls-image]: https://img.shields.io/coveralls/expressjs/serve-static.svg?style=flat [coveralls-url]: https://coveralls.io/r/expressjs/serve-static [downloads-image]: https://img.shields.io/npm/dm/serve-static.svg?style=flat [downloads-url]: https://npmjs.org/package/serve-static [gratipay-image]: https://img.shields.io/gratipay/dougwilson.svg?style=flat [gratipay-url]: https://gratipay.com/dougwilson/ serve-static-1.6.4/index.js000066400000000000000000000053321241526115700156070ustar00rootroot00000000000000/*! * serve-static * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014 Douglas Christopher Wilson * MIT Licensed */ /** * Module dependencies. */ var escapeHtml = require('escape-html'); var merge = require('utils-merge'); var parseurl = require('parseurl'); var resolve = require('path').resolve; var send = require('send'); var url = require('url'); /** * @param {String} root * @param {Object} options * @return {Function} * @api public */ exports = module.exports = function serveStatic(root, options) { if (!root) { throw new TypeError('root path required') } if (typeof root !== 'string') { throw new TypeError('root path must be a string') } // copy options object options = merge({}, options) // resolve root to absolute root = resolve(root) // default redirect var redirect = options.redirect !== false // headers listener var setHeaders = options.setHeaders delete options.setHeaders if (setHeaders && typeof setHeaders !== 'function') { throw new TypeError('option setHeaders must be function') } // setup options for send options.maxage = options.maxage || options.maxAge || 0 options.root = root return function serveStatic(req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { return next() } var opts = merge({}, options) var originalUrl = parseurl.original(req) var path = parseurl(req).pathname var hasTrailingSlash = originalUrl.pathname[originalUrl.pathname.length - 1] === '/' if (path === '/' && !hasTrailingSlash) { // make sure redirect occurs at mount path = '' } // create send stream var stream = send(req, path, opts) if (redirect) { // redirect relative to originalUrl stream.on('directory', function redirect() { if (hasTrailingSlash) { return next() } originalUrl.pathname += '/' var target = url.format(originalUrl) res.statusCode = 303 res.setHeader('Content-Type', 'text/html; charset=utf-8') res.setHeader('Location', target) res.end('Redirecting to ' + escapeHtml(target) + '\n') }) } else { // forward to next middleware on directory stream.on('directory', next) } // add headers listener if (setHeaders) { stream.on('headers', setHeaders) } // forward non-404 errors stream.on('error', function error(err) { next(err.status === 404 ? null : err) }) // pipe stream.pipe(res) } } /** * Expose mime module. * * If you wish to extend the mime table use this * reference to the "mime" module in the npm registry. */ exports.mime = send.mime serve-static-1.6.4/package.json000066400000000000000000000016571241526115700164360ustar00rootroot00000000000000{ "name": "serve-static", "description": "Serve static files", "version": "1.6.4", "author": "Douglas Christopher Wilson ", "license": "MIT", "repository": "expressjs/serve-static", "dependencies": { "escape-html": "1.0.1", "parseurl": "~1.3.0", "send": "0.9.3", "utils-merge": "1.0.0" }, "devDependencies": { "istanbul": "0.3.2", "mocha": "~1.21.0", "should": "~4.0.0", "supertest": "~0.14.0" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 0.8.0" }, "scripts": { "test": "mocha --reporter spec --bail --check-leaks --require should test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks --require should test/", "test-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks --require should test/" } } serve-static-1.6.4/test/000077500000000000000000000000001241526115700151165ustar00rootroot00000000000000serve-static-1.6.4/test/fixtures/000077500000000000000000000000001241526115700167675ustar00rootroot00000000000000serve-static-1.6.4/test/fixtures/.hidden000066400000000000000000000000131241526115700202150ustar00rootroot00000000000000I am hiddenserve-static-1.6.4/test/fixtures/foo bar000066400000000000000000000000031241526115700202130ustar00rootroot00000000000000bazserve-static-1.6.4/test/fixtures/nums000066400000000000000000000000111241526115700176640ustar00rootroot00000000000000123456789serve-static-1.6.4/test/fixtures/todo.html000066400000000000000000000000221241526115700206140ustar00rootroot00000000000000
  • groceries
  • serve-static-1.6.4/test/fixtures/todo.txt000066400000000000000000000000131241526115700204670ustar00rootroot00000000000000- groceriesserve-static-1.6.4/test/fixtures/users/000077500000000000000000000000001241526115700201305ustar00rootroot00000000000000serve-static-1.6.4/test/fixtures/users/index.html000066400000000000000000000000271241526115700221240ustar00rootroot00000000000000

    tobi, loki, jane

    serve-static-1.6.4/test/fixtures/users/tobi.txt000066400000000000000000000000061241526115700216220ustar00rootroot00000000000000ferretserve-static-1.6.4/test/test.js000066400000000000000000000371441241526115700164440ustar00rootroot00000000000000 var http = require('http'); var path = require('path'); var request = require('supertest'); var serveStatic = require('..'); var fixtures = __dirname + '/fixtures'; var relative = path.relative(process.cwd(), fixtures); var skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relative; describe('serveStatic()', function(){ describe('basic operations', function(){ var server; before(function () { server = createServer(); }); it('should require root path', function(){ serveStatic.bind().should.throw(/root path required/); }); it('should require root path to be string', function(){ serveStatic.bind(null, 42).should.throw(/root path.*string/); }); it('should serve static files', function(done){ request(server) .get('/todo.txt') .expect(200, '- groceries', done); }); it('should support nesting', function(done){ request(server) .get('/users/tobi.txt') .expect(200, 'ferret', done); }); it('should set Content-Type', function(done){ request(server) .get('/todo.txt') .expect('Content-Type', 'text/plain; charset=UTF-8') .expect(200, done); }); it('should set Last-Modified', function(done){ request(server) .get('/todo.txt') .expect('Last-Modified', /\d{2} \w{3} \d{4}/) .expect(200, done) }) it('should default max-age=0', function(done){ request(server) .get('/todo.txt') .expect('Cache-Control', 'public, max-age=0') .expect(200, done); }); it('should support urlencoded pathnames', function(done){ request(server) .get('/foo%20bar') .expect(200, 'baz', done); }); it('should not choke on auth-looking URL', function(done){ request(server) .get('//todo@txt') .expect(404, done); }); it('should support index.html', function(done){ request(server) .get('/users/') .expect(200) .expect('Content-Type', /html/) .expect('

    tobi, loki, jane

    ', done); }); it('should support ../', function(done){ request(server) .get('/users/../todo.txt') .expect(200, '- groceries', done); }); it('should support HEAD', function(done){ request(server) .head('/todo.txt') .expect(200, '', done); }); it('should skip POST requests', function(done){ request(server) .post('/todo.txt') .expect(404, 'sorry!', done); }); it('should support conditional requests', function(done){ request(server) .get('/todo.txt') .end(function(err, res){ if (err) throw err; request(server) .get('/todo.txt') .set('If-None-Match', res.headers.etag) .expect(304, done); }); }); it('should ignore hidden files', function(done){ request(server) .get('/.hidden') .expect(404, done); }); it('should set max-age=0 by default', function(done){ request(server) .get('/todo.txt') .expect('cache-control', 'public, max-age=0') .expect(200, done) }); }); (skipRelative ? describe.skip : describe)('current dir', function(){ var server; before(function () { server = createServer('.'); }); it('should be served with "."', function(done){ var dest = relative.split(path.sep).join('/'); request(server) .get('/' + dest + '/todo.txt') .expect(200, '- groceries', done); }) }) describe('extensions', function () { it('should be not be enabled by default', function (done) { var server = createServer(fixtures); request(server) .get('/todo') .expect(404, done); }) it('should be configurable', function (done) { var server = createServer(fixtures, {'extensions': 'txt'}); request(server) .get('/todo') .expect(200, '- groceries', done); }) it('should support disabling extensions', function (done) { var server = createServer(fixtures, {'extensions': false}); request(server) .get('/todo') .expect(404, done); }) it('should support fallbacks', function (done) { var server = createServer(fixtures, {'extensions': ['htm', 'html', 'txt']}); request(server) .get('/todo') .expect(200, '
  • groceries
  • ', done); }) it('should 404 if nothing found', function (done) { var server = createServer(fixtures, {'extensions': ['htm', 'html', 'txt']}); request(server) .get('/bob') .expect(404, done) }) }) describe('hidden files', function(){ var server; before(function () { server = createServer(fixtures, {'dotfiles': 'allow'}); }); it('should be served when dotfiles: "allow" is given', function(done){ request(server) .get('/.hidden') .expect(200, 'I am hidden', done); }) }) describe('lastModified', function(){ describe('when false', function () { it('should not include Last-Modifed', function (done) { request(createServer(fixtures, {'lastModified': false})) .get('/nums') .expect(200, '123456789', function (err, res) { if (err) return done(err) res.headers.should.not.have.property('last-modified') done() }) }) }) describe('when true', function () { it('should include Last-Modifed', function (done) { request(createServer(fixtures, {'lastModified': true})) .get('/nums') .expect(200, '123456789', function (err, res) { if (err) return done(err) res.headers.should.have.property('last-modified') done() }) }) }) }) describe('maxAge', function(){ it('should accept string', function(done){ request(createServer(fixtures, {'maxAge': '30d'})) .get('/todo.txt') .expect('cache-control', 'public, max-age=' + 60*60*24*30) .expect(200, done) }) it('should be reasonable when infinite', function(done){ request(createServer(fixtures, {'maxAge': Infinity})) .get('/todo.txt') .expect('cache-control', 'public, max-age=' + 60*60*24*365) .expect(200, done) }); }); describe('redirect', function () { var server; before(function () { server = createServer(fixtures) }) it('should redirect directories', function(done){ request(server) .get('/users') .expect('Location', '/users/') .expect(303, done) }) it('should include HTML link', function(done){ request(server) .get('/users') .expect('Location', '/users/') .expect(303, //, done) }) it('should redirect directories with query string', function (done) { request(server) .get('/users?name=john') .expect('Location', '/users/?name=john') .expect(303, done) }) it('should not redirect incorrectly', function (done) { request(server) .get('/') .expect(404, done) }) describe('when false', function () { var server; before(function () { server = createServer(fixtures, {'redirect': false}) }) it('should disable redirect', function (done) { request(server) .get('/users') .expect(404, done) }) }) }) describe('setHeaders', function () { it('should reject non-functions', function () { serveStatic.bind(null, fixtures, {'setHeaders': 3}).should.throw(/setHeaders.*function/) }) it('should get called when sending file', function(done){ var server = createServer(fixtures, {'setHeaders': function (res) { res.setHeader('x-custom', 'set') }}) request(server) .get('/nums') .expect('x-custom', 'set') .expect(200, done) }) it('should not get called on 404', function(done){ var server = createServer(fixtures, {'setHeaders': function (res) { res.setHeader('x-custom', 'set') }}) request(server) .get('/bogus') .expect(404, function (err, res) { if (err) return done(err) res.headers.should.not.have.property('x-custom') done() }) }) it('should not get called on redirect', function(done){ var server = createServer(fixtures, {'setHeaders': function (res) { res.setHeader('x-custom', 'set') }}) request(server) .get('/users') .expect(303, function (err, res) { if (err) return done(err) res.headers.should.not.have.property('x-custom') done() }) }) }) describe('when traversing passed root', function(){ var server; before(function () { server = createServer(); }); it('should respond with 403 Forbidden', function(done){ request(server) .get('/users/../../todo.txt') .expect(403, done); }) it('should catch urlencoded ../', function(done){ request(server) .get('/users/%2e%2e/%2e%2e/todo.txt') .expect(403, done); }); }); describe('on ENOENT', function(){ var server; before(function () { server = createServer(); }); it('should next()', function(done){ request(server) .get('/does-not-exist') .expect(404, 'sorry!', done); }); }); describe('Range', function(){ var server; before(function () { server = createServer(); }); it('should support byte ranges', function(done){ request(server) .get('/nums') .set('Range', 'bytes=0-4') .expect('12345', done); }); it('should be inclusive', function(done){ request(server) .get('/nums') .set('Range', 'bytes=0-0') .expect('1', done); }); it('should set Content-Range', function(done){ request(server) .get('/nums') .set('Range', 'bytes=2-5') .expect('Content-Range', 'bytes 2-5/9', done); }); it('should support -n', function(done){ request(server) .get('/nums') .set('Range', 'bytes=-3') .expect('789', done); }); it('should support n-', function(done){ request(server) .get('/nums') .set('Range', 'bytes=3-') .expect('456789', done); }); it('should respond with 206 "Partial Content"', function(done){ request(server) .get('/nums') .set('Range', 'bytes=0-4') .expect(206, done); }); it('should set Content-Length to the # of octets transferred', function(done){ request(server) .get('/nums') .set('Range', 'bytes=2-3') .expect('Content-Length', '2') .expect(206, '34', done); }); describe('when last-byte-pos of the range is greater than current length', function(){ it('is taken to be equal to one less than the current length', function(done){ request(server) .get('/nums') .set('Range', 'bytes=2-50') .expect('Content-Range', 'bytes 2-8/9', done) }); it('should adapt the Content-Length accordingly', function(done){ request(server) .get('/nums') .set('Range', 'bytes=2-50') .expect('Content-Length', '7') .expect(206, done); }); }); describe('when the first- byte-pos of the range is greater than the current length', function(){ it('should respond with 416', function(done){ request(server) .get('/nums') .set('Range', 'bytes=9-50') .expect(416, done); }); it('should include a Content-Range field with a byte-range- resp-spec of "*" and an instance-length specifying the current length', function(done){ request(server) .get('/nums') .set('Range', 'bytes=9-50') .expect('Content-Range', 'bytes */9', done) }); }); describe('when syntactically invalid', function(){ it('should respond with 200 and the entire contents', function(done){ request(server) .get('/nums') .set('Range', 'asdf') .expect('123456789', done); }); }); }); describe('with a malformed URL', function(){ var server; before(function () { server = createServer(); }); it('should respond with 400', function(done){ request(server) .get('/%') .expect(400, done); }); }); describe('on ENAMETOOLONG', function(){ var server; before(function () { server = createServer(); }); it('should next()', function(done){ var path = Array(100).join('foobar'); request(server) .get('/' + path) .expect(404, done); }); }); describe('on ENOTDIR', function(){ var server; before(function () { server = createServer(); }); it('should next()', function(done) { request(server) .get('/todo.txt/a.php') .expect(404, done); }); }); describe('when index at mount point', function(){ var server; before(function () { server = createServer('test/fixtures/users', null, function (req) { req.originalUrl = req.url; req.url = '/' + req.url.split('/').slice(2).join('/'); }); }); it('should redirect correctly', function (done) { request(server) .get('/users') .expect('Location', '/users/') .expect(303, done); }); }); describe('when mounted', function(){ var server; before(function () { server = createServer(fixtures, null, function (req) { req.originalUrl = req.url; req.url = '/' + req.url.split('/').slice(3).join('/'); }); }); it('should redirect relative to the originalUrl', function(done){ request(server) .get('/static/users') .expect('Location', '/static/users/') .expect(303, done); }); it('should not choke on auth-looking URL', function(done){ request(server) .get('//todo@txt') .expect('Location', '//todo@txt/') .expect(303, done); }); }); describe('when responding non-2xx or 304', function(){ var server; before(function () { var n = 0; server = createServer(fixtures, null, function (req, res) { if (n++) res.statusCode = 500; }); }); it('should respond as-is', function(done){ request(server) .get('/todo.txt') .expect(200) .end(function(err, res){ if (err) throw err; request(server) .get('/todo.txt') .set('If-None-Match', res.headers.etag) .expect(500, '- groceries', done); }); }); }); describe('when index file serving disabled', function(){ var server; before(function () { server = createServer(fixtures, {'index': false}, function (req) { // mimic express/connect mount req.originalUrl = req.url; req.url = '/' + req.url.split('/').slice(2).join('/'); }); }); it('should next() on directory', function (done) { request(server) .get('/static/users/') .expect(404, 'sorry!', done); }); it('should redirect to trailing slash', function (done) { request(server) .get('/static/users') .expect('Location', '/static/users/') .expect(303, done); }); it('should next() on mount point', function (done) { request(server) .get('/static/') .expect(404, 'sorry!', done); }); it('should redirect to trailing slash mount point', function (done) { request(server) .get('/static') .expect('Location', '/static/') .expect(303, done); }); }); }); function createServer(dir, opts, fn) { dir = dir || fixtures; var _serve = serveStatic(dir, opts); return http.createServer(function (req, res) { fn && fn(req, res); _serve(req, res, function (err) { res.statusCode = err ? (err.status || 500) : 404; res.end(err ? err.stack : 'sorry!'); }); }); }