Merge tag '4.17.1'

This commit is contained in:
Douglas Christopher Wilson 2019-06-08 19:43:21 -04:00
commit 121fe9982b
38 changed files with 3318 additions and 84 deletions

View File

@ -7,14 +7,16 @@ node_js:
- "3.3"
- "4.9"
- "5.12"
- "6.14"
- "6.17"
- "7.10"
- "8.12"
- "8.16"
- "9.11"
- "10.15"
- "11.15"
- "12.3"
matrix:
include:
- node_js: "9"
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
- node_js: "10"
- node_js: "13"
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
allow_failures:
# Allow the nightly installs to fail
@ -60,5 +62,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

View File

@ -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.

View File

@ -1,3 +1,8 @@
5.x
===
This incorporates all changes after 4.16.4 up to 4.17.1.
5.0.0-alpha.7 / 2018-10-26
==========================
@ -113,6 +118,62 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
* add:
- `app.router` is a reference to the base router
4.17.1 / 2019-05-25
===================
* Revert "Improve error message for `null`/`undefined` to `res.status`"
4.17.0 / 2019-05-16
===================
* 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`
* 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: cookie@0.4.0
- Add `SameSite=None` support
* 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
* 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
- 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.1
- Set stricter CSP header in redirect response
- deps: parseurl@~1.3.3
- deps: send@0.17.1
* 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
===================
@ -409,7 +470,7 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
- 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
@ -424,7 +485,7 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
- 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
@ -434,7 +495,7 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
* 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
@ -1212,13 +1273,13 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
- 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
@ -2098,7 +2159,7 @@ This is the first Express 5.0 alpha release, based off 4.10.1.
- 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
@ -3283,7 +3344,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
@ -3478,7 +3539,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
@ -3524,7 +3585,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

View File

@ -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')
@ -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
@ -125,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)
@ -147,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/

View File

@ -7,9 +7,13 @@ 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: "8.16"
- nodejs_version: "9.11"
- nodejs_version: "10.15"
- nodejs_version: "11.15"
- nodejs_version: "12.3"
cache:
- node_modules
install:

View File

@ -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!');

View File

@ -1,6 +1,6 @@
body {
padding: 50px;
font: 16px "Helvetica Neue", Helvetica, Arial;
font: 16px "Helvetica Neue", Helvetica, Arial, sans-serif;
}
a {
color: #107aff;

View File

@ -1 +1 @@
foo
// foo

View File

@ -75,5 +75,7 @@ exports.Router = Router;
*/
exports.json = bodyParser.json
exports.raw = bodyParser.raw
exports.static = require('serve-static');
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded

View File

@ -409,6 +409,10 @@ defineGetter(req, 'host', function host(){
if (!val || !trust(this.connection.remoteAddress, 0)) {
val = this.get('Host');
} else if (val.indexOf(',') !== -1) {
// Note: X-Forwarded-Host is normally only ever a
// single value, but this is to be safe.
val = val.substring(0, val.indexOf(',')).trimRight()
}
return val || undefined;

View File

@ -355,6 +355,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;
@ -686,7 +690,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
@ -988,6 +992,7 @@ function stringify (value, replacer, spaces, escape) {
return '\\u003e'
case 0x26:
return '\\u0026'
/* istanbul ignore next: unreachable default */
default:
return c
}

View File

@ -27,49 +27,49 @@
"api"
],
"dependencies": {
"accepts": "~1.3.5",
"accepts": "~1.3.7",
"array-flatten": "2.1.1",
"body-parser": "1.18.3",
"content-disposition": "0.5.2",
"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": "3.1.0",
"depd": "~1.1.2",
"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",
"on-finished": "~2.3.0",
"parseurl": "~1.3.2",
"parseurl": "~1.3.3",
"path-is-absolute": "1.0.1",
"proxy-addr": "~2.0.4",
"qs": "6.5.2",
"range-parser": "~1.2.0",
"proxy-addr": "~2.0.5",
"qs": "6.7.0",
"range-parser": "~1.2.1",
"router": "2.0.0-alpha.1",
"safe-buffer": "5.1.2",
"send": "0.16.2",
"serve-static": "1.13.2",
"setprototypeof": "1.1.0",
"statuses": "~1.4.0",
"type-is": "~1.6.16",
"send": "0.17.1",
"serve-static": "1.14.1",
"setprototypeof": "1.1.1",
"statuses": "~1.5.0",
"type-is": "~1.6.18",
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"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",
"hbs": "4.0.1",
"express-session": "1.16.1",
"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",

View File

@ -154,15 +154,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(){
@ -572,7 +569,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) {
@ -893,15 +890,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)
})
})
@ -984,15 +978,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){

View File

@ -1,4 +1,5 @@
var assert = require('assert')
var express = require('../');
var request = require('supertest');
@ -7,6 +8,31 @@ 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 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)
})
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)
})
it('should expose the application prototype', function(){
express.application.set.should.be.a.Function()
})

664
test/express.json.js Normal file
View File

@ -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 + '\'')
}
}

387
test/express.raw.js Normal file
View File

@ -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
}

813
test/express.static.js Normal file
View File

@ -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('<p>tobi, loki, jane</p>', 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, '<li>groceries</li>', 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, /<a href="\/users\/">/, 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 <a href="\/snow%20%E2%98%83\/">\/snow%20%E2%98%83\/<\/a></, done)
})
it('should respond with default Content-Security-Policy', function (done) {
request(this.app)
.get('/users')
.expect('Content-Security-Policy', "default-src 'none'")
.expect(301, done)
})
it('should not redirect incorrectly', function (done) {
request(this.app)
.get('/')
.expect(404, done)
})
describe('when false', function () {
before(function () {
this.app = createApp(fixtures, { 'redirect': false })
})
it('should disable redirect', function (done) {
request(this.app)
.get('/users')
.expect(404, done)
})
})
})
describe('setHeaders', function () {
before(function () {
this.app = express()
this.app.use(express.static(fixtures, { 'setHeaders': function (res) {
res.setHeader('x-custom', 'set')
} }))
})
it('should reject non-functions', function () {
assert.throws(express.static.bind(null, fixtures, { 'setHeaders': 3 }), /setHeaders.*function/)
})
it('should get called when sending file', function (done) {
request(this.app)
.get('/nums.txt')
.expect('x-custom', 'set')
.expect(200, done)
})
it('should not get called on 404', function (done) {
request(this.app)
.get('/bogus')
.expect(utils.shouldNotHaveHeader('x-custom'))
.expect(404, done)
})
it('should not get called on redirect', function (done) {
request(this.app)
.get('/users')
.expect(utils.shouldNotHaveHeader('x-custom'))
.expect(301, done)
})
})
describe('when traversing past root', function () {
before(function () {
this.app = createApp(fixtures, { 'fallthrough': false })
})
it('should catch urlencoded ../', function (done) {
request(this.app)
.get('/users/%2e%2e/%2e%2e/todo.txt')
.expect(403, done)
})
it('should not allow root path disclosure', function (done) {
request(this.app)
.get('/users/../../fixtures/todo.txt')
.expect(403, done)
})
})
describe('when request has "Range" header', function () {
before(function () {
this.app = createApp()
})
it('should support byte ranges', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=0-4')
.expect('12345', done)
})
it('should be inclusive', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=0-0')
.expect('1', done)
})
it('should set Content-Range', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=2-5')
.expect('Content-Range', 'bytes 2-5/9', done)
})
it('should support -n', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=-3')
.expect('789', done)
})
it('should support n-', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=3-')
.expect('456789', done)
})
it('should respond with 206 "Partial Content"', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=0-4')
.expect(206, done)
})
it('should set Content-Length to the # of octets transferred', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=2-3')
.expect('Content-Length', '2')
.expect(206, '34', done)
})
describe('when last-byte-pos of the range is greater than current length', function () {
it('is taken to be equal to one less than the current length', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=2-50')
.expect('Content-Range', 'bytes 2-8/9', done)
})
it('should adapt the Content-Length accordingly', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=2-50')
.expect('Content-Length', '7')
.expect(206, done)
})
})
describe('when the first- byte-pos of the range is greater than the current length', function () {
it('should respond with 416', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=9-50')
.expect(416, done)
})
it('should include a Content-Range header of complete length', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'bytes=9-50')
.expect('Content-Range', 'bytes */9')
.expect(416, done)
})
})
describe('when syntactically invalid', function () {
it('should respond with 200 and the entire contents', function (done) {
request(this.app)
.get('/nums.txt')
.set('Range', 'asdf')
.expect('123456789', done)
})
})
})
describe('when index at mount point', function () {
before(function () {
this.app = express()
this.app.use('/users', express.static(fixtures + '/users'))
})
it('should redirect correctly', function (done) {
request(this.app)
.get('/users')
.expect('Location', '/users/')
.expect(301, done)
})
})
describe('when mounted', function () {
before(function () {
this.app = express()
this.app.use('/static', express.static(fixtures))
})
it('should redirect relative to the originalUrl', function (done) {
request(this.app)
.get('/static/users')
.expect('Location', '/static/users/')
.expect(301, done)
})
it('should not choke on auth-looking URL', function (done) {
request(this.app)
.get('//todo@txt')
.expect(404, done)
})
})
//
// NOTE: This is not a real part of the API, but
// over time this has become something users
// are doing, so this will prevent unseen
// regressions around this use-case.
//
describe('when mounted "root" as a file', function () {
before(function () {
this.app = express()
this.app.use('/todo.txt', express.static(fixtures + '/todo.txt'))
})
it('should load the file when on trailing slash', function (done) {
request(this.app)
.get('/todo.txt')
.expect(200, '- groceries', done)
})
it('should 404 when trailing slash', function (done) {
request(this.app)
.get('/todo.txt/')
.expect(404, done)
})
})
describe('when responding non-2xx or 304', function () {
it('should not alter the status', function (done) {
var app = express()
app.use(function (req, res, next) {
res.status(501)
next()
})
app.use(express.static(fixtures))
request(app)
.get('/todo.txt')
.expect(501, '- groceries', done)
})
})
describe('when index file serving disabled', function () {
before(function () {
this.app = express()
this.app.use('/static', express.static(fixtures, { 'index': false }))
this.app.use(function (req, res, next) {
res.sendStatus(404)
})
})
it('should next() on directory', function (done) {
request(this.app)
.get('/static/users/')
.expect(404, 'Not Found', done)
})
it('should redirect to trailing slash', function (done) {
request(this.app)
.get('/static/users')
.expect('Location', '/static/users/')
.expect(301, done)
})
it('should next() on mount point', function (done) {
request(this.app)
.get('/static/')
.expect(404, 'Not Found', done)
})
it('should redirect to trailing slash mount point', function (done) {
request(this.app)
.get('/static')
.expect('Location', '/static/')
.expect(301, done)
})
})
})
function createApp (dir, options, fn) {
var app = express()
var root = dir || fixtures
app.use(express.static(root, options))
app.use(function (req, res, next) {
res.sendStatus(404)
})
return app
}

441
test/express.text.js Normal file
View File

@ -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('<b>tobi</b>')
.expect(200, '"<b>tobi</b>"', 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('<b>tobi</b>')
.expect(200, '"<b>tobi</b>"', 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('<user>tobi</user>')
.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
}

734
test/express.urlencoded.js Normal file
View File

@ -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)
}
}

0
test/fixtures/empty.txt vendored Normal file
View File

1
test/fixtures/nums.txt vendored Normal file
View File

@ -0,0 +1 @@
123456789

1
test/fixtures/pets/names.txt vendored Normal file
View File

@ -0,0 +1 @@
tobi,loki

0
test/fixtures/snow ☃/.gitkeep vendored Normal file
View File

1
test/fixtures/todo.html vendored Normal file
View File

@ -0,0 +1 @@
<li>groceries</li>

1
test/fixtures/todo.txt vendored Normal file
View File

@ -0,0 +1 @@
- groceries

1
test/fixtures/users/index.html vendored Normal file
View File

@ -0,0 +1 @@
<p>tobi, loki, jane</p>

1
test/fixtures/users/tobi.txt vendored Normal file
View File

@ -0,0 +1 @@
ferret

View File

@ -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)
})
})

View File

@ -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){

View File

@ -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();

View File

@ -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(){

View File

@ -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) {
@ -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);

View File

@ -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)
})
})

View File

@ -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) {

View File

@ -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)
})
})

View File

@ -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'));

View File

@ -1,3 +1,3 @@
process.env.NODE_ENV = 'test';
process.env.NO_DEPRECATION = 'express';
process.env.NO_DEPRECATION = 'body-parser,express';

View File

@ -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.
*