Merge tag '4.16.2'

This commit is contained in:
Douglas Christopher Wilson 2017-10-13 22:27:30 -04:00
commit 62e12fe710
31 changed files with 497 additions and 112 deletions

1
.gitignore vendored
View File

@ -15,6 +15,7 @@ Desktop.ini
# npm
node_modules
package-lock.json
*.log
*.gz

View File

@ -9,10 +9,13 @@ node_js:
- "5.12"
- "6.11"
- "7.10"
- "8.4"
matrix:
include:
- node_js: "8"
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
- node_js: "9"
env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
allow_failures:
# Allow the nightly installs to fail
- env: "NVM_NODEJS_ORG_MIRROR=https://nodejs.org/download/nightly"
@ -21,6 +24,9 @@ cache:
directories:
- node_modules
before_install:
# Skip updating shrinkwrap / lock
- "npm config set shrinkwrap false"
# Remove all non-test dependencies
- "npm rm --save-dev connect-redis"

View File

@ -1,6 +1,8 @@
5.x
===
This incorporates all changes after 4.15.5 up to 4.16.2.
* remove:
- `path-to-regexp` dependency
@ -94,6 +96,69 @@ 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.16.2 / 2017-10-09
===================
* Fix `TypeError` in `res.send` when given `Buffer` and `ETag` header set
* perf: skip parsing of entire `X-Forwarded-Proto` header
4.16.1 / 2017-09-29
===================
* deps: send@0.16.1
* deps: serve-static@1.13.1
- Fix regression when `root` is incorrectly set to a file
- deps: send@0.16.1
4.16.0 / 2017-09-28
===================
* Add `"json escape"` setting for `res.json` and `res.jsonp`
* Add `express.json` and `express.urlencoded` to parse bodies
* Add `options` argument to `res.download`
* Improve error message when autoloading invalid view engine
* Improve error messages when non-function provided as middleware
* Skip `Buffer` encoding when not generating ETag for small response
* Use `safe-buffer` for improved Buffer API
* deps: accepts@~1.3.4
- deps: mime-types@~2.1.16
* deps: content-type@~1.0.4
- perf: remove argument reassignment
- perf: skip parameter parsing when no parameters
* deps: etag@~1.8.1
- perf: replace regular expression with substring
* deps: finalhandler@1.1.0
- Use `res.headersSent` when available
* deps: parseurl@~1.3.2
- perf: reduce overhead for full URLs
- perf: unroll the "fast-path" `RegExp`
* deps: proxy-addr@~2.0.2
- Fix trimming leading / trailing OWS in `X-Forwarded-For`
- deps: forwarded@~0.1.2
- deps: ipaddr.js@1.5.2
- perf: reduce overhead when no `X-Forwarded-For` header
* deps: qs@6.5.1
- Fix parsing & compacting very deep objects
* deps: send@0.16.0
- Add 70 new types for file extensions
- Add `immutable` option
- Fix missing `</html>` in default error & redirects
- Set charset as "UTF-8" for .js and .json
- Use instance methods on steam to check for listeners
- deps: mime@1.4.1
- perf: improve path validation speed
* deps: serve-static@1.13.0
- Add 70 new types for file extensions
- Add `immutable` option
- Set charset as "UTF-8" for .js and .json
- deps: send@0.16.0
* deps: setprototypeof@1.1.0
* deps: utils-merge@1.0.1
* deps: vary@~1.1.2
- perf: improve header token parsing speed
* perf: re-use options object when generating ETags
* perf: remove dead `.charset` set in `res.jsonp`
4.15.5 / 2017-09-24
===================

View File

