index.js 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293
  1. /*!
  2. * finalhandler
  3. * Copyright(c) 2014-2022 Douglas Christopher Wilson
  4. * MIT Licensed
  5. */
  6. 'use strict'
  7. /**
  8. * Module dependencies.
  9. * @private
  10. */
  11. var debug = require('debug')('finalhandler')
  12. var encodeUrl = require('encodeurl')
  13. var escapeHtml = require('escape-html')
  14. var onFinished = require('on-finished')
  15. var parseUrl = require('parseurl')
  16. var statuses = require('statuses')
  17. /**
  18. * Module variables.
  19. * @private
  20. */
  21. var isFinished = onFinished.isFinished
  22. /**
  23. * Create a minimal HTML document.
  24. *
  25. * @param {string} message
  26. * @private
  27. */
  28. function createHtmlDocument (message) {
  29. var body = escapeHtml(message)
  30. .replaceAll('\n', '<br>')
  31. .replaceAll(' ', ' &nbsp;')
  32. return '<!DOCTYPE html>\n' +
  33. '<html lang="en">\n' +
  34. '<head>\n' +
  35. '<meta charset="utf-8">\n' +
  36. '<title>Error</title>\n' +
  37. '</head>\n' +
  38. '<body>\n' +
  39. '<pre>' + body + '</pre>\n' +
  40. '</body>\n' +
  41. '</html>\n'
  42. }
  43. /**
  44. * Module exports.
  45. * @public
  46. */
  47. module.exports = finalhandler
  48. /**
  49. * Create a function to handle the final response.
  50. *
  51. * @param {Request} req
  52. * @param {Response} res
  53. * @param {Object} [options]
  54. * @return {Function}
  55. * @public
  56. */
  57. function finalhandler (req, res, options) {
  58. var opts = options || {}
  59. // get environment
  60. var env = opts.env || process.env.NODE_ENV || 'development'
  61. // get error callback
  62. var onerror = opts.onerror
  63. return function (err) {
  64. var headers
  65. var msg
  66. var status
  67. // ignore 404 on in-flight response
  68. if (!err && res.headersSent) {
  69. debug('cannot 404 after headers sent')
  70. return
  71. }
  72. // unhandled error
  73. if (err) {
  74. // respect status code from error
  75. status = getErrorStatusCode(err)
  76. if (status === undefined) {
  77. // fallback to status code on response
  78. status = getResponseStatusCode(res)
  79. } else {
  80. // respect headers from error
  81. headers = getErrorHeaders(err)
  82. }
  83. // get error message
  84. msg = getErrorMessage(err, status, env)
  85. } else {
  86. // not found
  87. status = 404
  88. msg = 'Cannot ' + req.method + ' ' + encodeUrl(getResourceName(req))
  89. }
  90. debug('default %s', status)
  91. // schedule onerror callback
  92. if (err && onerror) {
  93. setImmediate(onerror, err, req, res)
  94. }
  95. // cannot actually respond
  96. if (res.headersSent) {
  97. debug('cannot %d after headers sent', status)
  98. if (req.socket) {
  99. req.socket.destroy()
  100. }
  101. return
  102. }
  103. // send response
  104. send(req, res, status, headers, msg)
  105. }
  106. }
  107. /**
  108. * Get headers from Error object.
  109. *
  110. * @param {Error} err
  111. * @return {object}
  112. * @private
  113. */
  114. function getErrorHeaders (err) {
  115. if (!err.headers || typeof err.headers !== 'object') {
  116. return undefined
  117. }
  118. return { ...err.headers }
  119. }
  120. /**
  121. * Get message from Error object, fallback to status message.
  122. *
  123. * @param {Error} err
  124. * @param {number} status
  125. * @param {string} env
  126. * @return {string}
  127. * @private
  128. */
  129. function getErrorMessage (err, status, env) {
  130. var msg
  131. if (env !== 'production') {
  132. // use err.stack, which typically includes err.message
  133. msg = err.stack
  134. // fallback to err.toString() when possible
  135. if (!msg && typeof err.toString === 'function') {
  136. msg = err.toString()
  137. }
  138. }
  139. return msg || statuses.message[status]
  140. }
  141. /**
  142. * Get status code from Error object.
  143. *
  144. * @param {Error} err
  145. * @return {number}
  146. * @private
  147. */
  148. function getErrorStatusCode (err) {
  149. // check err.status
  150. if (typeof err.status === 'number' && err.status >= 400 && err.status < 600) {
  151. return err.status
  152. }
  153. // check err.statusCode
  154. if (typeof err.statusCode === 'number' && err.statusCode >= 400 && err.statusCode < 600) {
  155. return err.statusCode
  156. }
  157. return undefined
  158. }
  159. /**
  160. * Get resource name for the request.
  161. *
  162. * This is typically just the original pathname of the request
  163. * but will fallback to "resource" is that cannot be determined.
  164. *
  165. * @param {IncomingMessage} req
  166. * @return {string}
  167. * @private
  168. */
  169. function getResourceName (req) {
  170. try {
  171. return parseUrl.original(req).pathname
  172. } catch (e) {
  173. return 'resource'
  174. }
  175. }
  176. /**
  177. * Get status code from response.
  178. *
  179. * @param {OutgoingMessage} res
  180. * @return {number}
  181. * @private
  182. */
  183. function getResponseStatusCode (res) {
  184. var status = res.statusCode
  185. // default status code to 500 if outside valid range
  186. if (typeof status !== 'number' || status < 400 || status > 599) {
  187. status = 500
  188. }
  189. return status
  190. }
  191. /**
  192. * Send response.
  193. *
  194. * @param {IncomingMessage} req
  195. * @param {OutgoingMessage} res
  196. * @param {number} status
  197. * @param {object} headers
  198. * @param {string} message
  199. * @private
  200. */
  201. function send (req, res, status, headers, message) {
  202. function write () {
  203. // response body
  204. var body = createHtmlDocument(message)
  205. // response status
  206. res.statusCode = status
  207. if (req.httpVersionMajor < 2) {
  208. res.statusMessage = statuses.message[status]
  209. }
  210. // remove any content headers
  211. res.removeHeader('Content-Encoding')
  212. res.removeHeader('Content-Language')
  213. res.removeHeader('Content-Range')
  214. // response headers
  215. for (const [key, value] of Object.entries(headers ?? {})) {
  216. res.setHeader(key, value)
  217. }
  218. // security headers
  219. res.setHeader('Content-Security-Policy', "default-src 'none'")
  220. res.setHeader('X-Content-Type-Options', 'nosniff')
  221. // standard headers
  222. res.setHeader('Content-Type', 'text/html; charset=utf-8')
  223. res.setHeader('Content-Length', Buffer.byteLength(body, 'utf8'))
  224. if (req.method === 'HEAD') {
  225. res.end()
  226. return
  227. }
  228. res.end(body, 'utf8')
  229. }
  230. if (isFinished(req)) {
  231. write()
  232. return
  233. }
  234. // unpipe everything from the request
  235. req.unpipe()
  236. // flush the request
  237. onFinished(req, write)
  238. req.resume()
  239. }