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.
		
		
		
		
			
				
					211 lines
				
				4.5 KiB
			
		
		
			
		
	
	
					211 lines
				
				4.5 KiB
			| 
								 
											4 years ago
										 
									 | 
							
								/*!
							 | 
						||
| 
								 | 
							
								 * 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)
							 | 
						||
| 
								 | 
							
								  }
							 | 
						||
| 
								 | 
							
								}
							 |