@ -21,10 +21,22 @@ app.listen(3000)
## Installation
This is a [Node.js](https://nodejs.org/en/) module available through the
[npm registry](https://www.npmjs.com/).
Before installing, [download and install Node.js](https://nodejs.org/en/download/).
Node.js 0.10 or higher is required.
Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
```bash
$ npm install express
```
Follow [our installing guide](http://expressjs.com/en/starter/installing.html)
for more information.
## Features
* Robust routing

View File

@ -9,10 +9,12 @@ environment:
- nodejs_version: "5.12"
- nodejs_version: "6.11"
- nodejs_version: "7.10"
- nodejs_version: "8.4"
cache:
- node_modules
install:
- ps: Install-Product node $env:nodejs_version
- npm config set shrinkwrap false
- npm rm --save-dev connect-redis
- if exist node_modules npm prune
- if exist node_modules npm rebuild

View File

@ -13,10 +13,8 @@ while (n--) {
});
}
var body = new Buffer('Hello World');
app.use(function(req, res, next){
res.send(body);
res.send('Hello World')
});
app.listen(3333);

View File

@ -3,7 +3,6 @@
*/
var express = require('../..');
var bodyParser = require('body-parser');
var hash = require('pbkdf2-password')()
var path = require('path');
var session = require('express-session');
@ -17,7 +16,7 @@ app.set('views', path.join(__dirname, 'views'));
// middleware
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.urlencoded({ extended: false }))
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored

View File

@ -6,7 +6,6 @@ var express = require('../../');
var app = module.exports = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
// custom log format
if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
@ -18,7 +17,7 @@ if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
app.use(cookieParser('my secret here'));
// parses x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
app.use(express.urlencoded({ extended: false }))
app.get('/', function(req, res){
if (req.cookies.remember) {

View File

@ -6,7 +6,6 @@ var express = require('../..');
var logger = require('morgan');
var path = require('path');
var session = require('express-session');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var app = module.exports = express();
@ -43,7 +42,7 @@ app.use(session({
}));
// parse request bodies (req.body)
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }))
// allow overriding methods in query (?_method=put)
app.use(methodOverride('_method'));

View File

@ -7,7 +7,6 @@ var path = require('path');
var app = express();
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
var site = require('./site');
var post = require('./post');
@ -27,7 +26,7 @@ if (!module.parent) {
app.use(methodOverride('_method'));
app.use(cookieParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.urlencoded({ extended: true }))
app.use(express.static(path.join(__dirname, 'public')));
// General

View File

@ -205,7 +205,7 @@ app.use = function use(fn) {
var fns = flatten(slice.call(arguments, offset));
if (fns.length === 0) {
throw new TypeError('app.use() requires middleware functions');
throw new TypeError('app.use() requires a middleware function')
}
// get router
@ -332,7 +332,7 @@ app.param = function param(name, fn) {
* Assign `setting` to `val`, or return `setting`'s value.
*
* app.set('foo', 'bar');
* app.get('foo');
* app.set('foo');
* // => "bar"
*
* Mounted servers inherit their parent server's settings.

View File

@ -12,6 +12,7 @@
* Module dependencies.
*/
var bodyParser = require('body-parser')
var EventEmitter = require('events').EventEmitter;
var mixin = require('merge-descriptors');
var proto = require('./application');
@ -73,4 +74,6 @@ exports.Router = Router;
* Expose middleware
*/
exports.json = bodyParser.json
exports.static = require('serve-static');
exports.urlencoded = bodyParser.urlencoded

View File

@ -294,8 +294,12 @@ defineGetter(req, 'protocol', function protocol(){
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
proto = this.get('X-Forwarded-Proto') || proto;
return proto.split(/\s*,\s*/)[0];
var header = this.get('X-Forwarded-Proto') || proto
var index = header.indexOf(',')
return index !== -1
? header.substring(0, index).trim()
: header.trim()
});
/**

View File

@ -12,6 +12,7 @@
* @private
*/
var Buffer = require('safe-buffer').Buffer
var contentDisposition = require('content-disposition');
var encodeUrl = require('encodeurl');
var escapeHtml = require('escape-html');
@ -94,7 +95,7 @@ res.links = function(links){
*
* Examples:
*
* res.send(new Buffer('wahoo'));
* res.send(Buffer.from('wahoo'));
* res.send({ some: 'json' });
* res.send('<p>some html</p>');
*
@ -105,7 +106,6 @@ res.links = function(links){
res.send = function send(body) {
var chunk = body;
var encoding;
var len;
var req = this.req;
var type;
@ -145,23 +145,33 @@ res.send = function send(body) {
}
}
// determine if ETag should be generated
var etagFn = app.get('etag fn')
var generateETag = !this.get('ETag') && typeof etagFn === 'function'
// populate Content-Length
var len
if (chunk !== undefined) {
if (!Buffer.isBuffer(chunk)) {
// convert chunk to Buffer; saves later double conversions
chunk = new Buffer(chunk, encoding);
if (Buffer.isBuffer(chunk)) {
// get length of Buffer
len = chunk.length
} else if (!generateETag && chunk.length < 1000) {
// just calculate length when no ETag + small chunk
len = Buffer.byteLength(chunk, encoding)
} else {
// convert chunk to Buffer and calculate
chunk = Buffer.from(chunk, encoding)
encoding = undefined;
len = chunk.length
}
len = chunk.length;
this.set('Content-Length', len);
}
// populate ETag
var etag;
var generateETag = len !== undefined && app.get('etag fn');
if (typeof generateETag === 'function' && !this.get('ETag')) {
if ((etag = generateETag(chunk, encoding))) {
if (generateETag && len !== undefined) {
if ((etag = etagFn(chunk, encoding))) {
this.set('ETag', etag);
}
}
@ -203,9 +213,10 @@ res.send = function send(body) {
res.json = function json(obj) {
// settings
var app = this.app;
var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = stringify(obj, replacer, spaces);
var body = stringify(obj, replacer, spaces, escape)
// content-type
if (!this.get('Content-Type')) {
@ -230,9 +241,10 @@ res.json = function json(obj) {
res.jsonp = function jsonp(obj) {
// settings
var app = this.app;
var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = stringify(obj, replacer, spaces);
var body = stringify(obj, replacer, spaces, escape)
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
@ -248,7 +260,6 @@ res.jsonp = function jsonp(obj) {
// jsonp
if (typeof callback === 'string' && callback.length !== 0) {
this.charset = 'utf-8';
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'text/javascript');
@ -378,19 +389,29 @@ res.sendFile = function sendFile(path, options, callback) {
* when the data transfer is complete, or when an error has
* ocurred. Be sure to check `res.headersSent` if you plan to respond.
*
* Optionally providing an `options` object to use with `res.sendFile()`.
* This function will set the `Content-Disposition` header, overriding
* any `Content-Disposition` header passed as header options in order
* to set the attachment and filename.
*
* This method uses `res.sendFile()`.
*
* @public
*/
res.download = function download(path, filename, callback) {
res.download = function download (path, filename, options, callback) {
var done = callback;
var name = filename;
var opts = options || null
// support function as second arg
// support function as second or third arg
if (typeof filename === 'function') {
done = filename;
name = null;
opts = null
} else if (typeof options === 'function') {
done = options
opts = null
}
// set Content-Disposition when file is sent
@ -398,10 +419,26 @@ res.download = function download(path, filename, callback) {
'Content-Disposition': contentDisposition(name || path)
};
// merge user-provided headers
if (opts && opts.headers) {
var keys = Object.keys(opts.headers)
for (var i = 0; i < keys.length; i++) {
var key = keys[i]
if (key.toLowerCase() !== 'content-disposition') {
headers[key] = opts.headers[key]
}
}
}
// merge user-provided options
opts = Object.create(opts)
opts.headers = headers
// Resolve the full path for sendFile
var fullPath = resolve(path);
return this.sendFile(fullPath, { headers: headers }, done);
// send file
return this.sendFile(fullPath, opts, done)
};
/**
@ -924,14 +961,38 @@ function sendfile(res, file, options, callback) {
}
/**
* Stringify JSON, like JSON.stringify, but v8 optimized.
* Stringify JSON, like JSON.stringify, but v8 optimized, with the
* ability to escape characters that can trigger HTML sniffing.
*
* @param {*} value
* @param {function} replaces
* @param {number} spaces
* @param {boolean} escape
* @returns {string}
* @private
*/
function stringify(value, replacer, spaces) {
function stringify (value, replacer, spaces, escape) {
// v8 checks arguments.length for optimizing simple call
// https://bugs.chromium.org/p/v8/issues/detail?id=4730
return replacer || spaces
var json = replacer || spaces
? JSON.stringify(value, replacer, spaces)
: JSON.stringify(value);
if (escape) {
json = json.replace(/[<>&]/g, function (c) {
switch (c.charCodeAt(0)) {
case 0x3c:
return '\\u003c'
case 0x3e:
return '\\u003e'
case 0x26:
return '\\u0026'
default:
return c
}
})
}
return json
}

View File

@ -12,8 +12,9 @@
* @api private
*/
var mime = require('send').mime;
var Buffer = require('safe-buffer').Buffer
var contentType = require('content-type');
var mime = require('send').mime;
var etag = require('etag');
var proxyaddr = require('proxy-addr');
var qs = require('qs');
@ -28,13 +29,7 @@ var querystring = require('querystring');
* @api private
*/
exports.etag = function (body, encoding) {
var buf = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body;
return etag(buf, {weak: false});
};
exports.etag = createETagGenerator({ weak: false })
/**
* Return weak ETag for `body`.
@ -45,13 +40,7 @@ exports.etag = function (body, encoding) {
* @api private
*/
exports.wetag = function wetag(body, encoding){
var buf = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body;
return etag(buf, {weak: true});
};
exports.wetag = createETagGenerator({ weak: true })
/**
* Normalize the given `type`, for example "html" becomes "text/html".
@ -232,6 +221,25 @@ exports.setCharset = function setCharset(type, charset) {
return contentType.format(parsed);
};
/**
* Create an ETag generator function, generating ETags with
* the given options.
*
* @param {object} options
* @return {function}
* @private
*/
function createETagGenerator (options) {
return function generateETag (body, encoding) {
var buf = !Buffer.isBuffer(body)
? Buffer.from(body, encoding)
: body
return etag(buf, options)
}
}
/**
* Parse an extended query string with qs.
*

View File

@ -76,7 +76,15 @@ function View(name, options) {
// load engine
var mod = this.ext.substr(1)
debug('require "%s"', mod)
opts.engines[this.ext] = require(mod).__express
// default engine export
var fn = require(mod).__express
if (typeof fn !== 'function') {
throw new Error('Module "' + mod + '" does not provide a view engine.')
}
opts.engines[this.ext] = fn
}
// store loaded engine

View File

@ -27,50 +27,51 @@
"api"
],
"dependencies": {
"accepts": "~1.3.3",
"accepts": "~1.3.4",
"array-flatten": "2.1.1",
"body-parser": "1.18.2",
"content-disposition": "0.5.2",
"content-type": "~1.0.2",
"content-type": "~1.0.4",
"cookie": "0.3.1",
"cookie-signature": "1.0.6",
"debug": "2.6.9",
"depd": "~1.1.1",
"encodeurl": "~1.0.1",
"escape-html": "~1.0.3",
"etag": "~1.8.0",
"finalhandler": "~1.0.6",
"etag": "~1.8.1",
"finalhandler": "1.1.0",
"fresh": "0.5.2",
"merge-descriptors": "1.0.1",
"methods": "~1.1.2",
"on-finished": "~2.3.0",
"parseurl": "~1.3.1",
"parseurl": "~1.3.2",
"path-is-absolute": "1.0.1",
"proxy-addr": "~1.1.5",
"qs": "6.5.0",
"proxy-addr": "~2.0.2",
"qs": "6.5.1",
"range-parser": "~1.2.0",
"router": "~1.3.1",
"send": "0.15.6",
"serve-static": "1.12.6",
"setprototypeof": "1.0.3",
"safe-buffer": "5.1.1",
"send": "0.16.1",
"serve-static": "1.13.1",
"setprototypeof": "1.1.0",
"statuses": "~1.3.1",
"type-is": "~1.6.15",
"utils-merge": "1.0.0",
"vary": "~1.1.1"
"utils-merge": "1.0.1",
"vary": "~1.1.2"
},
"devDependencies": {
"after": "0.8.2",
"body-parser": "1.18.1",
"cookie-parser": "~1.4.3",
"cookie-session": "1.3.1",
"cookie-session": "1.3.2",
"ejs": "2.5.7",
"eslint": "2.13.1",
"express-session": "1.15.5",
"express-session": "1.15.6",
"hbs": "4.0.1",
"istanbul": "0.4.5",
"marked": "0.3.6",
"method-override": "2.3.9",
"method-override": "2.3.10",
"mocha": "3.5.3",
"morgan": "1.8.2",
"morgan": "1.9.0",
"multiparty": "4.1.3",
"pbkdf2-password": "1.2.1",
"should": "13.1.0",
@ -90,9 +91,9 @@
],
"scripts": {
"lint": "eslint .",
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/",
"test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/"
"test": "mocha --require test/support/env --reporter spec --bail --check-leaks --no-exit test/ test/acceptance/",
"test-ci": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks --no-exit test/ test/acceptance/",
"test-cov": "istanbul cover node_modules/mocha/bin/_mocha -- --require test/support/env --reporter dot --check-leaks --no-exit test/ test/acceptance/",
"test-tap": "mocha --require test/support/env --reporter tap --check-leaks --no-exit test/ test/acceptance/"
}
}

View File

@ -368,17 +368,29 @@ describe('Router', function(){
})
describe('.use', function() {
it('should require arguments', function(){
var router = new Router();
assert.throws(router.use.bind(router), /argument handler is required/)
it('should require middleware', function () {
var router = new Router()
assert.throws(function () { router.use('/') }, /argument handler is required/)
})
it('should not accept non-functions', function(){
var router = new Router();
assert.throws(router.use.bind(router, '/', 'hello'), /argument handler must be a function/)
assert.throws(router.use.bind(router, '/', 5), /argument handler must be a function/)
assert.throws(router.use.bind(router, '/', null), /argument handler must be a function/)
assert.throws(router.use.bind(router, '/', new Date()), /argument handler must be a function/)
it('should reject string as middleware', function () {
var router = new Router()
assert.throws(function () { router.use('/', 'foo') }, /argument handler must be a function/)
})
it('should reject number as middleware', function () {
var router = new Router()
assert.throws(function () { router.use('/', 42) }, /argument handler must be a function/)
})
it('should reject null as middleware', function () {
var router = new Router()
assert.throws(function () { router.use('/', null) }, /argument handler must be a function/)
})
it('should reject Date as middleware', function () {
var router = new Router()
assert.throws(function () { router.use('/', new Date()) }, /argument handler must be a function/)
})
it('should be called for any URL', function (done) {

View File

@ -1,6 +1,6 @@
var after = require('after');
var assert = require('assert');
var assert = require('assert')
var express = require('..');
var request = require('supertest');
@ -254,17 +254,29 @@ describe('app', function(){
})
describe('.use(path, middleware)', function(){
it('should reject missing functions', function () {
var app = express();
assert.throws(app.use.bind(app, '/'), /requires middleware function/);
it('should require middleware', function () {
var app = express()
assert.throws(function () { app.use('/') }, 'TypeError: app.use() requires a middleware function')
})
it('should reject non-functions as middleware', function () {
var app = express();
assert.throws(app.use.bind(app, '/', 'hi'), /argument handler must be a function/);
assert.throws(app.use.bind(app, '/', 5), /argument handler must be a function/);
assert.throws(app.use.bind(app, '/', null), /argument handler must be a function/);
assert.throws(app.use.bind(app, '/', new Date()), /argument handler must be a function/);
it('should reject string as middleware', function () {
var app = express()
assert.throws(function () { app.use('/', 'foo') }, /argument handler must be a function/)
})
it('should reject number as middleware', function () {
var app = express()
assert.throws(function () { app.use('/', 42) }, /argument handler must be a function/)
})
it('should reject null as middleware', function () {
var app = express()
assert.throws(function () { app.use('/', null) }, /argument handler must be a function/)
})
it('should reject Date as middleware', function () {
var app = express()
assert.throws(function () { app.use('/', new Date()) }, /argument handler must be a function/)
})
it('should strip path from req.url', function (done) {

0
test/fixtures/broken.send vendored Normal file
View File

View File

@ -28,7 +28,7 @@ describe('middleware', function(){
});
});
request(app.listen())
request(app)
.get('/')
.set('Content-Type', 'application/json')
.send('{"foo":"bar"}')

View File

@ -1,4 +1,5 @@
var Buffer = require('safe-buffer').Buffer
var express = require('../')
, request = require('supertest');
@ -36,7 +37,7 @@ describe('res', function(){
app.use(function(req, res){
res.attachment('/path/to/image.png');
res.send(new Buffer(4));
res.send(Buffer.alloc(4, '.'))
});
request(app)

View File

@ -71,6 +71,86 @@ describe('res', function(){
})
})
describe('.download(path, filename, options, fn)', function () {
it('should invoke the callback', function (done) {
var app = express()
var cb = after(2, done)
var options = {}
app.use(function (req, res) {
res.download('test/fixtures/user.html', 'document', options, done)
})
request(app)
.get('/')
.expect(200)
.expect('Content-Type', 'text/html; charset=UTF-8')
.expect('Content-Disposition', 'attachment; filename="document"')
.end(cb)
})
it('should allow options to res.sendFile()', function (done) {
var app = express()
app.use(function (req, res) {
res.download('test/fixtures/.name', 'document', {
dotfiles: 'allow',
maxAge: '4h'
})
})
request(app)
.get('/')
.expect(200)
.expect('Content-Disposition', 'attachment; filename="document"')
.expect('Cache-Control', 'public, max-age=14400')
.expect('tobi')
.end(done)
})
describe('when options.headers contains Content-Disposition', function () {
it('should should be ignored', function (done) {
var app = express()
app.use(function (req, res) {
res.download('test/fixtures/user.html', 'document', {
headers: {
'Content-Type': 'text/x-custom',
'Content-Disposition': 'inline'
}
})
})
request(app)
.get('/')
.expect(200)
.expect('Content-Type', 'text/x-custom')
.expect('Content-Disposition', 'attachment; filename="document"')
.end(done)
})
it('should should be ignored case-insensitively', function (done) {
var app = express()
app.use(function (req, res) {
res.download('test/fixtures/user.html', 'document', {
headers: {
'content-type': 'text/x-custom',
'content-disposition': 'inline'
}
})
})
request(app)
.get('/')
.expect(200)
.expect('Content-Type', 'text/x-custom')
.expect('Content-Disposition', 'attachment; filename="document"')
.end(done)
})
})
})
describe('on failure', function(){
it('should invoke the callback', function(done){
var app = express();

View File

@ -1,4 +1,5 @@
var after = require('after')
var express = require('../')
, request = require('supertest')
, assert = require('assert');
@ -173,21 +174,23 @@ function test(app) {
.expect('hey', done);
})
it('should set the correct charset for the Content-Type', function() {
it('should set the correct charset for the Content-Type', function (done) {
var cb = after(3, done)
request(app)
.get('/')
.set('Accept', 'text/html')
.expect('Content-Type', 'text/html; charset=utf-8');
.expect('Content-Type', 'text/html; charset=utf-8', cb)
request(app)
.get('/')
.set('Accept', 'text/plain')
.expect('Content-Type', 'text/plain; charset=utf-8');
.expect('Content-Type', 'text/plain; charset=utf-8', cb)
request(app)
.get('/')
.set('Accept', 'application/json')
.expect('Content-Type', 'application/json');
.expect('Content-Type', 'application/json; charset=utf-8', cb)
})
it('should Vary: Accept', function(done){

View File

@ -102,6 +102,28 @@ describe('res', function(){
})
})
describe('"json escape" setting', function () {
it('should be undefined by default', function () {
var app = express()
assert.strictEqual(app.get('json escape'), undefined)
})
it('should unicode escape HTML-sniffing characters', function (done) {
var app = express()
app.enable('json escape')
app.use(function (req, res) {
res.json({ '&': '<script>' })
})
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"\\u0026":"\\u003cscript\\u003e"}', done)
})
})
describe('"json replacer" setting', function(){
it('should be passed to JSON.stringify()', function(done){
var app = express();

View File

@ -242,6 +242,28 @@ describe('res', function(){
})
})
describe('"json escape" setting', function () {
it('should be undefined by default', function () {
var app = express()
assert.strictEqual(app.get('json escape'), undefined)
})
it('should unicode escape HTML-sniffing characters', function (done) {
var app = express()
app.enable('json escape')
app.use(function (req, res) {
res.jsonp({ '&': '\u2028<script>\u2029' })
})
request(app)
.get('/?callback=foo')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /foo\({"\\u0026":"\\u2028\\u003cscript\\u003e\\u2029"}\)/, done)
})
})
describe('"json replacer" setting', function(){
it('should be passed to JSON.stringify()', function(done){
var app = express();

View File

@ -35,6 +35,20 @@ describe('res', function(){
.expect('<p>tobi</p>', done);
})
it('should error without "view engine" set and file extension to a non-engine module', function (done) {
var app = createApp()
app.locals.user = { name: 'tobi' }
app.use(function (req, res) {
res.render(path.join(__dirname, 'fixtures', 'broken.send'))
})
request(app)
.get('/')
.expect(500, /does not provide a view engine/, done)
})
it('should error without "view engine" set and no file extension', function (done) {
var app = createApp();

View File

@ -1,4 +1,5 @@
var Buffer = require('safe-buffer').Buffer
var express = require('..');
var methods = require('methods');
var request = require('supertest');
@ -121,7 +122,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
res.set('Content-Type', 'text/plain; charset=iso-8859-1').send(new Buffer('hi'));
res.set('Content-Type', 'text/plain; charset=iso-8859-1').send(Buffer.from('hi'))
});
request(app)
@ -136,7 +137,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
res.send(new Buffer('hello'));
res.send(Buffer.from('hello'))
});
request(app)
@ -149,8 +150,7 @@ describe('res', function(){
var app = express();
app.use(function (req, res) {
var str = Array(1000).join('-');
res.send(new Buffer(str));
res.send(Buffer.alloc(999, '-'))
});
request(app)
@ -163,7 +163,7 @@ describe('res', function(){
var app = express();
app.use(function(req, res){
res.set('Content-Type', 'text/plain').send(new Buffer('hey'));
res.set('Content-Type', 'text/plain').send(Buffer.from('hey'))
});
request(app)
@ -171,6 +171,19 @@ describe('res', function(){
.expect('Content-Type', 'text/plain; charset=utf-8')
.expect(200, 'hey', done);
})
it('should not override ETag', function (done) {
var app = express()
app.use(function (req, res) {
res.type('text/plain').set('ETag', '"foo"').send(Buffer.from('hey'))
})
request(app)
.get('/')
.expect('ETag', '"foo"')
.expect(200, 'hey', done)
})
})
describe('.send(Object)', function(){
@ -467,7 +480,7 @@ describe('res', function(){
app.set('etag', function (body, encoding) {
var chunk = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
? Buffer.from(body, encoding)
: body;
chunk.toString().should.equal('hello, world!');
return '"custom"';

View File

@ -101,7 +101,7 @@ describe('res', function(){
app.use(function (req, res) {
setImmediate(function () {
res.sendFile(path.resolve(fixtures, 'name.txt'));
cb();
server.close(cb)
});
test.abort();
});
@ -111,7 +111,8 @@ describe('res', function(){
cb();
});
var test = request(app).get('/');
var server = app.listen()
var test = request(server).get('/')
test.expect(200, cb);
})
@ -179,6 +180,44 @@ describe('res', function(){
});
});
describe('with "immutable" option', function () {
it('should add immutable cache-control directive', function (done) {
var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
immutable: true,
maxAge: '4h'
})
request(app)
.get('/')
.expect('Cache-Control', 'public, max-age=14400, immutable')
.expect(200, done)
})
})
describe('with "maxAge" option', function () {
it('should set cache-control max-age from number', function (done) {
var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
maxAge: 14400000
})
request(app)
.get('/')
.expect('Cache-Control', 'public, max-age=14400')
.expect(200, done)
})
it('should set cache-control max-age from string', function (done) {
var app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
maxAge: '4h'
})
request(app)
.get('/')
.expect('Cache-Control', 'public, max-age=14400')
.expect(200, done)
})
})
describe('with "root" option', function () {
it('should not transfer relative with without', function (done) {
var app = createApp('test/fixtures/name.txt');
@ -225,13 +264,14 @@ describe('res', function(){
res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
should(err).be.ok()
err.code.should.equal('ECONNABORTED');
cb();
server.close(cb)
});
});
test.abort();
});
var test = request(app).get('/');
var server = app.listen()
var test = request(server).get('/')
test.expect(200, cb);
})
@ -244,13 +284,14 @@ describe('res', function(){
res.sendFile(path.resolve(fixtures, 'name.txt'), function (err) {
should(err).be.ok()
err.code.should.equal('ECONNABORTED');
cb();
server.close(cb)
});
});
test.abort();
});
var test = request(app).get('/');
var server = app.listen()
var test = request(server).get('/')
test.expect(200, cb);
})

View File

@ -13,7 +13,8 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Type', 'application/javascript', done);
.expect('Content-Type', 'application/javascript; charset=utf-8')
.end(done)
})
it('should default to application/octet-stream', function(done){

View File

@ -1,5 +1,6 @@
var assert = require('assert');
var Buffer = require('safe-buffer').Buffer
var utils = require('../lib/utils');
describe('utils.etag(body, encoding)', function(){
@ -14,8 +15,7 @@ describe('utils.etag(body, encoding)', function(){
})
it('should support buffer', function(){
var buf = new Buffer('express!')
utils.etag(buf)
utils.etag(Buffer.from('express!'))
.should.eql('"8-O2uVAFaQ1rZvlKLT14RnuvjPIdg"')
})
@ -59,8 +59,7 @@ describe('utils.wetag(body, encoding)', function(){
})
it('should support buffer', function(){
var buf = new Buffer('express!')
utils.wetag(buf)
utils.wetag(Buffer.from('express!'))
.should.eql('W/"8-O2uVAFaQ1rZvlKLT14RnuvjPIdg"')
})