diff --git a/lib/index.js b/lib/index.js index 19029e5..7db5996 100644 --- a/lib/index.js +++ b/lib/index.js @@ -5,45 +5,66 @@ const path = require('path'); const url = require('fast-url-parser'); const fs = require('fs-extra'); const slasher = require('glob-slasher'); +const minimatch = require('minimatch'); const pathToRegExp = require('path-to-regexp'); +const mime = require('mime/lite'); const getHandlers = methods => { - const {createReadStream} = fs; + const {stat, createReadStream} = fs; return Object.assign({ + stat, createReadStream }, methods); }; -const toRegExp = (location, keys = null) => { - const normalized = slasher(location).replace('*', '(.*)'); - return pathToRegExp(normalized, keys); +const sourceMatches = (source, requestPath, allowSegments) => { + const keys = []; + const slashed = slasher(source); + + let results = null; + + if (allowSegments) { + const normalized = slashed.replace('*', '(.*)'); + const expression = pathToRegExp(normalized, keys); + + results = expression.exec(requestPath); + } + + if (results || minimatch(requestPath, slashed)) { + return { + keys, + results + }; + } + + return null; }; const toTarget = (source, destination, previousPath) => { - const keys = []; - const expression = toRegExp(source, keys); - const results = expression.exec(previousPath); - - if (results) { - const props = {}; - const {protocol} = url.parse(destination); - const normalizedDest = protocol ? destination : slasher(destination); - const toPath = pathToRegExp.compile(normalizedDest); - - for (let index = 0; index < keys.length; index++) { - const {name} = keys[index]; - props[name] = results[index + 1]; - } + const matches = sourceMatches(source, previousPath, true); - return toPath(props); + if (!matches) { + return null; } - return null; + const {keys, results} = matches; + + const props = {}; + const {protocol} = url.parse(destination); + const normalizedDest = protocol ? destination : slasher(destination); + const toPath = pathToRegExp.compile(normalizedDest); + + for (let index = 0; index < keys.length; index++) { + const {name} = keys[index]; + props[name] = results[index + 1]; + } + + return toPath(props); }; -const applyRewrites = (requestPath, rewrites) => { - if (!Array.isArray(rewrites)) { +const applyRewrites = (requestPath, rewrites = []) => { + if (rewrites.length === 0) { return requestPath; } @@ -59,8 +80,8 @@ const applyRewrites = (requestPath, rewrites) => { return requestPath; }; -const applyRedirect = (rewrittenURL, redirects) => { - if (!Array.isArray(redirects)) { +const applyRedirect = (rewrittenURL, redirects = []) => { + if (redirects.length === 0) { return null; } @@ -81,6 +102,40 @@ const applyRedirect = (rewrittenURL, redirects) => { return null; }; +const appendHeaders = (target, source) => { + for (let index = 0; index < source.length; index++) { + const {key, value} = source[index]; + target[key] = value; + } +}; + +const getHeaders = async (handlers, customHeaders = [], {relative, absolute}) => { + const related = {}; + + if (customHeaders.length > 0) { + // By iterating over all headers and never stopping, developers + // can specify multiple header sources in the config that + // might match a single path. + for (let index = 0; index < customHeaders.length; index++) { + const {source, headers} = customHeaders[index]; + + if (sourceMatches(source, relative)) { + appendHeaders(related, headers); + } + } + } + + const stats = await handlers.stat(absolute); + + const defaultHeaders = { + 'Content-Type': mime.getType(relative), + 'Last-Modified': stats.mtime.toUTCString(), + 'Content-Length': stats.size + }; + + return Object.assign(defaultHeaders, related); +}; + module.exports = async (request, response, config = {}, methods) => { const cwd = process.cwd(); const current = config.path ? path.join(cwd, config.path) : cwd; @@ -102,7 +157,12 @@ module.exports = async (request, response, config = {}, methods) => { const relatedExists = await fs.exists(related); if (relatedExists) { - // response.writeHead(200, getHeaders(related)); + const headers = await getHeaders(handlers, config.headers, { + relative: rewrittenURL, + absolute: related + }); + + response.writeHead(200, headers); handlers.createReadStream(related).pipe(response); return; diff --git a/package.json b/package.json index dfb1c26..31d5608 100644 --- a/package.json +++ b/package.json @@ -34,6 +34,7 @@ "fs-extra": "6.0.1", "glob-slasher": "1.0.1", "mime": "2.3.1", + "minimatch": "3.0.4", "path-to-regexp": "2.2.1" } } diff --git a/yarn.lock b/yarn.lock index 47ea38b..bf4c38a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -578,7 +578,7 @@ mimic-fn@^1.0.0: version "1.2.0" resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022" -minimatch@^3.0.2, minimatch@^3.0.4: +minimatch@3.0.4, minimatch@^3.0.2, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" dependencies: