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.
		
		
		
		
		
			
		
			
				
					
					
						
							331 lines
						
					
					
						
							6.4 KiB
						
					
					
				
			
		
		
	
	
							331 lines
						
					
					
						
							6.4 KiB
						
					
					
				/*! | 
						|
 * finalhandler | 
						|
 * Copyright(c) 2014-2017 Douglas Christopher Wilson | 
						|
 * MIT Licensed | 
						|
 */ | 
						|
 | 
						|
'use strict' | 
						|
 | 
						|
/** | 
						|
 * Module dependencies. | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
var debug = require('debug')('finalhandler') | 
						|
var encodeUrl = require('encodeurl') | 
						|
var escapeHtml = require('escape-html') | 
						|
var onFinished = require('on-finished') | 
						|
var parseUrl = require('parseurl') | 
						|
var statuses = require('statuses') | 
						|
var unpipe = require('unpipe') | 
						|
 | 
						|
/** | 
						|
 * Module variables. | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
var DOUBLE_SPACE_REGEXP = /\x20{2}/g | 
						|
var NEWLINE_REGEXP = /\n/g | 
						|
 | 
						|
/* istanbul ignore next */ | 
						|
var defer = typeof setImmediate === 'function' | 
						|
  ? setImmediate | 
						|
  : function (fn) { process.nextTick(fn.bind.apply(fn, arguments)) } | 
						|
var isFinished = onFinished.isFinished | 
						|
 | 
						|
/** | 
						|
 * Create a minimal HTML document. | 
						|
 * | 
						|
 * @param {string} message | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function createHtmlDocument (message) { | 
						|
  var body = escapeHtml(message) | 
						|
    .replace(NEWLINE_REGEXP, '<br>') | 
						|
    .replace(DOUBLE_SPACE_REGEXP, '  ') | 
						|
 | 
						|
  return '<!DOCTYPE html>\n' + | 
						|
    '<html lang="en">\n' + | 
						|
    '<head>\n' + | 
						|
    '<meta charset="utf-8">\n' + | 
						|
    '<title>Error</title>\n' + | 
						|
    '</head>\n' + | 
						|
    '<body>\n' + | 
						|
    '<pre>' + body + '</pre>\n' + | 
						|
    '</body>\n' + | 
						|
    '</html>\n' | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Module exports. | 
						|
 * @public | 
						|
 */ | 
						|
 | 
						|
module.exports = finalhandler | 
						|
 | 
						|
/** | 
						|
 * Create a function to handle the final response. | 
						|
 * | 
						|
 * @param {Request} req | 
						|
 * @param {Response} res | 
						|
 * @param {Object} [options] | 
						|
 * @return {Function} | 
						|
 * @public | 
						|
 */ | 
						|
 | 
						|
function finalhandler (req, res, options) { | 
						|
  var opts = options || {} | 
						|
 | 
						|
  // get environment | 
						|
  var env = opts.env || process.env.NODE_ENV || 'development' | 
						|
 | 
						|
  // get error callback | 
						|
  var onerror = opts.onerror | 
						|
 | 
						|
  return function (err) { | 
						|
    var headers | 
						|
    var msg | 
						|
    var status | 
						|
 | 
						|
    // ignore 404 on in-flight response | 
						|
    if (!err && headersSent(res)) { | 
						|
      debug('cannot 404 after headers sent') | 
						|
      return | 
						|
    } | 
						|
 | 
						|
    // unhandled error | 
						|
    if (err) { | 
						|
      // respect status code from error | 
						|
      status = getErrorStatusCode(err) | 
						|
 | 
						|
      if (status === undefined) { | 
						|
        // fallback to status code on response | 
						|
        status = getResponseStatusCode(res) | 
						|
      } else { | 
						|
        // respect headers from error | 
						|
        headers = getErrorHeaders(err) | 
						|
      } | 
						|
 | 
						|
      // get error message | 
						|
      msg = getErrorMessage(err, status, env) | 
						|
    } else { | 
						|
      // not found | 
						|
      status = 404 | 
						|
      msg = 'Cannot ' + req.method + ' ' + encodeUrl(getResourceName(req)) | 
						|
    } | 
						|
 | 
						|
    debug('default %s', status) | 
						|
 | 
						|
    // schedule onerror callback | 
						|
    if (err && onerror) { | 
						|
      defer(onerror, err, req, res) | 
						|
    } | 
						|
 | 
						|
    // cannot actually respond | 
						|
    if (headersSent(res)) { | 
						|
      debug('cannot %d after headers sent', status) | 
						|
      req.socket.destroy() | 
						|
      return | 
						|
    } | 
						|
 | 
						|
    // send response | 
						|
    send(req, res, status, headers, msg) | 
						|
  } | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Get headers from Error object. | 
						|
 * | 
						|
 * @param {Error} err | 
						|
 * @return {object} | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function getErrorHeaders (err) { | 
						|
  if (!err.headers || typeof err.headers !== 'object') { | 
						|
    return undefined | 
						|
  } | 
						|
 | 
						|
  var headers = Object.create(null) | 
						|
  var keys = Object.keys(err.headers) | 
						|
 | 
						|
  for (var i = 0; i < keys.length; i++) { | 
						|
    var key = keys[i] | 
						|
    headers[key] = err.headers[key] | 
						|
  } | 
						|
 | 
						|
  return headers | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Get message from Error object, fallback to status message. | 
						|
 * | 
						|
 * @param {Error} err | 
						|
 * @param {number} status | 
						|
 * @param {string} env | 
						|
 * @return {string} | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function getErrorMessage (err, status, env) { | 
						|
  var msg | 
						|
 | 
						|
  if (env !== 'production') { | 
						|
    // use err.stack, which typically includes err.message | 
						|
    msg = err.stack | 
						|
 | 
						|
    // fallback to err.toString() when possible | 
						|
    if (!msg && typeof err.toString === 'function') { | 
						|
      msg = err.toString() | 
						|
    } | 
						|
  } | 
						|
 | 
						|
  return msg || statuses[status] | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Get status code from Error object. | 
						|
 * | 
						|
 * @param {Error} err | 
						|
 * @return {number} | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function getErrorStatusCode (err) { | 
						|
  // check err.status | 
						|
  if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) { | 
						|
    return err.status | 
						|
  } | 
						|
 | 
						|
  // check err.statusCode | 
						|
  if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) { | 
						|
    return err.statusCode | 
						|
  } | 
						|
 | 
						|
  return undefined | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Get resource name for the request. | 
						|
 * | 
						|
 * This is typically just the original pathname of the request | 
						|
 * but will fallback to "resource" is that cannot be determined. | 
						|
 * | 
						|
 * @param {IncomingMessage} req | 
						|
 * @return {string} | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function getResourceName (req) { | 
						|
  try { | 
						|
    return parseUrl.original(req).pathname | 
						|
  } catch (e) { | 
						|
    return 'resource' | 
						|
  } | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Get status code from response. | 
						|
 * | 
						|
 * @param {OutgoingMessage} res | 
						|
 * @return {number} | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function getResponseStatusCode (res) { | 
						|
  var status = res.statusCode | 
						|
 | 
						|
  // default status code to 500 if outside valid range | 
						|
  if (typeof status !== 'number' || status < 400 || status > 599) { | 
						|
    status = 500 | 
						|
  } | 
						|
 | 
						|
  return status | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Determine if the response headers have been sent. | 
						|
 * | 
						|
 * @param {object} res | 
						|
 * @returns {boolean} | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function headersSent (res) { | 
						|
  return typeof res.headersSent !== 'boolean' | 
						|
    ? Boolean(res._header) | 
						|
    : res.headersSent | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Send response. | 
						|
 * | 
						|
 * @param {IncomingMessage} req | 
						|
 * @param {OutgoingMessage} res | 
						|
 * @param {number} status | 
						|
 * @param {object} headers | 
						|
 * @param {string} message | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function send (req, res, status, headers, message) { | 
						|
  function write () { | 
						|
    // response body | 
						|
    var body = createHtmlDocument(message) | 
						|
 | 
						|
    // response status | 
						|
    res.statusCode = status | 
						|
    res.statusMessage = statuses[status] | 
						|
 | 
						|
    // response headers | 
						|
    setHeaders(res, headers) | 
						|
 | 
						|
    // security headers | 
						|
    res.setHeader('Content-Security-Policy', "default-src 'none'") | 
						|
    res.setHeader('X-Content-Type-Options', 'nosniff') | 
						|
 | 
						|
    // standard headers | 
						|
    res.setHeader('Content-Type', 'text/html; charset=utf-8') | 
						|
    res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8')) | 
						|
 | 
						|
    if (req.method === 'HEAD') { | 
						|
      res.end() | 
						|
      return | 
						|
    } | 
						|
 | 
						|
    res.end(body, 'utf8') | 
						|
  } | 
						|
 | 
						|
  if (isFinished(req)) { | 
						|
    write() | 
						|
    return | 
						|
  } | 
						|
 | 
						|
  // unpipe everything from the request | 
						|
  unpipe(req) | 
						|
 | 
						|
  // flush the request | 
						|
  onFinished(req, write) | 
						|
  req.resume() | 
						|
} | 
						|
 | 
						|
/** | 
						|
 * Set response headers from an object. | 
						|
 * | 
						|
 * @param {OutgoingMessage} res | 
						|
 * @param {object} headers | 
						|
 * @private | 
						|
 */ | 
						|
 | 
						|
function setHeaders (res, headers) { | 
						|
  if (!headers) { | 
						|
    return | 
						|
  } | 
						|
 | 
						|
  var keys = Object.keys(headers) | 
						|
  for (var i = 0; i < keys.length; i++) { | 
						|
    var key = keys[i] | 
						|
    res.setHeader(key, headers[key]) | 
						|
  } | 
						|
}
 | 
						|
 |