From 94fdb674b1df2e36d389fce51f5e07071f807adb Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Mon, 25 Sep 2017 20:57:54 -0400 Subject: [PATCH 01/26] build: support Node.js 8.x --- .gitignore | 1 + .travis.yml | 4 ++++ appveyor.yml | 2 ++ 3 files changed, 7 insertions(+) diff --git a/.gitignore b/.gitignore index 9723e60591..5fee6a2dc9 100644 --- a/.gitignore +++ b/.gitignore @@ -15,6 +15,7 @@ Desktop.ini # npm node_modules +package-lock.json *.log *.gz diff --git a/.travis.yml b/.travis.yml index 3dbeb41fcd..3e487a6f18 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,6 +9,7 @@ node_js: - "5.12" - "6.11" - "7.10" + - "8.4" matrix: include: - node_js: "8" @@ -21,6 +22,9 @@ cache: directories: - node_modules before_install: + # Skip updating shrinkwrap / lock + - "npm config set shrinkwrap false" + # Remove all non-test dependencies - "npm rm --save-dev connect-redis" diff --git a/appveyor.yml b/appveyor.yml index 9863c08e27..193660af71 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,10 +9,12 @@ environment: - nodejs_version: "5.12" - nodejs_version: "6.11" - nodejs_version: "7.10" + - nodejs_version: "8.4" cache: - node_modules install: - ps: Install-Product node $env:nodejs_version + - npm config set shrinkwrap false - npm rm --save-dev connect-redis - if exist node_modules npm prune - if exist node_modules npm rebuild From c3fb7e5adc1fd40e301ed1e25f0d5b5a393d0295 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Mon, 25 Sep 2017 21:06:00 -0400 Subject: [PATCH 02/26] build: test against Node.js 9.x nightly --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 3e487a6f18..855168ff54 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,6 +14,8 @@ matrix: include: - node_js: "8" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" + - node_js: "9" + env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" allow_failures: # Allow the nightly installs to fail - env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" From 80f1ea9bec3c5aedb08a6917ecc24fb8d22b707d Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Mon, 25 Sep 2017 21:11:33 -0400 Subject: [PATCH 03/26] Improve error message when autoloading invalid view engine fixes #3403 --- History.md | 5 +++++ lib/view.js | 10 +++++++++- test/fixtures/broken.send | 0 test/res.render.js | 14 ++++++++++++++ 4 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 test/fixtures/broken.send diff --git a/History.md b/History.md index 707677eb17..765f050318 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +unreleased +========== + + * Improve error message when autoloading invalid view engine + 4.15.5 / 2017-09-24 =================== diff --git a/lib/view.js b/lib/view.js index 99d5aed7a0..cf101caeab 100644 --- a/lib/view.js +++ b/lib/view.js @@ -76,7 +76,15 @@ function View(name, options) { // load engine var mod = this.ext.substr(1) debug('require "%s"', mod) - opts.engines[this.ext] = require(mod).__express + + // default engine export + var fn = require(mod).__express + + if (typeof fn !== 'function') { + throw new Error('Module "' + mod + '" does not provide a view engine.') + } + + opts.engines[this.ext] = fn } // store loaded engine diff --git a/test/fixtures/broken.send b/test/fixtures/broken.send new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/res.render.js b/test/res.render.js index e19e8cc542..643a57002a 100644 --- a/test/res.render.js +++ b/test/res.render.js @@ -35,6 +35,20 @@ describe('res', function(){ .expect('

tobi

