| /*! |
| * 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() |
| } |
| |
| // append trailing slash |
| originalUrl.path = null |
| originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/') |
| |
| // reformat the URL |
| var target = url.format(originalUrl) |
| |
| // send redirect response |
| res.statusCode = 303 |
| res.setHeader('Content-Type', 'text/html; charset=utf-8') |
| res.setHeader('Location', target) |
| res.end('Redirecting to <a href="' + escapeHtml(target) + '">' + escapeHtml(target) + '</a>\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 |
| |
| /** |
| * Collapse all leading slashes into a single slash |
| * @private |
| */ |
| function collapseLeadingSlashes(str) { |
| for (var i = 0; i < str.length; i++) { |
| if (str[i] !== '/') { |
| break |
| } |
| } |
| |
| return i > 1 |
| ? '/' + str.substr(i) |
| : str |
| } |