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