pax_global_header00006660000000000000000000000064142164675020014521gustar00rootroot0000000000000052 comment=ea685c187cb9b18e96cfbc42f23d265d8a41dc23 finalhandler-1.2.0/000077500000000000000000000000001421646750200141505ustar00rootroot00000000000000finalhandler-1.2.0/.eslintignore000066400000000000000000000000261421646750200166510ustar00rootroot00000000000000coverage node_modules finalhandler-1.2.0/.eslintrc.yml000066400000000000000000000002321421646750200165710ustar00rootroot00000000000000root: true extends: - standard - plugin:markdown/recommended plugins: - markdown overrides: - files: '**/*.md' processor: 'markdown/markdown' finalhandler-1.2.0/.github/000077500000000000000000000000001421646750200155105ustar00rootroot00000000000000finalhandler-1.2.0/.github/workflows/000077500000000000000000000000001421646750200175455ustar00rootroot00000000000000finalhandler-1.2.0/.github/workflows/ci.yml000066400000000000000000000116071421646750200206700ustar00rootroot00000000000000name: ci on: - pull_request - push jobs: test: runs-on: ubuntu-latest strategy: matrix: name: - Node.js 0.8 - Node.js 0.10 - Node.js 0.12 - io.js 1.x - io.js 2.x - io.js 3.x - Node.js 4.x - Node.js 5.x - Node.js 6.x - Node.js 7.x - Node.js 8.x - Node.js 9.x - Node.js 10.x - Node.js 11.x - Node.js 12.x - Node.js 13.x - Node.js 14.x - Node.js 15.x - Node.js 16.x - Node.js 17.x include: - name: Node.js 0.8 node-version: "0.8" npm-i: mocha@2.5.3 supertest@1.1.0 npm-rm: nyc - name: Node.js 0.10 node-version: "0.10" npm-i: mocha@2.5.3 nyc@10.3.2 supertest@2.0.0 - name: Node.js 0.12 node-version: "0.12" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: io.js 1.x node-version: "1.8" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: io.js 2.x node-version: "2.5" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: io.js 3.x node-version: "3.3" npm-i: mocha@3.5.3 nyc@10.3.2 supertest@2.0.0 - name: Node.js 4.x node-version: "4.9" npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - name: Node.js 5.x node-version: "5.12" npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - name: Node.js 6.x node-version: "6.17" npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6 - name: Node.js 7.x node-version: "7.10" npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6 - name: Node.js 8.x node-version: "8.16" npm-i: mocha@7.2.0 - name: Node.js 9.x node-version: "9.11" npm-i: mocha@7.2.0 - name: Node.js 10.x node-version: "10.24" npm-i: mocha@8.4.0 - name: Node.js 11.x node-version: "11.15" npm-i: mocha@8.4.0 - name: Node.js 12.x node-version: "12.22" - name: Node.js 13.x node-version: "13.14" - name: Node.js 14.x node-version: "14.19" - name: Node.js 15.x node-version: "15.14" - name: Node.js 16.x node-version: "16.14" - name: Node.js 17.x node-version: "17.7" steps: - uses: actions/checkout@v2 - name: Install Node.js ${{ matrix.node-version }} shell: bash -eo pipefail -l {0} run: | nvm install --default ${{ matrix.node-version }} if [[ "${{ matrix.node-version }}" == 0.* && "$(cut -d. -f2 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then nvm install --alias=npm 0.10 nvm use ${{ matrix.node-version }} sed -i '1s;^.*$;'"$(printf '#!%q' "$(nvm which npm)")"';' "$(readlink -f "$(which npm)")" npm config set strict-ssl false fi dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - name: Configure npm run: npm config set shrinkwrap false - name: Remove npm module(s) ${{ matrix.npm-rm }} run: npm rm --silent --save-dev ${{ matrix.npm-rm }} if: matrix.npm-rm != '' - name: Install npm module(s) ${{ matrix.npm-i }} run: npm install --save-dev ${{ matrix.npm-i }} if: matrix.npm-i != '' - name: Setup Node.js version-specific dependencies shell: bash run: | # eslint for linting # - remove on Node.js < 10 if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 10 ]]; then node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ grep -E '^eslint(-|$)' | \ sort -r | \ xargs -n1 npm rm --silent --save-dev fi - name: Install Node.js dependencies run: npm install - name: List environment id: list_env shell: bash run: | echo "node@$(node -v)" echo "npm@$(npm -v)" npm -s ls ||: (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print "::set-output name=" $2 "::" $3 }' - name: Run tests shell: bash run: | if npm -ps ls nyc | grep -q nyc; then npm run test-ci else npm test fi - name: Lint code if: steps.list_env.outputs.eslint != '' run: npm run lint - name: Collect code coverage uses: coverallsapp/github-action@master if: steps.list_env.outputs.nyc != '' with: github-token: ${{ secrets.GITHUB_TOKEN }} flag-name: run-${{ matrix.test_number }} parallel: true coverage: needs: test runs-on: ubuntu-latest steps: - name: Uploade code coverage uses: coverallsapp/github-action@master with: github-token: ${{ secrets.github_token }} parallel-finished: true finalhandler-1.2.0/.gitignore000066400000000000000000000001051421646750200161340ustar00rootroot00000000000000.nyc_output/ coverage/ node_modules/ npm-debug.log package-lock.json finalhandler-1.2.0/HISTORY.md000066400000000000000000000103041421646750200156310ustar00rootroot000000000000001.2.0 / 2022-03-22 ================== * Remove set content headers that break response * deps: on-finished@2.4.1 * deps: statuses@2.0.1 - Rename `425 Unordered Collection` to standard `425 Too Early` 1.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.2.0/LICENSE000066400000000000000000000021371421646750200151600ustar00rootroot00000000000000(The MIT License) Copyright (c) 2014-2022 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.2.0/README.md000066400000000000000000000100451421646750200154270ustar00rootroot00000000000000# finalhandler [![NPM Version][npm-image]][npm-url] [![NPM Downloads][downloads-image]][downloads-url] [![Node.js Version][node-image]][node-url] [![Build Status][github-actions-ci-image]][github-actions-ci-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` or `res` will be terminated if a response has already started. 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 [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 [github-actions-ci-image]: https://img.shields.io/github/workflow/status/pillarjs/finalhandler/ci/master?label=ci [github-actions-ci-url]: https://github.com/jshttp/pillarjs/finalhandler?query=workflow%3Aci finalhandler-1.2.0/SECURITY.md000066400000000000000000000022621421646750200157430ustar00rootroot00000000000000# Security Policies and Procedures ## Reporting a Bug The `finalhandler` team and community take all security bugs seriously. Thank you for improving the security of Express. We appreciate your efforts and responsible disclosure and will make every effort to acknowledge your contributions. Report security bugs by emailing the current owner(s) of `finalhandler`. This information can be found in the npm registry using the command `npm owner ls finalhandler`. If unsure or unable to get the information from the above, open an issue in the [project issue tracker](https://github.com/pillarjs/finalhandler/issues) asking for the current contact information. To ensure the timely response to your report, please ensure that the entirety of the report is contained within the email body and not solely behind a web link or an attachment. At least one owner will acknowledge your email within 48 hours, and will send a more detailed response within 48 hours indicating the next steps in handling your report. After the initial reply to your report, the owners will endeavor to keep you informed of the progress towards a fix and full announcement, and may ask for additional information or guidance. finalhandler-1.2.0/index.js000066400000000000000000000150411421646750200156160ustar00rootroot00000000000000/*! * finalhandler * Copyright(c) 2014-2022 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.message[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.message[status] // remove any content headers res.removeHeader('Content-Encoding') res.removeHeader('Content-Language') res.removeHeader('Content-Range') // 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.2.0/package.json000066400000000000000000000022671421646750200164450ustar00rootroot00000000000000{ "name": "finalhandler", "description": "Node.js final http responder", "version": "1.2.0", "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.4.1", "parseurl": "~1.3.3", "statuses": "2.0.1", "unpipe": "~1.0.0" }, "devDependencies": { "eslint": "7.32.0", "eslint-config-standard": "14.1.1", "eslint-plugin-import": "2.25.4", "eslint-plugin-markdown": "2.2.1", "eslint-plugin-node": "11.1.0", "eslint-plugin-promise": "5.2.0", "eslint-plugin-standard": "4.1.0", "mocha": "9.2.2", "nyc": "15.1.0", "readable-stream": "2.3.6", "safe-buffer": "5.2.1", "supertest": "6.2.2" }, "files": [ "LICENSE", "HISTORY.md", "SECURITY.md", "index.js" ], "engines": { "node": ">= 0.8" }, "scripts": { "lint": "eslint .", "test": "mocha --reporter spec --bail --check-leaks test/", "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", "test-cov": "nyc --reporter=html --reporter=text npm test" } } finalhandler-1.2.0/test/000077500000000000000000000000001421646750200151275ustar00rootroot00000000000000finalhandler-1.2.0/test/.eslintrc.yml000066400000000000000000000000231421646750200175460ustar00rootroot00000000000000env: mocha: true finalhandler-1.2.0/test/support/000077500000000000000000000000001421646750200166435ustar00rootroot00000000000000finalhandler-1.2.0/test/support/sws.js000066400000000000000000000005051421646750200200150ustar00rootroot00000000000000var 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.2.0/test/support/utils.js000066400000000000000000000055171421646750200203510ustar00rootroot00000000000000var 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.2.0/test/test.js000066400000000000000000000406001421646750200164440ustar00rootroot00000000000000 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 include Content-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(createError('boom!'))) .head('/foo') .expect(500) .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('headers set', function () {
    it('should persist set headers', function (done) {
      var server = http.createServer(function (req, res) {
        var done = finalhandler(req, res)
        res.setHeader('Server', 'foobar')
        done()
      })

      request(server)
        .get('/foo')
        .expect(404)
        .expect('Server', 'foobar')
        .end(done)
    })

    it('should override content-type and length', function (done) {
      var server = http.createServer(function (req, res) {
        var done = finalhandler(req, res)
        res.setHeader('Content-Type', 'image/png')
        res.setHeader('Content-Length', '50')
        done()
      })

      request(server)
        .get('/foo')
        .expect(404)
        .expect('Content-Type', 'text/html; charset=utf-8')
        .expect('Content-Length', '142')
        .end(done)
    })

    it('should remove other content headers', function (done) {
      var server = http.createServer(function (req, res) {
        var done = finalhandler(req, res)
        res.setHeader('Content-Encoding', 'gzip')
        res.setHeader('Content-Language', 'jp')
        res.setHeader('Content-Range', 'bytes 0-2/10')
        done()
      })

      request(server)
        .get('/foo')
        .expect(404)
        .expect(shouldNotHaveHeader('Content-Encoding'))
        .expect(shouldNotHaveHeader('Content-Language'))
        .expect(shouldNotHaveHeader('Content-Range'))
        .end(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')
        .on('request', function onrequest (test) {
          test.req.on('response', function onresponse (res) {
            if (res.listeners('error').length > 0) {
              // forward aborts as errors for supertest
              res.on('aborted', function onabort () {
                res.emit('error', new Error('aborted'))
              })
            }
          })
        })
        .end(function (err) {
          if (err && err.message !== 'aborted') return done(err)
          assert.strictEqual(this.res.statusCode, 301)
          assert.strictEqual(this.res.text, '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()
        })
    })
  })
})