diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12a1296..dae43be 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,6 +25,10 @@ jobs: - 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.10 @@ -57,11 +61,11 @@ jobs: - name: Node.js 6.x node-version: "6.17" - npm-i: mocha@6.2.3 nyc@14.1.1 + 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 + npm-i: mocha@6.2.3 nyc@14.1.1 supertest@6.1.6 - name: Node.js 8.x node-version: "8.16" @@ -86,16 +90,16 @@ jobs: node-version: "13.14" - name: Node.js 14.x - node-version: "14.18" + node-version: "14.19" - name: Node.js 15.x node-version: "15.14" - name: Node.js 16.x - node-version: "16.13" + node-version: "16.14" - name: Node.js 17.x - node-version: "17.2" + node-version: "17.7" steps: - uses: actions/checkout@v2 diff --git a/HISTORY.md b/HISTORY.md index e1861a2..9aaa5fb 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -16,6 +16,21 @@ - Remove `DEBUG_FD` environment variable support - Support 256 namespace colors +0.18.0 / 2022-03-23 +=================== + + * Fix emitted 416 error missing headers property + * Limit the headers removed for 304 response + * deps: depd@2.0.0 + - Replace internal `eval` usage with `Function` constructor + - Use instance methods on `process` to check for listeners + * deps: destroy@1.2.0 + * deps: http-errors@2.0.0 + - deps: depd@2.0.0 + - deps: statuses@2.0.1 + * deps: on-finished@2.4.1 + * deps: statuses@2.0.1 + 0.17.2 / 2021-12-11 =================== diff --git a/LICENSE b/LICENSE index 4aa69e8..b6ea1c1 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ (The MIT License) Copyright (c) 2012 TJ Holowaychuk -Copyright (c) 2014-2016 Douglas Christopher Wilson +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 diff --git a/README.md b/README.md index 984a952..e95fb4b 100644 --- a/README.md +++ b/README.md @@ -311,8 +311,8 @@ server.listen(3000) [appveyor-url]: https://ci.appveyor.com/project/dougwilson/send [coveralls-image]: https://badgen.net/coveralls/c/github/pillarjs/send/master [coveralls-url]: https://coveralls.io/r/pillarjs/send?branch=master -[github-actions-ci-image]: https://badgen.net/github/checks/pillarjs/send/master?label=ci -[github-actions-ci-url]: https://github.com/pillarjs/send/actions?query=workflow%3Aci +[github-actions-ci-image]: https://badgen.net/github/checks/pillarjs/send/master?label=linux +[github-actions-ci-url]: https://github.com/pillarjs/send/actions/workflows/ci.yml [node-image]: https://badgen.net/npm/node/send [node-url]: https://nodejs.org/en/download/ [npm-downloads-image]: https://badgen.net/npm/dm/send diff --git a/SECURITY.md b/SECURITY.md new file mode 100644 index 0000000..46b48f7 --- /dev/null +++ b/SECURITY.md @@ -0,0 +1,24 @@ +# Security Policies and Procedures + +## Reporting a Bug + +The `send` 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 `send`. This information +can be found in the npm registry using the command `npm owner ls send`. +If unsure or unable to get the information from the above, open an issue +in the [project issue tracker](https://github.com/pillarjs/send/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. diff --git a/appveyor.yml b/appveyor.yml index d577cca..1332a99 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -15,10 +15,10 @@ environment: - nodejs_version: "11.15" - nodejs_version: "12.22" - nodejs_version: "13.14" - - nodejs_version: "14.18" + - nodejs_version: "14.19" - nodejs_version: "15.14" - - nodejs_version: "16.13" - - nodejs_version: "17.2" + - nodejs_version: "16.14" + - nodejs_version: "17.7" cache: - node_modules install: @@ -61,10 +61,13 @@ install: # supertest for http calls # - use 2.0.0 for Node.js < 4 # - use 3.4.2 for Node.js < 6 + # - use 6.1.6 for Node.js < 8 if ([int]$env:nodejs_version.split(".")[0] -lt 4) { npm install --silent --save-dev supertest@2.0.0 } elseif ([int]$env:nodejs_version.split(".")[0] -lt 6) { npm install --silent --save-dev supertest@3.4.2 + } elseif ([int]$env:nodejs_version.split(".")[0] -lt 8) { + npm install --silent --save-dev supertest@6.1.6 } # Update Node.js modules - ps: | diff --git a/index.js b/index.js index 3520709..90fbd4f 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,7 @@ /*! * send * Copyright(c) 2012 TJ Holowaychuk - * Copyright(c) 2014-2016 Douglas Christopher Wilson + * Copyright(c) 2014-2022 Douglas Christopher Wilson * MIT Licensed */ @@ -166,13 +166,11 @@ util.inherits(SendStream, Stream) SendStream.prototype.error = function error (status, err) { // emit if listeners instead of responding if (hasListeners(this, 'error')) { - return this.emit('error', createError(status, err, { - expose: false - })) + return this.emit('error', createHttpError(status, err)) } var res = this.res - var msg = statuses[status] || String(status) + var msg = statuses.message[status] || String(status) var doc = createHtmlDocument('Error', escapeHtml(msg)) // clear existing headers @@ -248,21 +246,19 @@ SendStream.prototype.isPreconditionFailure = function isPreconditionFailure () { } /** - * Strip content-* header fields. + * Strip various content header fields for a change in entity. * * @private */ SendStream.prototype.removeContentHeaderFields = function removeContentHeaderFields () { var res = this.res - var headers = getHeaderNames(res) - for (var i = 0; i < headers.length; i++) { - var header = headers[i] - if (header.substr(0, 8) === 'content-' && header !== 'content-location') { - res.removeHeader(header) - } - } + res.removeHeader('Content-Encoding') + res.removeHeader('Content-Language') + res.removeHeader('Content-Length') + res.removeHeader('Content-Range') + res.removeHeader('Content-Type') } /** @@ -677,8 +673,6 @@ SendStream.prototype.sendIndex = function sendIndex (path) { */ SendStream.prototype.stream = function stream (path, options) { - // TODO: this is all lame, refactor meeee - var finished = false var self = this var res = this.res @@ -687,20 +681,18 @@ SendStream.prototype.stream = function stream (path, options) { this.emit('stream', stream) stream.pipe(res) - // response finished, done with the fd - onFinished(res, function onfinished () { - finished = true - destroy(stream) - }) + // cleanup + function cleanup () { + destroy(stream, true) + } - // error handling code-smell - stream.on('error', function onerror (err) { - // request already finished - if (finished) return + // response finished, cleanup + onFinished(res, cleanup) - // clean up stream - finished = true - destroy(stream) + // error handling + stream.on('error', function onerror (err) { + // clean up stream early + cleanup() // error self.onStatError(err) @@ -858,6 +850,24 @@ function createHtmlDocument (title, body) { '\n' } +/** + * Create a HttpError object from simple arguments. + * + * @param {number} status + * @param {Error|object} err + * @private + */ + +function createHttpError (status, err) { + if (!err) { + return createError(status) + } + + return err instanceof Error + ? createError(status, err, { expose: false }) + : createError(status, err) +} + /** * decodeURIComponent. * diff --git a/package.json b/package.json index 4a1a3fc..58e2a03 100644 --- a/package.json +++ b/package.json @@ -17,35 +17,36 @@ ], "dependencies": { "debug": "3.1.0", - "destroy": "~1.0.4", + "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", - "http-errors": "1.8.1", + "http-errors": "2.0.0", "mime-types": "~2.1.34", "ms": "2.1.3", - "on-finished": "~2.3.0", + "on-finished": "2.4.1", "range-parser": "~1.2.1", - "statuses": "~1.5.0" + "statuses": "2.0.1" }, "devDependencies": { "after": "0.8.2", "eslint": "7.32.0", "eslint-config-standard": "14.1.1", - "eslint-plugin-import": "2.25.3", + "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.1.3", + "mocha": "9.2.2", "nyc": "15.1.0", - "supertest": "6.1.6" + "supertest": "6.2.2" }, "files": [ "HISTORY.md", "LICENSE", "README.md", + "SECURITY.md", "index.js" ], "engines": { diff --git a/test/send.js b/test/send.js index e6c2e02..050e4b9 100644 --- a/test/send.js +++ b/test/send.js @@ -447,6 +447,27 @@ describe('send(file).pipe(res)', function () { }) }) + it('should not remove all Content-* headers', function (done) { + var server = createServer({ root: fixtures }, function (req, res) { + res.setHeader('Content-Location', 'http://localhost/name.txt') + res.setHeader('Content-Security-Policy', 'default-src \'self\'') + }) + + request(server) + .get('/name.txt') + .expect(200, function (err, res) { + if (err) return done(err) + request(server) + .get('/name.txt') + .set('If-None-Match', res.headers.etag) + .expect(shouldNotHaveHeader('Content-Length')) + .expect(shouldNotHaveHeader('Content-Type')) + .expect('Content-Location', 'http://localhost/name.txt') + .expect('Content-Security-Policy', 'default-src \'self\'') + .expect(304, done) + }) + }) + describe('where "If-Match" is set', function () { it('should respond with 200 when "*"', function (done) { request(app) @@ -650,6 +671,24 @@ describe('send(file).pipe(res)', function () { .expect('Content-Range', 'bytes */9') .expect(416, done) }) + + it('should emit error 416 with content-range header', function (done) { + var server = http.createServer(function (req, res) { + send(req, req.url, { root: fixtures }) + .on('error', function (err) { + res.setHeader('X-Content-Range', err.headers['Content-Range']) + res.statusCode = err.statusCode + res.end(err.message) + }) + .pipe(res) + }) + + request(server) + .get('/nums.txt') + .set('Range', 'bytes=9-50') + .expect('X-Content-Range', 'bytes */9') + .expect(416, done) + }) }) describe('when syntactically invalid', function () {