From d0421ac7e1f897e15a2f7a9328e9bfd938b95a9f Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Fri, 26 Oct 2018 23:34:00 -0400 Subject: [PATCH 01/56] tests: use supertest to perform assertions --- test/app.router.js | 24 +++++++++--------------- test/res.cookie.js | 7 ++----- test/res.locals.js | 10 ++++------ 3 files changed, 15 insertions(+), 26 deletions(-) diff --git a/test/app.router.js b/test/app.router.js index a6c8cef202..d716ea4b04 100644 --- a/test/app.router.js +++ b/test/app.router.js @@ -152,15 +152,12 @@ describe('app.router', function(){ app.use(function(req, res, next){ calls.push('after'); - res.end(); + res.json(calls) }); request(app) .get('/') - .end(function(res){ - calls.should.eql(['before', 'GET /', 'after']) - done(); - }) + .expect(200, ['before', 'GET /', 'after'], done) }) describe('when given a regexp', function(){ @@ -891,15 +888,12 @@ describe('app.router', function(){ app.get('/foo', function(req, res, next){ calls.push('/foo 2'); - res.end('done'); + res.json(calls) }); request(app) .get('/foo') - .expect('done', function(){ - calls.should.eql(['/foo/:bar?', '/foo', '/foo 2']); - done(); - }) + .expect(200, ['/foo/:bar?', '/foo', '/foo 2'], done) }) }) @@ -982,15 +976,15 @@ describe('app.router', function(){ }); app.use(function(err, req, res, next){ - res.end(err.message); + res.json({ + calls: calls, + error: err.message + }) }) request(app) .get('/foo') - .expect('fail', function(){ - calls.should.eql(['/foo/:bar?', '/foo']); - done(); - }) + .expect(200, { calls: ['/foo/:bar?', '/foo'], error: 'fail' }, done) }) it('should call handler in same route, if exists', function(done){ diff --git a/test/res.cookie.js b/test/res.cookie.js index 4eeaaf094a..271a0969e6 100644 --- a/test/res.cookie.js +++ b/test/res.cookie.js @@ -108,15 +108,12 @@ describe('res', function(){ app.use(function(req, res){ res.cookie('name', 'tobi', options) - res.end(); + res.json(options) }); request(app) .get('/') - .end(function(err, res){ - options.should.eql(optionsCopy); - done(); - }) + .expect(200, optionsCopy, done) }) }) diff --git a/test/res.locals.js b/test/res.locals.js index 3c83e66c54..a1c819667a 100644 --- a/test/res.locals.js +++ b/test/res.locals.js @@ -8,13 +8,12 @@ describe('res', function(){ var app = express(); app.use(function(req, res){ - Object.keys(res.locals).should.eql([]); - res.end(); + res.json(res.locals) }); request(app) .get('/') - .expect(200, done); + .expect(200, {}, done) }) }) @@ -30,12 +29,11 @@ describe('res', function(){ }); app.use(function(req, res){ - res.locals.foo.should.equal('bar'); - res.end(); + res.json(res.locals) }); request(app) .get('/') - .expect(200, done); + .expect(200, { foo: 'bar' }, done) }) }) From a6b119d27a2f1703d51d1938e6fe98b0ee2d5651 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sat, 27 Oct 2018 00:05:00 -0400 Subject: [PATCH 02/56] build: coveralls@2.12.0 --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index ebeed7008d..a36ad43c97 100644 --- a/.travis.yml +++ b/.travis.yml @@ -60,5 +60,5 @@ script: after_script: - | # Upload coverage to coveralls - npm install --save-dev coveralls@2.10.0 + npm install --save-dev coveralls@2.12.0 coveralls < ./coverage/lcov.info From 6295b4592014515e137c17e854f83d1c0274198a Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sat, 27 Oct 2018 00:50:58 -0400 Subject: [PATCH 03/56] build: test against Node.js 11.x nightly --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index a36ad43c97..a49b22edcf 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ matrix: env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" - node_js: "10" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" + - node_js: "11" + 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 003459b795b3ab2ae97c2131585b0560c1d35716 Mon Sep 17 00:00:00 2001 From: Nacim Goura Date: Tue, 10 Apr 2018 11:33:43 +0200 Subject: [PATCH 04/56] build: support Node.js 9.x closes #3617 --- .travis.yml | 3 +-- appveyor.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index a49b22edcf..aad455e48b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -10,10 +10,9 @@ node_js: - "6.14" - "7.10" - "8.12" + - "9.11" matrix: include: - - node_js: "9" - env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" - node_js: "10" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" - node_js: "11" diff --git a/appveyor.yml b/appveyor.yml index fc3582e4a5..0a911723cf 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -10,6 +10,7 @@ environment: - nodejs_version: "6.14" - nodejs_version: "7.10" - nodejs_version: "8.12" + - nodejs_version: "9.11" cache: - node_modules install: From 44e539e1dcdc010638812fc96f541da3f02d35de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Felix=20Bu=CC=88nemann?= Date: Wed, 16 May 2018 18:27:06 +0200 Subject: [PATCH 05/56] build: support Node.js 10.x closes #3617 --- .travis.yml | 3 +-- appveyor.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index aad455e48b..c802e4fd3a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,10 +11,9 @@ node_js: - "7.10" - "8.12" - "9.11" + - "10.12" matrix: include: - - node_js: "10" - env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" - node_js: "11" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" allow_failures: diff --git a/appveyor.yml b/appveyor.yml index 0a911723cf..4006a5e51d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,6 +11,7 @@ environment: - nodejs_version: "7.10" - nodejs_version: "8.12" - nodejs_version: "9.11" + - nodejs_version: "10.12" cache: - node_modules install: From 6bcdfef6ad148672872e4f5930a01a5a45dd9df0 Mon Sep 17 00:00:00 2001 From: void Date: Sun, 4 Mar 2018 04:56:32 +0400 Subject: [PATCH 06/56] Improve error message for non-strings to res.sendFile closes #3582 --- History.md | 5 +++++ lib/response.js | 4 ++++ test/res.sendFile.js | 8 ++++++++ 3 files changed, 17 insertions(+) diff --git a/History.md b/History.md index 2f6eab101a..6a0699421a 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +unreleased +========== + + * Improve error message for non-strings to `res.sendFile` + 4.16.4 / 2018-10-10 =================== diff --git a/lib/response.js b/lib/response.js index 2e445ac02c..11adeb614a 100644 --- a/lib/response.js +++ b/lib/response.js @@ -411,6 +411,10 @@ res.sendFile = function sendFile(path, options, callback) { throw new TypeError('path argument is required to res.sendFile'); } + if (typeof path !== 'string') { + throw new TypeError('path must be a string to res.sendFile') + } + // support function as second arg if (typeof options === 'function') { done = options; diff --git a/test/res.sendFile.js b/test/res.sendFile.js index d7585b7704..5f494f1e0b 100644 --- a/test/res.sendFile.js +++ b/test/res.sendFile.js @@ -20,6 +20,14 @@ describe('res', function(){ .expect(500, /path.*required/, done); }); + it('should error for non-string path', function (done) { + var app = createApp(42) + + request(app) + .get('/') + .expect(500, /TypeError: path must be a string to res.sendFile/, done) + }) + it('should transfer a file', function (done) { var app = createApp(path.resolve(fixtures, 'name.txt')); From 8da51108e7bb501344c537d3f1f846a7477ae329 Mon Sep 17 00:00:00 2001 From: Joshua Caron Date: Thu, 12 Nov 2015 13:33:06 -0500 Subject: [PATCH 07/56] Improve error message for null/undefined to res.status closes #2795 closes #2797 closes #3111 --- History.md | 1 + lib/response.js | 4 ++++ test/res.status.js | 32 ++++++++++++++++++++++++++++++++ 3 files changed, 37 insertions(+) diff --git a/History.md b/History.md index 6a0699421a..35147d390d 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ unreleased ========== * Improve error message for non-strings to `res.sendFile` + * Improve error message for `null`/`undefined` to `res.status` 4.16.4 / 2018-10-10 =================== diff --git a/lib/response.js b/lib/response.js index 11adeb614a..60f8979e85 100644 --- a/lib/response.js +++ b/lib/response.js @@ -64,6 +64,10 @@ var charsetRegExp = /;\s*charset\s*=/; */ res.status = function status(code) { + if (code === undefined || code === null) { + throw new TypeError('code argument is required to res.status') + } + this.statusCode = code; return this; }; diff --git a/test/res.status.js b/test/res.status.js index 8c173a645c..3f928ec0b0 100644 --- a/test/res.status.js +++ b/test/res.status.js @@ -16,5 +16,37 @@ describe('res', function(){ .expect('Created') .expect(201, done); }) + + describe('when code is undefined', function () { + it('should throw a TypeError', function (done) { + var app = express() + + app.use(function (req, res) { + res.status(undefined).send('OK') + }) + + request(app) + .get('/') + .expect(500) + .expect(/TypeError: code argument is required to res.status/) + .end(done) + }) + }) + + describe('when code is null', function () { + it('should throw a TypeError', function (done) { + var app = express() + + app.use(function (req, res) { + res.status(null).send('OK') + }) + + request(app) + .get('/') + .expect(500) + .expect(/TypeError: code argument is required to res.status/) + .end(done) + }) + }) }) }) From b93ffd4bdc09c3af925eed80c28bd37f63bb3cfc Mon Sep 17 00:00:00 2001 From: Horatiu Eugen Vlad Date: Sun, 3 Dec 2017 19:52:46 +0100 Subject: [PATCH 08/56] Support multiple hosts in X-Forwarded-Host fixes #3494 closes #3495 --- History.md | 1 + lib/request.js | 4 ++++ test/req.hostname.js | 50 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 55 insertions(+) diff --git a/History.md b/History.md index 35147d390d..c29a4490bd 100644 --- a/History.md +++ b/History.md @@ -3,6 +3,7 @@ unreleased * Improve error message for non-strings to `res.sendFile` * Improve error message for `null`/`undefined` to `res.status` + * Support multiple hosts in `X-Forwarded-Host` 4.16.4 / 2018-10-10 =================== diff --git a/lib/request.js b/lib/request.js index 8bb86a9acc..a9400ef99d 100644 --- a/lib/request.js +++ b/lib/request.js @@ -430,6 +430,10 @@ defineGetter(req, 'hostname', function hostname(){ if (!host || !trust(this.connection.remoteAddress, 0)) { host = this.get('Host'); + } else if (host.indexOf(',') !== -1) { + // Note: X-Forwarded-Host is normally only ever a + // single value, but this is to be safe. + host = host.substring(0, host.indexOf(',')).trimRight() } if (!host) return; diff --git a/test/req.hostname.js b/test/req.hostname.js index 816cd59799..09bfb89989 100644 --- a/test/req.hostname.js +++ b/test/req.hostname.js @@ -116,6 +116,56 @@ describe('req', function(){ .set('Host', 'example.com') .expect('example.com', done); }) + + describe('when multiple X-Forwarded-Host', function () { + it('should use the first value', function (done) { + var app = express() + + app.enable('trust proxy') + + app.use(function (req, res) { + res.send(req.hostname) + }) + + request(app) + .get('/') + .set('Host', 'localhost') + .set('X-Forwarded-Host', 'example.com, foobar.com') + .expect(200, 'example.com', done) + }) + + it('should remove OWS around comma', function (done) { + var app = express() + + app.enable('trust proxy') + + app.use(function (req, res) { + res.send(req.hostname) + }) + + request(app) + .get('/') + .set('Host', 'localhost') + .set('X-Forwarded-Host', 'example.com , foobar.com') + .expect(200, 'example.com', done) + }) + + it('should strip port number', function (done) { + var app = express() + + app.enable('trust proxy') + + app.use(function (req, res) { + res.send(req.hostname) + }) + + request(app) + .get('/') + .set('Host', 'localhost') + .set('X-Forwarded-Host', 'example.com:8080 , foobar.com:8888') + .expect(200, 'example.com', done) + }) + }) }) describe('when "trust proxy" is disabled', function(){ From 95c31f7041fe31b24175ce9a6537a0d0d6b807f7 Mon Sep 17 00:00:00 2001 From: HubCodes Date: Thu, 13 Dec 2018 16:04:27 +0900 Subject: [PATCH 09/56] docs: fix typo in contributing closes #3827 --- Contributing.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contributing.md b/Contributing.md index 41386568d6..f84c0138cb 100644 --- a/Contributing.md +++ b/Contributing.md @@ -19,7 +19,7 @@ expertise to resolve rare disputes. Log an issue for any question or problem you might have. When in doubt, log an issue, and any additional policies about what to include will be provided in the responses. The only -exception is security dislosures which should be sent privately. +exception is security disclosures which should be sent privately. Committers may direct you to another repository, ask for additional clarifications, and add appropriate metadata before the issue is addressed. From 0ae10bb15471745795a44d9316e324b697725524 Mon Sep 17 00:00:00 2001 From: Austin Scriver Date: Mon, 26 Nov 2018 20:00:47 -0700 Subject: [PATCH 10/56] docs: fix typos in history closes #3810 --- History.md | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/History.md b/History.md index c29a4490bd..20cee35645 100644 --- a/History.md +++ b/History.md @@ -301,7 +301,7 @@ unreleased - Fix including type extensions in parameters in `Accept` parsing - Fix parsing `Accept` parameters with quoted equals - Fix parsing `Accept` parameters with quoted semicolons - - Many performance improvments + - Many performance improvements - deps: mime-types@~2.1.11 - deps: negotiator@0.6.1 * deps: content-type@~1.0.2 @@ -316,7 +316,7 @@ unreleased - perf: enable strict mode - perf: hoist regular expression - perf: use for loop in parse - - perf: use string concatination for serialization + - perf: use string concatenation for serialization * deps: finalhandler@0.5.0 - Change invalid or non-numeric status code to 500 - Overwrite status message to match set status code @@ -326,7 +326,7 @@ unreleased * deps: proxy-addr@~1.1.2 - Fix accepting various invalid netmasks - Fix IPv6-mapped IPv4 validation edge cases - - IPv4 netmasks must be contingous + - IPv4 netmasks must be contiguous - IPv6 addresses cannot be used as a netmask - deps: ipaddr.js@1.1.1 * deps: qs@6.2.0 @@ -1104,13 +1104,13 @@ unreleased - deps: negotiator@0.4.6 * deps: debug@1.0.2 * deps: send@0.4.3 - - Do not throw un-catchable error on file open race condition + - Do not throw uncatchable error on file open race condition - Use `escape-html` for HTML escaping - deps: debug@1.0.2 - deps: finished@1.2.2 - deps: fresh@0.2.2 * deps: serve-static@1.2.3 - - Do not throw un-catchable error on file open race condition + - Do not throw uncatchable error on file open race condition - deps: send@0.4.3 4.4.2 / 2014-06-09 @@ -1990,7 +1990,7 @@ unreleased - deps: serve-static@1.2.3 * deps: debug@1.0.2 * deps: send@0.4.3 - - Do not throw un-catchable error on file open race condition + - Do not throw uncatchable error on file open race condition - Use `escape-html` for HTML escaping - deps: debug@1.0.2 - deps: finished@1.2.2 @@ -3175,7 +3175,7 @@ Shaw] * Updated haml submodule * Changed ETag; removed inode, modified time only * Fixed LF to CRLF for setting multiple cookies - * Fixed cookie complation; values are now urlencoded + * Fixed cookie compilation; values are now urlencoded * Fixed cookies parsing; accepts quoted values and url escaped cookies 0.11.0 / 2010-05-06 @@ -3370,7 +3370,7 @@ Shaw] * Added "plot" format option for Profiler (for gnuplot processing) * Added request number to Profiler plugin - * Fixed binary encoding for multi-part file uploads, was previously defaulting to UTF8 + * Fixed binary encoding for multipart file uploads, was previously defaulting to UTF8 * Fixed issue with routes not firing when not files are present. Closes #184 * Fixed process.Promise -> events.Promise @@ -3416,7 +3416,7 @@ Shaw] * Updated sample chat app to show messages on load * Updated libxmljs parseString -> parseHtmlString * Fixed `make init` to work with older versions of git - * Fixed specs can now run independent specs for those who cant build deps. Closes #127 + * Fixed specs can now run independent specs for those who can't build deps. Closes #127 * Fixed issues introduced by the node url module changes. Closes 126. * Fixed two assertions failing due to Collection#keys() returning strings * Fixed faulty Collection#toArray() spec due to keys() returning strings From 02f3933b6962114cf46c637105db7983d32a156a Mon Sep 17 00:00:00 2001 From: Alvin Smith Date: Thu, 29 Nov 2018 21:02:55 +1300 Subject: [PATCH 11/56] examples: minor fixes to some examples closes #3812 --- examples/downloads/index.js | 2 +- examples/mvc/public/style.css | 2 +- examples/static-files/public/js/app.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/downloads/index.js b/examples/downloads/index.js index e6f3fa9db6..5f0772697c 100644 --- a/examples/downloads/index.js +++ b/examples/downloads/index.js @@ -21,7 +21,7 @@ app.get('/files/:file(*)', function(req, res, next){ res.download(filePath, function (err) { if (!err) return; // file sent - if (err && err.status !== 404) return next(err); // non-404 error + if (err.status !== 404) return next(err); // non-404 error // file for download not found res.statusCode = 404; res.send('Cant find that file, sorry!'); diff --git a/examples/mvc/public/style.css b/examples/mvc/public/style.css index 69fde2e23a..8a23f9d41c 100644 --- a/examples/mvc/public/style.css +++ b/examples/mvc/public/style.css @@ -1,6 +1,6 @@ body { padding: 50px; - font: 16px "Helvetica Neue", Helvetica, Arial; + font: 16px "Helvetica Neue", Helvetica, Arial, sans-serif; } a { color: #107aff; diff --git a/examples/static-files/public/js/app.js b/examples/static-files/public/js/app.js index 257cc5642c..775eb734b0 100644 --- a/examples/static-files/public/js/app.js +++ b/examples/static-files/public/js/app.js @@ -1 +1 @@ -foo +// foo From 186a206a0aed799699e6a7400aed9aeef31c21e9 Mon Sep 17 00:00:00 2001 From: Ciro Santilli Date: Tue, 5 Feb 2019 09:39:46 +0000 Subject: [PATCH 12/56] docs: add listening address to example closes #3873 --- Readme.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Readme.md b/Readme.md index 582e8958c5..9053d79006 100644 --- a/Readme.md +++ b/Readme.md @@ -90,6 +90,8 @@ $ npm install $ npm start ``` + View the website at: http://localhost:3000 + ## Philosophy The Express philosophy is to provide small, robust tooling for HTTP servers, making From 6f12eee8abcfdd672c7a3d638d3dbf744a6b1801 Mon Sep 17 00:00:00 2001 From: Louis Date: Thu, 17 Jan 2019 18:33:01 +0100 Subject: [PATCH 13/56] docs: fix typo in jsdoc comment closes #3859 --- lib/response.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/response.js b/lib/response.js index 60f8979e85..c1514901c5 100644 --- a/lib/response.js +++ b/lib/response.js @@ -822,7 +822,7 @@ res.clearCookie = function clearCookie(name, options) { * // "Remember Me" for 15 minutes * res.cookie('rememberme', '1', { expires: new Date(Date.now() + 900000), httpOnly: true }); * - * // save as above + * // same as above * res.cookie('rememberme', '1', { maxAge: 900000, httpOnly: true }) * * @param {String} name From b9b1b19758b0996680100c65ae87128d623c5f7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=A0=95=ED=99=98?= Date: Fri, 1 Feb 2019 09:56:14 +0900 Subject: [PATCH 14/56] tests: fix typos in descriptions closes #3875 --- test/app.router.js | 2 +- test/req.acceptsCharset.js | 4 ++-- test/req.acceptsCharsets.js | 4 ++-- test/req.acceptsEncodings.js | 2 +- test/req.query.js | 2 +- test/res.download.js | 4 ++-- 6 files changed, 9 insertions(+), 9 deletions(-) diff --git a/test/app.router.js b/test/app.router.js index d716ea4b04..5a31b5fb90 100644 --- a/test/app.router.js +++ b/test/app.router.js @@ -567,7 +567,7 @@ describe('app.router', function(){ .expect('/user/tobi.json', done) }) - it('should decore the capture', function (done) { + it('should decode the capture', function (done) { var app = express() app.get('*', function (req, res) { diff --git a/test/req.acceptsCharset.js b/test/req.acceptsCharset.js index 0d0ed8b5e4..f7d0cc0e30 100644 --- a/test/req.acceptsCharset.js +++ b/test/req.acceptsCharset.js @@ -18,8 +18,8 @@ describe('req', function(){ }) }) - describe('when Accept-Charset is not present', function(){ - it('should return true when present', function(done){ + describe('when Accept-Charset is present', function () { + it('should return true', function (done) { var app = express(); app.use(function(req, res, next){ diff --git a/test/req.acceptsCharsets.js b/test/req.acceptsCharsets.js index 2f4574c524..d1c459174a 100644 --- a/test/req.acceptsCharsets.js +++ b/test/req.acceptsCharsets.js @@ -18,8 +18,8 @@ describe('req', function(){ }) }) - describe('when Accept-Charset is not present', function(){ - it('should return true when present', function(done){ + describe('when Accept-Charset is present', function () { + it('should return true', function (done) { var app = express(); app.use(function(req, res, next){ diff --git a/test/req.acceptsEncodings.js b/test/req.acceptsEncodings.js index aba8ea5fbe..a5cf747d41 100644 --- a/test/req.acceptsEncodings.js +++ b/test/req.acceptsEncodings.js @@ -3,7 +3,7 @@ var express = require('../') , request = require('supertest'); describe('req', function(){ - describe('.acceptsEncodingss', function(){ + describe('.acceptsEncodings', function () { it('should be true if encoding accepted', function(done){ var app = express(); diff --git a/test/req.query.js b/test/req.query.js index d3d29abd16..7819420ce0 100644 --- a/test/req.query.js +++ b/test/req.query.js @@ -70,7 +70,7 @@ describe('req', function(){ }); }); - describe('when "query parser" disabled', function () { + describe('when "query parser" enabled', function () { it('should not parse complex keys', function (done) { var app = createApp(true); diff --git a/test/res.download.js b/test/res.download.js index 084b3c7164..cf3b3ca53e 100644 --- a/test/res.download.js +++ b/test/res.download.js @@ -110,7 +110,7 @@ describe('res', function(){ }) describe('when options.headers contains Content-Disposition', function () { - it('should should be ignored', function (done) { + it('should be ignored', function (done) { var app = express() app.use(function (req, res) { @@ -130,7 +130,7 @@ describe('res', function(){ .end(done) }) - it('should should be ignored case-insensitively', function (done) { + it('should be ignored case-insensitively', function (done) { var app = express() app.use(function (req, res) { From 6eda52a3dc953f297942a98c333b609519141c49 Mon Sep 17 00:00:00 2001 From: Marcin Wanago Date: Wed, 30 Jan 2019 23:42:29 +0100 Subject: [PATCH 15/56] docs: use const in readme example fixes #3867 closes #3868 --- Readme.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Readme.md b/Readme.md index 9053d79006..b25b240c7d 100644 --- a/Readme.md +++ b/Readme.md @@ -9,8 +9,8 @@ [![Test Coverage][coveralls-image]][coveralls-url] ```js -var express = require('express') -var app = express() +const express = require('express') +const app = express() app.get('/', function (req, res) { res.send('Hello World') From 8a97346eaf3a5e39ba8185b244af4918d2ca43b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=A0=95=ED=99=98?= <6pack@madup.com> Date: Fri, 1 Feb 2019 21:33:07 +0900 Subject: [PATCH 16/56] tests: assert calls order in middleware basic tests closes #3878 --- test/middleware.basic.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/middleware.basic.js b/test/middleware.basic.js index ce59589230..4616842ed6 100644 --- a/test/middleware.basic.js +++ b/test/middleware.basic.js @@ -1,4 +1,5 @@ +var assert = require('assert') var express = require('../'); var request = require('supertest'); @@ -33,6 +34,7 @@ describe('middleware', function(){ .set('Content-Type', 'application/json') .send('{"foo":"bar"}') .expect('Content-Type', 'application/json') + .expect(function () { assert.deepEqual(calls, ['one', 'two']) }) .expect(200, '{"foo":"bar"}', done) }) }) From 9e5d1a30c3671f99b5d80a231b697a311f5fe489 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 17 Apr 2019 10:44:27 -0400 Subject: [PATCH 17/56] build: test against Node.js 12.x nightly --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index c802e4fd3a..36e7e75e56 100644 --- a/.travis.yml +++ b/.travis.yml @@ -16,6 +16,8 @@ matrix: include: - node_js: "11" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" + - node_js: "12" + 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 cf5c813d2f6499be2d38a55a26dd5bd1b71b749c Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 17 Apr 2019 15:51:33 -0400 Subject: [PATCH 18/56] build: hbs@4.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 74196ad68e..0b8bec4863 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "ejs": "2.6.1", "eslint": "2.13.1", "express-session": "1.15.6", - "hbs": "4.0.1", + "hbs": "4.0.4", "istanbul": "0.4.5", "marked": "0.5.1", "method-override": "3.0.0", From 4218d04183e0f7bd04f3db7d23b53d6d3857ed10 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 17 Apr 2019 15:58:26 -0400 Subject: [PATCH 19/56] build: marked@0.6.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b8bec4863..6dd72c1f7c 100644 --- a/package.json +++ b/package.json @@ -68,7 +68,7 @@ "express-session": "1.15.6", "hbs": "4.0.4", "istanbul": "0.4.5", - "marked": "0.5.1", + "marked": "0.6.2", "method-override": "3.0.0", "mocha": "5.2.0", "morgan": "1.9.1", From 952484f73a84743c53abfc4b51a6614f2d1e13cd Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 17 Apr 2019 16:00:02 -0400 Subject: [PATCH 20/56] deps: content-disposition@0.5.3 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 20cee35645..5343b0ba85 100644 --- a/History.md +++ b/History.md @@ -4,6 +4,7 @@ unreleased * Improve error message for non-strings to `res.sendFile` * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` + * deps: content-disposition@0.5.3 4.16.4 / 2018-10-10 =================== diff --git a/package.json b/package.json index 6dd72c1f7c..9255d77d07 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "accepts": "~1.3.5", "array-flatten": "1.1.1", "body-parser": "1.18.3", - "content-disposition": "0.5.2", + "content-disposition": "0.5.3", "content-type": "~1.0.4", "cookie": "0.3.1", "cookie-signature": "1.0.6", From 50eb5e43774a78737a82405c17ac8ca3ff5532ff Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 17 Apr 2019 16:05:17 -0400 Subject: [PATCH 21/56] deps: proxy-addr@~2.0.5 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 5343b0ba85..f8a5c0c0e1 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,8 @@ unreleased * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` * deps: content-disposition@0.5.3 + * deps: proxy-addr@~2.0.5 + - deps: ipaddr.js@1.9.0 4.16.4 / 2018-10-10 =================== diff --git a/package.json b/package.json index 9255d77d07..92102c2a76 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "on-finished": "~2.3.0", "parseurl": "~1.3.2", "path-to-regexp": "0.1.7", - "proxy-addr": "~2.0.4", + "proxy-addr": "~2.0.5", "qs": "6.5.2", "range-parser": "~1.2.0", "safe-buffer": "5.1.2", From 03341204ff13fb35ac7082adf3dd694081ab68d3 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 17 Apr 2019 16:06:35 -0400 Subject: [PATCH 22/56] deps: parseurl@~1.3.3 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index f8a5c0c0e1..e1f238bb90 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,7 @@ unreleased * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` * deps: content-disposition@0.5.3 + * deps: parseurl@~1.3.3 * deps: proxy-addr@~2.0.5 - deps: ipaddr.js@1.9.0 diff --git a/package.json b/package.json index 92102c2a76..e548b60927 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,7 @@ "merge-descriptors": "1.0.1", "methods": "~1.1.2", "on-finished": "~2.3.0", - "parseurl": "~1.3.2", + "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.5", "qs": "6.5.2", From b02d3a1744db80a1ad6951f55f4cb6b996db4b53 Mon Sep 17 00:00:00 2001 From: James George Date: Thu, 27 Dec 2018 12:59:51 +0530 Subject: [PATCH 23/56] docs: add link to contributing guide closes #3846 --- Readme.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Readme.md b/Readme.md index b25b240c7d..81d8d91615 100644 --- a/Readme.md +++ b/Readme.md @@ -127,6 +127,10 @@ $ npm install $ npm test ``` +## Contributing + +[Contributing Guide](Contributing.md) + ## People The original author of Express is [TJ Holowaychuk](https://github.com/tj) From 7eacdcef190990f2e5a199bb1140322c687e65d7 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Mon, 22 Apr 2019 13:40:23 -0400 Subject: [PATCH 24/56] deps: setprototypeof@1.1.1 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index e1f238bb90..4f9dcbe96f 100644 --- a/History.md +++ b/History.md @@ -8,6 +8,7 @@ unreleased * deps: parseurl@~1.3.3 * deps: proxy-addr@~2.0.5 - deps: ipaddr.js@1.9.0 + * deps: setprototypeof@1.1.1 4.16.4 / 2018-10-10 =================== diff --git a/package.json b/package.json index e548b60927..719d689137 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "safe-buffer": "5.1.2", "send": "0.16.2", "serve-static": "1.13.2", - "setprototypeof": "1.1.0", + "setprototypeof": "1.1.1", "statuses": "~1.4.0", "type-is": "~1.6.16", "utils-merge": "1.0.1", From 9afa1cfc85171dfd8b3595e7b25b7be783c79ae8 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 30 Apr 2019 22:17:03 -0400 Subject: [PATCH 25/56] deps: statuses@~1.5.0 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 4f9dcbe96f..f0001629d8 100644 --- a/History.md +++ b/History.md @@ -9,6 +9,8 @@ unreleased * deps: proxy-addr@~2.0.5 - deps: ipaddr.js@1.9.0 * deps: setprototypeof@1.1.1 + * deps: statuses@~1.5.0 + - Add `103 Early Hints` 4.16.4 / 2018-10-10 =================== diff --git a/package.json b/package.json index 719d689137..3834b1f1d2 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,7 @@ "send": "0.16.2", "serve-static": "1.13.2", "setprototypeof": "1.1.1", - "statuses": "~1.4.0", + "statuses": "~1.5.0", "type-is": "~1.6.16", "utils-merge": "1.0.1", "vary": "~1.1.2" From 40dbfa2de21588d23a8e8e8d43dae5664f0267af Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 30 Apr 2019 22:24:35 -0400 Subject: [PATCH 26/56] deps: accepts@~1.3.7 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index f0001629d8..850f4333cf 100644 --- a/History.md +++ b/History.md @@ -4,6 +4,7 @@ unreleased * Improve error message for non-strings to `res.sendFile` * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` + * deps: accepts@~1.3.7 * deps: content-disposition@0.5.3 * deps: parseurl@~1.3.3 * deps: proxy-addr@~2.0.5 diff --git a/package.json b/package.json index 3834b1f1d2..ee093cc1ac 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "api" ], "dependencies": { - "accepts": "~1.3.5", + "accepts": "~1.3.7", "array-flatten": "1.1.1", "body-parser": "1.18.3", "content-disposition": "0.5.3", From 6d9dd2da49c8d5fce9fccdd9d8257004040a19a7 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 30 Apr 2019 22:48:56 -0400 Subject: [PATCH 27/56] deps: type-is@~1.6.18 --- History.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 850f4333cf..6a7aab401c 100644 --- a/History.md +++ b/History.md @@ -12,6 +12,9 @@ unreleased * deps: setprototypeof@1.1.1 * deps: statuses@~1.5.0 - Add `103 Early Hints` + * deps: type-is@~1.6.18 + - deps: mime-types@~2.1.24 + - perf: prevent internal `throw` on invalid type 4.16.4 / 2018-10-10 =================== diff --git a/package.json b/package.json index ee093cc1ac..efb712f81f 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "serve-static": "1.13.2", "setprototypeof": "1.1.1", "statuses": "~1.5.0", - "type-is": "~1.6.16", + "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" }, From 32f5293afa2edb7dd9052922dda3ebdd9eab658d Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 30 Apr 2019 23:06:50 -0400 Subject: [PATCH 28/56] deps: qs@6.7.0 --- History.md | 2 ++ package.json | 2 +- test/req.query.js | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index 6a7aab401c..5051207e8a 100644 --- a/History.md +++ b/History.md @@ -9,6 +9,8 @@ unreleased * deps: parseurl@~1.3.3 * deps: proxy-addr@~2.0.5 - deps: ipaddr.js@1.9.0 + * deps: qs@6.7.0 + - Fix parsing array brackets after index * deps: setprototypeof@1.1.1 * deps: statuses@~1.5.0 - Add `103 Early Hints` diff --git a/package.json b/package.json index efb712f81f..9730acccbb 100644 --- a/package.json +++ b/package.json @@ -47,7 +47,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.5", - "qs": "6.5.2", + "qs": "6.7.0", "range-parser": "~1.2.0", "safe-buffer": "5.1.2", "send": "0.16.2", diff --git a/test/req.query.js b/test/req.query.js index 7819420ce0..0e810b8ef9 100644 --- a/test/req.query.js +++ b/test/req.query.js @@ -25,8 +25,8 @@ describe('req', function(){ var app = createApp('extended'); request(app) - .get('/?user[name]=tj') - .expect(200, '{"user":{"name":"tj"}}', done); + .get('/?foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!') + .expect(200, '{"foo":[{"bar":"baz","fizz":"buzz"},"done!"]}', done); }); it('should parse parameters with dots', function (done) { From 2f782d8478948124a1c96fe04de259385163100f Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 30 Apr 2019 23:31:32 -0400 Subject: [PATCH 29/56] deps: body-parser@1.19.0 --- History.md | 10 ++++++++++ package.json | 2 +- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 5051207e8a..3c2b59a6e2 100644 --- a/History.md +++ b/History.md @@ -5,6 +5,16 @@ unreleased * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` * deps: accepts@~1.3.7 + * deps: body-parser@1.19.0 + - Add encoding MIK + - Add petabyte (`pb`) support + - Fix parsing array brackets after index + - deps: bytes@3.1.0 + - deps: http-errors@1.7.2 + - deps: iconv-lite@0.4.24 + - deps: qs@6.7.0 + - deps: raw-body@2.4.0 + - deps: type-is@~1.6.17 * deps: content-disposition@0.5.3 * deps: parseurl@~1.3.3 * deps: proxy-addr@~2.0.5 diff --git a/package.json b/package.json index 9730acccbb..b443c34c82 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "dependencies": { "accepts": "~1.3.7", "array-flatten": "1.1.1", - "body-parser": "1.18.3", + "body-parser": "1.19.0", "content-disposition": "0.5.3", "content-type": "~1.0.4", "cookie": "0.3.1", From 955f2a5f78c75cd58f8c4517725a14dd3ee199a4 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 1 May 2019 22:59:42 -0400 Subject: [PATCH 30/56] tests: add express.json test suite --- test/exports.js | 6 + test/express.json.js | 664 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 670 insertions(+) create mode 100644 test/express.json.js diff --git a/test/exports.js b/test/exports.js index 2a80eedbbe..bc2bb410fc 100644 --- a/test/exports.js +++ b/test/exports.js @@ -1,4 +1,5 @@ +var assert = require('assert') var express = require('../'); var request = require('supertest'); var should = require('should'); @@ -8,6 +9,11 @@ describe('exports', function(){ express.Router.should.be.a.Function() }) + it('should expose json middleware', function () { + assert.equal(typeof express.json, 'function') + assert.equal(express.json.length, 1) + }) + it('should expose the application prototype', function(){ express.application.set.should.be.a.Function() }) diff --git a/test/express.json.js b/test/express.json.js new file mode 100644 index 0000000000..907fa0cfeb --- /dev/null +++ b/test/express.json.js @@ -0,0 +1,664 @@ + +var assert = require('assert') +var Buffer = require('safe-buffer').Buffer +var express = require('..') +var request = require('supertest') + +describe('express.json()', function () { + it('should parse JSON', function (done) { + request(createApp()) + .post('/') + .set('Content-Type', 'application/json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should handle Content-Length: 0', function (done) { + request(createApp()) + .post('/') + .set('Content-Type', 'application/json') + .set('Content-Length', '0') + .expect(200, '{}', done) + }) + + it('should handle empty message-body', function (done) { + request(createApp()) + .post('/') + .set('Content-Type', 'application/json') + .set('Transfer-Encoding', 'chunked') + .expect(200, '{}', done) + }) + + it('should handle no message-body', function (done) { + request(createApp()) + .post('/') + .set('Content-Type', 'application/json') + .unset('Transfer-Encoding') + .expect(200, '{}', done) + }) + + it('should 400 when invalid content-length', function (done) { + var app = express() + + app.use(function (req, res, next) { + req.headers['content-length'] = '20' // bad length + next() + }) + + app.use(express.json()) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .send('{"str":') + .expect(400, /content length/, done) + }) + + it('should handle duplicated middleware', function (done) { + var app = express() + + app.use(express.json()) + app.use(express.json()) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + describe('when JSON is invalid', function () { + before(function () { + this.app = createApp() + }) + + it('should 400 for bad token', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('{:') + .expect(400, parseError('{:'), done) + }) + + it('should 400 for incomplete', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('{"user"') + .expect(400, parseError('{"user"'), done) + }) + + it('should error with type = "entity.parse.failed"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'type') + .send(' {"user"') + .expect(400, 'entity.parse.failed', done) + }) + + it('should include original body on error object', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'body') + .send(' {"user"') + .expect(400, ' {"user"', done) + }) + }) + + describe('with limit option', function () { + it('should 413 when over limit with Content-Length', function (done) { + var buf = Buffer.alloc(1024, '.') + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'application/json') + .set('Content-Length', '1034') + .send(JSON.stringify({ str: buf.toString() })) + .expect(413, done) + }) + + it('should error with type = "entity.too.large"', function (done) { + var buf = Buffer.alloc(1024, '.') + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'application/json') + .set('Content-Length', '1034') + .set('X-Error-Property', 'type') + .send(JSON.stringify({ str: buf.toString() })) + .expect(413, 'entity.too.large', done) + }) + + it('should 413 when over limit with chunked encoding', function (done) { + var buf = Buffer.alloc(1024, '.') + var server = createApp({ limit: '1kb' }) + var test = request(server).post('/') + test.set('Content-Type', 'application/json') + test.set('Transfer-Encoding', 'chunked') + test.write('{"str":') + test.write('"' + buf.toString() + '"}') + test.expect(413, done) + }) + + it('should accept number of bytes', function (done) { + var buf = Buffer.alloc(1024, '.') + request(createApp({ limit: 1024 })) + .post('/') + .set('Content-Type', 'application/json') + .send(JSON.stringify({ str: buf.toString() })) + .expect(413, done) + }) + + it('should not change when options altered', function (done) { + var buf = Buffer.alloc(1024, '.') + var options = { limit: '1kb' } + var server = createApp(options) + + options.limit = '100kb' + + request(server) + .post('/') + .set('Content-Type', 'application/json') + .send(JSON.stringify({ str: buf.toString() })) + .expect(413, done) + }) + + it('should not hang response', function (done) { + var buf = Buffer.alloc(10240, '.') + var server = createApp({ limit: '8kb' }) + var test = request(server).post('/') + test.set('Content-Type', 'application/json') + test.write(buf) + test.write(buf) + test.write(buf) + test.expect(413, done) + }) + }) + + describe('with inflate option', function () { + describe('when false', function () { + before(function () { + this.app = createApp({ inflate: false }) + }) + + it('should not accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex')) + test.expect(415, 'content encoding unsupported', done) + }) + }) + + describe('when true', function () { + before(function () { + this.app = createApp({ inflate: true }) + }) + + it('should accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + }) + }) + + describe('with strict option', function () { + describe('when undefined', function () { + before(function () { + this.app = createApp() + }) + + it('should 400 on primitives', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('true') + .expect(400, parseError('#rue').replace('#', 't'), done) + }) + }) + + describe('when false', function () { + before(function () { + this.app = createApp({ strict: false }) + }) + + it('should parse primitives', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('true') + .expect(200, 'true', done) + }) + }) + + describe('when true', function () { + before(function () { + this.app = createApp({ strict: true }) + }) + + it('should not parse primitives', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('true') + .expect(400, parseError('#rue').replace('#', 't'), done) + }) + + it('should not parse primitives with leading whitespaces', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send(' true') + .expect(400, parseError(' #rue').replace('#', 't'), done) + }) + + it('should allow leading whitespaces in JSON', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send(' { "user": "tobi" }') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should error with type = "entity.parse.failed"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'type') + .send('true') + .expect(400, 'entity.parse.failed', done) + }) + + it('should include correct message in stack trace', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'stack') + .send('true') + .expect(400) + .expect(shouldContainInBody(parseError('#rue').replace('#', 't'))) + .end(done) + }) + }) + }) + + describe('with type option', function () { + describe('when "application/vnd.api+json"', function () { + before(function () { + this.app = createApp({ type: 'application/vnd.api+json' }) + }) + + it('should parse JSON for custom type', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/vnd.api+json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should ignore standard type', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('{"user":"tobi"}') + .expect(200, '{}', done) + }) + }) + + describe('when ["application/json", "application/vnd.api+json"]', function () { + before(function () { + this.app = createApp({ + type: ['application/json', 'application/vnd.api+json'] + }) + }) + + it('should parse JSON for "application/json"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should parse JSON for "application/vnd.api+json"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/vnd.api+json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should ignore "application/x-json"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-json') + .send('{"user":"tobi"}') + .expect(200, '{}', done) + }) + }) + + describe('when a function', function () { + it('should parse when truthy value returned', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return req.headers['content-type'] === 'application/vnd.api+json' + } + + request(app) + .post('/') + .set('Content-Type', 'application/vnd.api+json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should work without content-type', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return true + } + + var test = request(app).post('/') + test.write('{"user":"tobi"}') + test.expect(200, '{"user":"tobi"}', done) + }) + + it('should not invoke without a body', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + throw new Error('oops!') + } + + request(app) + .get('/') + .expect(404, done) + }) + }) + }) + + describe('with verify option', function () { + it('should assert value if function', function () { + assert.throws(createApp.bind(null, { verify: 'lol' }), + /TypeError: option verify must be function/) + }) + + it('should error from verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x5b) throw new Error('no arrays') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .send('["tobi"]') + .expect(403, 'no arrays', done) + }) + + it('should error with type = "entity.verify.failed"', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x5b) throw new Error('no arrays') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'type') + .send('["tobi"]') + .expect(403, 'entity.verify.failed', done) + }) + + it('should allow custom codes', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] !== 0x5b) return + var err = new Error('no arrays') + err.status = 400 + throw err + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .send('["tobi"]') + .expect(400, 'no arrays', done) + }) + + it('should allow custom type', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] !== 0x5b) return + var err = new Error('no arrays') + err.type = 'foo.bar' + throw err + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'type') + .send('["tobi"]') + .expect(403, 'foo.bar', done) + }) + + it('should include original body on error object', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x5b) throw new Error('no arrays') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .set('X-Error-Property', 'body') + .send('["tobi"]') + .expect(403, '["tobi"]', done) + }) + + it('should allow pass-through', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x5b) throw new Error('no arrays') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/json') + .send('{"user":"tobi"}') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should work with different charsets', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x5b) throw new Error('no arrays') + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'application/json; charset=utf-16') + test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should 415 on unknown charset prior to verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + throw new Error('unexpected verify call') + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'application/json; charset=x-bogus') + test.write(Buffer.from('00000000', 'hex')) + test.expect(415, 'unsupported charset "X-BOGUS"', done) + }) + }) + + describe('charset', function () { + before(function () { + this.app = createApp() + }) + + it('should parse utf-8', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json; charset=utf-8') + test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should parse utf-16', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json; charset=utf-16') + test.write(Buffer.from('feff007b0022006e0061006d00650022003a00228bba0022007d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should parse when content-length != char length', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json; charset=utf-8') + test.set('Content-Length', '13') + test.write(Buffer.from('7b2274657374223a22c3a5227d', 'hex')) + test.expect(200, '{"test":"å"}', done) + }) + + it('should default to utf-8', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should fail on unknown charset', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json; charset=koi8-r') + test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex')) + test.expect(415, 'unsupported charset "KOI8-R"', done) + }) + + it('should error with type = "charset.unsupported"', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json; charset=koi8-r') + test.set('X-Error-Property', 'type') + test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex')) + test.expect(415, 'charset.unsupported', done) + }) + }) + + describe('encoding', function () { + before(function () { + this.app = createApp({ limit: '1kb' }) + }) + + it('should parse without encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should support identity encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'identity') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('7b226e616d65223a22e8aeba227d', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should support gzip encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should support deflate encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'deflate') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('789cab56ca4bcc4d55b2527ab16e97522d00274505ac', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should be case-insensitive', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'GZIP') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should 415 on unknown encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'nulls') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('000000000000', 'hex')) + test.expect(415, 'unsupported content encoding "nulls"', done) + }) + + it('should error with type = "encoding.unsupported"', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'nulls') + test.set('Content-Type', 'application/json') + test.set('X-Error-Property', 'type') + test.write(Buffer.from('000000000000', 'hex')) + test.expect(415, 'encoding.unsupported', done) + }) + + it('should 400 on malformed encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex')) + test.expect(400, done) + }) + + it('should 413 when inflated value exceeds limit', function (done) { + // gzip'd data exceeds 1kb, but deflated below 1kb + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/json') + test.write(Buffer.from('1f8b080000000000000bedc1010d000000c2a0f74f6d0f071400000000000000', 'hex')) + test.write(Buffer.from('0000000000000000000000000000000000000000000000000000000000000000', 'hex')) + test.write(Buffer.from('0000000000000000004f0625b3b71650c30000', 'hex')) + test.expect(413, done) + }) + }) +}) + +function createApp (options) { + var app = express() + + app.use(express.json(options)) + + app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.send(String(err[req.headers['x-error-property'] || 'message'])) + }) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + return app +} + +function parseError (str) { + try { + JSON.parse(str); throw new SyntaxError('strict violation') + } catch (e) { + return e.message + } +} + +function shouldContainInBody (str) { + return function (res) { + assert.ok(res.text.indexOf(str) !== -1, + 'expected \'' + res.text + '\' to contain \'' + str + '\'') + } +} From 8b71f39516135f8b729de3456f3ae7f6d33442a1 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 1 May 2019 23:29:28 -0400 Subject: [PATCH 31/56] tests: add express.urlencoded test suite --- test/exports.js | 5 + test/express.urlencoded.js | 734 +++++++++++++++++++++++++++++++++++++ test/support/env.js | 2 +- 3 files changed, 740 insertions(+), 1 deletion(-) create mode 100644 test/express.urlencoded.js diff --git a/test/exports.js b/test/exports.js index bc2bb410fc..57b3265bd6 100644 --- a/test/exports.js +++ b/test/exports.js @@ -14,6 +14,11 @@ describe('exports', function(){ assert.equal(express.json.length, 1) }) + it('should expose urlencoded middleware', function () { + assert.equal(typeof express.urlencoded, 'function') + assert.equal(express.urlencoded.length, 1) + }) + it('should expose the application prototype', function(){ express.application.set.should.be.a.Function() }) diff --git a/test/express.urlencoded.js b/test/express.urlencoded.js new file mode 100644 index 0000000000..6011de05f7 --- /dev/null +++ b/test/express.urlencoded.js @@ -0,0 +1,734 @@ + +var assert = require('assert') +var Buffer = require('safe-buffer').Buffer +var express = require('..') +var request = require('supertest') + +describe('express.urlencoded()', function () { + before(function () { + this.app = createApp() + }) + + it('should parse x-www-form-urlencoded', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should 400 when invalid content-length', function (done) { + var app = express() + + app.use(function (req, res, next) { + req.headers['content-length'] = '20' // bad length + next() + }) + + app.use(express.urlencoded()) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('str=') + .expect(400, /content length/, done) + }) + + it('should handle Content-Length: 0', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Content-Length', '0') + .send('') + .expect(200, '{}', done) + }) + + it('should handle empty message-body', function (done) { + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Transfer-Encoding', 'chunked') + .send('') + .expect(200, '{}', done) + }) + + it('should handle duplicated middleware', function (done) { + var app = express() + + app.use(express.urlencoded()) + app.use(express.urlencoded()) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should parse extended syntax', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user[name][first]=Tobi') + .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done) + }) + + describe('with extended option', function () { + describe('when false', function () { + before(function () { + this.app = createApp({ extended: false }) + }) + + it('should not parse extended syntax', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user[name][first]=Tobi') + .expect(200, '{"user[name][first]":"Tobi"}', done) + }) + + it('should parse multiple key instances', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=Tobi&user=Loki') + .expect(200, '{"user":["Tobi","Loki"]}', done) + }) + }) + + describe('when true', function () { + before(function () { + this.app = createApp({ extended: true }) + }) + + it('should parse multiple key instances', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=Tobi&user=Loki') + .expect(200, '{"user":["Tobi","Loki"]}', done) + }) + + it('should parse extended syntax', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user[name][first]=Tobi') + .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done) + }) + + it('should parse parameters with dots', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user.name=Tobi') + .expect(200, '{"user.name":"Tobi"}', done) + }) + + it('should parse fully-encoded extended syntax', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user%5Bname%5D%5Bfirst%5D=Tobi') + .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done) + }) + + it('should parse array index notation', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('foo[0]=bar&foo[1]=baz') + .expect(200, '{"foo":["bar","baz"]}', done) + }) + + it('should parse array index notation with large array', function (done) { + var str = 'f[0]=0' + + for (var i = 1; i < 500; i++) { + str += '&f[' + i + ']=' + i.toString(16) + } + + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(str) + .expect(function (res) { + var obj = JSON.parse(res.text) + assert.strictEqual(Object.keys(obj).length, 1) + assert.strictEqual(Array.isArray(obj.f), true) + assert.strictEqual(obj.f.length, 500) + }) + .expect(200, done) + }) + + it('should parse array of objects syntax', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!') + .expect(200, '{"foo":[{"bar":"baz","fizz":"buzz"},"done!"]}', done) + }) + + it('should parse deep object', function (done) { + var str = 'foo' + + for (var i = 0; i < 500; i++) { + str += '[p]' + } + + str += '=bar' + + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(str) + .expect(function (res) { + var obj = JSON.parse(res.text) + assert.strictEqual(Object.keys(obj).length, 1) + assert.strictEqual(typeof obj.foo, 'object') + + var depth = 0 + var ref = obj.foo + while ((ref = ref.p)) { depth++ } + assert.strictEqual(depth, 500) + }) + .expect(200, done) + }) + }) + }) + + describe('with inflate option', function () { + describe('when false', function () { + before(function () { + this.app = createApp({ inflate: false }) + }) + + it('should not accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(415, 'content encoding unsupported', done) + }) + }) + + describe('when true', function () { + before(function () { + this.app = createApp({ inflate: true }) + }) + + it('should accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + }) + }) + + describe('with limit option', function () { + it('should 413 when over limit with Content-Length', function (done) { + var buf = Buffer.alloc(1024, '.') + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('Content-Length', '1028') + .send('str=' + buf.toString()) + .expect(413, done) + }) + + it('should 413 when over limit with chunked encoding', function (done) { + var buf = Buffer.alloc(1024, '.') + var app = createApp({ limit: '1kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.set('Transfer-Encoding', 'chunked') + test.write('str=') + test.write(buf.toString()) + test.expect(413, done) + }) + + it('should accept number of bytes', function (done) { + var buf = Buffer.alloc(1024, '.') + request(createApp({ limit: 1024 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('str=' + buf.toString()) + .expect(413, done) + }) + + it('should not change when options altered', function (done) { + var buf = Buffer.alloc(1024, '.') + var options = { limit: '1kb' } + var app = createApp(options) + + options.limit = '100kb' + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('str=' + buf.toString()) + .expect(413, done) + }) + + it('should not hang response', function (done) { + var buf = Buffer.alloc(10240, '.') + var app = createApp({ limit: '8kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(buf) + test.write(buf) + test.write(buf) + test.expect(413, done) + }) + }) + + describe('with parameterLimit option', function () { + describe('with extended: false', function () { + it('should reject 0', function () { + assert.throws(createApp.bind(null, { extended: false, parameterLimit: 0 }), + /TypeError: option parameterLimit must be a positive number/) + }) + + it('should reject string', function () { + assert.throws(createApp.bind(null, { extended: false, parameterLimit: 'beep' }), + /TypeError: option parameterLimit must be a positive number/) + }) + + it('should 413 if over limit', function (done) { + request(createApp({ extended: false, parameterLimit: 10 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(11)) + .expect(413, /too many parameters/, done) + }) + + it('should error with type = "parameters.too.many"', function (done) { + request(createApp({ extended: false, parameterLimit: 10 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('X-Error-Property', 'type') + .send(createManyParams(11)) + .expect(413, 'parameters.too.many', done) + }) + + it('should work when at the limit', function (done) { + request(createApp({ extended: false, parameterLimit: 10 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(10)) + .expect(expectKeyCount(10)) + .expect(200, done) + }) + + it('should work if number is floating point', function (done) { + request(createApp({ extended: false, parameterLimit: 10.1 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(11)) + .expect(413, /too many parameters/, done) + }) + + it('should work with large limit', function (done) { + request(createApp({ extended: false, parameterLimit: 5000 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(5000)) + .expect(expectKeyCount(5000)) + .expect(200, done) + }) + + it('should work with Infinity limit', function (done) { + request(createApp({ extended: false, parameterLimit: Infinity })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(10000)) + .expect(expectKeyCount(10000)) + .expect(200, done) + }) + }) + + describe('with extended: true', function () { + it('should reject 0', function () { + assert.throws(createApp.bind(null, { extended: true, parameterLimit: 0 }), + /TypeError: option parameterLimit must be a positive number/) + }) + + it('should reject string', function () { + assert.throws(createApp.bind(null, { extended: true, parameterLimit: 'beep' }), + /TypeError: option parameterLimit must be a positive number/) + }) + + it('should 413 if over limit', function (done) { + request(createApp({ extended: true, parameterLimit: 10 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(11)) + .expect(413, /too many parameters/, done) + }) + + it('should error with type = "parameters.too.many"', function (done) { + request(createApp({ extended: true, parameterLimit: 10 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('X-Error-Property', 'type') + .send(createManyParams(11)) + .expect(413, 'parameters.too.many', done) + }) + + it('should work when at the limit', function (done) { + request(createApp({ extended: true, parameterLimit: 10 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(10)) + .expect(expectKeyCount(10)) + .expect(200, done) + }) + + it('should work if number is floating point', function (done) { + request(createApp({ extended: true, parameterLimit: 10.1 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(11)) + .expect(413, /too many parameters/, done) + }) + + it('should work with large limit', function (done) { + request(createApp({ extended: true, parameterLimit: 5000 })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(5000)) + .expect(expectKeyCount(5000)) + .expect(200, done) + }) + + it('should work with Infinity limit', function (done) { + request(createApp({ extended: true, parameterLimit: Infinity })) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(createManyParams(10000)) + .expect(expectKeyCount(10000)) + .expect(200, done) + }) + }) + }) + + describe('with type option', function () { + describe('when "application/vnd.x-www-form-urlencoded"', function () { + before(function () { + this.app = createApp({ type: 'application/vnd.x-www-form-urlencoded' }) + }) + + it('should parse for custom type', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/vnd.x-www-form-urlencoded') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should ignore standard type', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=tobi') + .expect(200, '{}', done) + }) + }) + + describe('when ["urlencoded", "application/x-pairs"]', function () { + before(function () { + this.app = createApp({ + type: ['urlencoded', 'application/x-pairs'] + }) + }) + + it('should parse "application/x-www-form-urlencoded"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should parse "application/x-pairs"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-pairs') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should ignore application/x-foo', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/x-foo') + .send('user=tobi') + .expect(200, '{}', done) + }) + }) + + describe('when a function', function () { + it('should parse when truthy value returned', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return req.headers['content-type'] === 'application/vnd.something' + } + + request(app) + .post('/') + .set('Content-Type', 'application/vnd.something') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should work without content-type', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return true + } + + var test = request(app).post('/') + test.write('user=tobi') + test.expect(200, '{"user":"tobi"}', done) + }) + + it('should not invoke without a body', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + throw new Error('oops!') + } + + request(app) + .get('/') + .expect(404, done) + }) + }) + }) + + describe('with verify option', function () { + it('should assert value if function', function () { + assert.throws(createApp.bind(null, { verify: 'lol' }), + /TypeError: option verify must be function/) + }) + + it('should error from verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x20) throw new Error('no leading space') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(' user=tobi') + .expect(403, 'no leading space', done) + }) + + it('should error with type = "entity.verify.failed"', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x20) throw new Error('no leading space') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('X-Error-Property', 'type') + .send(' user=tobi') + .expect(403, 'entity.verify.failed', done) + }) + + it('should allow custom codes', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] !== 0x20) return + var err = new Error('no leading space') + err.status = 400 + throw err + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send(' user=tobi') + .expect(400, 'no leading space', done) + }) + + it('should allow custom type', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] !== 0x20) return + var err = new Error('no leading space') + err.type = 'foo.bar' + throw err + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .set('X-Error-Property', 'type') + .send(' user=tobi') + .expect(403, 'foo.bar', done) + }) + + it('should allow pass-through', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x5b) throw new Error('no arrays') + } }) + + request(app) + .post('/') + .set('Content-Type', 'application/x-www-form-urlencoded') + .send('user=tobi') + .expect(200, '{"user":"tobi"}', done) + }) + + it('should 415 on unknown charset prior to verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + throw new Error('unexpected verify call') + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded; charset=x-bogus') + test.write(Buffer.from('00000000', 'hex')) + test.expect(415, 'unsupported charset "X-BOGUS"', done) + }) + }) + + describe('charset', function () { + before(function () { + this.app = createApp() + }) + + it('should parse utf-8', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8') + test.write(Buffer.from('6e616d653de8aeba', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should parse when content-length != char length', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded; charset=utf-8') + test.set('Content-Length', '7') + test.write(Buffer.from('746573743dc3a5', 'hex')) + test.expect(200, '{"test":"å"}', done) + }) + + it('should default to utf-8', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('6e616d653de8aeba', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should fail on unknown charset', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded; charset=koi8-r') + test.write(Buffer.from('6e616d653dcec5d4', 'hex')) + test.expect(415, 'unsupported charset "KOI8-R"', done) + }) + }) + + describe('encoding', function () { + before(function () { + this.app = createApp({ limit: '10kb' }) + }) + + it('should parse without encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('6e616d653de8aeba', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should support identity encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'identity') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('6e616d653de8aeba', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should support gzip encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should support deflate encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'deflate') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('789ccb4bcc4db57db16e17001068042f', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should be case-insensitive', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'GZIP') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(200, '{"name":"论"}', done) + }) + + it('should fail on unknown encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'nulls') + test.set('Content-Type', 'application/x-www-form-urlencoded') + test.write(Buffer.from('000000000000', 'hex')) + test.expect(415, 'unsupported content encoding "nulls"', done) + }) + }) +}) + +function createManyParams (count) { + var str = '' + + if (count === 0) { + return str + } + + str += '0=0' + + for (var i = 1; i < count; i++) { + var n = i.toString(36) + str += '&' + n + '=' + n + } + + return str +} + +function createApp (options) { + var app = express() + + app.use(express.urlencoded(options)) + + app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.send(String(err[req.headers['x-error-property'] || 'message'])) + }) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + return app +} + +function expectKeyCount (count) { + return function (res) { + assert.strictEqual(Object.keys(JSON.parse(res.text)).length, count) + } +} diff --git a/test/support/env.js b/test/support/env.js index 0701f5e334..000638ceea 100644 --- a/test/support/env.js +++ b/test/support/env.js @@ -1,3 +1,3 @@ process.env.NODE_ENV = 'test'; -process.env.NO_DEPRECATION = 'express'; +process.env.NO_DEPRECATION = 'body-parser,express'; From 6f7a8301a1828febe0b10af62152558d118b039c Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 2 May 2019 17:49:29 -0400 Subject: [PATCH 32/56] tests: add express.static test suite --- test/exports.js | 5 + test/express.static.js | 813 +++++++++++++++++++++ test/fixtures/empty.txt | 0 test/fixtures/nums.txt | 1 + test/fixtures/pets/names.txt | 1 + "test/fixtures/snow \342\230\203/.gitkeep" | 0 test/fixtures/todo.html | 1 + test/fixtures/todo.txt | 1 + test/fixtures/users/index.html | 1 + test/fixtures/users/tobi.txt | 1 + test/support/utils.js | 34 + 11 files changed, 858 insertions(+) create mode 100644 test/express.static.js create mode 100644 test/fixtures/empty.txt create mode 100644 test/fixtures/nums.txt create mode 100644 test/fixtures/pets/names.txt create mode 100644 "test/fixtures/snow \342\230\203/.gitkeep" create mode 100644 test/fixtures/todo.html create mode 100644 test/fixtures/todo.txt create mode 100644 test/fixtures/users/index.html create mode 100644 test/fixtures/users/tobi.txt diff --git a/test/exports.js b/test/exports.js index 57b3265bd6..e5fb6f2a9c 100644 --- a/test/exports.js +++ b/test/exports.js @@ -14,6 +14,11 @@ describe('exports', function(){ assert.equal(express.json.length, 1) }) + it('should expose static middleware', function () { + assert.equal(typeof express.static, 'function') + assert.equal(express.static.length, 2) + }) + it('should expose urlencoded middleware', function () { assert.equal(typeof express.urlencoded, 'function') assert.equal(express.urlencoded.length, 1) diff --git a/test/express.static.js b/test/express.static.js new file mode 100644 index 0000000000..7c9852243e --- /dev/null +++ b/test/express.static.js @@ -0,0 +1,813 @@ + +var assert = require('assert') +var Buffer = require('safe-buffer').Buffer +var express = require('..') +var path = require('path') +var request = require('supertest') +var utils = require('./support/utils') + +var fixtures = path.join(__dirname, '/fixtures') +var relative = path.relative(process.cwd(), fixtures) + +var skipRelative = ~relative.indexOf('..') || path.resolve(relative) === relative + +describe('express.static()', function () { + describe('basic operations', function () { + before(function () { + this.app = createApp() + }) + + it('should require root path', function () { + assert.throws(express.static.bind(), /root path required/) + }) + + it('should require root path to be string', function () { + assert.throws(express.static.bind(null, 42), /root path.*string/) + }) + + it('should serve static files', function (done) { + request(this.app) + .get('/todo.txt') + .expect(200, '- groceries', done) + }) + + it('should support nesting', function (done) { + request(this.app) + .get('/users/tobi.txt') + .expect(200, 'ferret', done) + }) + + it('should set Content-Type', function (done) { + request(this.app) + .get('/todo.txt') + .expect('Content-Type', 'text/plain; charset=UTF-8') + .expect(200, done) + }) + + it('should set Last-Modified', function (done) { + request(this.app) + .get('/todo.txt') + .expect('Last-Modified', /\d{2} \w{3} \d{4}/) + .expect(200, done) + }) + + it('should default max-age=0', function (done) { + request(this.app) + .get('/todo.txt') + .expect('Cache-Control', 'public, max-age=0') + .expect(200, done) + }) + + it('should support urlencoded pathnames', function (done) { + request(this.app) + .get('/%25%20of%20dogs.txt') + .expect(200, '20%', done) + }) + + it('should not choke on auth-looking URL', function (done) { + request(this.app) + .get('//todo@txt') + .expect(404, 'Not Found', done) + }) + + it('should support index.html', function (done) { + request(this.app) + .get('/users/') + .expect(200) + .expect('Content-Type', /html/) + .expect('

