From 00c9e5c6ea1473c1ee093aae60b290b755405101 Mon Sep 17 00:00:00 2001 From: ctcpip Date: Mon, 19 Aug 2024 15:36:24 -0500 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20move=20to=20neostandard=20/=20eslin?= =?UTF-8?q?t-config-express?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Wes Todd --- .eslintignore | 2 - .eslintrc.yml | 9 - eslint.config.cjs | 5 + index.js | 125 +++---- package.json | 9 +- scripts/version-history.js | 54 +-- test/.eslintrc | 5 - test/test.js | 662 ++++++++++++++++++------------------- 8 files changed, 428 insertions(+), 443 deletions(-) delete mode 100644 .eslintignore delete mode 100644 .eslintrc.yml create mode 100644 eslint.config.cjs delete mode 100644 test/.eslintrc diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 62562b7..0000000 --- a/.eslintignore +++ /dev/null @@ -1,2 +0,0 @@ -coverage -node_modules diff --git a/.eslintrc.yml b/.eslintrc.yml deleted file mode 100644 index cf3015f..0000000 --- a/.eslintrc.yml +++ /dev/null @@ -1,9 +0,0 @@ -root: true -extends: - - standard - - plugin:markdown/recommended -plugins: - - markdown -overrides: - - files: '**/*.md' - processor: 'markdown/markdown' diff --git a/eslint.config.cjs b/eslint.config.cjs new file mode 100644 index 0000000..12e6952 --- /dev/null +++ b/eslint.config.cjs @@ -0,0 +1,5 @@ +const expressLintConfig = require('eslint-config-express'); + +module.exports = [ + ...expressLintConfig, +]; diff --git a/index.js b/index.js index 1c1bb46..b10d858 100644 --- a/index.js +++ b/index.js @@ -6,26 +6,26 @@ * MIT Licensed */ -'use strict' +'use strict'; /** * Module dependencies. * @private */ -var encodeUrl = require('encodeurl') -var escapeHtml = require('escape-html') -var parseUrl = require('parseurl') -var resolve = require('path').resolve -var send = require('send') -var url = require('url') +const encodeUrl = require('encodeurl'); +const escapeHtml = require('escape-html'); +const parseUrl = require('parseurl'); +const resolve = require('path').resolve; +const send = require('send'); +const url = require('url'); /** * Module exports. * @public */ -module.exports = serveStatic +module.exports = serveStatic; /** * @param {string} root @@ -36,93 +36,93 @@ module.exports = serveStatic function serveStatic (root, options) { if (!root) { - throw new TypeError('root path required') + throw new TypeError('root path required'); } if (typeof root !== 'string') { - throw new TypeError('root path must be a string') + throw new TypeError('root path must be a string'); } // copy options object - var opts = Object.create(options || null) + const opts = Object.create(options || null); // fall-though - var fallthrough = opts.fallthrough !== false + const fallthrough = opts.fallthrough !== false; // default redirect - var redirect = opts.redirect !== false + const redirect = opts.redirect !== false; // headers listener - var setHeaders = opts.setHeaders + const setHeaders = opts.setHeaders; if (setHeaders && typeof setHeaders !== 'function') { - throw new TypeError('option setHeaders must be function') + throw new TypeError('option setHeaders must be function'); } // setup options for send - opts.maxage = opts.maxage || opts.maxAge || 0 - opts.root = resolve(root) + opts.maxage = opts.maxage || opts.maxAge || 0; + opts.root = resolve(root); // construct directory listener - var onDirectory = redirect + const onDirectory = redirect ? createRedirectDirectoryListener() - : createNotFoundDirectoryListener() + : createNotFoundDirectoryListener(); return function serveStatic (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { if (fallthrough) { - return next() + return next(); } // method not allowed - res.statusCode = 405 - res.setHeader('Allow', 'GET, HEAD') - res.setHeader('Content-Length', '0') - res.end() - return + res.statusCode = 405; + res.setHeader('Allow', 'GET, HEAD'); + res.setHeader('Content-Length', '0'); + res.end(); + return; } - var forwardError = !fallthrough - var originalUrl = parseUrl.original(req) - var path = parseUrl(req).pathname + let forwardError = !fallthrough; + const originalUrl = parseUrl.original(req); + let path = parseUrl(req).pathname; // make sure redirect occurs at mount if (path === '/' && originalUrl.pathname.substr(-1) !== '/') { - path = '' + path = ''; } // create send stream - var stream = send(req, path, opts) + const stream = send(req, path, opts); // add directory handler - stream.on('directory', onDirectory) + stream.on('directory', onDirectory); // add headers listener if (setHeaders) { - stream.on('headers', setHeaders) + stream.on('headers', setHeaders); } // add file listener for fallthrough if (fallthrough) { stream.on('file', function onFile () { // once file is determined, always forward error - forwardError = true - }) + forwardError = true; + }); } // forward errors stream.on('error', function error (err) { if (forwardError || !(err.statusCode < 500)) { - next(err) - return + next(err); + return; } - next() - }) + next(); + }); // pipe - stream.pipe(res) - } + stream.pipe(res); + }; } /** @@ -130,15 +130,16 @@ function serveStatic (root, options) { * @private */ function collapseLeadingSlashes (str) { - for (var i = 0; i < str.length; i++) { + let i; + for (i = 0; i < str.length; i++) { if (str.charCodeAt(i) !== 0x2f /* / */) { - break + break; } } return i > 1 ? '/' + str.substr(i) - : str + : str; } /** @@ -159,7 +160,7 @@ function createHtmlDocument (title, body) { '\n' + '
' + body + '
\n' + '\n' + - '\n' + '\n'; } /** @@ -169,8 +170,8 @@ function createHtmlDocument (title, body) { function createNotFoundDirectoryListener () { return function notFound () { - this.error(404) - } + this.error(404); + }; } /** @@ -181,29 +182,29 @@ function createNotFoundDirectoryListener () { function createRedirectDirectoryListener () { return function redirect (res) { if (this.hasTrailingSlash()) { - this.error(404) - return + this.error(404); + return; } // get original URL - var originalUrl = parseUrl.original(this.req) + const originalUrl = parseUrl.original(this.req); // append trailing slash - originalUrl.path = null - originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/') + originalUrl.path = null; + originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/'); // reformat the URL - var loc = encodeUrl(url.format(originalUrl)) - var doc = createHtmlDocument('Redirecting', 'Redirecting to ' + - escapeHtml(loc) + '') + const loc = encodeUrl(url.format(originalUrl)); + const doc = createHtmlDocument('Redirecting', 'Redirecting to ' + + escapeHtml(loc) + ''); // send redirect response - res.statusCode = 301 - res.setHeader('Content-Type', 'text/html; charset=UTF-8') - res.setHeader('Content-Length', Buffer.byteLength(doc)) - res.setHeader('Content-Security-Policy', "default-src 'none'") - res.setHeader('X-Content-Type-Options', 'nosniff') - res.setHeader('Location', loc) - res.end(doc) - } + res.statusCode = 301; + res.setHeader('Content-Type', 'text/html; charset=UTF-8'); + res.setHeader('Content-Length', Buffer.byteLength(doc)); + res.setHeader('Content-Security-Policy', "default-src 'none'"); + res.setHeader('X-Content-Type-Options', 'nosniff'); + res.setHeader('Location', loc); + res.end(doc); + }; } diff --git a/package.json b/package.json index e1840f0..a98f45f 100644 --- a/package.json +++ b/package.json @@ -12,13 +12,7 @@ "send": "^1.0.0" }, "devDependencies": { - "eslint": "7.32.0", - "eslint-config-standard": "14.1.1", - "eslint-plugin-import": "2.25.4", - "eslint-plugin-markdown": "2.2.1", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-promise": "5.2.0", - "eslint-plugin-standard": "4.1.0", + "eslint-config-express": "^0.2.0", "mocha": "^10.7.0", "nyc": "^17.0.0", "safe-buffer": "^5.2.1", @@ -34,6 +28,7 @@ }, "scripts": { "lint": "eslint .", + "lint:fix": "eslint . --fix", "test": "mocha --reporter spec --bail --check-leaks test/", "test-ci": "nyc --reporter=lcov --reporter=text npm test", "test-cov": "nyc --reporter=html --reporter=text npm test", diff --git a/scripts/version-history.js b/scripts/version-history.js index b8a2b0e..7977cec 100644 --- a/scripts/version-history.js +++ b/scripts/version-history.js @@ -1,63 +1,63 @@ -'use strict' +'use strict'; -var fs = require('fs') -var path = require('path') +const fs = require('fs'); +const path = require('path'); -var HISTORY_FILE_PATH = path.join(__dirname, '..', 'HISTORY.md') -var MD_HEADER_REGEXP = /^====*$/ -var VERSION = process.env.npm_package_version -var VERSION_PLACEHOLDER_REGEXP = /^(?:unreleased|(\d+\.)+x)$/ +const HISTORY_FILE_PATH = path.join(__dirname, '..', 'HISTORY.md'); +const MD_HEADER_REGEXP = /^====*$/; +const VERSION = process.env.npm_package_version; +const VERSION_PLACEHOLDER_REGEXP = /^(?:unreleased|(\d+\.)+x)$/; -var historyFileLines = fs.readFileSync(HISTORY_FILE_PATH, 'utf-8').split('\n') +const historyFileLines = fs.readFileSync(HISTORY_FILE_PATH, 'utf-8').split('\n'); if (!MD_HEADER_REGEXP.test(historyFileLines[1])) { - console.error('Missing header in HISTORY.md') - process.exit(1) + console.error('Missing header in HISTORY.md'); + process.exit(1); } if (!VERSION_PLACEHOLDER_REGEXP.test(historyFileLines[0])) { - console.error('Missing placegolder version in HISTORY.md') - process.exit(1) + console.error('Missing placegolder version in HISTORY.md'); + process.exit(1); } if (historyFileLines[0].indexOf('x') !== -1) { - var versionCheckRegExp = new RegExp('^' + historyFileLines[0].replace('x', '.+') + '$') + const versionCheckRegExp = new RegExp('^' + historyFileLines[0].replace('x', '.+') + '$'); if (!versionCheckRegExp.test(VERSION)) { - console.error('Version %s does not match placeholder %s', VERSION, historyFileLines[0]) - process.exit(1) + console.error('Version %s does not match placeholder %s', VERSION, historyFileLines[0]); + process.exit(1); } } -historyFileLines[0] = VERSION + ' / ' + getLocaleDate() -historyFileLines[1] = repeat('=', historyFileLines[0].length) +historyFileLines[0] = VERSION + ' / ' + getLocaleDate(); +historyFileLines[1] = repeat('=', historyFileLines[0].length); -fs.writeFileSync(HISTORY_FILE_PATH, historyFileLines.join('\n')) +fs.writeFileSync(HISTORY_FILE_PATH, historyFileLines.join('\n')); function getLocaleDate () { - var now = new Date() + const now = new Date(); return zeroPad(now.getFullYear(), 4) + '-' + zeroPad(now.getMonth() + 1, 2) + '-' + - zeroPad(now.getDate(), 2) + zeroPad(now.getDate(), 2); } function repeat (str, length) { - var out = '' + let out = ''; - for (var i = 0; i < length; i++) { - out += str + for (let i = 0; i < length; i++) { + out += str; } - return out + return out; } function zeroPad (number, length) { - var num = number.toString() + let num = number.toString(); while (num.length < length) { - num = '0' + num + num = '0' + num; } - return num + return num; } diff --git a/test/.eslintrc b/test/.eslintrc deleted file mode 100644 index 7eeefc3..0000000 --- a/test/.eslintrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "env": { - "mocha": true - } -} diff --git a/test/test.js b/test/test.js index 2603568..3868fff 100644 --- a/test/test.js +++ b/test/test.js @@ -1,151 +1,151 @@ +const { describe, it, before } = require('mocha'); +const assert = require('assert'); +const Buffer = require('safe-buffer').Buffer; +const http = require('http'); +const path = require('path'); +const request = require('supertest'); +const serveStatic = require('..'); -var assert = require('assert') -var Buffer = require('safe-buffer').Buffer -var http = require('http') -var path = require('path') -var request = require('supertest') -var serveStatic = require('..') +const fixtures = path.join(__dirname, '/fixtures'); +const relative = path.relative(process.cwd(), fixtures); -var fixtures = path.join(__dirname, '/fixtures') -var relative = path.relative(process.cwd(), fixtures) - -var skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relative +const skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relative; describe('serveStatic()', function () { describe('basic operations', function () { - var server + let server; before(function () { - server = createServer() - }) + server = createServer(); + }); it('should require root path', function () { - assert.throws(serveStatic.bind(), /root path required/) - }) + assert.throws(serveStatic.bind(), /root path required/); + }); it('should require root path to be string', function () { - assert.throws(serveStatic.bind(null, 42), /root path.*string/) - }) + assert.throws(serveStatic.bind(null, 42), /root path.*string/); + }); it('should serve static files', function (done) { request(server) .get('/todo.txt') - .expect(200, '- groceries', done) - }) + .expect(200, '- groceries', done); + }); it('should support nesting', function (done) { request(server) .get('/users/tobi.txt') - .expect(200, 'ferret', done) - }) + .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) - }) + .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) - }) + .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) - }) + .expect(200, done); + }); it('should support urlencoded pathnames', function (done) { request(server) .get('/foo%20bar') .expect(200) .expect(shouldHaveBody(Buffer.from('baz'))) - .end(done) - }) + .end(done); + }); it('should not choke on auth-looking URL', function (done) { request(server) .get('//todo@txt') - .expect(404, done) - }) + .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) - }) + .expect('

tobi, loki, jane

', done); + }); it('should support ../', function (done) { request(server) .get('/users/../todo.txt') - .expect(200, '- groceries', done) - }) + .expect(200, '- groceries', done); + }); it('should support HEAD', function (done) { request(server) .head('/todo.txt') .expect(200) .expect(shouldNotHaveBody()) - .end(done) - }) + .end(done); + }); it('should skip POST requests', function (done) { request(server) .post('/todo.txt') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); it('should support conditional requests', function (done) { request(server) .get('/todo.txt') .end(function (err, res) { - if (err) throw err + if (err) throw err; request(server) .get('/todo.txt') .set('If-None-Match', res.headers.etag) - .expect(304, done) - }) - }) + .expect(304, done); + }); + }); it('should support precondition checks', function (done) { request(server) .get('/todo.txt') .set('If-Match', '"foo"') - .expect(412, done) - }) + .expect(412, done); + }); it('should serve zero-length files', function (done) { request(server) .get('/empty.txt') - .expect(200, '', done) - }) + .expect(200, '', done); + }); it('should ignore hidden files', function (done) { request(server) .get('/.hidden') - .expect(404, done) - }) + .expect(404, done); + }); }); (skipRelative ? describe.skip : describe)('current dir', function () { - var server + let server; before(function () { - server = createServer('.') - }) + server = createServer('.'); + }); it('should be served with "."', function (done) { - var dest = relative.split(path.sep).join('/') + const dest = relative.split(path.sep).join('/'); request(server) .get('/' + dest + '/todo.txt') - .expect(200, '- groceries', done) - }) - }) + .expect(200, '- groceries', done); + }); + }); describe('acceptRanges', function () { describe('when false', function () { @@ -153,8 +153,8 @@ describe('serveStatic()', function () { request(createServer(fixtures, { acceptRanges: false })) .get('/nums.txt') .expect(shouldNotHaveHeader('Accept-Ranges')) - .expect(200, '123456789', done) - }) + .expect(200, '123456789', done); + }); it('should ignore Rage request header', function (done) { request(createServer(fixtures, { acceptRanges: false })) @@ -162,17 +162,17 @@ describe('serveStatic()', function () { .set('Range', 'bytes=0-3') .expect(shouldNotHaveHeader('Accept-Ranges')) .expect(shouldNotHaveHeader('Content-Range')) - .expect(200, '123456789', done) - }) - }) + .expect(200, '123456789', done); + }); + }); describe('when true', function () { it('should include Accept-Ranges', function (done) { request(createServer(fixtures, { acceptRanges: true })) .get('/nums.txt') .expect('Accept-Ranges', 'bytes') - .expect(200, '123456789', done) - }) + .expect(200, '123456789', done); + }); it('should obey Rage request header', function (done) { request(createServer(fixtures, { acceptRanges: true })) @@ -180,10 +180,10 @@ describe('serveStatic()', function () { .set('Range', 'bytes=0-3') .expect('Accept-Ranges', 'bytes') .expect('Content-Range', 'bytes 0-3/9') - .expect(206, '1234', done) - }) - }) - }) + .expect(206, '1234', done); + }); + }); + }); describe('cacheControl', function () { describe('when false', function () { @@ -191,242 +191,242 @@ describe('serveStatic()', function () { request(createServer(fixtures, { cacheControl: false })) .get('/nums.txt') .expect(shouldNotHaveHeader('Cache-Control')) - .expect(200, '123456789', done) - }) + .expect(200, '123456789', done); + }); it('should ignore maxAge', function (done) { request(createServer(fixtures, { cacheControl: false, maxAge: 12000 })) .get('/nums.txt') .expect(shouldNotHaveHeader('Cache-Control')) - .expect(200, '123456789', done) - }) - }) + .expect(200, '123456789', done); + }); + }); describe('when true', function () { it('should include Cache-Control', function (done) { request(createServer(fixtures, { cacheControl: true })) .get('/nums.txt') .expect('Cache-Control', 'public, max-age=0') - .expect(200, '123456789', done) - }) - }) - }) + .expect(200, '123456789', done); + }); + }); + }); describe('extensions', function () { it('should be not be enabled by default', function (done) { - var server = createServer(fixtures) + const server = createServer(fixtures); request(server) .get('/todo') - .expect(404, done) - }) + .expect(404, done); + }); it('should be configurable', function (done) { - var server = createServer(fixtures, { extensions: 'txt' }) + const server = createServer(fixtures, { extensions: 'txt' }); request(server) .get('/todo') - .expect(200, '- groceries', done) - }) + .expect(200, '- groceries', done); + }); it('should support disabling extensions', function (done) { - var server = createServer(fixtures, { extensions: false }) + const server = createServer(fixtures, { extensions: false }); request(server) .get('/todo') - .expect(404, done) - }) + .expect(404, done); + }); it('should support fallbacks', function (done) { - var server = createServer(fixtures, { extensions: ['htm', 'html', 'txt'] }) + const server = createServer(fixtures, { extensions: ['htm', 'html', 'txt'] }); request(server) .get('/todo') - .expect(200, '
  • groceries
  • ', done) - }) + .expect(200, '
  • groceries
  • ', done); + }); it('should 404 if nothing found', function (done) { - var server = createServer(fixtures, { extensions: ['htm', 'html', 'txt'] }) + const server = createServer(fixtures, { extensions: ['htm', 'html', 'txt'] }); request(server) .get('/bob') - .expect(404, done) - }) - }) + .expect(404, done); + }); + }); describe('fallthrough', function () { it('should default to true', function (done) { request(createServer()) .get('/does-not-exist') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); describe('when true', function () { before(function () { - this.server = createServer(fixtures, { fallthrough: true }) - }) + this.server = createServer(fixtures, { fallthrough: true }); + }); it('should fall-through when OPTIONS request', function (done) { request(this.server) .options('/todo.txt') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); it('should fall-through when URL malformed', function (done) { request(this.server) .get('/%') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); it('should fall-through when traversing past root', function (done) { request(this.server) .get('/users/../../todo.txt') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); it('should fall-through when URL too long', function (done) { - var root = fixtures + Array(10000).join('/foobar') + const root = fixtures + Array(10000).join('/foobar'); request(createServer(root, { fallthrough: true })) .get('/') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); describe('with redirect: true', function () { before(function () { - this.server = createServer(fixtures, { fallthrough: true, redirect: true }) - }) + this.server = createServer(fixtures, { fallthrough: true, redirect: true }); + }); it('should fall-through when directory', function (done) { request(this.server) .get('/pets/') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); it('should redirect when directory without slash', function (done) { request(this.server) .get('/pets') - .expect(301, /Redirecting/, done) - }) - }) + .expect(301, /Redirecting/, done); + }); + }); describe('with redirect: false', function () { before(function () { - this.server = createServer(fixtures, { fallthrough: true, redirect: false }) - }) + this.server = createServer(fixtures, { fallthrough: true, redirect: false }); + }); it('should fall-through when directory', function (done) { request(this.server) .get('/pets/') - .expect(404, 'sorry!', done) - }) + .expect(404, 'sorry!', done); + }); it('should fall-through when directory without slash', function (done) { request(this.server) .get('/pets') - .expect(404, 'sorry!', done) - }) - }) - }) + .expect(404, 'sorry!', done); + }); + }); + }); describe('when false', function () { before(function () { - this.server = createServer(fixtures, { fallthrough: false }) - }) + this.server = createServer(fixtures, { fallthrough: false }); + }); it('should 405 when OPTIONS request', function (done) { request(this.server) .options('/todo.txt') .expect('Allow', 'GET, HEAD') - .expect(405, done) - }) + .expect(405, done); + }); it('should 400 when URL malformed', function (done) { request(this.server) .get('/%') - .expect(400, /BadRequestError/, done) - }) + .expect(400, /BadRequestError/, done); + }); it('should 403 when traversing past root', function (done) { request(this.server) .get('/users/../../todo.txt') - .expect(403, /ForbiddenError/, done) - }) + .expect(403, /ForbiddenError/, done); + }); it('should 404 when URL too long', function (done) { - var root = fixtures + Array(10000).join('/foobar') + const root = fixtures + Array(10000).join('/foobar'); request(createServer(root, { fallthrough: false })) .get('/') - .expect(404, /ENAMETOOLONG/, done) - }) + .expect(404, /ENAMETOOLONG/, done); + }); describe('with redirect: true', function () { before(function () { - this.server = createServer(fixtures, { fallthrough: false, redirect: true }) - }) + this.server = createServer(fixtures, { fallthrough: false, redirect: true }); + }); it('should 404 when directory', function (done) { request(this.server) .get('/pets/') - .expect(404, /NotFoundError|ENOENT/, done) - }) + .expect(404, /NotFoundError|ENOENT/, done); + }); it('should redirect when directory without slash', function (done) { request(this.server) .get('/pets') - .expect(301, /Redirecting/, done) - }) - }) + .expect(301, /Redirecting/, done); + }); + }); describe('with redirect: false', function () { before(function () { - this.server = createServer(fixtures, { fallthrough: false, redirect: false }) - }) + this.server = createServer(fixtures, { fallthrough: false, redirect: false }); + }); it('should 404 when directory', function (done) { request(this.server) .get('/pets/') - .expect(404, /NotFoundError|ENOENT/, done) - }) + .expect(404, /NotFoundError|ENOENT/, done); + }); it('should 404 when directory without slash', function (done) { request(this.server) .get('/pets') - .expect(404, /NotFoundError|ENOENT/, done) - }) - }) - }) - }) + .expect(404, /NotFoundError|ENOENT/, done); + }); + }); + }); + }); describe('hidden files', function () { - var server + let server; before(function () { - server = createServer(fixtures, { dotfiles: 'allow' }) - }) + server = createServer(fixtures, { dotfiles: 'allow' }); + }); it('should be served when dotfiles: "allow" is given', function (done) { request(server) .get('/.hidden') .expect(200) .expect(shouldHaveBody(Buffer.from('I am hidden'))) - .end(done) - }) - }) + .end(done); + }); + }); describe('immutable', function () { it('should default to false', function (done) { request(createServer(fixtures)) .get('/nums.txt') - .expect('Cache-Control', 'public, max-age=0', done) - }) + .expect('Cache-Control', 'public, max-age=0', done); + }); it('should set immutable directive in Cache-Control', function (done) { request(createServer(fixtures, { immutable: true, maxAge: '1h' })) .get('/nums.txt') - .expect('Cache-Control', 'public, max-age=3600, immutable', done) - }) - }) + .expect('Cache-Control', 'public, max-age=3600, immutable', done); + }); + }); describe('lastModified', function () { describe('when false', function () { @@ -434,329 +434,329 @@ describe('serveStatic()', function () { request(createServer(fixtures, { lastModified: false })) .get('/nums.txt') .expect(shouldNotHaveHeader('Last-Modified')) - .expect(200, '123456789', done) - }) - }) + .expect(200, '123456789', done); + }); + }); describe('when true', function () { it('should include Last-Modifed', function (done) { request(createServer(fixtures, { lastModified: true })) .get('/nums.txt') .expect('Last-Modified', /^\w{3}, \d+ \w+ \d+ \d+:\d+:\d+ \w+$/) - .expect(200, '123456789', done) - }) - }) - }) + .expect(200, '123456789', 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) - }) + .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) - }) - }) + .expect(200, done); + }); + }); describe('redirect', function () { - var server + let server; before(function () { server = createServer(fixtures, null, function (req, res) { - req.url = req.url.replace(/\/snow(\/|$)/, '/snow \u2603$1') - }) - }) + req.url = req.url.replace(/\/snow(\/|$)/, '/snow \u2603$1'); + }); + }); it('should redirect directories', function (done) { request(server) .get('/users') .expect('Location', '/users/') - .expect(301, done) - }) + .expect(301, done); + }); it('should include HTML link', function (done) { request(server) .get('/users') .expect('Location', '/users/') - .expect(301, //, done) - }) + .expect(301, //, done); + }); it('should redirect directories with query string', function (done) { request(server) .get('/users?name=john') .expect('Location', '/users/?name=john') - .expect(301, done) - }) + .expect(301, done); + }); it('should not redirect to protocol-relative locations', function (done) { request(server) .get('//users') .expect('Location', '/users/') - .expect(301, done) - }) + .expect(301, done); + }); it('should ensure redirect URL is properly encoded', function (done) { request(server) .get('/snow') .expect('Location', '/snow%20%E2%98%83/') .expect('Content-Type', /html/) - .expect(301, />Redirecting to \/snow%20%E2%98%83\/<\/a>Redirecting to \/snow%20%E2%98%83\/<\/a>