3 * Copyright(c) 2010 Sencha Inc.
4 * Copyright(c) 2011 TJ Holowaychuk
5 * Copyright(c) 2014 Douglas Christopher Wilson
10 * Module dependencies.
13 var escapeHtml = require('escape-html');
14 var merge = require('utils-merge');
15 var parseurl = require('parseurl');
16 var resolve = require('path').resolve;
17 var send = require('send');
18 var url = require('url');
21 * @param {String} root
22 * @param {Object} options
27 exports = module.exports = function serveStatic(root, options) {
29 throw new TypeError('root path required')
32 if (typeof root !== 'string') {
33 throw new TypeError('root path must be a string')
36 // copy options object
37 options = merge({}, options)
39 // resolve root to absolute
43 var redirect = options.redirect !== false
46 var setHeaders = options.setHeaders
47 delete options.setHeaders
49 if (setHeaders && typeof setHeaders !== 'function') {
50 throw new TypeError('option setHeaders must be function')
53 // setup options for send
54 options.maxage = options.maxage || options.maxAge || 0
57 return function serveStatic(req, res, next) {
58 if (req.method !== 'GET' && req.method !== 'HEAD') {
62 var opts = merge({}, options)
63 var originalUrl = parseurl.original(req)
64 var path = parseurl(req).pathname
65 var hasTrailingSlash = originalUrl.pathname[originalUrl.pathname.length - 1] === '/'
67 if (path === '/' && !hasTrailingSlash) {
68 // make sure redirect occurs at mount
73 var stream = send(req, path, opts)
76 // redirect relative to originalUrl
77 stream.on('directory', function redirect() {
78 if (hasTrailingSlash) {
82 // append trailing slash
83 originalUrl.path = null
84 originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
87 var target = url.format(originalUrl)
89 // send redirect response
91 res.setHeader('Content-Type', 'text/html; charset=utf-8')
92 res.setHeader('Location', target)
93 res.end('Redirecting to <a href="' + escapeHtml(target) + '">' + escapeHtml(target) + '</a>\n')
96 // forward to next middleware on directory
97 stream.on('directory', next)
100 // add headers listener
102 stream.on('headers', setHeaders)
105 // forward non-404 errors
106 stream.on('error', function error(err) {
107 next(err.status === 404 ? null : err)
116 * Expose mime module.
118 * If you wish to extend the mime table use this
119 * reference to the "mime" module in the npm registry.
122 exports.mime = send.mime
125 * Collapse all leading slashes into a single slash
128 function collapseLeadingSlashes(str) {
129 for (var i = 0; i < str.length; i++) {
130 if (str[i] !== '/') {
136 ? '/' + str.substr(i)