tobi, loki, jane

', done) + }) + + it('should support ../', function (done) { + request(this.app) + .get('/users/../todo.txt') + .expect(200, '- groceries', done) + }) + + it('should support HEAD', function (done) { + request(this.app) + .head('/todo.txt') + .expect(200) + .expect(utils.shouldNotHaveBody()) + .end(done) + }) + + it('should skip POST requests', function (done) { + request(this.app) + .post('/todo.txt') + .expect(404, 'Not Found', done) + }) + + it('should support conditional requests', function (done) { + var app = this.app + + request(app) + .get('/todo.txt') + .end(function (err, res) { + if (err) throw err + request(app) + .get('/todo.txt') + .set('If-None-Match', res.headers.etag) + .expect(304, done) + }) + }) + + it('should support precondition checks', function (done) { + request(this.app) + .get('/todo.txt') + .set('If-Match', '"foo"') + .expect(412, done) + }) + + it('should serve zero-length files', function (done) { + request(this.app) + .get('/empty.txt') + .expect(200, '', done) + }) + + it('should ignore hidden files', function (done) { + request(this.app) + .get('/.name') + .expect(404, 'Not Found', done) + }) + }); + + (skipRelative ? describe.skip : describe)('current dir', function () { + before(function () { + this.app = createApp('.') + }) + + it('should be served with "."', function (done) { + var dest = relative.split(path.sep).join('/') + request(this.app) + .get('/' + dest + '/todo.txt') + .expect(200, '- groceries', done) + }) + }) + + describe('acceptRanges', function () { + describe('when false', function () { + it('should not include Accept-Ranges', function (done) { + request(createApp(fixtures, { 'acceptRanges': false })) + .get('/nums.txt') + .expect(utils.shouldNotHaveHeader('Accept-Ranges')) + .expect(200, '123456789', done) + }) + + it('should ignore Rage request header', function (done) { + request(createApp(fixtures, { 'acceptRanges': false })) + .get('/nums.txt') + .set('Range', 'bytes=0-3') + .expect(utils.shouldNotHaveHeader('Accept-Ranges')) + .expect(utils.shouldNotHaveHeader('Content-Range')) + .expect(200, '123456789', done) + }) + }) + + describe('when true', function () { + it('should include Accept-Ranges', function (done) { + request(createApp(fixtures, { 'acceptRanges': true })) + .get('/nums.txt') + .expect('Accept-Ranges', 'bytes') + .expect(200, '123456789', done) + }) + + it('should obey Rage request header', function (done) { + request(createApp(fixtures, { 'acceptRanges': true })) + .get('/nums.txt') + .set('Range', 'bytes=0-3') + .expect('Accept-Ranges', 'bytes') + .expect('Content-Range', 'bytes 0-3/9') + .expect(206, '1234', done) + }) + }) + }) + + describe('cacheControl', function () { + describe('when false', function () { + it('should not include Cache-Control', function (done) { + request(createApp(fixtures, { 'cacheControl': false })) + .get('/nums.txt') + .expect(utils.shouldNotHaveHeader('Cache-Control')) + .expect(200, '123456789', done) + }) + + it('should ignore maxAge', function (done) { + request(createApp(fixtures, { 'cacheControl': false, 'maxAge': 12000 })) + .get('/nums.txt') + .expect(utils.shouldNotHaveHeader('Cache-Control')) + .expect(200, '123456789', done) + }) + }) + + describe('when true', function () { + it('should include Cache-Control', function (done) { + request(createApp(fixtures, { 'cacheControl': true })) + .get('/nums.txt') + .expect('Cache-Control', 'public, max-age=0') + .expect(200, '123456789', done) + }) + }) + }) + + describe('extensions', function () { + it('should be not be enabled by default', function (done) { + request(createApp(fixtures)) + .get('/todo') + .expect(404, done) + }) + + it('should be configurable', function (done) { + request(createApp(fixtures, { 'extensions': 'txt' })) + .get('/todo') + .expect(200, '- groceries', done) + }) + + it('should support disabling extensions', function (done) { + request(createApp(fixtures, { 'extensions': false })) + .get('/todo') + .expect(404, done) + }) + + it('should support fallbacks', function (done) { + request(createApp(fixtures, { 'extensions': ['htm', 'html', 'txt'] })) + .get('/todo') + .expect(200, '
  • groceries
  • ', done) + }) + + it('should 404 if nothing found', function (done) { + request(createApp(fixtures, { 'extensions': ['htm', 'html', 'txt'] })) + .get('/bob') + .expect(404, done) + }) + }) + + describe('fallthrough', function () { + it('should default to true', function (done) { + request(createApp()) + .get('/does-not-exist') + .expect(404, 'Not Found', done) + }) + + describe('when true', function () { + before(function () { + this.app = createApp(fixtures, { 'fallthrough': true }) + }) + + it('should fall-through when OPTIONS request', function (done) { + request(this.app) + .options('/todo.txt') + .expect(404, 'Not Found', done) + }) + + it('should fall-through when URL malformed', function (done) { + request(this.app) + .get('/%') + .expect(404, 'Not Found', done) + }) + + it('should fall-through when traversing past root', function (done) { + request(this.app) + .get('/users/../../todo.txt') + .expect(404, 'Not Found', done) + }) + + it('should fall-through when URL too long', function (done) { + var app = express() + var root = fixtures + Array(10000).join('/foobar') + + app.use(express.static(root, { 'fallthrough': true })) + app.use(function (req, res, next) { + res.sendStatus(404) + }) + + request(app) + .get('/') + .expect(404, 'Not Found', done) + }) + + describe('with redirect: true', function () { + before(function () { + this.app = createApp(fixtures, { 'fallthrough': true, 'redirect': true }) + }) + + it('should fall-through when directory', function (done) { + request(this.app) + .get('/pets/') + .expect(404, 'Not Found', done) + }) + + it('should redirect when directory without slash', function (done) { + request(this.app) + .get('/pets') + .expect(301, /Redirecting/, done) + }) + }) + + describe('with redirect: false', function () { + before(function () { + this.app = createApp(fixtures, { 'fallthrough': true, 'redirect': false }) + }) + + it('should fall-through when directory', function (done) { + request(this.app) + .get('/pets/') + .expect(404, 'Not Found', done) + }) + + it('should fall-through when directory without slash', function (done) { + request(this.app) + .get('/pets') + .expect(404, 'Not Found', done) + }) + }) + }) + + describe('when false', function () { + before(function () { + this.app = createApp(fixtures, { 'fallthrough': false }) + }) + + it('should 405 when OPTIONS request', function (done) { + request(this.app) + .options('/todo.txt') + .expect('Allow', 'GET, HEAD') + .expect(405, done) + }) + + it('should 400 when URL malformed', function (done) { + request(this.app) + .get('/%') + .expect(400, /BadRequestError/, done) + }) + + it('should 403 when traversing past root', function (done) { + request(this.app) + .get('/users/../../todo.txt') + .expect(403, /ForbiddenError/, done) + }) + + it('should 404 when URL too long', function (done) { + var app = express() + var root = fixtures + Array(10000).join('/foobar') + + app.use(express.static(root, { 'fallthrough': false })) + app.use(function (req, res, next) { + res.sendStatus(404) + }) + + request(app) + .get('/') + .expect(404, /ENAMETOOLONG/, done) + }) + + describe('with redirect: true', function () { + before(function () { + this.app = createApp(fixtures, { 'fallthrough': false, 'redirect': true }) + }) + + it('should 404 when directory', function (done) { + request(this.app) + .get('/pets/') + .expect(404, /NotFoundError|ENOENT/, done) + }) + + it('should redirect when directory without slash', function (done) { + request(this.app) + .get('/pets') + .expect(301, /Redirecting/, done) + }) + }) + + describe('with redirect: false', function () { + before(function () { + this.app = createApp(fixtures, { 'fallthrough': false, 'redirect': false }) + }) + + it('should 404 when directory', function (done) { + request(this.app) + .get('/pets/') + .expect(404, /NotFoundError|ENOENT/, done) + }) + + it('should 404 when directory without slash', function (done) { + request(this.app) + .get('/pets') + .expect(404, /NotFoundError|ENOENT/, done) + }) + }) + }) + }) + + describe('hidden files', function () { + before(function () { + this.app = createApp(fixtures, { 'dotfiles': 'allow' }) + }) + + it('should be served when dotfiles: "allow" is given', function (done) { + request(this.app) + .get('/.name') + .expect(200) + .expect(utils.shouldHaveBody(Buffer.from('tobi'))) + .end(done) + }) + }) + + describe('immutable', function () { + it('should default to false', function (done) { + request(createApp(fixtures)) + .get('/nums.txt') + .expect('Cache-Control', 'public, max-age=0', done) + }) + + it('should set immutable directive in Cache-Control', function (done) { + request(createApp(fixtures, { 'immutable': true, 'maxAge': '1h' })) + .get('/nums.txt') + .expect('Cache-Control', 'public, max-age=3600, immutable', done) + }) + }) + + describe('lastModified', function () { + describe('when false', function () { + it('should not include Last-Modifed', function (done) { + request(createApp(fixtures, { 'lastModified': false })) + .get('/nums.txt') + .expect(utils.shouldNotHaveHeader('Last-Modified')) + .expect(200, '123456789', done) + }) + }) + + describe('when true', function () { + it('should include Last-Modifed', function (done) { + request(createApp(fixtures, { 'lastModified': true })) + .get('/nums.txt') + .expect('Last-Modified', /^\w{3}, \d+ \w+ \d+ \d+:\d+:\d+ \w+$/) + .expect(200, '123456789', done) + }) + }) + }) + + describe('maxAge', function () { + it('should accept string', function (done) { + request(createApp(fixtures, { 'maxAge': '30d' })) + .get('/todo.txt') + .expect('cache-control', 'public, max-age=' + (60 * 60 * 24 * 30)) + .expect(200, done) + }) + + it('should be reasonable when infinite', function (done) { + request(createApp(fixtures, { 'maxAge': Infinity })) + .get('/todo.txt') + .expect('cache-control', 'public, max-age=' + (60 * 60 * 24 * 365)) + .expect(200, done) + }) + }) + + describe('redirect', function () { + before(function () { + this.app = express() + this.app.use(function (req, res, next) { + req.originalUrl = req.url = + req.originalUrl.replace(/\/snow(\/|$)/, '/snow \u2603$1') + next() + }) + this.app.use(express.static(fixtures)) + }) + + it('should redirect directories', function (done) { + request(this.app) + .get('/users') + .expect('Location', '/users/') + .expect(301, done) + }) + + it('should include HTML link', function (done) { + request(this.app) + .get('/users') + .expect('Location', '/users/') + .expect(301, //, done) + }) + + it('should redirect directories with query string', function (done) { + request(this.app) + .get('/users?name=john') + .expect('Location', '/users/?name=john') + .expect(301, done) + }) + + it('should not redirect to protocol-relative locations', function (done) { + request(this.app) + .get('//users') + .expect('Location', '/users/') + .expect(301, done) + }) + + it('should ensure redirect URL is properly encoded', function (done) { + request(this.app) + .get('/snow') + .expect('Location', '/snow%20%E2%98%83/') + .expect('Content-Type', /html/) + .expect(301, />Redirecting to \/snow%20%E2%98%83\/<\/a>groceries \ No newline at end of file diff --git a/test/fixtures/todo.txt b/test/fixtures/todo.txt new file mode 100644 index 0000000000..8c3539d946 --- /dev/null +++ b/test/fixtures/todo.txt @@ -0,0 +1 @@ +- groceries \ No newline at end of file diff --git a/test/fixtures/users/index.html b/test/fixtures/users/index.html new file mode 100644 index 0000000000..00a2db41f7 --- /dev/null +++ b/test/fixtures/users/index.html @@ -0,0 +1 @@ +

    tobi, loki, jane

    \ No newline at end of file diff --git a/test/fixtures/users/tobi.txt b/test/fixtures/users/tobi.txt new file mode 100644 index 0000000000..9d9529d47d --- /dev/null +++ b/test/fixtures/users/tobi.txt @@ -0,0 +1 @@ +ferret \ No newline at end of file diff --git a/test/support/utils.js b/test/support/utils.js index ec6b801bc0..579f042a0c 100644 --- a/test/support/utils.js +++ b/test/support/utils.js @@ -3,14 +3,48 @@ * Module dependencies. * @private */ + var assert = require('assert'); +var Buffer = require('safe-buffer').Buffer /** * Module exports. * @public */ + +exports.shouldHaveBody = shouldHaveBody +exports.shouldNotHaveBody = shouldNotHaveBody exports.shouldNotHaveHeader = shouldNotHaveHeader; +/** + * Assert that a supertest response has a specific body. + * + * @param {Buffer} buf + * @returns {function} + */ + +function shouldHaveBody (buf) { + return function (res) { + var body = !Buffer.isBuffer(res.body) + ? Buffer.from(res.text) + : res.body + assert.ok(body, 'response has body') + assert.strictEqual(body.toString('hex'), buf.toString('hex')) + } +} + +/** + * Assert that a supertest response does not have a body. + * + * @returns {function} + */ + +function shouldNotHaveBody () { + return function (res) { + assert.ok(res.text === '' || res.text === undefined) + } +} + /** * Assert that a supertest response does not have a header. * From 70a19472f1ec22642ea98baa5f76b5ba656e7235 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 7 May 2019 23:05:37 -0400 Subject: [PATCH 33/56] deps: send@0.17.0 --- History.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 3c2b59a6e2..688efb690e 100644 --- a/History.md +++ b/History.md @@ -21,6 +21,12 @@ unreleased - deps: ipaddr.js@1.9.0 * deps: qs@6.7.0 - Fix parsing array brackets after index + * deps: send@0.17.0 + - deps: http-errors@~1.7.2 + - deps: mime@1.6.0 + - deps: ms@2.1.1 + - deps: statuses@~1.5.0 + - perf: remove redundant `path.normalize` call * deps: setprototypeof@1.1.1 * deps: statuses@~1.5.0 - Add `103 Early Hints` diff --git a/package.json b/package.json index b443c34c82..53753369f7 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "qs": "6.7.0", "range-parser": "~1.2.0", "safe-buffer": "5.1.2", - "send": "0.16.2", + "send": "0.17.0", "serve-static": "1.13.2", "setprototypeof": "1.1.1", "statuses": "~1.5.0", From 60aacac1670f01857961fb7d765eb015efb0be5b Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Tue, 7 May 2019 23:42:36 -0400 Subject: [PATCH 34/56] deps: serve-static@1.14.0 closes #3486 --- History.md | 3 +++ package.json | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 688efb690e..71db3e8ef7 100644 --- a/History.md +++ b/History.md @@ -27,6 +27,9 @@ unreleased - deps: ms@2.1.1 - deps: statuses@~1.5.0 - perf: remove redundant `path.normalize` call + * deps: serve-static@1.14.0 + - deps: parseurl@~1.3.3 + - deps: send@0.17.0 * deps: setprototypeof@1.1.1 * deps: statuses@~1.5.0 - Add `103 Early Hints` diff --git a/package.json b/package.json index 53753369f7..ef83d58d11 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "range-parser": "~1.2.0", "safe-buffer": "5.1.2", "send": "0.17.0", - "serve-static": "1.13.2", + "serve-static": "1.14.0", "setprototypeof": "1.1.1", "statuses": "~1.5.0", "type-is": "~1.6.18", From 0bcdd88dd089c8da7f29e76e8f152a40ca0bcf69 Mon Sep 17 00:00:00 2001 From: Amit Zur Date: Wed, 8 Aug 2018 07:42:00 +0300 Subject: [PATCH 35/56] Add express.raw to parse bodies into Buffer closes #3708 --- History.md | 1 + lib/express.js | 1 + test/exports.js | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/History.md b/History.md index 71db3e8ef7..35259befee 100644 --- a/History.md +++ b/History.md @@ -1,6 +1,7 @@ unreleased ========== + * Add `express.raw` to parse bodies into `Buffer` * Improve error message for non-strings to `res.sendFile` * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` diff --git a/lib/express.js b/lib/express.js index 594007b5b4..f618ccc125 100644 --- a/lib/express.js +++ b/lib/express.js @@ -77,6 +77,7 @@ exports.Router = Router; exports.json = bodyParser.json exports.query = require('./middleware/query'); +exports.raw = bodyParser.raw exports.static = require('serve-static'); exports.urlencoded = bodyParser.urlencoded diff --git a/test/exports.js b/test/exports.js index e5fb6f2a9c..f43df44c34 100644 --- a/test/exports.js +++ b/test/exports.js @@ -14,6 +14,11 @@ describe('exports', function(){ assert.equal(express.json.length, 1) }) + it('should expose raw middleware', function () { + assert.equal(typeof express.raw, 'function') + assert.equal(express.raw.length, 1) + }) + it('should expose static middleware', function () { assert.equal(typeof express.static, 'function') assert.equal(express.static.length, 2) From 11192bd168c5996efe718664a3f4d8f77dbaa71b Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 8 May 2019 00:58:08 -0400 Subject: [PATCH 36/56] tests: add express.raw test suite --- test/express.raw.js | 387 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 387 insertions(+) create mode 100644 test/express.raw.js diff --git a/test/express.raw.js b/test/express.raw.js new file mode 100644 index 0000000000..571c29ca9b --- /dev/null +++ b/test/express.raw.js @@ -0,0 +1,387 @@ + +var assert = require('assert') +var Buffer = require('safe-buffer').Buffer +var express = require('..') +var request = require('supertest') + +describe('express.raw()', function () { + before(function () { + this.app = createApp() + }) + + it('should parse application/octet-stream', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/octet-stream') + .send('the user is tobi') + .expect(200, { buf: '746865207573657220697320746f6269' }, done) + }) + + it('should 400 when invalid content-length', function (done) { + var app = express() + + app.use(function (req, res, next) { + req.headers['content-length'] = '20' // bad length + next() + }) + + app.use(express.raw()) + + app.post('/', function (req, res) { + if (Buffer.isBuffer(req.body)) { + res.json({ buf: req.body.toString('hex') }) + } else { + res.json(req.body) + } + }) + + request(app) + .post('/') + .set('Content-Type', 'application/octet-stream') + .send('stuff') + .expect(400, /content length/, done) + }) + + it('should handle Content-Length: 0', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/octet-stream') + .set('Content-Length', '0') + .expect(200, { buf: '' }, done) + }) + + it('should handle empty message-body', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'application/octet-stream') + .set('Transfer-Encoding', 'chunked') + .send('') + .expect(200, { buf: '' }, done) + }) + + it('should handle duplicated middleware', function (done) { + var app = express() + + app.use(express.raw()) + app.use(express.raw()) + + app.post('/', function (req, res) { + if (Buffer.isBuffer(req.body)) { + res.json({ buf: req.body.toString('hex') }) + } else { + res.json(req.body) + } + }) + + request(app) + .post('/') + .set('Content-Type', 'application/octet-stream') + .send('the user is tobi') + .expect(200, { buf: '746865207573657220697320746f6269' }, done) + }) + + describe('with limit option', function () { + it('should 413 when over limit with Content-Length', function (done) { + var buf = Buffer.alloc(1028, '.') + var app = createApp({ limit: '1kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.set('Content-Length', '1028') + test.write(buf) + test.expect(413, done) + }) + + it('should 413 when over limit with chunked encoding', function (done) { + var buf = Buffer.alloc(1028, '.') + var app = createApp({ limit: '1kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.set('Transfer-Encoding', 'chunked') + test.write(buf) + test.expect(413, done) + }) + + it('should accept number of bytes', function (done) { + var buf = Buffer.alloc(1028, '.') + var app = createApp({ limit: 1024 }) + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(buf) + test.expect(413, done) + }) + + it('should not change when options altered', function (done) { + var buf = Buffer.alloc(1028, '.') + var options = { limit: '1kb' } + var app = createApp(options) + + options.limit = '100kb' + + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(buf) + test.expect(413, done) + }) + + it('should not hang response', function (done) { + var buf = Buffer.alloc(10240, '.') + var app = createApp({ limit: '8kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(buf) + test.write(buf) + test.write(buf) + test.expect(413, done) + }) + }) + + describe('with inflate option', function () { + describe('when false', function () { + before(function () { + this.app = createApp({ inflate: false }) + }) + + it('should not accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(415, 'content encoding unsupported', done) + }) + }) + + describe('when true', function () { + before(function () { + this.app = createApp({ inflate: true }) + }) + + it('should accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(200, { buf: '6e616d653de8aeba' }, done) + }) + }) + }) + + describe('with type option', function () { + describe('when "application/vnd+octets"', function () { + before(function () { + this.app = createApp({ type: 'application/vnd+octets' }) + }) + + it('should parse for custom type', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/vnd+octets') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, { buf: '000102' }, done) + }) + + it('should ignore standard type', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, '{}', done) + }) + }) + + describe('when ["application/octet-stream", "application/vnd+octets"]', function () { + before(function () { + this.app = createApp({ + type: ['application/octet-stream', 'application/vnd+octets'] + }) + }) + + it('should parse "application/octet-stream"', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, { buf: '000102' }, done) + }) + + it('should parse "application/vnd+octets"', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/vnd+octets') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, { buf: '000102' }, done) + }) + + it('should ignore "application/x-foo"', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/x-foo') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, '{}', done) + }) + }) + + describe('when a function', function () { + it('should parse when truthy value returned', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return req.headers['content-type'] === 'application/vnd.octet' + } + + var test = request(app).post('/') + test.set('Content-Type', 'application/vnd.octet') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, { buf: '000102' }, done) + }) + + it('should work without content-type', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return true + } + + var test = request(app).post('/') + test.write(Buffer.from('000102', 'hex')) + test.expect(200, { buf: '000102' }, done) + }) + + it('should not invoke without a body', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + throw new Error('oops!') + } + + request(app) + .get('/') + .expect(404, done) + }) + }) + }) + + describe('with verify option', function () { + it('should assert value is function', function () { + assert.throws(createApp.bind(null, { verify: 'lol' }), + /TypeError: option verify must be function/) + }) + + it('should error from verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x00) throw new Error('no leading null') + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('000102', 'hex')) + test.expect(403, 'no leading null', done) + }) + + it('should allow custom codes', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] !== 0x00) return + var err = new Error('no leading null') + err.status = 400 + throw err + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('000102', 'hex')) + test.expect(400, 'no leading null', done) + }) + + it('should allow pass-through', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x00) throw new Error('no leading null') + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('0102', 'hex')) + test.expect(200, { buf: '0102' }, done) + }) + }) + + describe('charset', function () { + before(function () { + this.app = createApp() + }) + + it('should ignore charset', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/octet-stream; charset=utf-8') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, { buf: '6e616d6520697320e8aeba' }, done) + }) + }) + + describe('encoding', function () { + before(function () { + this.app = createApp({ limit: '10kb' }) + }) + + it('should parse without encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('6e616d653de8aeba', 'hex')) + test.expect(200, { buf: '6e616d653de8aeba' }, done) + }) + + it('should support identity encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'identity') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('6e616d653de8aeba', 'hex')) + test.expect(200, { buf: '6e616d653de8aeba' }, done) + }) + + it('should support gzip encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(200, { buf: '6e616d653de8aeba' }, done) + }) + + it('should support deflate encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'deflate') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('789ccb4bcc4db57db16e17001068042f', 'hex')) + test.expect(200, { buf: '6e616d653de8aeba' }, done) + }) + + it('should be case-insensitive', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'GZIP') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex')) + test.expect(200, { buf: '6e616d653de8aeba' }, done) + }) + + it('should fail on unknown encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'nulls') + test.set('Content-Type', 'application/octet-stream') + test.write(Buffer.from('000000000000', 'hex')) + test.expect(415, 'unsupported content encoding "nulls"', done) + }) + }) +}) + +function createApp (options) { + var app = express() + + app.use(express.raw(options)) + + app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.send(String(err[req.headers['x-error-property'] || 'message'])) + }) + + app.post('/', function (req, res) { + if (Buffer.isBuffer(req.body)) { + res.json({ buf: req.body.toString('hex') }) + } else { + res.json(req.body) + } + }) + + return app +} From 7f4e37f3ea0bf99287472dd72f48d12a3b3d0b71 Mon Sep 17 00:00:00 2001 From: Ilya Guterman Date: Mon, 23 Oct 2017 18:00:19 +0300 Subject: [PATCH 37/56] Add express.text to parse bodies into string closes #3455 --- History.md | 1 + lib/express.js | 1 + test/exports.js | 5 +++++ 3 files changed, 7 insertions(+) diff --git a/History.md b/History.md index 35259befee..2410810d33 100644 --- a/History.md +++ b/History.md @@ -2,6 +2,7 @@ unreleased ========== * Add `express.raw` to parse bodies into `Buffer` + * Add `express.text` to parse bodies into string * Improve error message for non-strings to `res.sendFile` * Improve error message for `null`/`undefined` to `res.status` * Support multiple hosts in `X-Forwarded-Host` diff --git a/lib/express.js b/lib/express.js index f618ccc125..d188a16db7 100644 --- a/lib/express.js +++ b/lib/express.js @@ -79,6 +79,7 @@ exports.json = bodyParser.json exports.query = require('./middleware/query'); exports.raw = bodyParser.raw exports.static = require('serve-static'); +exports.text = bodyParser.text exports.urlencoded = bodyParser.urlencoded /** diff --git a/test/exports.js b/test/exports.js index f43df44c34..7624a8c864 100644 --- a/test/exports.js +++ b/test/exports.js @@ -24,6 +24,11 @@ describe('exports', function(){ assert.equal(express.static.length, 2) }) + it('should expose text middleware', function () { + assert.equal(typeof express.text, 'function') + assert.equal(express.text.length, 1) + }) + it('should expose urlencoded middleware', function () { assert.equal(typeof express.urlencoded, 'function') assert.equal(express.urlencoded.length, 1) From bb5211fa1cdf6da767960c2a8aa97f8c8f31e9c5 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Wed, 8 May 2019 23:39:45 -0400 Subject: [PATCH 38/56] tests: add express.text test suite --- test/express.text.js | 441 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 441 insertions(+) create mode 100644 test/express.text.js diff --git a/test/express.text.js b/test/express.text.js new file mode 100644 index 0000000000..7c92f90e5a --- /dev/null +++ b/test/express.text.js @@ -0,0 +1,441 @@ + +var assert = require('assert') +var Buffer = require('safe-buffer').Buffer +var express = require('..') +var request = require('supertest') + +describe('express.text()', function () { + before(function () { + this.app = createApp() + }) + + it('should parse text/plain', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'text/plain') + .send('user is tobi') + .expect(200, '"user is tobi"', done) + }) + + it('should 400 when invalid content-length', function (done) { + var app = express() + + app.use(function (req, res, next) { + req.headers['content-length'] = '20' // bad length + next() + }) + + app.use(express.text()) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + request(app) + .post('/') + .set('Content-Type', 'text/plain') + .send('user') + .expect(400, /content length/, done) + }) + + it('should handle Content-Length: 0', function (done) { + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'text/plain') + .set('Content-Length', '0') + .expect(200, '""', done) + }) + + it('should handle empty message-body', function (done) { + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'text/plain') + .set('Transfer-Encoding', 'chunked') + .send('') + .expect(200, '""', done) + }) + + it('should handle duplicated middleware', function (done) { + var app = express() + + app.use(express.text()) + app.use(express.text()) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + request(app) + .post('/') + .set('Content-Type', 'text/plain') + .send('user is tobi') + .expect(200, '"user is tobi"', done) + }) + + describe('with defaultCharset option', function () { + it('should change default charset', function (done) { + var app = createApp({ defaultCharset: 'koi8-r' }) + var test = request(app).post('/') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('6e616d6520697320cec5d4', 'hex')) + test.expect(200, '"name is нет"', done) + }) + + it('should honor content-type charset', function (done) { + var app = createApp({ defaultCharset: 'koi8-r' }) + var test = request(app).post('/') + test.set('Content-Type', 'text/plain; charset=utf-8') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + }) + + describe('with limit option', function () { + it('should 413 when over limit with Content-Length', function (done) { + var buf = Buffer.alloc(1028, '.') + request(createApp({ limit: '1kb' })) + .post('/') + .set('Content-Type', 'text/plain') + .set('Content-Length', '1028') + .send(buf.toString()) + .expect(413, done) + }) + + it('should 413 when over limit with chunked encoding', function (done) { + var buf = Buffer.alloc(1028, '.') + var app = createApp({ limit: '1kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'text/plain') + test.set('Transfer-Encoding', 'chunked') + test.write(buf.toString()) + test.expect(413, done) + }) + + it('should accept number of bytes', function (done) { + var buf = Buffer.alloc(1028, '.') + request(createApp({ limit: 1024 })) + .post('/') + .set('Content-Type', 'text/plain') + .send(buf.toString()) + .expect(413, done) + }) + + it('should not change when options altered', function (done) { + var buf = Buffer.alloc(1028, '.') + var options = { limit: '1kb' } + var app = createApp(options) + + options.limit = '100kb' + + request(app) + .post('/') + .set('Content-Type', 'text/plain') + .send(buf.toString()) + .expect(413, done) + }) + + it('should not hang response', function (done) { + var buf = Buffer.alloc(10240, '.') + var app = createApp({ limit: '8kb' }) + var test = request(app).post('/') + test.set('Content-Type', 'text/plain') + test.write(buf) + test.write(buf) + test.write(buf) + test.expect(413, done) + }) + }) + + describe('with inflate option', function () { + describe('when false', function () { + before(function () { + this.app = createApp({ inflate: false }) + }) + + it('should not accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b000000', 'hex')) + test.expect(415, 'content encoding unsupported', done) + }) + }) + + describe('when true', function () { + before(function () { + this.app = createApp({ inflate: true }) + }) + + it('should accept content-encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b000000', 'hex')) + test.expect(200, '"name is 论"', done) + }) + }) + }) + + describe('with type option', function () { + describe('when "text/html"', function () { + before(function () { + this.app = createApp({ type: 'text/html' }) + }) + + it('should parse for custom type', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'text/html') + .send('tobi') + .expect(200, '"tobi"', done) + }) + + it('should ignore standard type', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'text/plain') + .send('user is tobi') + .expect(200, '{}', done) + }) + }) + + describe('when ["text/html", "text/plain"]', function () { + before(function () { + this.app = createApp({ type: ['text/html', 'text/plain'] }) + }) + + it('should parse "text/html"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'text/html') + .send('tobi') + .expect(200, '"tobi"', done) + }) + + it('should parse "text/plain"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'text/plain') + .send('tobi') + .expect(200, '"tobi"', done) + }) + + it('should ignore "text/xml"', function (done) { + request(this.app) + .post('/') + .set('Content-Type', 'text/xml') + .send('tobi') + .expect(200, '{}', done) + }) + }) + + describe('when a function', function () { + it('should parse when truthy value returned', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return req.headers['content-type'] === 'text/vnd.something' + } + + request(app) + .post('/') + .set('Content-Type', 'text/vnd.something') + .send('user is tobi') + .expect(200, '"user is tobi"', done) + }) + + it('should work without content-type', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + return true + } + + var test = request(app).post('/') + test.write('user is tobi') + test.expect(200, '"user is tobi"', done) + }) + + it('should not invoke without a body', function (done) { + var app = createApp({ type: accept }) + + function accept (req) { + throw new Error('oops!') + } + + request(app) + .get('/') + .expect(404, done) + }) + }) + }) + + describe('with verify option', function () { + it('should assert value is function', function () { + assert.throws(createApp.bind(null, { verify: 'lol' }), + /TypeError: option verify must be function/) + }) + + it('should error from verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x20) throw new Error('no leading space') + } }) + + request(app) + .post('/') + .set('Content-Type', 'text/plain') + .send(' user is tobi') + .expect(403, 'no leading space', done) + }) + + it('should allow custom codes', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] !== 0x20) return + var err = new Error('no leading space') + err.status = 400 + throw err + } }) + + request(app) + .post('/') + .set('Content-Type', 'text/plain') + .send(' user is tobi') + .expect(400, 'no leading space', done) + }) + + it('should allow pass-through', function (done) { + var app = createApp({ verify: function (req, res, buf) { + if (buf[0] === 0x20) throw new Error('no leading space') + } }) + + request(app) + .post('/') + .set('Content-Type', 'text/plain') + .send('user is tobi') + .expect(200, '"user is tobi"', done) + }) + + it('should 415 on unknown charset prior to verify', function (done) { + var app = createApp({ verify: function (req, res, buf) { + throw new Error('unexpected verify call') + } }) + + var test = request(app).post('/') + test.set('Content-Type', 'text/plain; charset=x-bogus') + test.write(Buffer.from('00000000', 'hex')) + test.expect(415, 'unsupported charset "X-BOGUS"', done) + }) + }) + + describe('charset', function () { + before(function () { + this.app = createApp() + }) + + it('should parse utf-8', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'text/plain; charset=utf-8') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should parse codepage charsets', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'text/plain; charset=koi8-r') + test.write(Buffer.from('6e616d6520697320cec5d4', 'hex')) + test.expect(200, '"name is нет"', done) + }) + + it('should parse when content-length != char length', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'text/plain; charset=utf-8') + test.set('Content-Length', '11') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should default to utf-8', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should 415 on unknown charset', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'text/plain; charset=x-bogus') + test.write(Buffer.from('00000000', 'hex')) + test.expect(415, 'unsupported charset "X-BOGUS"', done) + }) + }) + + describe('encoding', function () { + before(function () { + this.app = createApp({ limit: '10kb' }) + }) + + it('should parse without encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should support identity encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'identity') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('6e616d6520697320e8aeba', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should support gzip encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'gzip') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b000000', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should support deflate encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'deflate') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('789ccb4bcc4d55c82c5678b16e17001a6f050e', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should be case-insensitive', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'GZIP') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b000000', 'hex')) + test.expect(200, '"name is 论"', done) + }) + + it('should fail on unknown encoding', function (done) { + var test = request(this.app).post('/') + test.set('Content-Encoding', 'nulls') + test.set('Content-Type', 'text/plain') + test.write(Buffer.from('000000000000', 'hex')) + test.expect(415, 'unsupported content encoding "nulls"', done) + }) + }) +}) + +function createApp (options) { + var app = express() + + app.use(express.text(options)) + + app.use(function (err, req, res, next) { + res.status(err.status || 500) + res.send(err.message) + }) + + app.post('/', function (req, res) { + res.json(req.body) + }) + + return app +} From 7b076bd8e1c428da4887856d34b813aba2732c19 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 9 May 2019 18:03:12 -0400 Subject: [PATCH 39/56] build: Node.js@6.17 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 36e7e75e56..b07c383bb1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,7 +7,7 @@ node_js: - "3.3" - "4.9" - "5.12" - - "6.14" + - "6.17" - "7.10" - "8.12" - "9.11" diff --git a/appveyor.yml b/appveyor.yml index 4006a5e51d..f970f8a717 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -7,7 +7,7 @@ environment: - nodejs_version: "3.3" - nodejs_version: "4.9" - nodejs_version: "5.12" - - nodejs_version: "6.14" + - nodejs_version: "6.17" - nodejs_version: "7.10" - nodejs_version: "8.12" - nodejs_version: "9.11" From e91702872994523dbb9f7da1bf30854c5dfb834a Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 9 May 2019 18:03:56 -0400 Subject: [PATCH 40/56] build: Node.js@8.16 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b07c383bb1..457029de67 100644 --- a/.travis.yml +++ b/.travis.yml @@ -9,7 +9,7 @@ node_js: - "5.12" - "6.17" - "7.10" - - "8.12" + - "8.16" - "9.11" - "10.12" matrix: diff --git a/appveyor.yml b/appveyor.yml index f970f8a717..c2cd643e1c 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -9,7 +9,7 @@ environment: - nodejs_version: "5.12" - nodejs_version: "6.17" - nodejs_version: "7.10" - - nodejs_version: "8.12" + - nodejs_version: "8.16" - nodejs_version: "9.11" - nodejs_version: "10.12" cache: From c754c8ad7b33a1d9ec6bec88bc44734c16c36167 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 9 May 2019 19:45:01 -0400 Subject: [PATCH 41/56] build: support Node.js 11.x --- .travis.yml | 3 +-- appveyor.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 457029de67..b55dc4ae52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,9 @@ node_js: - "8.16" - "9.11" - "10.12" + - "11.15" matrix: include: - - node_js: "11" - env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" - node_js: "12" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" allow_failures: diff --git a/appveyor.yml b/appveyor.yml index c2cd643e1c..82463dee7d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -12,6 +12,7 @@ environment: - nodejs_version: "8.16" - nodejs_version: "9.11" - nodejs_version: "10.12" + - nodejs_version: "11.15" cache: - node_modules install: From bc07a41693f8c7e9bde2bfb4cd5390ad6e3b1337 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 9 May 2019 22:09:56 -0400 Subject: [PATCH 42/56] deps: finalhandler@~1.1.2 --- History.md | 4 ++++ package.json | 2 +- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index 2410810d33..9ddfe35b88 100644 --- a/History.md +++ b/History.md @@ -18,6 +18,10 @@ unreleased - deps: raw-body@2.4.0 - deps: type-is@~1.6.17 * deps: content-disposition@0.5.3 + * deps: finalhandler@~1.1.2 + - Set stricter `Content-Security-Policy` header + - deps: parseurl@~1.3.3 + - deps: statuses@~1.5.0 * deps: parseurl@~1.3.3 * deps: proxy-addr@~2.0.5 - deps: ipaddr.js@1.9.0 diff --git a/package.json b/package.json index ef83d58d11..6f89e3369d 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.1.1", + "finalhandler": "~1.1.2", "fresh": "0.5.2", "merge-descriptors": "1.0.1", "methods": "~1.1.2", From 8267c4b72422e68654849a71bfb74141d77bb875 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Fri, 10 May 2019 23:49:13 -0400 Subject: [PATCH 43/56] deps: send@0.17.1 --- History.md | 4 +++- package.json | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/History.md b/History.md index 9ddfe35b88..312f4b9695 100644 --- a/History.md +++ b/History.md @@ -27,10 +27,12 @@ unreleased - deps: ipaddr.js@1.9.0 * deps: qs@6.7.0 - Fix parsing array brackets after index - * deps: send@0.17.0 + * deps: send@0.17.1 + - Set stricter CSP header in redirect & error responses - deps: http-errors@~1.7.2 - deps: mime@1.6.0 - deps: ms@2.1.1 + - deps: range-parser@~1.2.1 - deps: statuses@~1.5.0 - perf: remove redundant `path.normalize` call * deps: serve-static@1.14.0 diff --git a/package.json b/package.json index 6f89e3369d..34904ac000 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "qs": "6.7.0", "range-parser": "~1.2.0", "safe-buffer": "5.1.2", - "send": "0.17.0", + "send": "0.17.1", "serve-static": "1.14.0", "setprototypeof": "1.1.1", "statuses": "~1.5.0", From 88f9733ffa58ce89bd5a9b207f0c8b4c2965fec6 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sat, 11 May 2019 19:29:33 -0400 Subject: [PATCH 44/56] deps: serve-static@1.14.1 --- History.md | 5 +++-- package.json | 2 +- test/express.static.js | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/History.md b/History.md index 312f4b9695..b0685594a3 100644 --- a/History.md +++ b/History.md @@ -35,9 +35,10 @@ unreleased - deps: range-parser@~1.2.1 - deps: statuses@~1.5.0 - perf: remove redundant `path.normalize` call - * deps: serve-static@1.14.0 + * deps: serve-static@1.14.1 + - Set stricter CSP header in redirect response - deps: parseurl@~1.3.3 - - deps: send@0.17.0 + - deps: send@0.17.1 * deps: setprototypeof@1.1.1 * deps: statuses@~1.5.0 - Add `103 Early Hints` diff --git a/package.json b/package.json index 34904ac000..3a6a00d153 100644 --- a/package.json +++ b/package.json @@ -51,7 +51,7 @@ "range-parser": "~1.2.0", "safe-buffer": "5.1.2", "send": "0.17.1", - "serve-static": "1.14.0", + "serve-static": "1.14.1", "setprototypeof": "1.1.1", "statuses": "~1.5.0", "type-is": "~1.6.18", diff --git a/test/express.static.js b/test/express.static.js index 7c9852243e..485ee4c0c1 100644 --- a/test/express.static.js +++ b/test/express.static.js @@ -513,7 +513,7 @@ describe('express.static()', function () { it('should respond with default Content-Security-Policy', function (done) { request(this.app) .get('/users') - .expect('Content-Security-Policy', "default-src 'self'") + .expect('Content-Security-Policy', "default-src 'none'") .expect(301, done) }) From da6f701317d154e47921139257ffcefb15d15ca7 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sun, 12 May 2019 22:01:06 -0400 Subject: [PATCH 45/56] deps: range-parser@~1.2.1 --- History.md | 1 + package.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index b0685594a3..ef9aec948c 100644 --- a/History.md +++ b/History.md @@ -27,6 +27,7 @@ unreleased - deps: ipaddr.js@1.9.0 * deps: qs@6.7.0 - Fix parsing array brackets after index + * deps: range-parser@~1.2.1 * deps: send@0.17.1 - Set stricter CSP header in redirect & error responses - deps: http-errors@~1.7.2 diff --git a/package.json b/package.json index 3a6a00d153..c722e23077 100644 --- a/package.json +++ b/package.json @@ -48,7 +48,7 @@ "path-to-regexp": "0.1.7", "proxy-addr": "~2.0.5", "qs": "6.7.0", - "range-parser": "~1.2.0", + "range-parser": "~1.2.1", "safe-buffer": "5.1.2", "send": "0.17.1", "serve-static": "1.14.1", From e502dde3c8c82ff107603f78d6cac9a33a699dd7 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sun, 12 May 2019 22:09:35 -0400 Subject: [PATCH 46/56] build: Node.js@10.15 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index b55dc4ae52..4706b75982 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,7 @@ node_js: - "7.10" - "8.16" - "9.11" - - "10.12" + - "10.15" - "11.15" matrix: include: diff --git a/appveyor.yml b/appveyor.yml index 82463dee7d..b222f6b7b7 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -11,7 +11,7 @@ environment: - nodejs_version: "7.10" - nodejs_version: "8.16" - nodejs_version: "9.11" - - nodejs_version: "10.12" + - nodejs_version: "10.15" - nodejs_version: "11.15" cache: - node_modules From 5266f3a5cb25fdd6846b76a727d601506791c4ce Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sun, 12 May 2019 22:15:36 -0400 Subject: [PATCH 47/56] build: test against Node.js 13.x nightly --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 4706b75982..73422a7aa6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,8 @@ matrix: include: - node_js: "12" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" + - node_js: "13" + 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 b9ecb9afe336ad00eb6e2dbc055e838649fe784f Mon Sep 17 00:00:00 2001 From: huadong zuo Date: Wed, 24 Apr 2019 19:44:09 +0800 Subject: [PATCH 48/56] build: support Node.js 12.x closes #3946 --- .travis.yml | 3 +-- appveyor.yml | 1 + 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 73422a7aa6..7b11677ec8 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,10 +13,9 @@ node_js: - "9.11" - "10.15" - "11.15" + - "12.2" matrix: include: - - node_js: "12" - env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" - node_js: "13" env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly" allow_failures: diff --git a/appveyor.yml b/appveyor.yml index b222f6b7b7..84476a597d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,6 +13,7 @@ environment: - nodejs_version: "9.11" - nodejs_version: "10.15" - nodejs_version: "11.15" + - nodejs_version: "12.2" cache: - node_modules install: From efcb17dcb21699ef685eff4455a9443f707a4901 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 16 May 2019 09:55:26 -0400 Subject: [PATCH 49/56] deps: cookie@0.4.0 closes #3958 --- History.md | 2 ++ package.json | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/History.md b/History.md index ef9aec948c..da36866652 100644 --- a/History.md +++ b/History.md @@ -18,6 +18,8 @@ unreleased - deps: raw-body@2.4.0 - deps: type-is@~1.6.17 * deps: content-disposition@0.5.3 + * deps: cookie@0.4.0 + - Add `SameSite=None` support * deps: finalhandler@~1.1.2 - Set stricter `Content-Security-Policy` header - deps: parseurl@~1.3.3 diff --git a/package.json b/package.json index c722e23077..b8b0b19e9f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "body-parser": "1.19.0", "content-disposition": "0.5.3", "content-type": "~1.0.4", - "cookie": "0.3.1", + "cookie": "0.4.0", "cookie-signature": "1.0.6", "debug": "2.6.9", "depd": "~1.1.2", From 94e48a16f273963dc37829352b7381e4e9222315 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 16 May 2019 09:58:40 -0400 Subject: [PATCH 50/56] build: update example dependencies --- package.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index b8b0b19e9f..87b80ce33d 100644 --- a/package.json +++ b/package.json @@ -60,12 +60,12 @@ }, "devDependencies": { "after": "0.8.2", - "connect-redis": "3.4.0", - "cookie-parser": "~1.4.3", - "cookie-session": "1.3.2", + "connect-redis": "3.4.1", + "cookie-parser": "~1.4.4", + "cookie-session": "1.3.3", "ejs": "2.6.1", "eslint": "2.13.1", - "express-session": "1.15.6", + "express-session": "1.16.1", "hbs": "4.0.4", "istanbul": "0.4.5", "marked": "0.6.2", From b8e50568af9c73ef1ade434e92c60d389868361d Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 16 May 2019 10:04:24 -0400 Subject: [PATCH 51/56] tests: ignore unreachable line --- lib/response.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/response.js b/lib/response.js index c1514901c5..a4f10cbb2e 100644 --- a/lib/response.js +++ b/lib/response.js @@ -1135,6 +1135,7 @@ function stringify (value, replacer, spaces, escape) { return '\\u003e' case 0x26: return '\\u0026' + /* istanbul ignore next: unreachable default */ default: return c } From 9dadca2c64ae717063b0e9071143065896ebb676 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 16 May 2019 20:53:07 -0400 Subject: [PATCH 52/56] docs: remove Gratipay links --- Readme.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/Readme.md b/Readme.md index 81d8d91615..1f91297312 100644 --- a/Readme.md +++ b/Readme.md @@ -153,7 +153,3 @@ The current lead maintainer is [Douglas Christopher Wilson](https://github.com/d [appveyor-url]: https://ci.appveyor.com/project/dougwilson/express [coveralls-image]: https://img.shields.io/coveralls/expressjs/express/master.svg [coveralls-url]: https://coveralls.io/r/expressjs/express?branch=master -[gratipay-image-visionmedia]: https://img.shields.io/gratipay/visionmedia.svg -[gratipay-url-visionmedia]: https://gratipay.com/visionmedia/ -[gratipay-image-dougwilson]: https://img.shields.io/gratipay/dougwilson.svg -[gratipay-url-dougwilson]: https://gratipay.com/dougwilson/ From 10c7756764fbe969b307b15a72fd074479c00f8d Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Thu, 16 May 2019 21:25:42 -0400 Subject: [PATCH 53/56] 4.17.0 --- History.md | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index da36866652..cca851d2e0 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,5 @@ -unreleased -========== +4.17.0 / 2019-05-16 +=================== * Add `express.raw` to parse bodies into `Buffer` * Add `express.text` to parse bodies into string diff --git a/package.json b/package.json index 87b80ce33d..726af2f6ba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.16.4", + "version": "4.17.0", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", From eed05a1464485edc5154ce989a679ba602f11ed8 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Fri, 24 May 2019 23:20:52 -0400 Subject: [PATCH 54/56] build: Node.js@12.3 --- .travis.yml | 2 +- appveyor.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 7b11677ec8..c3ebc583d6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,7 +13,7 @@ node_js: - "9.11" - "10.15" - "11.15" - - "12.2" + - "12.3" matrix: include: - node_js: "13" diff --git a/appveyor.yml b/appveyor.yml index 84476a597d..24434cd14a 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -13,7 +13,7 @@ environment: - nodejs_version: "9.11" - nodejs_version: "10.15" - nodejs_version: "11.15" - - nodejs_version: "12.2" + - nodejs_version: "12.3" cache: - node_modules install: From 0a48e18056865364b2461b2ece7ccb2d1075d3c9 Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sat, 25 May 2019 18:15:13 -0400 Subject: [PATCH 55/56] Revert "Improve error message for null/undefined to res.status" fixes #3968 --- History.md | 5 +++++ lib/response.js | 4 ---- test/res.status.js | 32 -------------------------------- 3 files changed, 5 insertions(+), 36 deletions(-) diff --git a/History.md b/History.md index cca851d2e0..631e0e5170 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,8 @@ +unreleased +========== + + * Revert "Improve error message for `null`/`undefined` to `res.status`" + 4.17.0 / 2019-05-16 =================== diff --git a/lib/response.js b/lib/response.js index a4f10cbb2e..c9f08cd54f 100644 --- a/lib/response.js +++ b/lib/response.js @@ -64,10 +64,6 @@ var charsetRegExp = /;\s*charset\s*=/; */ res.status = function status(code) { - if (code === undefined || code === null) { - throw new TypeError('code argument is required to res.status') - } - this.statusCode = code; return this; }; diff --git a/test/res.status.js b/test/res.status.js index 3f928ec0b0..8c173a645c 100644 --- a/test/res.status.js +++ b/test/res.status.js @@ -16,37 +16,5 @@ describe('res', function(){ .expect('Created') .expect(201, done); }) - - describe('when code is undefined', function () { - it('should throw a TypeError', function (done) { - var app = express() - - app.use(function (req, res) { - res.status(undefined).send('OK') - }) - - request(app) - .get('/') - .expect(500) - .expect(/TypeError: code argument is required to res.status/) - .end(done) - }) - }) - - describe('when code is null', function () { - it('should throw a TypeError', function (done) { - var app = express() - - app.use(function (req, res) { - res.status(null).send('OK') - }) - - request(app) - .get('/') - .expect(500) - .expect(/TypeError: code argument is required to res.status/) - .end(done) - }) - }) }) }) From e1b45ebd050b6f06aa38cda5aaf0c21708b0c71e Mon Sep 17 00:00:00 2001 From: Douglas Christopher Wilson Date: Sun, 26 May 2019 00:24:55 -0400 Subject: [PATCH 56/56] 4.17.1 --- History.md | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/History.md b/History.md index 631e0e5170..6e62a6ddb8 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,5 @@ -unreleased -========== +4.17.1 / 2019-05-25 +=================== * Revert "Improve error message for `null`/`undefined` to `res.status`" diff --git a/package.json b/package.json index 726af2f6ba..2979d57a22 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.17.0", + "version": "4.17.1", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ",