pax_global_header00006660000000000000000000000064134651555110014520gustar00rootroot0000000000000052 comment=15e78cab32ecbd4993d1575a065963b238336df9 finalhandler-1.1.2/000077500000000000000000000000001346515551100141505ustar00rootroot00000000000000finalhandler-1.1.2/.eslintignore000066400000000000000000000000261346515551100166510ustar00rootroot00000000000000coverage node_modules finalhandler-1.1.2/.eslintrc.yml000066400000000000000000000000351346515551100165720ustar00rootroot00000000000000root: true extends: standard finalhandler-1.1.2/.gitignore000066400000000000000000000000701346515551100161350ustar00rootroot00000000000000coverage/ node_modules/ npm-debug.log package-lock.json finalhandler-1.1.2/.travis.yml000066400000000000000000000055231346515551100162660ustar00rootroot00000000000000language: node_js node_js: - "0.8" - "0.10" - "0.12" - "1.8" - "2.5" - "3.3" - "4.9" - "5.12" - "6.17" - "7.10" - "8.16" - "9.11" - "10.15" - "11.15" - "12.2" sudo: false cache: directories: - node_modules before_install: - | # Setup utility functions function node_version_lt () { [[ "$(v "$TRAVIS_NODE_VERSION")" -lt "$(v "${1}")" ]] } function npm_module_installed () { npm -lsp ls | grep -Fq "$(pwd)/node_modules/${1}:${1}@" } function npm_remove_module_re () { node -e ' fs = require("fs"); p = JSON.parse(fs.readFileSync("package.json", "utf8")); r = RegExp(process.argv[1]); for (k in p.devDependencies) { if (r.test(k)) delete p.devDependencies[k]; } fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); ' "$@" } function npm_use_module () { node -e ' fs = require("fs"); p = JSON.parse(fs.readFileSync("package.json", "utf8")); p.devDependencies[process.argv[1]] = process.argv[2]; fs.writeFileSync("package.json", JSON.stringify(p, null, 2) + "\n"); ' "$@" } function v () { tr '.' '\n' <<< "${1}" \ | awk '{ printf "%03d", $0 }' \ | sed 's/^0*//' } # Configure npm - | # Skip updating shrinkwrap / lock npm config set shrinkwrap false # Setup Node.js version-specific dependencies - | # Configure eslint for linting if node_version_lt '6.0'; then npm_remove_module_re '^eslint(-|$)' fi - | # Configure istanbul for coverage if node_version_lt '0.10'; then npm_remove_module_re '^istanbul$' fi - | # Configure mocha for testing if node_version_lt '0.10'; then npm_use_module 'mocha' '2.5.3' elif node_version_lt '4.0' ; then npm_use_module 'mocha' '3.5.3' elif node_version_lt '6.0' ; then npm_use_module 'mocha' '5.2.0' fi - | # Configure supertest for http calls if node_version_lt '0.10'; then npm_use_module 'supertest' '1.1.0' elif node_version_lt '4.0' ; then npm_use_module 'supertest' '2.0.0' elif node_version_lt '6.0' ; then npm_use_module 'supertest' '3.4.2' fi # Update Node.js modules - | # Prune & rebuild node_modules if [[ -d node_modules ]]; then npm prune npm rebuild fi before_scrpt: - | # Contents of node_modules npm -s ls ||: script: - | # Run test script, depending on istanbul install if npm_module_installed 'istanbul'; then npm run-script test-ci else npm test fi - | # Run linting, if eslint exists if npm_module_installed 'eslint'; then npm run-script lint fi after_script: - | # Upload coverage to coveralls if exists if [[ -f ./coverage/lcov.info ]]; then npm install --save-dev coveralls@2 coveralls < ./coverage/lcov.info fi finalhandler-1.1.2/HISTORY.md000066400000000000000000000077601346515551100156450ustar00rootroot000000000000001.1.2 / 2019-05-09 ================== * Set stricter `Content-Security-Policy` header * deps: parseurl@~1.3.3 * deps: statuses@~1.5.0 1.1.1 / 2018-03-06 ================== * Fix 404 output for bad / missing pathnames * deps: encodeurl@~1.0.2 - Fix encoding `%` as last character * deps: statuses@~1.4.0 1.1.0 / 2017-09-24 ================== * Use `res.headersSent` when available 1.0.6 / 2017-09-22 ================== * deps: debug@2.6.9 1.0.5 / 2017-09-15 ================== * deps: parseurl@~1.3.2 - perf: reduce overhead for full URLs - perf: unroll the "fast-path" `RegExp` 1.0.4 / 2017-08-03 ================== * deps: debug@2.6.8 1.0.3 / 2017-05-16 ================== * deps: debug@2.6.7 - deps: ms@2.0.0 1.0.2 / 2017-04-22 ================== * deps: debug@2.6.4 - deps: ms@0.7.3 1.0.1 / 2017-03-21 ================== * Fix missing `` in HTML document * deps: debug@2.6.3 - Fix: `DEBUG_MAX_ARRAY_LENGTH` 1.0.0 / 2017-02-15 ================== * Fix exception when `err` cannot be converted to a string * Fully URL-encode the pathname in the 404 message * Only include the pathname in the 404 message * Send complete HTML document * Set `Content-Security-Policy: default-src 'self'` header * deps: debug@2.6.1 - Allow colors in workers - Deprecated `DEBUG_FD` environment variable set to `3` or higher - Fix error when running under React Native - Use same color for same namespace - deps: ms@0.7.2 0.5.1 / 2016-11-12 ================== * Fix exception when `err.headers` is not an object * deps: statuses@~1.3.1 * perf: hoist regular expressions * perf: remove duplicate validation path 0.5.0 / 2016-06-15 ================== * Change invalid or non-numeric status code to 500 * Overwrite status message to match set status code * Prefer `err.statusCode` if `err.status` is invalid * Set response headers from `err.headers` object * Use `statuses` instead of `http` module for status messages - Includes all defined status messages 0.4.1 / 2015-12-02 ================== * deps: escape-html@~1.0.3 - perf: enable strict mode - perf: optimize string replacement - perf: use faster string coercion 0.4.0 / 2015-06-14 ================== * Fix a false-positive when unpiping in Node.js 0.8 * Support `statusCode` property on `Error` objects * Use `unpipe` module for unpiping requests * deps: escape-html@1.0.2 * deps: on-finished@~2.3.0 - Add defined behavior for HTTP `CONNECT` requests - Add defined behavior for HTTP `Upgrade` requests - deps: ee-first@1.1.1 * perf: enable strict mode * perf: remove argument reassignment 0.3.6 / 2015-05-11 ================== * deps: debug@~2.2.0 - deps: ms@0.7.1 0.3.5 / 2015-04-22 ================== * deps: on-finished@~2.2.1 - Fix `isFinished(req)` when data buffered 0.3.4 / 2015-03-15 ================== * deps: debug@~2.1.3 - Fix high intensity foreground color for bold - deps: ms@0.7.0 0.3.3 / 2015-01-01 ================== * deps: debug@~2.1.1 * deps: on-finished@~2.2.0 0.3.2 / 2014-10-22 ================== * deps: on-finished@~2.1.1 - Fix handling of pipelined requests 0.3.1 / 2014-10-16 ================== * deps: debug@~2.1.0 - Implement `DEBUG_FD` env variable support 0.3.0 / 2014-09-17 ================== * Terminate in progress response only on error * Use `on-finished` to determine request status 0.2.0 / 2014-09-03 ================== * Set `X-Content-Type-Options: nosniff` header * deps: debug@~2.0.0 0.1.0 / 2014-07-16 ================== * Respond after request fully read - prevents hung responses and socket hang ups * deps: debug@1.0.4 0.0.3 / 2014-07-11 ================== * deps: debug@1.0.3 - Add support for multiple wildcards in namespaces 0.0.2 / 2014-06-19 ================== * Handle invalid status codes 0.0.1 / 2014-06-05 ================== * deps: debug@1.0.2 0.0.0 / 2014-06-05 ================== * Extracted from connect/express finalhandler-1.1.2/LICENSE000066400000000000000000000021371346515551100151600ustar00rootroot00000000000000(The MIT License) Copyright (c) 2014-2017 Douglas Christopher Wilson Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. finalhandler-1.1.2/README.md000066400000000000000000000076541346515551100154430ustar00rootroot00000000000000# finalhandler [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-image]][node-url] [![Build Status][travis-image]][travis-url] [![Test Coverage][coveralls-image]][coveralls-url] Node.js function to invoke as the final step to respond to HTTP request. ## Installation This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Installation is done using the [`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): ```sh $ npm install finalhandler ``` ## API ```js var finalhandler = require('finalhandler') ``` ### finalhandler(req, res, [options]) Returns function to be invoked as the final step for the given `req` and `res`. This function is to be invoked as `fn(err)`. If `err` is falsy, the handler will write out a 404 response to the `res`. If it is truthy, an error response will be written out to the `res`. When an error is written, the following information is added to the response: * The `res.statusCode` is set from `err.status` (or `err.statusCode`). If this value is outside the 4xx or 5xx range, it will be set to 500. * The `res.statusMessage` is set according to the status code. * The body will be the HTML of the status code message if `env` is `'production'`, otherwise will be `err.stack`. * Any headers specified in an `err.headers` object. The final handler will also unpipe anything from `req` when it is invoked. #### options.env By default, the environment is determined by `NODE_ENV` variable, but it can be overridden by this option. #### options.onerror Provide a function to be called with the `err` when it exists. Can be used for writing errors to a central location without excessive function generation. Called as `onerror(err, req, res)`. ## Examples ### always 404 ```js var finalhandler = require('finalhandler') var http = require('http') var server = http.createServer(function (req, res) { var done = finalhandler(req, res) done() }) server.listen(3000) ``` ### perform simple action ```js var finalhandler = require('finalhandler') var fs = require('fs') var http = require('http') var server = http.createServer(function (req, res) { var done = finalhandler(req, res) fs.readFile('index.html', function (err, buf) { if (err) return done(err) res.setHeader('Content-Type', 'text/html') res.end(buf) }) }) server.listen(3000) ``` ### use with middleware-style functions ```js var finalhandler = require('finalhandler') var http = require('http') var serveStatic = require('serve-static') var serve = serveStatic('public') var server = http.createServer(function (req, res) { var done = finalhandler(req, res) serve(req, res, done) }) server.listen(3000) ``` ### keep log of all errors ```js var finalhandler = require('finalhandler') var fs = require('fs') var http = require('http') var server = http.createServer(function (req, res) { var done = finalhandler(req, res, { onerror: logerror }) fs.readFile('index.html', function (err, buf) { if (err) return done(err) res.setHeader('Content-Type', 'text/html') res.end(buf) }) }) server.listen(3000) function logerror (err) { console.error(err.stack || err.toString()) } ``` ## License [MIT](LICENSE) [npm-image]: https://img.shields.io/npm/v/finalhandler.svg [npm-url]: https://npmjs.org/package/finalhandler [node-image]: https://img.shields.io/node/v/finalhandler.svg [node-url]: https://nodejs.org/en/download [travis-image]: https://img.shields.io/travis/pillarjs/finalhandler.svg [travis-url]: https://travis-ci.org/pillarjs/finalhandler [coveralls-image]: https://img.shields.io/coveralls/pillarjs/finalhandler.svg [coveralls-url]: https://coveralls.io/r/pillarjs/finalhandler?branch=master [downloads-image]: https://img.shields.io/npm/dm/finalhandler.svg [downloads-url]: https://npmjs.org/package/finalhandler finalhandler-1.1.2/index.js000066400000000000000000000145661346515551100156310ustar00rootroot00000000000000/*! * 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, '
') .replace(DOUBLE_SPACE_REGEXP, '  ') return '\n' + '\n' + '\n' + '\n' + 'Error\n' + '\n' + '\n' + '
' + body + '
\n' + '\n' + '\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]) } } finalhandler-1.1.2/package.json000066400000000000000000000024421346515551100164400ustar00rootroot00000000000000{ "name": "finalhandler", "description": "Node.js final http responder", "version": "1.1.2", "author": "Douglas Christopher Wilson ", "license": "MIT", "repository": "pillarjs/finalhandler", "dependencies": { "debug": "2.6.9", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "on-finished": "~2.3.0", "parseurl": "~1.3.3", "statuses": "~1.5.0", "unpipe": "~1.0.0" }, "devDependencies": { "eslint": "5.16.0", "eslint-config-standard": "12.0.0", "eslint-plugin-import": "2.17.2", "eslint-plugin-markdown": "1.0.0", "eslint-plugin-node": "8.0.1", "eslint-plugin-promise": "4.1.1", "eslint-plugin-standard": "4.0.0", "istanbul": "0.4.5", "mocha": "6.1.4", "readable-stream": "2.3.6", "safe-buffer": "5.1.2", "supertest": "4.0.2" }, "files": [ "LICENSE", "HISTORY.md", "index.js" ], "engines": { "node": ">= 0.8" }, "scripts": { "lint": "eslint --plugin markdown --ext js,md .", "test": "mocha --reporter spec --bail --check-leaks test/", "test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --reporter spec --check-leaks test/", "test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --reporter dot --check-leaks test/" } } finalhandler-1.1.2/test/000077500000000000000000000000001346515551100151275ustar00rootroot00000000000000finalhandler-1.1.2/test/.eslintrc.yml000066400000000000000000000000231346515551100175460ustar00rootroot00000000000000env: mocha: true finalhandler-1.1.2/test/support/000077500000000000000000000000001346515551100166435ustar00rootroot00000000000000finalhandler-1.1.2/test/support/sws.js000066400000000000000000000005051346515551100200150ustar00rootroot00000000000000var stream = require('readable-stream') var util = require('util') module.exports = SlowWriteStream function SlowWriteStream () { stream.Writable.call(this) } util.inherits(SlowWriteStream, stream.Writable) SlowWriteStream.prototype._write = function _write (chunk, encoding, callback) { setTimeout(callback, 1000) } finalhandler-1.1.2/test/support/utils.js000066400000000000000000000055171346515551100203510ustar00rootroot00000000000000var assert = require('assert') var finalhandler = require('../..') var http = require('http') var request = require('supertest') var SlowWriteStream = require('./sws') exports.assert = assert exports.createError = createError exports.createServer = createServer exports.createSlowWriteStream = createSlowWriteStream exports.rawrequest = rawrequest exports.request = request exports.shouldHaveStatusMessage = shouldHaveStatusMessage exports.shouldNotHaveBody = shouldNotHaveBody exports.shouldNotHaveHeader = shouldNotHaveHeader function createError (message, props) { var err = new Error(message) if (props) { for (var prop in props) { err[prop] = props[prop] } } return err } function createServer (err, opts) { return http.createServer(function (req, res) { var done = finalhandler(req, res, opts) if (typeof err === 'function') { err(req, res, done) return } done(err) }) } function createSlowWriteStream () { return new SlowWriteStream() } function rawrequest (server) { var _headers = {} var _path function expect (status, body, callback) { if (arguments.length === 2) { _headers[status.toLowerCase()] = body return this } server.listen(function onlisten () { var addr = this.address() var port = addr.port var req = http.get({ host: '127.0.0.1', path: _path, port: port }) req.on('error', callback) req.on('response', function onresponse (res) { var buf = '' res.setEncoding('utf8') res.on('data', function ondata (s) { buf += s }) res.on('end', function onend () { var err = null try { for (var key in _headers) { assert.strictEqual(res.headers[key], _headers[key]) } assert.strictEqual(res.statusCode, status) if (body instanceof RegExp) { assert.ok(body.test(buf), 'expected body ' + buf + ' to match ' + body) } else { assert.strictEqual(buf, body, 'expected ' + body + ' response body, got ' + buf) } } catch (e) { err = e } server.close() callback(err) }) }) }) } function get (path) { _path = path return { expect: expect } } return { get: get } } function shouldHaveStatusMessage (statusMessage) { return function (test) { assert.strictEqual(test.res.statusMessage, statusMessage, 'should have statusMessage "' + statusMessage + '"') } } function shouldNotHaveBody () { return function (res) { assert.ok(res.text === '' || res.text === undefined) } } function shouldNotHaveHeader (header) { return function (test) { assert.ok(test.res.headers[header] === undefined, 'response does not have header "' + header + '"') } } finalhandler-1.1.2/test/test.js000066400000000000000000000345351346515551100164560ustar00rootroot00000000000000 var Buffer = require('safe-buffer').Buffer var finalhandler = require('..') var http = require('http') var utils = require('./support/utils') var assert = utils.assert var createError = utils.createError var createServer = utils.createServer var createSlowWriteStream = utils.createSlowWriteStream var rawrequest = utils.rawrequest var request = utils.request var shouldHaveStatusMessage = utils.shouldHaveStatusMessage var shouldNotHaveBody = utils.shouldNotHaveBody var shouldNotHaveHeader = utils.shouldNotHaveHeader var describeStatusMessage = !/statusMessage/.test(http.IncomingMessage.toString()) ? describe.skip : describe describe('finalhandler(req, res)', function () { describe('headers', function () { it('should ignore err.headers without status code', function (done) { request(createServer(createError('oops!', { headers: { 'X-Custom-Header': 'foo' } }))) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should ignore err.headers with invalid res.status', function (done) { request(createServer(createError('oops!', { headers: { 'X-Custom-Header': 'foo' }, status: 601 }))) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should ignore err.headers with invalid res.statusCode', function (done) { request(createServer(createError('oops!', { headers: { 'X-Custom-Header': 'foo' }, statusCode: 601 }))) .get('/') .expect(shouldNotHaveHeader('X-Custom-Header')) .expect(500, done) }) it('should include err.headers with err.status', function (done) { request(createServer(createError('oops!', { headers: { 'X-Custom-Header': 'foo=500', 'X-Custom-Header2': 'bar' }, status: 500 }))) .get('/') .expect('X-Custom-Header', 'foo=500') .expect('X-Custom-Header2', 'bar') .expect(500, done) }) it('should include err.headers with err.statusCode', function (done) { request(createServer(createError('too many requests', { headers: { 'Retry-After': '5' }, statusCode: 429 }))) .get('/') .expect('Retry-After', '5') .expect(429, done) }) it('should ignore err.headers when not an object', function (done) { request(createServer(createError('oops!', { headers: 'foobar', statusCode: 500 }))) .get('/') .expect(500, done) }) }) describe('status code', function () { it('should 404 on no error', function (done) { request(createServer()) .get('/') .expect(404, done) }) it('should 500 on error', function (done) { request(createServer(createError())) .get('/') .expect(500, done) }) it('should use err.statusCode', function (done) { request(createServer(createError('nope', { statusCode: 400 }))) .get('/') .expect(400, done) }) it('should ignore non-error err.statusCode code', function (done) { request(createServer(createError('created', { statusCode: 201 }))) .get('/') .expect(500, done) }) it('should ignore non-numeric err.statusCode', function (done) { request(createServer(createError('oops', { statusCode: 'oh no' }))) .get('/') .expect(500, done) }) it('should use err.status', function (done) { request(createServer(createError('nope', { status: 400 }))) .get('/') .expect(400, done) }) it('should use err.status over err.statusCode', function (done) { request(createServer(createError('nope', { status: 400, statusCode: 401 }))) .get('/') .expect(400, done) }) it('should set status to 500 when err.status < 400', function (done) { request(createServer(createError('oops', { status: 202 }))) .get('/') .expect(500, done) }) it('should set status to 500 when err.status > 599', function (done) { request(createServer(createError('oops', { status: 601 }))) .get('/') .expect(500, done) }) it('should use err.statusCode over invalid err.status', function (done) { request(createServer(createError('nope', { status: 50, statusCode: 410 }))) .get('/') .expect(410, done) }) it('should ignore non-error err.status code', function (done) { request(createServer(createError('created', { status: 201 }))) .get('/') .expect(500, done) }) it('should ignore non-numeric err.status', function (done) { request(createServer(createError('oops', { status: 'oh no' }))) .get('/') .expect(500, done) }) }) describeStatusMessage('status message', function () { it('should be "Not Found" on no error', function (done) { request(createServer()) .get('/') .expect(shouldHaveStatusMessage('Not Found')) .expect(404, done) }) it('should be "Internal Server Error" on error', function (done) { request(createServer(createError())) .get('/') .expect(shouldHaveStatusMessage('Internal Server Error')) .expect(500, done) }) it('should be "Bad Request" when err.statusCode = 400', function (done) { request(createServer(createError('oops', { status: 400 }))) .get('/') .expect(shouldHaveStatusMessage('Bad Request')) .expect(400, done) }) it('should reset existing res.statusMessage', function (done) { function onRequest (req, res, next) { res.statusMessage = 'An Error Occurred' next(new Error()) } request(createServer(onRequest)) .get('/') .expect(shouldHaveStatusMessage('Internal Server Error')) .expect(500, done) }) }) describe('404 response', function () { it('should include method and pathname', function (done) { request(createServer()) .get('/foo') .expect(404, /
Cannot GET \/foo<\/pre>/, done)
    })

    it('should escape method and pathname characters', function (done) {
      rawrequest(createServer())
        .get('/')
        .expect(404, /
Cannot GET \/%3Cla'me%3E<\/pre>/, done)
    })

    it('should encode bad pathname characters', function (done) {
      rawrequest(createServer())
        .get('/foo%20ยง')
        .expect(404, /
Cannot GET \/foo%20%C2%A7<\/pre>/, done)
    })

    it('should fallback to generic pathname without URL', function (done) {
      var server = createServer(function (req, res, next) {
        req.url = undefined
        next()
      })

      request(server)
        .get('/foo')
        .expect(404, /
Cannot GET resource<\/pre>/, done)
    })

    it('should include original pathname', function (done) {
      var server = createServer(function (req, res, next) {
        var parts = req.url.split('/')
        req.originalUrl = req.url
        req.url = '/' + parts.slice(2).join('/')
        next()
      })

      request(server)
        .get('/foo/bar')
        .expect(404, /
Cannot GET \/foo\/bar<\/pre>/, done)
    })

    it('should include pathname only', function (done) {
      rawrequest(createServer())
        .get('http://localhost/foo?bar=1')
        .expect(404, /
Cannot GET \/foo<\/pre>/, done)
    })

    it('should handle HEAD', function (done) {
      request(createServer())
        .head('/foo')
        .expect(404)
        .expect(shouldNotHaveBody())
        .end(done)
    })

    it('should include X-Content-Type-Options header', function (done) {
      request(createServer())
        .get('/foo')
        .expect('X-Content-Type-Options', 'nosniff')
        .expect(404, done)
    })

    it('should includeContent-Security-Policy header', function (done) {
      request(createServer())
        .get('/foo')
        .expect('Content-Security-Policy', "default-src 'none'")
        .expect(404, done)
    })

    it('should not hang/error if there is a request body', function (done) {
      var buf = Buffer.alloc(1024 * 16, '.')
      var server = createServer()
      var test = request(server).post('/foo')
      test.write(buf)
      test.write(buf)
      test.write(buf)
      test.expect(404, done)
    })
  })

  describe('error response', function () {
    it('should include error stack', function (done) {
      request(createServer(createError('boom!')))
        .get('/foo')
        .expect(500, /
Error: boom!
   at/, done) }) it('should handle HEAD', function (done) { request(createServer()) .head('/foo') .expect(404) .expect(shouldNotHaveBody()) .end(done) }) it('should include X-Content-Type-Options header', function (done) { request(createServer(createError('boom!'))) .get('/foo') .expect('X-Content-Type-Options', 'nosniff') .expect(500, done) }) it('should includeContent-Security-Policy header', function (done) { request(createServer(createError('boom!'))) .get('/foo') .expect('Content-Security-Policy', "default-src 'none'") .expect(500, done) }) it('should handle non-error-objects', function (done) { request(createServer('lame string')) .get('/foo') .expect(500, /
lame string<\/pre>/, done)
    })

    it('should handle null prototype objects', function (done) {
      request(createServer(Object.create(null)))
        .get('/foo')
        .expect(500, /
Internal Server Error<\/pre>/, done)
    })

    it('should send staus code name when production', function (done) {
      var err = createError('boom!', {
        status: 501
      })
      request(createServer(err, {
        env: 'production'
      }))
        .get('/foo')
        .expect(501, /
Not Implemented<\/pre>/, done)
    })

    describe('when there is a request body', function () {
      it('should not hang/error when unread', function (done) {
        var buf = Buffer.alloc(1024 * 16, '.')
        var server = createServer(new Error('boom!'))
        var test = request(server).post('/foo')
        test.write(buf)
        test.write(buf)
        test.write(buf)
        test.expect(500, done)
      })

      it('should not hang/error when actively piped', function (done) {
        var buf = Buffer.alloc(1024 * 16, '.')
        var server = createServer(function (req, res, next) {
          req.pipe(stream)
          process.nextTick(function () {
            next(new Error('boom!'))
          })
        })
        var stream = createSlowWriteStream()
        var test = request(server).post('/foo')
        test.write(buf)
        test.write(buf)
        test.write(buf)
        test.expect(500, done)
      })

      it('should not hang/error when read', function (done) {
        var buf = Buffer.alloc(1024 * 16, '.')
        var server = createServer(function (req, res, next) {
          // read off the request
          req.once('end', function () {
            next(new Error('boom!'))
          })
          req.resume()
        })
        var test = request(server).post('/foo')
        test.write(buf)
        test.write(buf)
        test.write(buf)
        test.expect(500, done)
      })
    })

    describe('when res.statusCode set', function () {
      it('should keep when >= 400', function (done) {
        var server = http.createServer(function (req, res) {
          var done = finalhandler(req, res)
          res.statusCode = 503
          done(new Error('oops'))
        })

        request(server)
          .get('/foo')
          .expect(503, done)
      })

      it('should convert to 500 is not a number', function (done) {
        var server = http.createServer(function (req, res) {
          var done = finalhandler(req, res)
          res.statusCode = 'oh no'
          done(new Error('oops'))
        })

        request(server)
          .get('/foo')
          .expect(500, done)
      })

      it('should override with err.status', function (done) {
        var server = http.createServer(function (req, res) {
          var done = finalhandler(req, res)
          var err = createError('oops', {
            status: 414,
            statusCode: 503
          })
          done(err)
        })

        request(server)
          .get('/foo')
          .expect(414, done)
      })

      it('should default body to status message in production', function (done) {
        var err = createError('boom!', {
          status: 509
        })
        request(createServer(err, {
          env: 'production'
        }))
          .get('/foo')
          .expect(509, /
Bandwidth Limit Exceeded<\/pre>/, done)
      })
    })

    describe('when res.statusCode undefined', function () {
      it('should set to 500', function (done) {
        var server = http.createServer(function (req, res) {
          var done = finalhandler(req, res)
          res.statusCode = undefined
          done(new Error('oops'))
        })

        request(server)
          .get('/foo')
          .expect(500, done)
      })
    })
  })

  describe('request started', function () {
    it('should not respond', function (done) {
      var server = http.createServer(function (req, res) {
        var done = finalhandler(req, res)
        res.statusCode = 301
        res.write('0')
        process.nextTick(function () {
          done()
          res.end('1')
        })
      })

      request(server)
        .get('/foo')
        .expect(301, '01', done)
    })

    it('should terminate on error', function (done) {
      var server = http.createServer(function (req, res) {
        var done = finalhandler(req, res)
        res.statusCode = 301
        res.write('0')
        process.nextTick(function () {
          done(createError('too many requests', {
            status: 429,
            headers: { 'Retry-After': '5' }
          }))
          res.end('1')
        })
      })

      request(server)
        .get('/foo')
        .expect(301, '0', done)
    })
  })

  describe('onerror', function () {
    it('should be invoked when error', function (done) {
      var err = new Error('boom!')
      var error

      function log (e) {
        error = e
      }

      request(createServer(err, { onerror: log }))
        .get('/')
        .end(function () {
          assert.equal(error, err)
          done()
        })
    })
  })
})