You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
|
|
/*! * serve-static * Copyright(c) 2010 Sencha Inc. * Copyright(c) 2011 TJ Holowaychuk * Copyright(c) 2014-2016 Douglas Christopher Wilson * MIT Licensed */
'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')
/** * Module exports. * @public */
module.exports = serveStatic module.exports.mime = send.mime
/** * @param {string} root * @param {object} [options] * @return {function} * @public */
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
var opts = Object.create(options || null)
// fall-though
var fallthrough = opts.fallthrough !== false
// default redirect
var redirect = opts.redirect !== false
// headers listener
var setHeaders = opts.setHeaders
if (setHeaders && typeof setHeaders !== 'function') { throw new TypeError('option setHeaders must be function') }
// setup options for send
opts.maxage = opts.maxage || opts.maxAge || 0 opts.root = resolve(root)
// construct directory listener
var onDirectory = redirect ? createRedirectDirectoryListener() : createNotFoundDirectoryListener()
return function serveStatic (req, res, next) { if (req.method !== 'GET' && req.method !== 'HEAD') { if (fallthrough) { return next() }
// method not allowed
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
// make sure redirect occurs at mount
if (path === '/' && originalUrl.pathname.substr(-1) !== '/') { path = '' }
// create send stream
var stream = send(req, path, opts)
// add directory handler
stream.on('directory', onDirectory)
// add headers listener
if (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 }) }
// forward errors
stream.on('error', function error (err) { if (forwardError || !(err.statusCode < 500)) { next(err) return }
next() })
// pipe
stream.pipe(res) } }
/** * Collapse all leading slashes into a single slash * @private */ function collapseLeadingSlashes (str) { for (var i = 0; i < str.length; i++) { if (str.charCodeAt(i) !== 0x2f /* / */) { break } }
return i > 1 ? '/' + str.substr(i) : str }
/** * Create a minimal HTML document. * * @param {string} title * @param {string} body * @private */
function createHtmlDocument (title, body) { return '<!DOCTYPE html>\n' + '<html lang="en">\n' + '<head>\n' + '<meta charset="utf-8">\n' + '<title>' + title + '</title>\n' + '</head>\n' + '<body>\n' + '<pre>' + body + '</pre>\n' + '</body>\n' + '</html>\n' }
/** * Create a directory listener that just 404s. * @private */
function createNotFoundDirectoryListener () { return function notFound () { this.error(404) } }
/** * Create a directory listener that performs a redirect. * @private */
function createRedirectDirectoryListener () { return function redirect (res) { if (this.hasTrailingSlash()) { this.error(404) return }
// get original URL
var originalUrl = parseUrl.original(this.req)
// append trailing slash
originalUrl.path = null originalUrl.pathname = collapseLeadingSlashes(originalUrl.pathname + '/')
// reformat the URL
var loc = encodeUrl(url.format(originalUrl)) var doc = createHtmlDocument('Redirecting', 'Redirecting to <a href="' + escapeHtml(loc) + '">' + escapeHtml(loc) + '</a>')
// 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 'self'") res.setHeader('X-Content-Type-Options', 'nosniff') res.setHeader('Location', loc) res.end(doc) } }
|