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 a6c8cef2..d716ea4b 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 4eeaaf09..271a0969 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 3c83e66c..a1c81966 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 ebeed700..a36ad43c 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 a36ad43c..a49b22ed 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 a49b22ed..aad455e4 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 fc3582e4..0a911723 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 aad455e4..c802e4fd 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 0a911723..4006a5e5 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 2f6eab10..6a069942 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 2e445ac0..11adeb61 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 d7585b77..5f494f1e 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 6a069942..35147d39 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 11adeb61..60f8979e 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 8c173a64..3f928ec0 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 35147d39..c29a4490 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 8bb86a9a..a9400ef9 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 816cd597..09bfb899 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 41386568..f84c0138 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 c29a4490..20cee356 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 e6f3fa9d..5f077269 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 69fde2e2..8a23f9d4 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 257cc564..775eb734 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 582e8958..9053d790 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 60f8979e..c1514901 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 d716ea4b..5a31b5fb 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 0d0ed8b5..f7d0cc0e 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 2f4574c5..d1c45917 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 aba8ea5f..a5cf747d 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 d3d29abd..7819420c 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 084b3c71..cf3b3ca5 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 9053d790..b25b240c 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 ce595892..4616842e 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 c802e4fd..36e7e75e 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 74196ad6..0b8bec48 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 0b8bec48..6dd72c1f 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 20cee356..5343b0ba 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 6dd72c1f..9255d77d 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 5343b0ba..f8a5c0c0 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 9255d77d..92102c2a 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 f8a5c0c0..e1f238bb 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 92102c2a..e548b609 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 b25b240c..81d8d916 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 e1f238bb..4f9dcbe9 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 e548b609..719d6891 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 4f9dcbe9..f0001629 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 719d6891..3834b1f1 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 f0001629..850f4333 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 3834b1f1..ee093cc1 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 850f4333..6a7aab40 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 ee093cc1..efb712f8 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 6a7aab40..5051207e 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 efb712f8..9730accc 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 7819420c..0e810b8e 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 5051207e..3c2b59a6 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 9730accc..b443c34c 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 2a80eedb..bc2bb410 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 00000000..907fa0cf --- /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 bc2bb410..57b3265b 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 00000000..6011de05 --- /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 0701f5e3..000638ce 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 ☃/.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 ☃/.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 57b3265b..e5fb6f2a 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 00000000..7c985224 --- /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 00000000..8c3539d9 --- /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 00000000..00a2db41 --- /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 00000000..9d9529d4 --- /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 ec6b801b..579f042a 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 3c2b59a6..688efb69 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 b443c34c..53753369 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 688efb69..71db3e8e 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 53753369..ef83d58d 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 71db3e8e..35259bef 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 594007b5..f618ccc1 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 e5fb6f2a..f43df44c 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 00000000..571c29ca --- /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 35259bef..2410810d 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 f618ccc1..d188a16d 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 f43df44c..7624a8c8 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 00000000..7c92f90e --- /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 36e7e75e..b07c383b 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 4006a5e5..f970f8a7 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 b07c383b..457029de 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 f970f8a7..c2cd643e 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 457029de..b55dc4ae 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 c2cd643e..82463dee 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 2410810d..9ddfe35b 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 ef83d58d..6f89e336 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 9ddfe35b..312f4b96 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 6f89e336..34904ac0 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 312f4b96..b0685594 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 34904ac0..3a6a00d1 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 7c985224..485ee4c0 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 b0685594..ef9aec94 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 3a6a00d1..c722e230 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 b55dc4ae..4706b759 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 82463dee..b222f6b7 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 4706b759..73422a7a 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 73422a7a..7b11677e 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 b222f6b7..84476a59 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 ef9aec94..da368666 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 c722e230..b8b0b19e 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 b8b0b19e..87b80ce3 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 c1514901..a4f10cbb 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 81d8d916..1f912973 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 da368666..cca851d2 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 87b80ce3..726af2f6 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 7b11677e..c3ebc583 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 84476a59..24434cd1 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 cca851d2..631e0e51 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 a4f10cbb..c9f08cd5 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 3f928ec0..8c173a64 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 631e0e51..6e62a6dd 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 726af2f6..2979d57a 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 ",