diff --git a/lib/core/defaults.json b/lib/core/defaults.json deleted file mode 100644 index 3afa837b..00000000 --- a/lib/core/defaults.json +++ /dev/null @@ -1,22 +0,0 @@ -{ - "autoIndex": true, - "showDir": true, - "dirOverrides404": false, - "showDotfiles": true, - "humanReadable": true, - "hidePermissions": false, - "si": false, - "cache": "max-age=3600", - "coop": false, - "cors": false, - "privateNetworkAccess": false, - "gzip": true, - "brotli": false, - "forceContentEncoding": false, - "defaultExt": ".html", - "handleError": true, - "contentType": "application/octet-stream", - "weakEtags": true, - "weakCompare": true, - "handleOptionsMethod": false -} diff --git a/lib/core/index.js b/lib/core/index.js index a3d296ef..e386c541 100644 --- a/lib/core/index.js +++ b/lib/core/index.js @@ -103,19 +103,9 @@ module.exports = function createMiddleware(_dir, _options) { const root = path.join(path.resolve(dir), '/'); const opts = optsParser(options); - const cache = opts.cache; - const autoIndex = opts.autoIndex; - const baseDir = opts.baseDir; - let defaultExt = opts.defaultExt; - const handleError = opts.handleError; - const headers = opts.headers; - const weakEtags = opts.weakEtags; - const handleOptionsMethod = opts.handleOptionsMethod; opts.root = dir; - if (defaultExt && /^\./.test(defaultExt)) { - defaultExt = defaultExt.replace(/^\./, ''); - } + // Support hashes and .types files in mimeTypes @since 0.8 if (opts.mimeTypes) { @@ -197,7 +187,7 @@ module.exports = function createMiddleware(_dir, _options) { file = path.normalize( path.join( root, - path.relative(path.join('/', baseDir), pathname) + path.relative(path.join('/', opts.baseDir), pathname) ) ); // determine compressed forms if they were to exist, make sure to handle pre-compressed files, i.e. files with .br/.gz extension. we will serve them "as-is" @@ -209,11 +199,11 @@ module.exports = function createMiddleware(_dir, _options) { if ( file.endsWith('.br') ) brotliFile = file; } - Object.keys(headers).forEach((key) => { - res.setHeader(key, headers[key]); + Object.keys(opts.headers).forEach((key) => { + res.setHeader(key, opts.headers[key]); }); - if (req.method === 'OPTIONS' && handleOptionsMethod) { + if (req.method === 'OPTIONS' && opts.handleOptionsMethod) { res.end(); return; } @@ -238,8 +228,8 @@ module.exports = function createMiddleware(_dir, _options) { let contentType = mime.lookup(file, defaultType); const range = (req.headers && req.headers.range); const lastModified = (new Date(stat.mtime)).toUTCString(); - const etag = generateEtag(stat, weakEtags); - let cacheControl = cache; + const etag = generateEtag(stat, opts.weakEtags); + let cacheControl = opts.cache; let stream = null; if (contentType && isTextFile(contentType)) { if (stat.size < buffer.constants.MAX_LENGTH) { @@ -266,7 +256,7 @@ module.exports = function createMiddleware(_dir, _options) { } if (typeof cacheControl === 'function') { - cacheControl = cache(pathname); + cacheControl = opts.cache(pathname); } if (typeof cacheControl === 'number') { cacheControl = `max-age=${cacheControl}`; @@ -374,11 +364,11 @@ module.exports = function createMiddleware(_dir, _options) { // This means we're already trying ./404.html and can not find it. // So send plain text response with 404 status code status[404](res, next); - } else if (!path.extname(parsed.pathname).length && defaultExt) { + } else if (!path.extname(parsed.pathname).length && opts.defaultExt) { // If there is no file extension in the path and we have a default // extension try filename and default extension combination before rendering 404.html. middleware({ - url: `${parsed.pathname}.${defaultExt}${(parsed.search) ? parsed.search : ''}`, + url: `${parsed.pathname}.${opts.defaultExt}${(parsed.search) ? parsed.search : ''}`, headers: req.headers, }, res, next); } else if (opts.showDir && opts.dirOverrides404) { @@ -388,7 +378,7 @@ module.exports = function createMiddleware(_dir, _options) { return; } else { // Try to serve default ./404.html - const rawUrl = (handleError ? `/${path.join(baseDir, `404.${defaultExt}`)}` : req.url); + const rawUrl = (opts.handleError ? `/${path.join(opts.baseDir, `404.${opts.defaultExt}`)}` : req.url); const encodedUrl = ensureUriEncoded(rawUrl); middleware({ url: encodedUrl, @@ -399,7 +389,7 @@ module.exports = function createMiddleware(_dir, _options) { } else if (err) { status[500](res, next, { error: err }); } else if (stat.isDirectory()) { - if (!autoIndex && !opts.showDir) { + if (!opts.autoIndex && !opts.showDir) { status[404](res, next); return; } @@ -417,11 +407,11 @@ module.exports = function createMiddleware(_dir, _options) { return; } - if (autoIndex) { + if (opts.autoIndex) { middleware({ url: urlJoin( encodeURIComponent(pathname), - `/index.${defaultExt}` + `/index.${opts.defaultExt}` ), headers: req.headers, }, res, (autoIndexError) => { diff --git a/lib/core/opts.js b/lib/core/opts.js index fae772c8..d042c406 100644 --- a/lib/core/opts.js +++ b/lib/core/opts.js @@ -1,30 +1,65 @@ 'use strict'; -// This is so you can have options aliasing and defaults in one place. - -const defaults = require('./defaults.json'); const aliases = require('./aliases.json'); + +/** + * @typedef {Object} ParsedOptions + * @property {boolean} autoIndex + * @property {boolean} showDir + * @property {boolean} dirOverrides404 + * @property {boolean} showDotfiles + * @property {boolean} humanReadable + * @property {boolean} hidePermissions + * @property {boolean} si + * @property {string|function} cache + * @property {string} defaultExt + * @property {string} baseDir + * @property {boolean} gzip + * @property {boolean} brotli + * @property {boolean} forceContentEncoding + * @property {function} handleError + * @property {Object.} headers + * @property {string} contentType + * @property {Object|undefined} mimeTypes + * @property {boolean} weakEtags + * @property {boolean} weakCompare + * @property {boolean} handleOptionsMethod + */ + + +/** +* Converts a user-provided options object into a ParsedOptions object +* @param {object} opts - User provided options +* @returns {ParsedOptions} +*/ module.exports = (opts) => { - let autoIndex = defaults.autoIndex; - let showDir = defaults.showDir; - let dirOverrides404 = defaults.dirOverrides404; - let showDotfiles = defaults.showDotfiles; - let humanReadable = defaults.humanReadable; - let hidePermissions = defaults.hidePermissions; - let si = defaults.si; - let cache = defaults.cache; - let gzip = defaults.gzip; - let brotli = defaults.brotli; - let forceContentEncoding = defaults.forceContentEncoding; - let defaultExt = defaults.defaultExt; - let handleError = defaults.handleError; - const headers = {}; - let contentType = defaults.contentType; - let mimeTypes; - let weakEtags = defaults.weakEtags; - let weakCompare = defaults.weakCompare; - let handleOptionsMethod = defaults.handleOptionsMethod; + /** @type {ParsedOptions} */ + const options = { + autoIndex: true, + showDir: true, + dirOverrides404: false, + showDotfiles: true, + humanReadable: true, + hidePermissions: false, + si: false, + cache: "max-age=3600", + coop: false, + cors: false, + privateNetworkAccess: false, + gzip: true, + brotli: false, + forceContentEncoding: false, + defaultExt: "html", + baseDir: "/", + handleError: true, + contentType: "application/octet-stream", + weakEtags: true, + weakCompare: true, + handleOptionsMethod: false, + headers: {}, + mimeTypes: undefined, + }; function isDeclared(k) { return typeof opts[k] !== 'undefined' && opts[k] !== null; @@ -39,12 +74,12 @@ module.exports = (opts) => { function addHeader(key, value) { validateNoCRLF(key); validateNoCRLF(value); - headers[key] = value; + options.headers[key] = value; } function setHeader(str) { validateNoCRLF(str); - + const m = /^(.+?)\s*:\s*(.*)$/.exec(str); if (!m) { addHeader(str, true); // Use addHeader instead of direct assignment @@ -57,7 +92,7 @@ module.exports = (opts) => { if (opts) { aliases.autoIndex.some((k) => { if (isDeclared(k)) { - autoIndex = opts[k]; + options.autoIndex = opts[k]; return true; } return false; @@ -65,7 +100,7 @@ module.exports = (opts) => { aliases.showDir.some((k) => { if (isDeclared(k)) { - showDir = opts[k]; + options.showDir = opts[k]; return true; } return false; @@ -73,7 +108,7 @@ module.exports = (opts) => { aliases.dirOverrides404.some((k) => { if (isDeclared(k)) { - dirOverrides404 = opts[k]; + options.dirOverrides404 = opts[k]; return true; } return false; @@ -81,7 +116,7 @@ module.exports = (opts) => { aliases.showDotfiles.some((k) => { if (isDeclared(k)) { - showDotfiles = opts[k]; + options.showDotfiles = opts[k]; return true; } return false; @@ -89,7 +124,7 @@ module.exports = (opts) => { aliases.humanReadable.some((k) => { if (isDeclared(k)) { - humanReadable = opts[k]; + options.humanReadable = opts[k]; return true; } return false; @@ -97,7 +132,7 @@ module.exports = (opts) => { aliases.hidePermissions.some((k) => { if (isDeclared(k)) { - hidePermissions = opts[k]; + options.hidePermissions = opts[k]; return true; } return false; @@ -105,40 +140,49 @@ module.exports = (opts) => { aliases.si.some((k) => { if (isDeclared(k)) { - si = opts[k]; + options.si = opts[k]; return true; } return false; }); if (opts.defaultExt && typeof opts.defaultExt === 'string') { - defaultExt = opts.defaultExt; + let ext = opts.defaultExt; + // Remove the leading dot if it exists + if (/^\./.test(ext)) { + ext = ext.replace(/^\./, ''); + } + options.defaultExt = ext; } if (typeof opts.cache !== 'undefined' && opts.cache !== null) { if (typeof opts.cache === 'string') { - cache = opts.cache; + options.cache = opts.cache; } else if (typeof opts.cache === 'number') { - cache = `max-age=${opts.cache}`; + options.cache = `max-age=${opts.cache}`; } else if (typeof opts.cache === 'function') { - cache = opts.cache; + options.cache = opts.cache; } } if (typeof opts.gzip !== 'undefined' && opts.gzip !== null) { - gzip = opts.gzip; + options.gzip = opts.gzip; } if (typeof opts.brotli !== 'undefined' && opts.brotli !== null) { - brotli = opts.brotli; + options.brotli = opts.brotli; } if (typeof opts.forceContentEncoding !== 'undefined' && opts.forceContentEncoding !== null) { - forceContentEncoding = opts.forceContentEncoding; + options.forceContentEncoding = opts.forceContentEncoding; + } + + if (typeof opts.baseDir !== 'undefined' && opts.baseDir !== null) { + options.baseDir = opts.baseDir; } aliases.handleError.some((k) => { if (isDeclared(k)) { - handleError = opts[k]; + options.handleError = opts[k]; return true; } return false; @@ -146,23 +190,23 @@ module.exports = (opts) => { aliases.coop.forEach((k) => { if (isDeclared(k) && opts[k]) { - handleOptionsMethod = true; - headers['Cross-Origin-Opener-Policy'] = 'same-origin'; - headers['Cross-Origin-Embedder-Policy'] = 'require-corp'; + options.handleOptionsMethod = true; + options.headers['Cross-Origin-Opener-Policy'] = 'same-origin'; + options.headers['Cross-Origin-Embedder-Policy'] = 'require-corp'; } }); aliases.cors.forEach((k) => { if (isDeclared(k) && opts[k]) { - handleOptionsMethod = true; - headers['Access-Control-Allow-Origin'] = '*'; - headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since'; + options.handleOptionsMethod = true; + options.headers['Access-Control-Allow-Origin'] = '*'; + options.headers['Access-Control-Allow-Headers'] = 'Authorization, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since'; } }); aliases.privateNetworkAccess.forEach((k) => { if (isDeclared(k) && opts[k]) { - headers['Access-Control-Allow-Private-Network'] = 'true'; + options.headers['Access-Control-Allow-Private-Network'] = 'true'; } }); @@ -182,7 +226,7 @@ module.exports = (opts) => { aliases.contentType.some((k) => { if (isDeclared(k)) { - contentType = opts[k]; + options.contentType = opts[k]; return true; } return false; @@ -190,7 +234,7 @@ module.exports = (opts) => { aliases.mimeType.some((k) => { if (isDeclared(k)) { - mimeTypes = opts[k]; + options.mimeTypes = opts[k]; return true; } return false; @@ -198,7 +242,7 @@ module.exports = (opts) => { aliases.weakEtags.some((k) => { if (isDeclared(k)) { - weakEtags = opts[k]; + options.weakEtags = opts[k]; return true; } return false; @@ -206,7 +250,7 @@ module.exports = (opts) => { aliases.weakCompare.some((k) => { if (isDeclared(k)) { - weakCompare = opts[k]; + options.weakCompare = opts[k]; return true; } return false; @@ -214,33 +258,12 @@ module.exports = (opts) => { aliases.handleOptionsMethod.some((k) => { if (isDeclared(k)) { - handleOptionsMethod = handleOptionsMethod || opts[k]; + options.handleOptionsMethod = options.handleOptionsMethod || opts[k]; return true; } return false; }); } - return { - cache, - autoIndex, - showDir, - dirOverrides404, - showDotfiles, - humanReadable, - hidePermissions, - si, - defaultExt, - baseDir: (opts && opts.baseDir) || '/', - gzip, - brotli, - forceContentEncoding, - handleError, - headers, - contentType, - mimeTypes, - weakEtags, - weakCompare, - handleOptionsMethod, - }; + return options; };