', done); }) + it('should error without "view engine" set and file extension to a non-engine module', function (done) { + var app = createApp() + + app.locals.user = { name: 'tobi' } + + app.use(function (req, res) { + res.render(path.join(__dirname, 'fixtures', 'broken.send')) + }) + + request(app) + .get('/') + .expect(500, /does not provide a view engine/, done) + }) + it('should error without "view engine" set and no file extension', function (done) { var app = createApp(); From 48940e61202be07677659b3b9d87c967fc4e8bdc Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Mon, 25 Sep 2017 21:12:47 -0400 Subject: [PATCH 04/26] Skip Buffer encoding when not generating ETag for small response --- History.md | 1 + lib/response.js | 23 ++++++++++++++++------- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/History.md b/History.md index 765f050318..83ba2cc5d7 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ unreleased ========== * Improve error message when autoloading invalid view engine + * Skip `Buffer` encoding when not generating ETag for small response 4.15.5 / 2017-09-24 =================== diff --git a/lib/response.js b/lib/response.js index b852a60e2f..e34af49d69 100644 --- a/lib/response.js +++ b/lib/response.js @@ -106,7 +106,6 @@ res.links = function(links){ res.send = function send(body) { var chunk = body; var encoding; - var len; var req = this.req; var type; @@ -171,23 +170,33 @@ res.send = function send(body) { } } + // determine if ETag should be generated + var etagFn = app.get('etag fn') + var generateETag = !this.get('ETag') && typeof etagFn === 'function' + // populate Content-Length + var len if (chunk !== undefined) { - if (!Buffer.isBuffer(chunk)) { - // convert chunk to Buffer; saves later double conversions + if (!generateETag && chunk.length < 1000) { + // just calculate length when no ETag + small chunk + len = Buffer.byteLength(chunk, encoding) + } else if (!Buffer.isBuffer(chunk)) { + // convert chunk to Buffer and calculate chunk = new Buffer(chunk, encoding); encoding = undefined; + len = chunk.length + } else { + // get length of Buffer + len = chunk.length } - len = chunk.length; this.set('Content-Length', len); } // populate ETag var etag; - var generateETag = len !== undefined && app.get('etag fn'); - if (typeof generateETag === 'function' && !this.get('ETag')) { - if ((etag = generateETag(chunk, encoding))) { + if (generateETag && len !== undefined) { + if ((etag = etagFn(chunk, encoding))) { this.set('ETag', etag); } } From 550043c21727674a9d00c30504beb95cfbd7bbba Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Mon, 25 Sep 2017 21:14:00 -0400 Subject: [PATCH 05/26] deps: setprototypeof@1.1.0 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 83ba2cc5d7..e8fcafa414 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ unreleased * Improve error message when autoloading invalid view engine * Skip `Buffer` encoding when not generating ETag for small response + * deps: setprototypeof@1.1.0 4.15.5 / 2017-09-24 =================== diff --git a/package.json b/package.json index 954c20b3c3..cd94d1cf94 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "range-parser": "~1.2.0", "send": "0.15.6", "serve-static": "1.12.6", - "setprototypeof": "1.0.3", + "setprototypeof": "1.1.0", "statuses": "~1.3.1", "type-is": "~1.6.15", "utils-merge": "1.0.0", From 9a99c152703048591c031bd10d2a2e3ca55ebcac Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 27 Sep 2017 21:28:25 -0400 Subject: [PATCH 06/26] deps: accepts@~1.3.4 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index e8fcafa414..0c20649649 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,8 @@ unreleased * Improve error message when autoloading invalid view engine * Skip `Buffer` encoding when not generating ETag for small response + * deps: accepts@~1.3.4 + - deps: mime-types@~2.1.16 * deps: setprototypeof@1.1.0 4.15.5 / 2017-09-24 diff --git a/package.json b/package.json index cd94d1cf94..9351cf2e05 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "api" ], "dependencies": { - "accepts": "~1.3.3", + "accepts": "~1.3.4", "array-flatten": "1.1.1", "content-disposition": "0.5.2", "content-type": "~1.0.2", From 70589c3aef6fb64ce396848e78ca7ea0768d2d5d Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 27 Sep 2017 21:30:08 -0400 Subject: [PATCH 07/26] deps: content-type@~1.0.4 --- History.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 0c20649649..0f6f82bffe 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,9 @@ unreleased * Skip `Buffer` encoding when not generating ETag for small response * deps: accepts@~1.3.4 - deps: mime-types@~2.1.16 + * deps: content-type@~1.0.4 + - perf: remove argument reassignment + - perf: skip parameter parsing when no parameters * deps: setprototypeof@1.1.0 4.15.5 / 2017-09-24 diff --git a/package.json b/package.json index 9351cf2e05..5febf1048e 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "accepts": "~1.3.4", "array-flatten": "1.1.1", "content-disposition": "0.5.2", - "content-type": "~1.0.2", + "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", "debug": "2.6.9", From e62bb8bf9f68382414cdd7997fe661de4647c987 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 27 Sep 2017 21:30:43 -0400 Subject: [PATCH 08/26] deps: etag@~1.8.1 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 0f6f82bffe..077eec931b 100644 --- a/History.md +++ b/History.md @@ -8,6 +8,8 @@ unreleased * deps: content-type@~1.0.4 - perf: remove argument reassignment - perf: skip parameter parsing when no parameters + * deps: etag@~1.8.1 + - perf: replace regular expression with substring * deps: setprototypeof@1.1.0 4.15.5 / 2017-09-24 diff --git a/package.json b/package.json index 5febf1048e..78834c5072 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "depd": "~1.1.1", "encodeurl": "~1.0.1", "escape-html": "~1.0.3", - "etag": "~1.8.0", + "etag": "~1.8.1", "finalhandler": "~1.0.6", "fresh": "0.5.2", "merge-descriptors": "1.0.1", From ad7d96db479e6e9d93ab4848d5fe163905e123ed Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 27 Sep 2017 21:31:39 -0400 Subject: [PATCH 09/26] deps: qs@6.5.1 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 077eec931b..012d8a2ffe 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,8 @@ unreleased - perf: skip parameter parsing when no parameters * deps: etag@~1.8.1 - perf: replace regular expression with substring + * deps: qs@6.5.1 + - Fix parsing & compacting very deep objects * deps: setprototypeof@1.1.0 4.15.5 / 2017-09-24 diff --git a/package.json b/package.json index 78834c5072..0bbfeb8e6e 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "parseurl": "~1.3.1", "path-to-regexp": "0.1.7", "proxy-addr": "~1.1.5", - "qs": "6.5.0", + "qs": "6.5.1", "range-parser": "~1.2.0", "send": "0.15.6", "serve-static": "1.12.6", From 5cc761c86593f2e87c7a9dac02135548096bb952 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 00:04:47 -0400 Subject: [PATCH 10/26] deps: parseurl@~1.3.2 --- History.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 012d8a2ffe..2c9866dbb5 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,9 @@ unreleased - perf: skip parameter parsing when no parameters * deps: etag@~1.8.1 - perf: replace regular expression with substring + * deps: parseurl@~1.3.2 + - perf: reduce overhead for full URLs + - perf: unroll the "fast-path" `RegExp` * deps: qs@6.5.1 - Fix parsing & compacting very deep objects * deps: setprototypeof@1.1.0 diff --git a/package.json b/package.json index 0bbfeb8e6e..4330b141f0 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.1", + "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", "proxy-addr": "~1.1.5", "qs": "6.5.1", From 673d51f4f0fa83f6b663ed6f9f0426940d07664b Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 00:19:30 -0400 Subject: [PATCH 11/26] deps: utils-merge@1.0.1 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 2c9866dbb5..eb01929864 100644 --- a/History.md +++ b/History.md @@ -16,6 +16,7 @@ unreleased * deps: qs@6.5.1 - Fix parsing & compacting very deep objects * deps: setprototypeof@1.1.0 + * deps: utils-merge@1.0.1 4.15.5 / 2017-09-24 =================== diff --git a/package.json b/package.json index 4330b141f0..1298b94327 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "setprototypeof": "1.1.0", "statuses": "~1.3.1", "type-is": "~1.6.15", - "utils-merge": "1.0.0", + "utils-merge": "1.0.1", "vary": "~1.1.1" }, "devDependencies": { From c2f4fb535688eaec14c713190a4ab881e195a41a Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 00:42:05 -0400 Subject: [PATCH 12/26] deps: finalhandler@1.1.0 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index eb01929864..c4b34a7643 100644 --- a/History.md +++ b/History.md @@ -10,6 +10,8 @@ unreleased - perf: skip parameter parsing when no parameters * deps: etag@~1.8.1 - perf: replace regular expression with substring + * deps: finalhandler@1.1.0 + - Use `res.headersSent` when available * deps: parseurl@~1.3.2 - perf: reduce overhead for full URLs - perf: unroll the "fast-path" `RegExp` diff --git a/package.json b/package.json index 1298b94327..ea49823cbb 100644 --- a/package.json +++ b/package.json @@ -38,7 +38,7 @@ "encodeurl": "~1.0.1", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "~1.0.6", + "finalhandler": "1.1.0", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", From 02a9d5fb28e313fd94ee5ec24fe5da02fbc0d6eb Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 01:18:04 -0400 Subject: [PATCH 13/26] deps: proxy-addr@~2.0.2 closes #3432 --- History.md | 5 +++++ package.json | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index c4b34a7643..4352f26acd 100644 --- a/History.md +++ b/History.md @@ -15,6 +15,11 @@ unreleased * deps: parseurl@~1.3.2 - perf: reduce overhead for full URLs - perf: unroll the "fast-path" `RegExp` + * deps: proxy-addr@~2.0.2 + - Fix trimming leading / trailing OWS in `X-Forwarded-For` + - deps: forwarded@~0.1.2 + - deps: ipaddr.js@1.5.2 + - perf: reduce overhead when no `X-Forwarded-For` header * deps: qs@6.5.1 - Fix parsing & compacting very deep objects * deps: setprototypeof@1.1.0 diff --git a/package.json b/package.json index ea49823cbb..219e21fe23 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "~1.1.5", + "proxy-addr": "~2.0.2", "qs": "6.5.1", "range-parser": "~1.2.0", "send": "0.15.6", From d9d09b8b9041504b645f3173ca70ef173c7e1563 Mon Sep 17 00:00:00 2001 From: Lawrence Page Date: Thu, 18 May 2017 11:04:27 -0700 Subject: [PATCH 14/26] perf: re-use options object when generating ETags closes #3313 closes #3314 --- History.md | 1 + lib/utils.js | 35 +++++++++++++++++++++-------------- 2 files changed, 22 insertions(+), 14 deletions(-) diff --git a/History.md b/History.md index 4352f26acd..4b4f0dd5cb 100644 --- a/History.md +++ b/History.md @@ -24,6 +24,7 @@ unreleased - Fix parsing & compacting very deep objects * deps: setprototypeof@1.1.0 * deps: utils-merge@1.0.1 + * perf: re-use options object when generating ETags 4.15.5 / 2017-09-24 =================== diff --git a/lib/utils.js b/lib/utils.js index ae2a7f862d..80f4edae3a 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -31,13 +31,7 @@ var querystring = require('querystring'); * @api private */ -exports.etag = function (body, encoding) { - var buf = !Buffer.isBuffer(body) - ? new Buffer(body, encoding) - : body; - - return etag(buf, {weak: false}); -}; +exports.etag = createETagGenerator({ weak: false }) /** * Return weak ETag for `body`. @@ -48,13 +42,7 @@ exports.etag = function (body, encoding) { * @api private */ -exports.wetag = function wetag(body, encoding){ - var buf = !Buffer.isBuffer(body) - ? new Buffer(body, encoding) - : body; - - return etag(buf, {weak: true}); -}; +exports.wetag = createETagGenerator({ weak: true }) /** * Check if `path` looks absolute. @@ -273,6 +261,25 @@ exports.setCharset = function setCharset(type, charset) { return contentType.format(parsed); }; +/** + * Create an ETag generator function, generating ETags with + * the given options. + * + * @param {object} options + * @return {function} + * @private + */ + +function createETagGenerator (options) { + return function generateETag (body, encoding) { + var buf = !Buffer.isBuffer(body) + ? new Buffer(body, encoding) + : body + + return etag(buf, options) + } +} + /** * Parse an extended query string with qs. * From fa272edf843a31aa242390d46935437451707d54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hrvoje=20=C5=A0imi=C4=87?= Date: Wed, 27 Sep 2017 15:17:03 +0200 Subject: [PATCH 15/26] docs: fix typo in jsdoc comment closes #3430 --- lib/application.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/application.js b/lib/application.js index 1abe8d08f5..8097d81a3b 100644 --- a/lib/application.js +++ b/lib/application.js @@ -338,7 +338,7 @@ app.param = function param(name, fn) { * Assign `setting` to `val`, or return `setting`'s value. * * app.set('foo', 'bar'); - * app.get('foo'); + * app.set('foo'); * // => "bar" * * Mounted servers inherit their parent server's settings. From 12c37124689380837b24a7ed962432596237b440 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 08:26:39 -0400 Subject: [PATCH 16/26] Use safe-buffer for improved Buffer API --- History.md | 1 + benchmarks/middleware.js | 4 +--- lib/response.js | 5 +++-- lib/utils.js | 3 ++- package.json | 1 + test/res.attachment.js | 3 ++- test/res.send.js | 12 ++++++------ test/utils.js | 7 +++---- 8 files changed, 19 insertions(+), 17 deletions(-) diff --git a/History.md b/History.md index 4b4f0dd5cb..18f5e5da37 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ unreleased * Improve error message when autoloading invalid view engine * Skip `Buffer` encoding when not generating ETag for small response + * Use `safe-buffer` for improved Buffer API * deps: accepts@~1.3.4 - deps: mime-types@~2.1.16 * deps: content-type@~1.0.4 diff --git a/benchmarks/middleware.js b/benchmarks/middleware.js index efbac12983..df4df2c5ac 100644 --- a/benchmarks/middleware.js +++ b/benchmarks/middleware.js @@ -13,10 +13,8 @@ while (n--) { }); } -var body = new Buffer('Hello World'); - app.use(function(req, res, next){ - res.send(body); + res.send('Hello World') }); app.listen(3333); diff --git a/lib/response.js b/lib/response.js index e34af49d69..285daf4fb4 100644 --- a/lib/response.js +++ b/lib/response.js @@ -12,6 +12,7 @@ * @private */ +var Buffer = require('safe-buffer').Buffer var contentDisposition = require('content-disposition'); var deprecate = require('depd')('express'); var encodeUrl = require('encodeurl'); @@ -95,7 +96,7 @@ res.links = function(links){ * * Examples: * - * res.send(new Buffer('wahoo')); + * res.send(Buffer.from('wahoo')); * res.send({ some: 'json' }); * res.send('

