The LM Control website. Simple yet efficient.
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

index.js 6.4KB

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