some html

'); * @@ -182,7 +183,7 @@ res.send = function send(body) { len = Buffer.byteLength(chunk, encoding) } else if (!Buffer.isBuffer(chunk)) { // convert chunk to Buffer and calculate - chunk = new Buffer(chunk, encoding); + chunk = Buffer.from(chunk, encoding) encoding = undefined; len = chunk.length } else { diff --git a/lib/utils.js b/lib/utils.js index 80f4edae3a..bd81ac7f6d 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,6 +12,7 @@ * @api private */ +var Buffer = require('safe-buffer').Buffer var contentDisposition = require('content-disposition'); var contentType = require('content-type'); var deprecate = require('depd')('express'); @@ -273,7 +274,7 @@ exports.setCharset = function setCharset(type, charset) { function createETagGenerator (options) { return function generateETag (body, encoding) { var buf = !Buffer.isBuffer(body) - ? new Buffer(body, encoding) + ? Buffer.from(body, encoding) : body return etag(buf, options) diff --git a/package.json b/package.json index 219e21fe23..dbbd781004 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ "proxy-addr": "~2.0.2", "qs": "6.5.1", "range-parser": "~1.2.0", + "safe-buffer": "5.1.1", "send": "0.15.6", "serve-static": "1.12.6", "setprototypeof": "1.1.0", diff --git a/test/res.attachment.js b/test/res.attachment.js index 662b1dd4e0..4c3d4aa2f1 100644 --- a/test/res.attachment.js +++ b/test/res.attachment.js @@ -1,4 +1,5 @@ +var Buffer = require('safe-buffer').Buffer var express = require('../') , request = require('supertest'); @@ -36,7 +37,7 @@ describe('res', function(){ app.use(function(req, res){ res.attachment('/path/to/image.png'); - res.send(new Buffer(4)); + res.send(Buffer.alloc(4, '.')) }); request(app) diff --git a/test/res.send.js b/test/res.send.js index 88d231eab5..7aa8d7d90e 100644 --- a/test/res.send.js +++ b/test/res.send.js @@ -1,4 +1,5 @@ +var Buffer = require('safe-buffer').Buffer var express = require('..'); var methods = require('methods'); var request = require('supertest'); @@ -166,7 +167,7 @@ describe('res', function(){ var app = express(); app.use(function(req, res){ - res.set('Content-Type', 'text/plain; charset=iso-8859-1').send(new Buffer('hi')); + res.set('Content-Type', 'text/plain; charset=iso-8859-1').send(Buffer.from('hi')) }); request(app) @@ -181,7 +182,7 @@ describe('res', function(){ var app = express(); app.use(function(req, res){ - res.send(new Buffer('hello')); + res.send(Buffer.from('hello')) }); request(app) @@ -194,8 +195,7 @@ describe('res', function(){ var app = express(); app.use(function (req, res) { - var str = Array(1000).join('-'); - res.send(new Buffer(str)); + res.send(Buffer.alloc(999, '-')) }); request(app) @@ -208,7 +208,7 @@ describe('res', function(){ var app = express(); app.use(function(req, res){ - res.set('Content-Type', 'text/plain').send(new Buffer('hey')); + res.set('Content-Type', 'text/plain').send(Buffer.from('hey')) }); request(app) @@ -512,7 +512,7 @@ describe('res', function(){ app.set('etag', function (body, encoding) { var chunk = !Buffer.isBuffer(body) - ? new Buffer(body, encoding) + ? Buffer.from(body, encoding) : body; chunk.toString().should.equal('hello, world!'); return '"custom"'; diff --git a/test/utils.js b/test/utils.js index c49019fe12..b51d223af9 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,5 +1,6 @@ var assert = require('assert'); +var Buffer = require('safe-buffer').Buffer var utils = require('../lib/utils'); describe('utils.etag(body, encoding)', function(){ @@ -14,8 +15,7 @@ describe('utils.etag(body, encoding)', function(){ }) it('should support buffer', function(){ - var buf = new Buffer('express!') - utils.etag(buf) + utils.etag(Buffer.from('express!')) .should.eql('"8-O2uVAFaQ1rZvlKLT14RnuvjPIdg"') }) @@ -59,8 +59,7 @@ describe('utils.wetag(body, encoding)', function(){ }) it('should support buffer', function(){ - var buf = new Buffer('express!') - utils.wetag(buf) + utils.wetag(Buffer.from('express!')) .should.eql('W/"8-O2uVAFaQ1rZvlKLT14RnuvjPIdg"') }) From 2df1ad26a58bf51228d7600df0d62ed17a90ff71 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Hrvoje=20=C5=A0imi=C4=87?= Date: Tue, 26 Sep 2017 16:00:53 +0200 Subject: [PATCH 17/26] Improve error messages when non-function provided as middleware closes #3426 --- History.md | 1 + lib/application.js | 2 +- lib/router/index.js | 4 ++-- lib/router/route.js | 4 ++-- test/Router.js | 30 +++++++++++++++++++++--------- test/app.use.js | 31 ++++++++++++++++++++++--------- 6 files changed, 49 insertions(+), 23 deletions(-) diff --git a/History.md b/History.md index 18f5e5da37..63a1bdf60b 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ unreleased ========== * Improve error message when autoloading invalid view engine + * Improve error messages when non-function provided as middleware * Skip `Buffer` encoding when not generating ETag for small response * Use `safe-buffer` for improved Buffer API * deps: accepts@~1.3.4 diff --git a/lib/application.js b/lib/application.js index 8097d81a3b..91f77d241e 100644 --- a/lib/application.js +++ b/lib/application.js @@ -207,7 +207,7 @@ app.use = function use(fn) { var fns = flatten(slice.call(arguments, offset)); if (fns.length === 0) { - throw new TypeError('app.use() requires middleware functions'); + throw new TypeError('app.use() requires a middleware function') } // setup router diff --git a/lib/router/index.js b/lib/router/index.js index 51db4c28ff..60727ed6d6 100644 --- a/lib/router/index.js +++ b/lib/router/index.js @@ -448,14 +448,14 @@ proto.use = function use(fn) { var callbacks = flatten(slice.call(arguments, offset)); if (callbacks.length === 0) { - throw new TypeError('Router.use() requires middleware functions'); + throw new TypeError('Router.use() requires a middleware function') } for (var i = 0; i < callbacks.length; i++) { var fn = callbacks[i]; if (typeof fn !== 'function') { - throw new TypeError('Router.use() requires middleware function but got a ' + gettype(fn)); + throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn)) } // add the middleware diff --git a/lib/router/route.js b/lib/router/route.js index ea82ed29df..178df0d516 100644 --- a/lib/router/route.js +++ b/lib/router/route.js @@ -175,7 +175,7 @@ Route.prototype.all = function all() { if (typeof handle !== 'function') { var type = toString.call(handle); - var msg = 'Route.all() requires callback functions but got a ' + type; + var msg = 'Route.all() requires a callback function but got a ' + type throw new TypeError(msg); } @@ -198,7 +198,7 @@ methods.forEach(function(method){ if (typeof handle !== 'function') { var type = toString.call(handle); - var msg = 'Route.' + method + '() requires callback functions but got a ' + type; + var msg = 'Route.' + method + '() requires a callback function but got a ' + type throw new Error(msg); } diff --git a/test/Router.js b/test/Router.js index 18153d2926..057ce443df 100644 --- a/test/Router.js +++ b/test/Router.js @@ -368,17 +368,29 @@ describe('Router', function(){ }) describe('.use', function() { - it('should require arguments', function(){ - var router = new Router(); - router.use.bind(router).should.throw(/requires middleware function/) + it('should require middleware', function () { + var router = new Router() + assert.throws(function () { router.use('/') }, /requires a middleware function/) }) - it('should not accept non-functions', function(){ - var router = new Router(); - router.use.bind(router, '/', 'hello').should.throw(/requires middleware function.*string/) - router.use.bind(router, '/', 5).should.throw(/requires middleware function.*number/) - router.use.bind(router, '/', null).should.throw(/requires middleware function.*Null/) - router.use.bind(router, '/', new Date()).should.throw(/requires middleware function.*Date/) + it('should reject string as middleware', function () { + var router = new Router() + assert.throws(function () { router.use('/', 'foo') }, /requires a middleware function but got a string/) + }) + + it('should reject number as middleware', function () { + var router = new Router() + assert.throws(function () { router.use('/', 42) }, /requires a middleware function but got a number/) + }) + + it('should reject null as middleware', function () { + var router = new Router() + assert.throws(function () { router.use('/', null) }, /requires a middleware function but got a Null/) + }) + + it('should reject Date as middleware', function () { + var router = new Router() + assert.throws(function () { router.use('/', new Date()) }, /requires a middleware function but got a Date/) }) it('should be called for any URL', function (done) { diff --git a/test/app.use.js b/test/app.use.js index b2031e4c56..347937fbb3 100644 --- a/test/app.use.js +++ b/test/app.use.js @@ -1,5 +1,6 @@ var after = require('after'); +var assert = require('assert') var express = require('..'); var request = require('supertest'); @@ -253,17 +254,29 @@ describe('app', function(){ }) describe('.use(path, middleware)', function(){ - it('should reject missing functions', function () { - var app = express(); - app.use.bind(app, '/').should.throw(/requires middleware function/); + it('should require middleware', function () { + var app = express() + assert.throws(function () { app.use('/') }, /requires a middleware function/) }) - it('should reject non-functions as middleware', function () { - var app = express(); - app.use.bind(app, '/', 'hi').should.throw(/requires middleware function.*string/); - app.use.bind(app, '/', 5).should.throw(/requires middleware function.*number/); - app.use.bind(app, '/', null).should.throw(/requires middleware function.*Null/); - app.use.bind(app, '/', new Date()).should.throw(/requires middleware function.*Date/); + it('should reject string as middleware', function () { + var app = express() + assert.throws(function () { app.use('/', 'foo') }, /requires a middleware function but got a string/) + }) + + it('should reject number as middleware', function () { + var app = express() + assert.throws(function () { app.use('/', 42) }, /requires a middleware function but got a number/) + }) + + it('should reject null as middleware', function () { + var app = express() + assert.throws(function () { app.use('/', null) }, /requires a middleware function but got a Null/) + }) + + it('should reject Date as middleware', function () { + var app = express() + assert.throws(function () { app.use('/', new Date()) }, /requires a middleware function but got a Date/) }) it('should strip path from req.url', function (done) { From 44591fee234dd83e05894c5b055703db1f68184c Mon Sep 17 00:00:00 2001 From: chainhelen Date: Thu, 28 Sep 2017 12:25:27 +0800 Subject: [PATCH 18/26] deps: vary@~1.1.2 closes #3434 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 63a1bdf60b..04b6897833 100644 --- a/History.md +++ b/History.md @@ -26,6 +26,8 @@ unreleased - Fix parsing & compacting very deep objects * deps: setprototypeof@1.1.0 * deps: utils-merge@1.0.1 + * deps: vary@~1.1.2 + - perf: improve header token parsing speed * perf: re-use options object when generating ETags 4.15.5 / 2017-09-24 diff --git a/package.json b/package.json index dbbd781004..0a476d67d9 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "statuses": "~1.3.1", "type-is": "~1.6.15", "utils-merge": "1.0.1", - "vary": "~1.1.1" + "vary": "~1.1.2" }, "devDependencies": { "after": "0.8.2", From 95fb5cc26848d4c2c57b0a7a74f088538d47d312 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 10:30:10 -0400 Subject: [PATCH 19/26] perf: remove dead .charset set in res.jsonp --- History.md | 1 + lib/response.js | 1 - 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/History.md b/History.md index 04b6897833..c2d2595731 100644 --- a/History.md +++ b/History.md @@ -29,6 +29,7 @@ unreleased * deps: vary@~1.1.2 - perf: improve header token parsing speed * perf: re-use options object when generating ETags + * perf: remove dead `.charset` set in `res.jsonp` 4.15.5 / 2017-09-24 =================== diff --git a/lib/response.js b/lib/response.js index 285daf4fb4..9f61648c17 100644 --- a/lib/response.js +++ b/lib/response.js @@ -314,7 +314,6 @@ res.jsonp = function jsonp(obj) { // jsonp if (typeof callback === 'string' && callback.length !== 0) { - this.charset = 'utf-8'; this.set('X-Content-Type-Options', 'nosniff'); this.set('Content-Type', 'text/javascript'); From a24fd0ca6cfd29329444fddf678bcdd1c08e56ae Mon Sep 17 00:00:00 2001 From: Aaron Clover Date: Thu, 20 Jul 2017 21:24:04 +1000 Subject: [PATCH 20/26] Add options to res.download closes #3327 closes #3370 --- lib/response.js | 34 ++++++++++++++++--- test/res.download.js | 80 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 4 deletions(-) diff --git a/lib/response.js b/lib/response.js index 9f61648c17..ae028ae81b 100644 --- a/lib/response.js +++ b/lib/response.js @@ -515,19 +515,29 @@ res.sendfile = deprecate.function(res.sendfile, * when the data transfer is complete, or when an error has * ocurred. Be sure to check `res.headersSent` if you plan to respond. * - * This method uses `res.sendfile()`. + * Optionally providing an `options` object to use with `res.sendFile()`. + * This function will set the `Content-Disposition` header, overriding + * any `Content-Disposition` header passed as header options in order + * to set the attachment and filename. + * + * This method uses `res.sendFile()`. * * @public */ -res.download = function download(path, filename, callback) { +res.download = function download (path, filename, options, callback) { var done = callback; var name = filename; + var opts = options || null - // support function as second arg + // support function as second or third arg if (typeof filename === 'function') { done = filename; name = null; + opts = null + } else if (typeof options === 'function') { + done = options + opts = null } // set Content-Disposition when file is sent @@ -535,10 +545,26 @@ res.download = function download(path, filename, callback) { 'Content-Disposition': contentDisposition(name || path) }; + // merge user-provided headers + if (opts && opts.headers) { + var keys = Object.keys(opts.headers) + for (var i = 0; i < keys.length; i++) { + var key = keys[i] + if (key.toLowerCase() !== 'content-disposition') { + headers[key] = opts.headers[key] + } + } + } + + // merge user-provided options + opts = Object.create(opts) + opts.headers = headers + // Resolve the full path for sendFile var fullPath = resolve(path); - return this.sendFile(fullPath, { headers: headers }, done); + // send file + return this.sendFile(fullPath, opts, done) }; /** diff --git a/test/res.download.js b/test/res.download.js index fad56ee256..30215bf676 100644 --- a/test/res.download.js +++ b/test/res.download.js @@ -71,6 +71,86 @@ describe('res', function(){ }) }) + describe('.download(path, filename, options, fn)', function () { + it('should invoke the callback', function (done) { + var app = express() + var cb = after(2, done) + var options = {} + + app.use(function (req, res) { + res.download('test/fixtures/user.html', 'document', options, done) + }) + + request(app) + .get('/') + .expect(200) + .expect('Content-Type', 'text/html; charset=UTF-8') + .expect('Content-Disposition', 'attachment; filename="document"') + .end(cb) + }) + + it('should allow options to res.sendFile()', function (done) { + var app = express() + + app.use(function (req, res) { + res.download('test/fixtures/.name', 'document', { + dotfiles: 'allow', + maxAge: '4h' + }) + }) + + request(app) + .get('/') + .expect(200) + .expect('Content-Disposition', 'attachment; filename="document"') + .expect('Cache-Control', 'public, max-age=14400') + .expect('tobi') + .end(done) + }) + + describe('when options.headers contains Content-Disposition', function () { + it('should should be ignored', function (done) { + var app = express() + + app.use(function (req, res) { + res.download('test/fixtures/user.html', 'document', { + headers: { + 'Content-Type': 'text/x-custom', + 'Content-Disposition': 'inline' + } + }) + }) + + request(app) + .get('/') + .expect(200) + .expect('Content-Type', 'text/x-custom') + .expect('Content-Disposition', 'attachment; filename="document"') + .end(done) + }) + + it('should should be ignored case-insensitively', function (done) { + var app = express() + + app.use(function (req, res) { + res.download('test/fixtures/user.html', 'document', { + headers: { + 'content-type': 'text/x-custom', + 'content-disposition': 'inline' + } + }) + }) + + request(app) + .get('/') + .expect(200) + .expect('Content-Type', 'text/x-custom') + .expect('Content-Disposition', 'attachment; filename="document"') + .end(done) + }) + }) + }) + describe('on failure', function(){ it('should invoke the callback', function(done){ var app = express(); From 628438d8d890f3707b8eecf57aeff7d0da348e8e Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 28 Sep 2017 11:36:20 -0400 Subject: [PATCH 21/26] deps: update example dependencies --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 0a476d67d9..addbdcb7f3 100644 --- a/package.json +++ b/package.json @@ -59,18 +59,18 @@ }, "devDependencies": { "after": "0.8.2", - "body-parser": "1.18.1", + "body-parser": "1.18.2", "cookie-parser": "~1.4.3", - "cookie-session": "1.3.1", + "cookie-session": "1.3.2", "ejs": "2.5.7", "eslint": "2.13.1", - "express-session": "1.15.5", + "express-session": "1.15.6", "hbs": "4.0.1", "istanbul": "0.4.5", "marked": "0.3.6", - "method-override": "2.3.9", + "method-override": "2.3.10", "mocha": "3.5.3", - "morgan": "1.8.2", + "morgan": "1.9.0", "multiparty": "4.1.3", "pbkdf2-password": "1.2.1", "should": "13.1.0", From 715401478516c39ea9b2f855d4109d7d6e1131e0 Mon Sep 17 00:00:00 2001 From: Greg Guthe Date: Wed, 5 Apr 2017 17:42:09 -0400 Subject: [PATCH 22/26] Add "escape json" setting for res.json and res.jsonp closes #3268 closes #3269 --- History.md | 1 + lib/response.js | 36 +++++++++++++++++++++++++++++++----- test/res.json.js | 22 ++++++++++++++++++++++ test/res.jsonp.js | 22 ++++++++++++++++++++++ 4 files changed, 76 insertions(+), 5 deletions(-) diff --git a/History.md b/History.md index c2d2595731..248b1c7a67 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,7 @@ unreleased ========== + * Add `"json escape"` setting for `res.json` and `res.jsonp` * Improve error message when autoloading invalid view engine * Improve error messages when non-function provided as middleware * Skip `Buffer` encoding when not generating ETag for small response diff --git a/lib/response.js b/lib/response.js index ae028ae81b..832044be9a 100644 --- a/lib/response.js +++ b/lib/response.js @@ -254,9 +254,10 @@ res.json = function json(obj) { // settings var app = this.app; + var escape = app.get('json escape') var replacer = app.get('json replacer'); var spaces = app.get('json spaces'); - var body = stringify(val, replacer, spaces); + var body = stringify(val, replacer, spaces, escape) // content-type if (!this.get('Content-Type')) { @@ -296,9 +297,10 @@ res.jsonp = function jsonp(obj) { // settings var app = this.app; + var escape = app.get('json escape') var replacer = app.get('json replacer'); var spaces = app.get('json spaces'); - var body = stringify(val, replacer, spaces); + var body = stringify(val, replacer, spaces, escape) var callback = this.req.query[app.get('jsonp callback name')]; // content-type @@ -1098,14 +1100,38 @@ function sendfile(res, file, options, callback) { } /** - * Stringify JSON, like JSON.stringify, but v8 optimized. + * Stringify JSON, like JSON.stringify, but v8 optimized, with the + * ability to escape characters that can trigger HTML sniffing. + * + * @param {*} value + * @param {function} replaces + * @param {number} spaces + * @param {boolean} escape + * @returns {string} * @private */ -function stringify(value, replacer, spaces) { +function stringify (value, replacer, spaces, escape) { // v8 checks arguments.length for optimizing simple call // https://bugs.chromium.org/p/v8/issues/detail?id=4730 - return replacer || spaces + var json = replacer || spaces ? JSON.stringify(value, replacer, spaces) : JSON.stringify(value); + + if (escape) { + json = json.replace(/[<>&]/g, function (c) { + switch (c.charCodeAt(0)) { + case 0x3c: + return '\\u003c' + case 0x3e: + return '\\u003e' + case 0x26: + return '\\u0026' + default: + return c + } + }) + } + + return json } diff --git a/test/res.json.js b/test/res.json.js index 69f6723af5..1041376235 100644 --- a/test/res.json.js +++ b/test/res.json.js @@ -102,6 +102,28 @@ describe('res', function(){ }) }) + describe('"json escape" setting', function () { + it('should be undefined by default', function () { + var app = express() + assert.strictEqual(app.get('json escape'), undefined) + }) + + it('should unicode escape HTML-sniffing characters', function (done) { + var app = express() + + app.enable('json escape') + + app.use(function (req, res) { + res.json({ '&': '