Compare commits

...

136 Commits
4.4.3 ... 4.7.3

Author SHA1 Message Date
Douglas Christopher Wilson
52775a52ad 4.7.3 2014-08-04 16:14:29 -04:00
Douglas Christopher Wilson
112bc92d78 deps: serve-static@~1.4.3 2014-08-04 16:11:52 -04:00
Douglas Christopher Wilson
d8df26680f deps: send@0.7.3 2014-08-04 16:10:32 -04:00
Douglas Christopher Wilson
1854a5d35f 4.7.2 2014-07-27 16:00:54 -04:00
Douglas Christopher Wilson
54d3ffa9a0 deps: update example dependencies 2014-07-27 16:00:17 -04:00
Douglas Christopher Wilson
0ee4dd82b5 deps: serve-static@~1.4.2 2014-07-27 15:59:19 -04:00
Douglas Christopher Wilson
454c4b2350 Merge tag '3.15.2' 2014-07-27 15:58:45 -04:00
Douglas Christopher Wilson
696e150f0a 3.15.2 2014-07-27 15:53:45 -04:00
Douglas Christopher Wilson
819265c7ae deps: send@0.7.2 2014-07-27 15:41:54 -04:00
Douglas Christopher Wilson
baf8b14a71 deps: depd@0.4.4
fixes #2262
2014-07-27 15:41:19 -04:00
Douglas Christopher Wilson
06e7685d65 deps: connect@2.24.2 2014-07-27 15:40:19 -04:00
Douglas Christopher Wilson
8858f20d93 4.7.1 2014-07-26 18:53:26 -04:00
Douglas Christopher Wilson
65f67e2ec0 deps: serve-static@~1.4.1 2014-07-26 18:08:02 -04:00
Douglas Christopher Wilson
8e63521f68 Merge tag '3.15.1' 2014-07-26 18:06:11 -04:00
Douglas Christopher Wilson
9f4968aaa3 3.15.1 2014-07-26 17:42:18 -04:00
Douglas Christopher Wilson
afea3c0ae8 deps: mocha@~1.21.0 2014-07-26 17:36:42 -04:00
Douglas Christopher Wilson
edaabe66cf deps: send@0.7.1 2014-07-26 17:36:40 -04:00
Douglas Christopher Wilson
f724730e1a Adjust wording on deprecation messages 2014-07-26 17:36:39 -04:00
Douglas Christopher Wilson
e49d0dc9e3 deps: depd@0.4.3
fixes #2262
2014-07-26 17:36:24 -04:00
Douglas Christopher Wilson
e7a3fbaf48 deps: connect@2.24.1 2014-07-26 17:30:13 -04:00
Douglas Christopher Wilson
1a9a837c45 4.7.0 2014-07-25 20:26:11 -04:00
Douglas Christopher Wilson
ab8d116f42 tests: bail on failed test for developer 2014-07-23 18:17:31 -04:00
Douglas Christopher Wilson
d0b6b3dfcf tests: change Route tests to use callback 2014-07-23 17:28:14 -04:00
Douglas Christopher Wilson
f34944c539 Merge tag '3.15.0' 2014-07-23 16:15:00 -04:00
Ashley Streb
11c74d72eb Fix req.protocol for proxy-direct connections
fixes #2252
2014-07-23 16:08:20 -04:00
Douglas Christopher Wilson
035685918c deps: serve-static@~1.4.0 2014-07-23 14:56:27 -04:00
Douglas Christopher Wilson
88cffadcaa deps: send@0.7.0 2014-07-23 14:54:33 -04:00
Douglas Christopher Wilson
3b7ca43170 deps: jade@~1.5.0 2014-07-23 14:01:44 -04:00
Douglas Christopher Wilson
7cd86a01da deps: update example dependencies 2014-07-23 13:04:07 -04:00
Douglas Christopher Wilson
dc054d190a deps: parseurl@~1.2.0 2014-07-23 12:55:56 -04:00
Douglas Christopher Wilson
9019424725 deps: depd@0.4.2 2014-07-23 12:53:29 -04:00
Douglas Christopher Wilson
928952e7f0 3.15.0 2014-07-23 00:53:19 -04:00
Ashley Streb
a28b7a85cf Fix req.protocol for proxy-direct connections
fixes #2252
2014-07-23 00:19:10 -04:00
Douglas Christopher Wilson
3fc8dc54ee docs: replace Gittip badge 2014-07-23 00:13:21 -04:00
Douglas Christopher Wilson
0d77305a1a Pass options from res.sendfile to send
fixes #2017
2014-07-23 00:08:15 -04:00
Douglas Christopher Wilson
323c185079 deps: send@0.7.0 2014-07-23 00:05:45 -04:00
Douglas Christopher Wilson
1d0da9036b deps: parseurl@~1.2.0 2014-07-22 23:53:50 -04:00
Douglas Christopher Wilson
683ba1cd75 deps: depd@0.4.2 2014-07-22 23:52:44 -04:00
Douglas Christopher Wilson
e4ff5281c9 deps: connect@2.24.0 2014-07-22 23:50:36 -04:00
Douglas Christopher Wilson
7414a1f463 deps: debug@1.0.4 2014-07-18 14:18:35 -04:00
Douglas Christopher Wilson
fd3b40533b Deprecate combined status, response signatures
closes #2227
2014-07-17 22:49:41 -04:00
Douglas Christopher Wilson
21bb2ef30e deps: finalhandler@0.1.0 2014-07-16 20:47:24 -04:00
Douglas Christopher Wilson
da4c639954 deps: debug@1.0.4 2014-07-16 20:44:17 -04:00
Douglas Christopher Wilson
d046208ca2 build: add Young Jae Sim as contributor 2014-07-16 13:31:35 -04:00
Young Jae Sim
04f383087f docs: add expressjs.kr(expressjs.com in Korean)
closes #2242
closes #2244
2014-07-16 13:31:34 -04:00
Vasilyev Dmitry
fd86ab8da2 build: fix up jsdoc return values
closes #2243
2014-07-16 09:05:57 -04:00
Douglas Christopher Wilson
e29fa25bb4 Add configurable query parser
closes #2215
2014-07-13 23:15:00 -04:00
Douglas Christopher Wilson
b43205ca98 perf: prevent multiple Buffer creation in res.send 2014-07-13 21:14:30 -04:00
Douglas Christopher Wilson
112dbb2ab4 4.6.1 2014-07-12 22:11:48 -04:00
Douglas Christopher Wilson
3e32721e24 Fix subapp.mountpath regression for app.use(subapp)
fixes #2233
2014-07-12 22:07:43 -04:00
Douglas Christopher Wilson
8ba3f39b33 4.6.0 2014-07-11 23:31:30 -04:00
Douglas Christopher Wilson
82bdbad5e0 deps: finalhandler@0.0.3 2014-07-11 23:00:09 -04:00
Douglas Christopher Wilson
d29cf4d5e3 deps: serve-static@~1.3.2 2014-07-11 22:49:49 -04:00
Douglas Christopher Wilson
1623936a25 deps: send@0.6.0 2014-07-11 22:48:42 -04:00
Douglas Christopher Wilson
fa6a40526a deps: update example dependencies 2014-07-11 22:46:25 -04:00
Douglas Christopher Wilson
c6e6203020 perf: fix arguments reassign deopt in some res methods 2014-07-11 17:13:07 -04:00
Douglas Christopher Wilson
997a558a73 Accept multiple callbacks to app.use()
fixes #2224
2014-07-11 17:13:06 -04:00
Douglas Christopher Wilson
a01326adac Catch errors in multiple req.param(name, fn) handlers 2014-07-11 17:12:57 -04:00
Douglas Christopher Wilson
76e8bfa1dc router: refactor location of try blocks 2014-07-11 16:38:03 -04:00
Douglas Christopher Wilson
8dc67af606 router: speed up standard app.use(fn) 2014-07-11 16:37:10 -04:00
Douglas Christopher Wilson
996d319263 router: fix optimization on router exit 2014-07-11 16:36:20 -04:00
Douglas Christopher Wilson
1c3bd36be6 Support non-string path in app.use(path, fn)
fixes #2207
2014-07-11 16:35:37 -04:00
Douglas Christopher Wilson
4ea6f21b02 Merge tag '3.14.0' 2014-07-11 16:28:37 -04:00
Douglas Christopher Wilson
916c53737d 3.14.0 2014-07-11 13:11:31 -04:00
Douglas Christopher Wilson
ec5b9f5c61 tests: add test for error handler in route
closes #2228
2014-07-11 09:35:20 -04:00
Douglas Christopher Wilson
b2382a7336 Remove unnecessary escaping in res.jsonp 2014-07-11 00:20:11 -04:00
Douglas Christopher Wilson
f684a64df7 Add explicit "Rosetta Flash JSONP abuse" protection 2014-07-11 00:15:55 -04:00
Douglas Christopher Wilson
5d03d0eac8 Deprecate res.redirect(url, status) 2014-07-10 23:21:16 -04:00
Yad Smood
544c6665f5 Fix res.send(status, num) to send num as json
fixes #2226
2014-07-10 23:19:57 -04:00
Douglas Christopher Wilson
cf8005e63f deps: basic-auth@1.0.0 2014-07-10 23:07:45 -04:00
Douglas Christopher Wilson
25ef8425d2 deps: methods@1.1.0 2014-07-10 23:06:15 -04:00
Douglas Christopher Wilson
577cc1d1a0 deps: parseurl@~1.1.3 2014-07-10 23:03:26 -04:00
Douglas Christopher Wilson
3c87a6aede deps: debug@1.0.3 2014-07-10 23:02:24 -04:00
Douglas Christopher Wilson
7c1f90bf16 deps: istanbul@0.3.0 2014-07-10 22:57:08 -04:00
Douglas Christopher Wilson
7bcf5f5085 deps: connect@2.23.0 2014-07-10 22:56:31 -04:00
Ryan Seys
4fe1073f10 docs: fix nodejs.org link
closes #2222
2014-07-09 00:50:33 -04:00
Douglas Christopher Wilson
968b00c3d7 tests: add missing FQDN router URL tests 2014-07-08 19:40:22 -04:00
Douglas Christopher Wilson
ef497fdae4 tests: add mounting to strict routing test 2014-07-07 23:25:05 -04:00
Douglas Christopher Wilson
bcdeee2df5 4.5.1 2014-07-06 19:48:18 -04:00
Douglas Christopher Wilson
23a49ff61e Fix routing regression when altering req.method 2014-07-06 19:44:43 -04:00
Douglas Christopher Wilson
b4b2efee0f tests: add more app.use tests 2014-07-05 11:32:44 -04:00
Douglas Christopher Wilson
92c45199bd 4.5.0 2014-07-04 21:03:07 -04:00
Fishrock123
bd6908516d docs: cleanup readme, update package description
closes #2210
2014-07-04 17:05:48 -04:00
Douglas Christopher Wilson
e6eeec3f03 Add req.hostname
closes #2179
2014-07-04 14:00:37 -04:00
Douglas Christopher Wilson
269dc5323f Invoke router.param() only when route matches
fixes #2206
2014-07-04 13:46:23 -04:00
Douglas Christopher Wilson
efbc3f95ee deps: accepts@~1.0.7 2014-07-04 12:51:44 -04:00
Douglas Christopher Wilson
99e839a274 deps: update example dependencies 2014-07-04 01:24:42 -04:00
Douglas Christopher Wilson
32dbda1460 deps: istanbul@0.2.14 2014-07-04 01:23:14 -04:00
Douglas Christopher Wilson
bcee730354 Merge tag '3.13.0' 2014-07-04 01:14:40 -04:00
Douglas Christopher Wilson
abe0ffa311 3.13.0 2014-07-04 00:40:01 -04:00
Douglas Christopher Wilson
b601d64203 tests: fix inconsistent test 2014-07-04 00:38:59 -04:00
Douglas Christopher Wilson
f381f2d9b6 add deprecation message to req.auth 2014-07-04 00:24:01 -04:00
Douglas Christopher Wilson
12507cfcd0 deps: connect@2.22.0 2014-07-03 23:09:44 -04:00
Douglas Christopher Wilson
185e327e29 add deprecation message to app.configure 2014-07-03 14:13:39 -04:00
Douglas Christopher Wilson
c468f5ff20 deps: send@0.5.0 2014-07-03 13:10:33 -04:00
Douglas Christopher Wilson
ce3d1fe07e Add headers option to res.sendfile 2014-07-03 12:53:49 -04:00
Douglas Christopher Wilson
3136e98dce deps: send@0.5.0 2014-07-03 11:18:35 -04:00
Douglas Christopher Wilson
d1b1dfd472 deps: serve-static@~1.3.0 2014-07-03 11:11:18 -04:00
Douglas Christopher Wilson
ca306eace1 deps: update example dependencies 2014-07-03 10:49:53 -04:00
Douglas Christopher Wilson
fd35351594 Add mergeParams option to Router
fixes #2153
fixes #2203
2014-07-03 01:13:09 -04:00
Douglas Christopher Wilson
8a15f83d72 Merge tag '3.12.1' 2014-06-26 20:31:51 -04:00
Douglas Christopher Wilson
d91bf81c31 Merge tag '4.4.5' 2014-06-26 20:29:54 -04:00
Douglas Christopher Wilson
fd27f1f4e1 4.4.5 2014-06-26 20:26:43 -04:00
Douglas Christopher Wilson
f0ad557987 deps: cookie-signature@1.0.4 2014-06-26 20:25:20 -04:00
Douglas Christopher Wilson
9bb47fba30 3.12.1 2014-06-26 18:35:54 -04:00
Douglas Christopher Wilson
78d489d730 deps: cookie-signature@1.0.4 2014-06-26 18:26:34 -04:00
Douglas Christopher Wilson
8ffb9f9477 deps: connect@2.21.1 2014-06-26 18:25:48 -04:00
Douglas Christopher Wilson
9cb147370e deps: istanbul@0.2.12 2014-06-26 18:21:26 -04:00
Douglas Christopher Wilson
81036aa639 deps: finalhandler@0.0.2 2014-06-24 21:19:03 -04:00
Douglas Christopher Wilson
e7caaf6757 deps: type-is@~1.3.2 2014-06-24 21:18:12 -04:00
Douglas Christopher Wilson
a525252690 deps: istanbul@0.2.11 2014-06-24 21:15:43 -04:00
Douglas Christopher Wilson
0ebfc6b7bf tests: add test for OPTIONS in app.all 2014-06-23 20:35:44 -04:00
Douglas Christopher Wilson
381b3b0f77 add deprecation message to res.vary() 2014-06-23 16:26:28 -04:00
Douglas Christopher Wilson
ba8a4c5a8d add deprecation message to res.send(body, status) 2014-06-23 16:26:00 -04:00
Douglas Christopher Wilson
2cccbc186e add deprecation message to non-plural req.accepts* 2014-06-23 16:17:44 -04:00
Carlos Souza
83bbf0902d examples: tweak cors example
The PUT method needs clearance from the server, unlike GET, HEAD
and POST, so this demonstrates a case where the method in the
response is important.

closes #2195
2014-06-23 13:37:30 -04:00
Douglas Christopher Wilson
10618ced22 Reduce try-catch de-op area in param matching 2014-06-22 12:02:41 -04:00
Douglas Christopher Wilson
7f26cfca91 Fix handling when route.all is only route 2014-06-22 12:02:40 -04:00
Douglas Christopher Wilson
b89a597029 Restore req.params after invoking Router
fixes #2163
2014-06-22 12:02:36 -04:00
Guy Ellis Monster
746044b6c2 Replace __defineGetter__ with Object.defineProperty
closes #2162
2014-06-22 12:02:33 -04:00
Douglas Christopher Wilson
bdfd288eec fix behavior when handling request without routes
fixes #2159
2014-06-22 12:02:32 -04:00
Douglas Christopher Wilson
7e01531e50 use finalhandler for final response handling 2014-06-22 12:01:55 -04:00
Douglas Christopher Wilson
3ffceff3ed Merge tag '3.12.0' 2014-06-22 11:42:15 -04:00
Douglas Christopher Wilson
75422c16bf 3.12.0 2014-06-21 21:57:02 -04:00
Douglas Christopher Wilson
e66667e465 Use media-typer to alter content-type charset 2014-06-21 21:09:12 -04:00
Douglas Christopher Wilson
7d6208e0af deps: connect@2.21.0 2014-06-21 21:09:10 -04:00
Douglas Christopher Wilson
f498b660da 4.4.4 2014-06-20 16:56:51 -04:00
刘星
e606d99dc8 Fix res.attachment Unicode filenames in Safari
closes #2188
2014-06-20 16:55:05 -04:00
Douglas Christopher Wilson
6aba1b4c49 deps: accepts@~1.0.5 2014-06-20 16:44:50 -04:00
Douglas Christopher Wilson
6ee9433f29 deps: update example dependencies 2014-06-20 16:43:04 -04:00
Douglas Christopher Wilson
ffe663aedf deps: buffer-crc32@0.2.3 2014-06-20 00:35:38 -04:00
Douglas Christopher Wilson
2a105df9f2 3.11.0 2014-06-19 23:36:00 -04:00
Douglas Christopher Wilson
9c731f1883 deprecate things with depd module 2014-06-19 23:34:58 -04:00
Douglas Christopher Wilson
5a4e9125de deps: connect@2.20.2 2014-06-19 23:24:07 -04:00
Douglas Christopher Wilson
9db1367c2d deps: buffer-crc32@0.2.3 2014-06-19 22:55:21 -04:00
Douglas Christopher Wilson
8258ce14f4 fix "trim prefix" debug message in express:router
closes #2177
2014-06-14 12:57:32 -04:00
48 changed files with 2399 additions and 698 deletions

View File

@@ -1,3 +1,151 @@
4.7.3 / 2014-08-04
==================
* deps: send@0.7.3
- Fix incorrect 403 on Windows and Node.js 0.11
* deps: serve-static@~1.4.3
- Fix incorrect 403 on Windows and Node.js 0.11
- deps: send@0.7.3
4.7.2 / 2014-07-27
==================
* deps: depd@0.4.4
- Work-around v8 generating empty stack traces
* deps: send@0.7.2
- deps: depd@0.4.4
* deps: serve-static@~1.4.2
4.7.1 / 2014-07-26
==================
* deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low
* deps: send@0.7.1
- deps: depd@0.4.3
* deps: serve-static@~1.4.1
4.7.0 / 2014-07-25
==================
* fix `req.protocol` for proxy-direct connections
* configurable query parser with `app.set('query parser', parser)`
- `app.set('query parser', 'extended')` parse with "qs" module
- `app.set('query parser', 'simple')` parse with "querystring" core module
- `app.set('query parser', false)` disable query string parsing
- `app.set('query parser', true)` enable simple parsing
* deprecate `res.json(status, obj)` -- use `res.status(status).json(obj)` instead
* deprecate `res.jsonp(status, obj)` -- use `res.status(status).jsonp(obj)` instead
* deprecate `res.send(status, body)` -- use `res.status(status).send(body)` instead
* deps: debug@1.0.4
* deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable
- Remove non-standard grey color from color output
- Support `--no-deprecation` argument
- Support `--trace-deprecation` argument
* deps: finalhandler@0.1.0
- Respond after request fully read
- deps: debug@1.0.4
* deps: parseurl@~1.2.0
- Cache URLs based on original value
- Remove no-longer-needed URL mis-parse work-around
- Simplify the "fast-path" `RegExp`
* deps: send@0.7.0
- Add `dotfiles` option
- Cap `maxAge` value to 1 year
- deps: debug@1.0.4
- deps: depd@0.4.2
* deps: serve-static@~1.4.0
- deps: parseurl@~1.2.0
- deps: send@0.7.0
* perf: prevent multiple `Buffer` creation in `res.send`
4.6.1 / 2014-07-12
==================
* fix `subapp.mountpath` regression for `app.use(subapp)`
4.6.0 / 2014-07-11
==================
* accept multiple callbacks to `app.use()`
* add explicit "Rosetta Flash JSONP abuse" protection
- previous versions are not vulnerable; this is just explicit protection
* catch errors in multiple `req.param(name, fn)` handlers
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
* fix `res.send(status, num)` to send `num` as json (not error)
* remove unnecessary escaping when `res.jsonp` returns JSON response
* support non-string `path` in `app.use(path, fn)`
- supports array of paths
- supports `RegExp`
* router: fix optimization on router exit
* router: refactor location of `try` blocks
* router: speed up standard `app.use(fn)`
* deps: debug@1.0.3
- Add support for multiple wildcards in namespaces
* deps: finalhandler@0.0.3
- deps: debug@1.0.3
* deps: methods@1.1.0
- add `CONNECT`
* deps: parseurl@~1.1.3
- faster parsing of href-only URLs
* deps: path-to-regexp@0.1.3
* deps: send@0.6.0
- deps: debug@1.0.3
* deps: serve-static@~1.3.2
- deps: parseurl@~1.1.3
- deps: send@0.6.0
* perf: fix arguments reassign deopt in some `res` methods
4.5.1 / 2014-07-06
==================
* fix routing regression when altering `req.method`
4.5.0 / 2014-07-04
==================
* add deprecation message to non-plural `req.accepts*`
* add deprecation message to `res.send(body, status)`
* add deprecation message to `res.vary()`
* add `headers` option to `res.sendfile`
- use to set headers on successful file transfer
* add `mergeParams` option to `Router`
- merges `req.params` from parent routes
* add `req.hostname` -- correct name for what `req.host` returns
* deprecate things with `depd` module
* deprecate `req.host` -- use `req.hostname` instead
* fix behavior when handling request without routes
* fix handling when `route.all` is only route
* invoke `router.param()` only when route matches
* restore `req.params` after invoking router
* use `finalhandler` for final response handling
* use `media-typer` to alter content-type charset
* deps: accepts@~1.0.7
* deps: send@0.5.0
- Accept string for `maxage` (converted by `ms`)
- Include link in default redirect response
* deps: serve-static@~1.3.0
- Accept string for `maxAge` (converted by `ms`)
- Add `setHeaders` option
- Include HTML link in redirect response
- deps: send@0.5.0
* deps: type-is@~1.3.2
4.4.5 / 2014-06-26
==================
* deps: cookie-signature@1.0.4
- fix for timing attacks
4.4.4 / 2014-06-20
==================
* fix `res.attachment` Unicode filenames in Safari
* fix "trim prefix" debug message in `express:router`
* deps: accepts@~1.0.5
* deps: buffer-crc32@0.2.3
4.4.3 / 2014-06-11
==================
@@ -188,6 +336,154 @@
- `app.route()` - Proxy to the app's `Router#route()` method to create a new route
- Router & Route - public API
3.15.2 / 2014-07-27
===================
* deps: connect@2.24.2
- deps: body-parser@~1.5.2
- deps: depd@0.4.4
- deps: express-session@~1.7.2
- deps: morgan@~1.2.2
- deps: serve-static@~1.4.2
* deps: depd@0.4.4
- Work-around v8 generating empty stack traces
* deps: send@0.7.2
- deps: depd@0.4.4
3.15.1 / 2014-07-26
===================
* deps: connect@2.24.1
- deps: body-parser@~1.5.1
- deps: depd@0.4.3
- deps: express-session@~1.7.1
- deps: morgan@~1.2.1
- deps: serve-index@~1.1.4
- deps: serve-static@~1.4.1
* deps: depd@0.4.3
- Fix exception when global `Error.stackTraceLimit` is too low
* deps: send@0.7.1
- deps: depd@0.4.3
3.15.0 / 2014-07-22
===================
* Fix `req.protocol` for proxy-direct connections
* Pass options from `res.sendfile` to `send`
* deps: connect@2.24.0
- deps: body-parser@~1.5.0
- deps: compression@~1.0.9
- deps: connect-timeout@~1.2.1
- deps: debug@1.0.4
- deps: depd@0.4.2
- deps: express-session@~1.7.0
- deps: finalhandler@0.1.0
- deps: method-override@~2.1.2
- deps: morgan@~1.2.0
- deps: multiparty@3.3.1
- deps: parseurl@~1.2.0
- deps: serve-static@~1.4.0
* deps: debug@1.0.4
* deps: depd@0.4.2
- Add `TRACE_DEPRECATION` environment variable
- Remove non-standard grey color from color output
- Support `--no-deprecation` argument
- Support `--trace-deprecation` argument
* deps: parseurl@~1.2.0
- Cache URLs based on original value
- Remove no-longer-needed URL mis-parse work-around
- Simplify the "fast-path" `RegExp`
* deps: send@0.7.0
- Add `dotfiles` option
- Cap `maxAge` value to 1 year
- deps: debug@1.0.4
- deps: depd@0.4.2
3.14.0 / 2014-07-11
===================
* add explicit "Rosetta Flash JSONP abuse" protection
- previous versions are not vulnerable; this is just explicit protection
* deprecate `res.redirect(url, status)` -- use `res.redirect(status, url)` instead
* fix `res.send(status, num)` to send `num` as json (not error)
* remove unnecessary escaping when `res.jsonp` returns JSON response
* deps: basic-auth@1.0.0
- support empty password
- support empty username
* deps: connect@2.23.0
- deps: debug@1.0.3
- deps: express-session@~1.6.4
- deps: method-override@~2.1.0
- deps: parseurl@~1.1.3
- deps: serve-static@~1.3.1
* deps: debug@1.0.3
- Add support for multiple wildcards in namespaces
* deps: methods@1.1.0
- add `CONNECT`
* deps: parseurl@~1.1.3
- faster parsing of href-only URLs
3.13.0 / 2014-07-03
===================
* add deprecation message to `app.configure`
* add deprecation message to `req.auth`
* use `basic-auth` to parse `Authorization` header
* deps: connect@2.22.0
- deps: csurf@~1.3.0
- deps: express-session@~1.6.1
- deps: multiparty@3.3.0
- deps: serve-static@~1.3.0
* deps: send@0.5.0
- Accept string for `maxage` (converted by `ms`)
- Include link in default redirect response
3.12.1 / 2014-06-26
===================
* deps: connect@2.21.1
- deps: cookie-parser@1.3.2
- deps: cookie-signature@1.0.4
- deps: express-session@~1.5.2
- deps: type-is@~1.3.2
* deps: cookie-signature@1.0.4
- fix for timing attacks
3.12.0 / 2014-06-21
===================
* use `media-typer` to alter content-type charset
* deps: connect@2.21.0
- deprecate `connect(middleware)` -- use `app.use(middleware)` instead
- deprecate `connect.createServer()` -- use `connect()` instead
- fix `res.setHeader()` patch to work with with get -> append -> set pattern
- deps: compression@~1.0.8
- deps: errorhandler@~1.1.1
- deps: express-session@~1.5.0
- deps: serve-index@~1.1.3
3.11.0 / 2014-06-19
===================
* deprecate things with `depd` module
* deps: buffer-crc32@0.2.3
* deps: connect@2.20.2
- deprecate `verify` option to `json` -- use `body-parser` npm module instead
- deprecate `verify` option to `urlencoded` -- use `body-parser` npm module instead
- deprecate things with `depd` module
- use `finalhandler` for final response handling
- use `media-typer` to parse `content-type` for charset
- deps: body-parser@1.4.3
- deps: connect-timeout@1.1.1
- deps: cookie-parser@1.3.1
- deps: csurf@1.2.2
- deps: errorhandler@1.1.0
- deps: express-session@1.4.0
- deps: multiparty@3.2.9
- deps: serve-index@1.1.2
- deps: type-is@1.3.1
- deps: vhost@2.0.0
3.10.5 / 2014-06-11
===================
@@ -247,7 +543,7 @@
===================
* deps: connect@2.19.1
- deprecate `methodOverride()` -- use `method-override` module directly
- deprecate `methodOverride()` -- use `method-override` npm module instead
- deps: body-parser@1.3.0
- deps: method-override@2.0.1
- deps: multiparty@3.2.8

109
Readme.md
View File

@@ -1,48 +1,58 @@
[![express logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/)
[![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](https://expressjs.com/)
Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
[![NPM version](https://badge.fury.io/js/express.svg)](http://badge.fury.io/js/express)
[![NPM Version](https://badge.fury.io/js/express.svg)](https://badge.fury.io/js/express)
[![Build Status](https://travis-ci.org/visionmedia/express.svg?branch=master)](https://travis-ci.org/visionmedia/express)
[![Coverage Status](https://img.shields.io/coveralls/visionmedia/express.svg)](https://coveralls.io/r/visionmedia/express)
[![Gittip](http://img.shields.io/gittip/visionmedia.svg)](https://www.gittip.com/visionmedia/)
[![Gittip](http://img.shields.io/gittip/dougwilson.svg)](https://www.gittip.com/dougwilson/)
```js
var express = require('express');
var app = express();
var express = require('express')
var app = express()
app.get('/', function(req, res){
res.send('Hello World');
});
app.get('/', function (req, res) {
res.send('Hello World')
})
app.listen(3000);
app.listen(3000)
```
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/visionmedia/express/wiki/New-features-in-4.x).
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/visionmedia/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/visionmedia/express/wiki/New-features-in-4.x).
## Installation
### Installation
$ npm install express
```bash
$ npm install express
```
## Quick Start
The quickest way to get started with express is to utilize the executable [`express(1)`](http://github.com/expressjs/generator) to generate an application as shown below:
Install the executable. The executable's major version will match Express's:
$ npm install -g express-generator@3
The quickest way to get started with express is to utilize the executable [`express(1)`](https://github.com/expressjs/generator) to generate an application as shown below:
Create the app:
Install the executable. The executable's major version will match Express's:
$ express /tmp/foo && cd /tmp/foo
```bash
$ npm install -g express-generator@4
```
Install dependencies:
Create the app:
$ npm install
```bash
$ express /tmp/foo && cd /tmp/foo
```
Start the server:
Install dependencies:
$ npm start
```bash
$ npm install
```
Start the server:
```bash
$ npm start
```
## Features
@@ -61,53 +71,58 @@ app.listen(3000);
HTTP APIs.
Express does not force you to use any specific ORM or template engine. With support for over
14 template engines via [Consolidate.js](http://github.com/visionmedia/consolidate.js),
14 template engines via [Consolidate.js](https://github.com/visionmedia/consolidate.js),
you can quickly craft your perfect framework.
## More Information
* [Website and Documentation](http://expressjs.com/) stored at [visionmedia/expressjs.com](https://github.com/visionmedia/expressjs.com)
* Join #express on freenode
* [Google Group](http://groups.google.com/group/express-js) for discussion
* Follow [tjholowaychuk](http://twitter.com/tjholowaychuk) and [defunctzombie](https://twitter.com/defunctzombie) on twitter for updates
* Visit the [Wiki](http://github.com/visionmedia/express/wiki)
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/visionmedia/expressjs.com)]
* [Github Organization](https://github.com/expressjs) for Official Middleware & Modules
* [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
* Visit the [Wiki](https://github.com/visionmedia/express/wiki)
* [Google Group](https://groups.google.com/group/express-js) for discussion
* [Русскоязычная документация](http://jsman.ru/express/)
* [한국어 문서](http://expressjs.kr) - [[website repo](https://github.com/Hanul/expressjs.kr)]
* Run express examples [online](https://runnable.com/express)
## Viewing Examples
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
Clone the Express repo, then install the dev dependencies to install all the example / test suite dependencies:
$ git clone git://github.com/visionmedia/express.git --depth 1
$ cd express
$ npm install
```bash
$ git clone git://github.com/visionmedia/express.git --depth 1
$ cd express
$ npm install
```
Then run whichever tests you want:
Then run whichever example you want:
$ node examples/content-negotiation
You can also view live examples here:
You can also view live examples here:
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
<a href="https://runnable.com/express" target="_blank"><img src="https://runnable.com/external/styles/assets/runnablebtn.png" style="width:67px;height:25px;"></a>
## Running Tests
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
To run the test suite, first invoke the following command within the repo, installing the development dependencies:
$ npm install
```bash
$ npm install
```
Then run the tests:
Then run the tests:
```sh
```bash
$ npm test
```
## Contributors
Author: [TJ Holowaychuk](http://github.com/visionmedia)
Lead Maintainer: [Roman Shtylman](https://github.com/defunctzombie)
Contributors: https://github.com/visionmedia/express/graphs/contributors
### Contributors
## License
* Author: [TJ Holowaychuk](https://github.com/visionmedia)
* Lead Maintainer: [Douglas Christopher Wilson](https://github.com/dougwilson)
* [All Contributors](https://github.com/visionmedia/express/graphs/contributors)
MIT
### License
[MIT](LICENSE)

View File

@@ -5,7 +5,6 @@
var express = require('../..');
var hash = require('./pass').hash;
var bodyParser = require('body-parser');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = module.exports = express();
@@ -17,9 +16,12 @@ app.set('views', __dirname + '/views');
// middleware
app.use(bodyParser());
app.use(cookieParser('shhhh, very secret'));
app.use(session());
app.use(bodyParser.urlencoded({ extended: false }));
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'shhhh, very secret'
}));
// Session-persisted message middleware

View File

@@ -3,15 +3,11 @@
*/
var express = require('../../');
var cookie-parser = require('cookie-parser');
var app = module.exports = express();
// pass a secret to cookieParser() for signed cookies
app.use(cookieParser('manny is cool'));
// add req.session cookie support
app.use(cookieSession());
app.use(cookieSession({ secret: 'manny is cool' }));
// do something with the session
app.use(count);

View File

@@ -17,8 +17,8 @@ if ('test' != process.env.NODE_ENV) app.use(logger(':method :url'));
// for signing the cookies.
app.use(cookieParser('my secret here'));
// parses json, x-www-form-urlencoded, and multipart/form-data
app.use(bodyParser());
// parses x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }));
app.get('/', function(req, res){
if (req.cookies.remember) {

View File

@@ -15,7 +15,7 @@ app.use(express.static(__dirname + '/public'));
// api middleware
api.use(logger('dev'));
api.use(bodyParser());
api.use(bodyParser.json());
/**
* CORS support.
@@ -25,7 +25,7 @@ api.all('*', function(req, res, next){
if (!req.get('Origin')) return next();
// use "*" here to accept any origin
res.set('Access-Control-Allow-Origin', 'http://localhost:3000');
res.set('Access-Control-Allow-Methods', 'GET, POST');
res.set('Access-Control-Allow-Methods', 'PUT');
res.set('Access-Control-Allow-Headers', 'X-Requested-With, Content-Type');
// res.set('Access-Control-Allow-Max-Age', 3600);
if ('OPTIONS' == req.method) return res.send(200);
@@ -33,12 +33,12 @@ api.all('*', function(req, res, next){
});
/**
* POST a user.
* PUT an existing user.
*/
api.post('/user', function(req, res){
api.put('/user/:id', function(req, res){
console.log(req.body);
res.send(201);
res.send(204);
});
app.listen(3000);

View File

@@ -3,10 +3,10 @@
<body>
<script>
var req = new XMLHttpRequest;
req.open('POST', 'http://localhost:3001/user', false);
req.open('PUT', 'http://localhost:3001/user/1', false);
req.setRequestHeader('Content-Type', 'application/json');
req.send('{"name":"tobi","species":"ferret"}');
console.log(req.responseText);
</script>
</body>
</html>
</html>

View File

@@ -10,6 +10,7 @@ app.get('/', function(req, res){
+ '<li>Download <a href="/files/amazing.txt">amazing.txt</a>.</li>'
+ '<li>Download <a href="/files/utf-8 한中日.txt">utf-8 한中日.txt</a>.</li>'
+ '<li>Download <a href="/files/missing.txt">missing.txt</a>.</li>'
+ '<li>Download <a href="/files/CCTV大赛上海分赛区.txt">CCTV大赛上海分赛区.txt</a>.</li>'
+ '</ul>');
});

View File

@@ -0,0 +1,2 @@
Only for test.
The file name is faked.

View File

@@ -5,7 +5,6 @@
var express = require('../..');
var logger = require('morgan');
var session = require('express-session');
var cookieParser = require('cookie-parser');
var bodyParser = require('body-parser');
var methodOverride = require('method-override');
@@ -38,11 +37,14 @@ if (!module.parent) app.use(logger('dev'));
app.use(express.static(__dirname + '/public'));
// session support
app.use(cookieParser('some secret here'));
app.use(session());
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'some secret here'
}));
// parse request bodies (req.body)
app.use(bodyParser());
app.use(bodyParser.urlencoded({ extended: true }));
// allow overriding methods in query (?_method=put)
app.use(methodOverride('_method'));

View File

@@ -18,7 +18,7 @@ app.set('views', __dirname + '/views');
app.use(logger('dev'));
app.use(methodOverride('_method'));
app.use(cookieParser());
app.use(bodyParser());
app.use(bodyParser.urlencoded({ extended: true }));
app.use(express.static(__dirname + '/public'));
// General

View File

@@ -3,18 +3,16 @@
// $ redis-server
var express = require('../..');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var app = express();
// Required by session() middleware
// pass the secret for signed cookies
// (required by session())
app.use(cookieParser('keyboard cat'));
// Populates req.session
app.use(session());
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'keyboard cat'
}));
app.get('/', function(req, res){
var body = '';

View File

@@ -4,7 +4,6 @@
var express = require('../..');
var logger = require('morgan');
var cookieParser = require('cookie-parser');
var session = require('express-session');
// pass the express to the connect redis module
@@ -15,13 +14,13 @@ var app = express();
app.use(logger('dev'));
// Required by session() middleware
// pass the secret for signed cookies
// (required by session())
app.use(cookieParser('keyboard cat'));
// Populates req.session
app.use(session({ store: new RedisStore }));
app.use(session({
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
secret: 'keyboard cat',
store: new RedisStore
}));
app.get('/', function(req, res){
var body = '';

View File

@@ -2,8 +2,8 @@
* Module dependencies.
*/
var finalhandler = require('finalhandler');
var mixin = require('utils-merge');
var escapeHtml = require('escape-html');
var Router = require('./router');
var methods = require('methods');
var middleware = require('./middleware/init');
@@ -12,8 +12,9 @@ var debug = require('debug')('express:application');
var View = require('./view');
var http = require('http');
var compileETag = require('./utils').compileETag;
var compileQueryParser = require('./utils').compileQueryParser;
var compileTrust = require('./utils').compileTrust;
var deprecate = require('./utils').deprecate;
var deprecate = require('depd')('express');
var resolve = require('path').resolve;
/**
@@ -51,6 +52,7 @@ app.defaultConfiguration = function(){
this.set('etag', 'weak');
var env = process.env.NODE_ENV || 'development';
this.set('env', env);
this.set('query parser', 'extended');
this.set('subdomain offset', 2);
this.set('trust proxy', false);
@@ -104,7 +106,7 @@ app.lazyrouter = function() {
strict: this.enabled('strict routing')
});
this._router.use(query());
this._router.use(query(this.get('query parser fn')));
this._router.use(middleware.init(this));
}
};
@@ -119,45 +121,22 @@ app.lazyrouter = function() {
*/
app.handle = function(req, res, done) {
var env = this.get('env');
var router = this._router;
this._router.handle(req, res, function(err) {
if (done) {
return done(err);
}
// unhandled error
if (err) {
// default to 500
if (res.statusCode < 400) res.statusCode = 500;
debug('default %s', res.statusCode);
// respect err.status
if (err.status) res.statusCode = err.status;
// production gets a basic error message
var msg = 'production' == env
? http.STATUS_CODES[res.statusCode]
: err.stack || err.toString();
msg = escapeHtml(msg);
// log to stderr in a non-test env
if ('test' != env) console.error(err.stack || err.toString());
if (res.headersSent) return req.socket.destroy();
res.setHeader('Content-Type', 'text/html');
res.setHeader('Content-Length', Buffer.byteLength(msg));
if ('HEAD' == req.method) return res.end();
res.end(msg);
return;
}
// 404
debug('default 404');
res.statusCode = 404;
res.setHeader('Content-Type', 'text/html');
if ('HEAD' == req.method) return res.end();
res.end('Cannot ' + escapeHtml(req.method) + ' ' + escapeHtml(req.originalUrl) + '\n');
// final handler
done = done || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
// no routes
if (!router) {
debug('no routes defined on app');
done();
return;
}
router.handle(req, res, done);
};
/**
@@ -170,38 +149,49 @@ app.handle = function(req, res, done) {
* @api public
*/
app.use = function(route, fn){
app.use = function use(path, fn) {
var mount_app;
var mount_path;
// default route to '/'
if ('string' != typeof route) fn = route, route = '/';
// check for .use(path, app) or .use(app) signature
if (arguments.length <= 2) {
mount_path = typeof path === 'string'
? path
: '/';
mount_app = typeof path === 'function'
? path
: fn;
}
// setup router
this.lazyrouter();
var router = this._router;
// express app
if (fn.handle && fn.set) mount_app = fn;
if (mount_app && mount_app.handle && mount_app.set) {
debug('.use app under %s', mount_path);
mount_app.mountpath = mount_path;
mount_app.parent = this;
// restore .app property on req and res
if (mount_app) {
debug('.use app under %s', route);
mount_app.mountpath = route;
fn = function(req, res, next) {
// restore .app property on req and res
router.use(mount_path, function mounted_app(req, res, next) {
var orig = req.app;
mount_app.handle(req, res, function(err) {
req.__proto__ = orig.request;
res.__proto__ = orig.response;
next(err);
});
};
}
});
this.lazyrouter();
this._router.use(route, fn);
// mounted an app
if (mount_app) {
mount_app.parent = this;
// mounted an app
mount_app.emit('mount', this);
return this;
}
// pass-through use
router.use.apply(router, arguments);
return this;
};
@@ -318,6 +308,10 @@ app.set = function(setting, val){
debug('compile etag %s', val);
this.set('etag fn', compileETag(val));
break;
case 'query parser':
debug('compile query parser %s', val);
this.set('query parser fn', compileQueryParser(val));
break;
case 'trust proxy':
debug('compile trust proxy %s', val);
this.set('trust proxy fn', compileTrust(val));
@@ -449,7 +443,7 @@ app.all = function(path){
// del -> delete alias
app.del = deprecate(app.delete, 'app.del: Use app.delete instead');
app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
/**
* Render the given view `name` name with `options`
@@ -547,3 +541,14 @@ app.listen = function(){
var server = http.createServer(this);
return server.listen.apply(server, arguments);
};
/**
* Log error using console.error.
*
* @param {Error} err
* @api public
*/
function logerror(err){
if (this.get('env') !== 'test') console.error(err.stack || err.toString());
}

View File

@@ -2,36 +2,27 @@
* Module dependencies.
*/
var qs = require('qs');
var parseUrl = require('parseurl');
var qs = require('qs');
/**
* Query:
*
* Automatically parse the query-string when available,
* populating the `req.query` object using
* [qs](https://github.com/visionmedia/node-querystring).
*
* Examples:
*
* .use(connect.query())
* .use(function(req, res){
* res.end(JSON.stringify(req.query));
* });
*
* The `options` passed are provided to qs.parse function.
*
* @param {Object} options
* @return {Function}
* @api public
*/
module.exports = function query(options){
module.exports = function query(options) {
var queryparse = qs.parse;
if (typeof options === 'function') {
queryparse = options;
options = undefined;
}
return function query(req, res, next){
if (!req.query) {
req.query = ~req.url.indexOf('?')
? qs.parse(parseUrl(req).query, options)
: {};
var val = parseUrl(req).query;
req.query = queryparse(val, options);
}
next();

View File

@@ -3,6 +3,7 @@
*/
var accepts = require('accepts');
var deprecate = require('depd')('express');
var typeis = require('type-is');
var http = require('http');
var fresh = require('fresh');
@@ -106,53 +107,55 @@ req.accepts = function(){
};
/**
* Check if the given `encoding` is accepted.
* Check if the given `encoding`s are accepted.
*
* @param {String} encoding
* @param {String} ...encoding
* @return {Boolean}
* @api public
*/
req.acceptsEncoding = // backwards compatibility
req.acceptsEncodings = function(){
var accept = accepts(this);
return accept.encodings.apply(accept, arguments);
};
req.acceptsEncoding = deprecate.function(req.acceptsEncodings,
'req.acceptsEncoding: Use acceptsEncodings instead');
/**
* To do: update docs.
*
* Check if the given `charset` is acceptable,
* Check if the given `charset`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} charset
* @param {String} ...charset
* @return {Boolean}
* @api public
*/
req.acceptsCharset = // backwards compatibility
req.acceptsCharsets = function(){
var accept = accepts(this);
return accept.charsets.apply(accept, arguments);
};
req.acceptsCharset = deprecate.function(req.acceptsCharsets,
'req.acceptsCharset: Use acceptsCharsets instead');
/**
* To do: update docs.
*
* Check if the given `lang` is acceptable,
* Check if the given `lang`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
*
* @param {String} lang
* @param {String} ...lang
* @return {Boolean}
* @api public
*/
req.acceptsLanguage = // backwards compatibility
req.acceptsLanguages = function(){
var accept = accepts(this);
return accept.languages.apply(accept, arguments);
};
req.acceptsLanguage = deprecate.function(req.acceptsLanguages,
'req.acceptsLanguage: Use acceptsLanguages instead');
/**
* Parse Range header field,
* capping to the given `size`.
@@ -241,7 +244,9 @@ req.is = function(types){
* Return the protocol string "http" or "https"
* when requested with TLS. When the "trust proxy"
* setting trusts the socket address, the
* "X-Forwarded-Proto" header field will be trusted.
* "X-Forwarded-Proto" header field will be trusted
* and used if present.
*
* If you're running behind a reverse proxy that
* supplies https for you this may be enabled.
*
@@ -249,18 +254,19 @@ req.is = function(types){
* @api public
*/
req.__defineGetter__('protocol', function(){
defineGetter(req, 'protocol', function protocol(){
var proto = this.connection.encrypted
? 'https'
: 'http';
var trust = this.app.get('trust proxy fn');
if (!trust(this.connection.remoteAddress)) {
return this.connection.encrypted
? 'https'
: 'http';
return proto;
}
// Note: X-Forwarded-Proto is normally only ever a
// single value, but this is to be safe.
var proto = this.get('X-Forwarded-Proto') || 'http';
proto = this.get('X-Forwarded-Proto') || proto;
return proto.split(/\s*,\s*/)[0];
});
@@ -273,7 +279,7 @@ req.__defineGetter__('protocol', function(){
* @api public
*/
req.__defineGetter__('secure', function(){
defineGetter(req, 'secure', function secure(){
return 'https' == this.protocol;
});
@@ -287,7 +293,7 @@ req.__defineGetter__('secure', function(){
* @api public
*/
req.__defineGetter__('ip', function(){
defineGetter(req, 'ip', function ip(){
var trust = this.app.get('trust proxy fn');
return proxyaddr(this, trust);
});
@@ -304,7 +310,7 @@ req.__defineGetter__('ip', function(){
* @api public
*/
req.__defineGetter__('ips', function(){
defineGetter(req, 'ips', function ips() {
var trust = this.app.get('trust proxy fn');
var addrs = proxyaddr.all(this, trust);
return addrs.slice(1).reverse();
@@ -325,9 +331,9 @@ req.__defineGetter__('ips', function(){
* @api public
*/
req.__defineGetter__('subdomains', function(){
defineGetter(req, 'subdomains', function subdomains() {
var offset = this.app.get('subdomain offset');
return (this.host || '')
return (this.hostname || '')
.split('.')
.reverse()
.slice(offset);
@@ -340,12 +346,12 @@ req.__defineGetter__('subdomains', function(){
* @api public
*/
req.__defineGetter__('path', function(){
defineGetter(req, 'path', function path() {
return parse(this).pathname;
});
/**
* Parse the "Host" header field hostname.
* Parse the "Host" header field to a hostname.
*
* When the "trust proxy" setting trusts the socket
* address, the "X-Forwarded-Host" header field will
@@ -355,7 +361,7 @@ req.__defineGetter__('path', function(){
* @api public
*/
req.__defineGetter__('host', function(){
defineGetter(req, 'hostname', function hostname(){
var trust = this.app.get('trust proxy fn');
var host = this.get('X-Forwarded-Host');
@@ -376,6 +382,12 @@ req.__defineGetter__('host', function(){
: host;
});
// TODO: change req.host to return host in next major
defineGetter(req, 'host', deprecate.function(function host(){
return this.hostname;
}, 'req.host: Use req.hostname instead'));
/**
* Check if the request is fresh, aka
* Last-Modified and/or the ETag
@@ -385,7 +397,7 @@ req.__defineGetter__('host', function(){
* @api public
*/
req.__defineGetter__('fresh', function(){
defineGetter(req, 'fresh', function(){
var method = this.method;
var s = this.res.statusCode;
@@ -409,7 +421,7 @@ req.__defineGetter__('fresh', function(){
* @api public
*/
req.__defineGetter__('stale', function(){
defineGetter(req, 'stale', function stale(){
return !this.fresh;
});
@@ -420,7 +432,23 @@ req.__defineGetter__('stale', function(){
* @api public
*/
req.__defineGetter__('xhr', function(){
defineGetter(req, 'xhr', function xhr(){
var val = this.get('X-Requested-With') || '';
return 'xmlhttprequest' == val.toLowerCase();
});
/**
* Helper function for creating a getter on an object.
*
* @param {Object} obj
* @param {String} name
* @param {Function} getter
* @api private
*/
function defineGetter(obj, name, getter) {
Object.defineProperty(obj, name, {
configurable: true,
enumerable: true,
get: getter
});
};

View File

@@ -2,6 +2,7 @@
* Module dependencies.
*/
var deprecate = require('depd')('express');
var escapeHtml = require('escape-html');
var http = require('http');
var path = require('path');
@@ -11,7 +12,6 @@ var normalizeType = require('./utils').normalizeType;
var normalizeTypes = require('./utils').normalizeTypes;
var setCharset = require('./utils').setCharset;
var contentDisposition = require('./utils').contentDisposition;
var deprecate = require('./utils').deprecate;
var statusCodes = http.STATUS_CODES;
var cookie = require('cookie');
var send = require('send');
@@ -72,80 +72,99 @@ res.links = function(links){
* res.send(new Buffer('wahoo'));
* res.send({ some: 'json' });
* res.send('<p>some html</p>');
* res.send(404, 'Sorry, cant find that');
* res.send(404);
*
* @param {string|number|boolean|object|Buffer} body
* @api public
*/
res.send = function(body){
var req = this.req;
var head = 'HEAD' == req.method;
var type;
res.send = function send(body) {
var chunk = body;
var encoding;
var len;
var req = this.req;
var type;
// settings
var app = this.app;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.send(body, status) backwards compat
if ('number' != typeof body && 'number' == typeof arguments[1]) {
if (typeof arguments[0] !== 'number' && typeof arguments[1] === 'number') {
deprecate('res.send(body, status): Use res.status(status).send(body) instead');
this.statusCode = arguments[1];
} else {
this.statusCode = body;
body = arguments[1];
deprecate('res.send(status, body): Use res.status(status).send(body) instead');
this.statusCode = arguments[0];
chunk = arguments[1];
}
}
switch (typeof body) {
// response status
case 'number':
this.get('Content-Type') || this.type('txt');
this.statusCode = body;
body = http.STATUS_CODES[body];
break;
// disambiguate res.send(status) and res.send(status, num)
if (typeof chunk === 'number' && arguments.length === 1) {
// res.send(status) will set status message as text string
if (!this.get('Content-Type')) {
this.type('txt');
}
deprecate('res.send(status): Use res.status(status).end() instead');
this.statusCode = chunk;
chunk = http.STATUS_CODES[chunk];
}
switch (typeof chunk) {
// string defaulting to html
case 'string':
if (!this.get('Content-Type')) this.type('html');
if (!this.get('Content-Type')) {
this.type('html');
}
break;
case 'boolean':
case 'number':
case 'object':
if (null == body) {
body = '';
} else if (Buffer.isBuffer(body)) {
this.get('Content-Type') || this.type('bin');
if (chunk === null) {
chunk = '';
} else if (Buffer.isBuffer(chunk)) {
if (!this.get('Content-Type')) {
this.type('bin');
}
} else {
return this.json(body);
return this.json(chunk);
}
break;
}
// write strings in utf-8
if ('string' === typeof body) {
if (typeof chunk === 'string') {
encoding = 'utf8';
type = this.get('Content-Type');
// reflect this in content-type
if ('string' === typeof type) {
if (typeof type === 'string') {
this.set('Content-Type', setCharset(type, 'utf-8'));
}
}
// populate Content-Length
if (undefined !== body && !this.get('Content-Length')) {
len = Buffer.isBuffer(body)
? body.length
: Buffer.byteLength(body, encoding);
if (chunk !== undefined) {
if (!Buffer.isBuffer(chunk)) {
// convert chunk to Buffer; saves later double conversions
chunk = new Buffer(chunk, encoding);
encoding = undefined;
}
len = chunk.length;
this.set('Content-Length', len);
}
// method check
var isHead = req.method === 'HEAD';
// ETag support
var etag = len !== undefined && app.get('etag fn');
if (etag && ('GET' === req.method || 'HEAD' === req.method)) {
if (!this.get('ETag')) {
etag = etag(body, encoding);
if (len !== undefined && (isHead || req.method === 'GET')) {
var etag = app.get('etag fn');
if (etag && !this.get('ETag')) {
etag = etag(chunk, encoding);
etag && this.set('ETag', etag);
}
}
@@ -158,11 +177,16 @@ res.send = function(body){
this.removeHeader('Content-Type');
this.removeHeader('Content-Length');
this.removeHeader('Transfer-Encoding');
body = '';
chunk = '';
}
// skip body for HEAD
if (isHead) {
this.end();
}
// respond
this.end((head ? null : body), encoding);
this.end(chunk, encoding);
return this;
};
@@ -174,24 +198,24 @@ res.send = function(body){
*
* res.json(null);
* res.json({ user: 'tj' });
* res.json(500, 'oh noes!');
* res.json(404, 'I dont have that');
*
* @param {string|number|boolean|object} obj
* @api public
*/
res.json = function(obj){
res.json = function json(obj) {
var val = obj;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
if (typeof arguments[1] === 'number') {
deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonNumDeprecated.call(this, obj)
: jsonDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
@@ -199,20 +223,16 @@ res.json = function(obj){
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = JSON.stringify(obj, replacer, spaces);
var body = JSON.stringify(val, replacer, spaces);
// content-type
this.get('Content-Type') || this.set('Content-Type', 'application/json');
if (!this.get('Content-Type')) {
this.set('Content-Type', 'application/json');
}
return this.send(body);
};
var jsonDeprecated = deprecate(res.json,
'res.json(obj, status): Use res.json(status, obj) instead');
var jsonNumDeprecated = deprecate(res.json,
'res.json(num, status): Use res.status(status).json(num) instead');
/**
* Send JSON response with JSONP callback support.
*
@@ -220,24 +240,24 @@ var jsonNumDeprecated = deprecate(res.json,
*
* res.jsonp(null);
* res.jsonp({ user: 'tj' });
* res.jsonp(500, 'oh noes!');
* res.jsonp(404, 'I dont have that');
*
* @param {string|number|boolean|object} obj
* @api public
*/
res.jsonp = function(obj){
res.jsonp = function jsonp(obj) {
var val = obj;
// allow status / body
if (2 == arguments.length) {
if (arguments.length === 2) {
// res.json(body, status) backwards compat
if ('number' == typeof arguments[1]) {
if (typeof arguments[1] === 'number') {
deprecate('res.jsonp(obj, status): Use res.status(status).json(obj) instead');
this.statusCode = arguments[1];
return 'number' === typeof obj
? jsonpNumDeprecated.call(this, obj)
: jsonpDeprecated.call(this, obj);
} else {
this.statusCode = obj;
obj = arguments[1];
deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
this.statusCode = arguments[0];
val = arguments[1];
}
}
@@ -245,13 +265,14 @@ res.jsonp = function(obj){
var app = this.app;
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
var body = JSON.stringify(obj, replacer, spaces)
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
var body = JSON.stringify(val, replacer, spaces);
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
this.get('Content-Type') || this.set('Content-Type', 'application/json');
if (!this.get('Content-Type')) {
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'application/json');
}
// fixup callback
if (Array.isArray(callback)) {
@@ -259,21 +280,27 @@ res.jsonp = function(obj){
}
// jsonp
if (callback && 'string' === typeof callback) {
if (typeof callback === 'string' && callback.length !== 0) {
this.charset = 'utf-8';
this.set('X-Content-Type-Options', 'nosniff');
this.set('Content-Type', 'text/javascript');
var cb = callback.replace(/[^\[\]\w$.]/g, '');
body = 'typeof ' + cb + ' === \'function\' && ' + cb + '(' + body + ');';
// restrict callback charset
callback = callback.replace(/[^\[\]\w$.]/g, '');
// replace chars not allowed in JavaScript that are in JSON
body = body
.replace(/\u2028/g, '\\u2028')
.replace(/\u2029/g, '\\u2029');
// the /**/ is a specific security mitigation for "Rosetta Flash JSONP abuse"
// the typeof check is just to reduce client error noise
body = '/**/ typeof ' + callback + ' === \'function\' && ' + callback + '(' + body + ');';
}
return this.send(body);
};
var jsonpDeprecated = deprecate(res.json,
'res.jsonp(obj, status): Use res.jsonp(status, obj) instead');
var jsonpNumDeprecated = deprecate(res.json,
'res.jsonp(num, status): Use res.status(status).jsonp(num) instead');
/**
* Transfer the file at the given `path`.
*
@@ -285,9 +312,10 @@ var jsonpNumDeprecated = deprecate(res.json,
*
* Options:
*
* - `maxAge` defaulting to 0
* - `root` root directory for relative filenames
* - `hidden` serve hidden files, defaulting to false
* - `maxAge` defaulting to 0 (can be string converted by `ms`)
* - `root` root directory for relative filenames
* - `headers` object of headers to serve with file
* - `dotfiles` serve dotfiles, defaulting to false; can be `"allow"` to send them
*
* Other options are passed along to `send`.
*
@@ -338,14 +366,10 @@ res.sendfile = function(path, options, fn){
// clean up
cleanup();
if (!self.headersSent) self.removeHeader('Content-Disposition');
// callback available
if (fn) return fn(err);
// list in limbo if there's no callback
if (self.headersSent) return;
// delegate
next(err);
}
@@ -362,14 +386,26 @@ res.sendfile = function(path, options, fn){
req.socket.removeListener('error', error);
}
// Back-compat
options.maxage = options.maxage || options.maxAge || 0;
// transfer
var file = send(req, path, options);
file.on('error', error);
file.on('directory', next);
file.on('stream', stream);
if (options.headers) {
// set headers on successful transfer
file.on('headers', function headers(res) {
var obj = options.headers;
var keys = Object.keys(obj);
for (var i = 0; i < keys.length; i++) {
var k = keys[i];
res.setHeader(k, obj[k]);
}
});
}
// pipe
file.pipe(this);
this.on('finish', cleanup);
};
@@ -395,8 +431,13 @@ res.download = function(path, filename, fn){
}
filename = filename || path;
this.set('Content-Disposition', contentDisposition(filename));
return this.sendfile(path, fn);
// set Content-Disposition when file is sent
var headers = {
'Content-Disposition': contentDisposition(filename)
};
return this.sendfile(path, { headers: headers }, fn);
};
/**
@@ -540,8 +581,8 @@ res.attachment = function(filename){
*/
res.set =
res.header = function(field, val){
if (2 == arguments.length) {
res.header = function header(field, val) {
if (arguments.length === 2) {
if (Array.isArray(val)) val = val.map(String);
else val = String(val);
if ('content-type' == field.toLowerCase() && !/;\s*charset\s*=/.test(val)) {
@@ -574,7 +615,7 @@ res.get = function(field){
*
* @param {String} name
* @param {Object} options
* @param {ServerResponse} for chaining
* @return {ServerResponse} for chaining
* @api public
*/
@@ -605,6 +646,7 @@ res.clearCookie = function(name, options){
* @param {String} name
* @param {String|Object} val
* @param {Options} options
* @return {ServerResponse} for chaining
* @api public
*/
@@ -650,6 +692,7 @@ res.cookie = function(name, val, options){
* res.location('../login');
*
* @param {String} url
* @return {ServerResponse} for chaining
* @api public
*/
@@ -677,32 +720,30 @@ res.location = function(url){
* res.redirect('/foo/bar');
* res.redirect('http://example.com');
* res.redirect(301, 'http://example.com');
* res.redirect('http://example.com', 301);
* res.redirect('../login'); // /blog/post/1 -> /blog/login
*
* @param {String} url
* @param {Number} code
* @api public
*/
res.redirect = function(url){
var head = 'HEAD' == this.req.method;
var status = 302;
res.redirect = function redirect(url) {
var address = url;
var body;
var status = 302;
// allow status / url
if (2 == arguments.length) {
if ('number' == typeof url) {
status = url;
url = arguments[1];
if (arguments.length === 2) {
if (typeof arguments[0] === 'number') {
status = arguments[0];
address = arguments[1];
} else {
deprecate('res.redirect(ur, status): Use res.redirect(status, url) instead');
status = arguments[1];
}
}
// Set location header
this.location(url);
url = this.get('Location');
this.location(address);
address = this.get('Location');
// Support text/{plain,html} by default
this.format({
@@ -723,7 +764,12 @@ res.redirect = function(url){
// Respond
this.statusCode = status;
this.set('Content-Length', Buffer.byteLength(body));
this.end(head ? null : body);
if (this.req.method === 'HEAD') {
this.end();
}
this.end(body);
};
/**
@@ -731,14 +777,16 @@ res.redirect = function(url){
* this call is simply ignored.
*
* @param {Array|String} field
* @param {ServerResponse} for chaining
* @return {ServerResponse} for chaining
* @api public
*/
res.vary = function(field){
// checks for back-compat
if (!field) return this;
if (Array.isArray(field) && !field.length) return this;
if (!field || (Array.isArray(field) && !field.length)) {
deprecate('res.vary(): Provide a field name');
return this;
}
vary(this, field);

View File

@@ -5,9 +5,12 @@
var Route = require('./route');
var Layer = require('./layer');
var methods = require('methods');
var mixin = require('utils-merge');
var debug = require('debug')('express:router');
var parseUrl = require('parseurl');
var slice = Array.prototype.slice;
var toString = Object.prototype.toString;
var utils = require('../utils');
/**
* Initialize a new `Router` with the given `options`.
@@ -30,6 +33,7 @@ var proto = module.exports = function(options) {
router.params = {};
router._params = [];
router.caseSensitive = options.caseSensitive;
router.mergeParams = options.mergeParams;
router.strict = options.strict;
router.stack = [];
@@ -113,8 +117,6 @@ proto.handle = function(req, res, done) {
debug('dispatching %s %s', req.method, req.url);
var method = req.method.toLowerCase();
var search = 1 + req.url.indexOf('?');
var pathlength = search ? search - 1 : req.url.length;
var fqdn = 1 + req.url.substr(0, pathlength).indexOf('://');
@@ -132,17 +134,15 @@ proto.handle = function(req, res, done) {
var stack = self.stack;
// manage inter-router variables
var parent = req.next;
var parentParams = req.params;
var parentUrl = req.baseUrl || '';
done = wrap(done, function(old, err) {
req.baseUrl = parentUrl;
req.next = parent;
old(err);
});
done = restore(done, req, 'baseUrl', 'next', 'params');
// setup next layer
req.next = next;
// for options requests, respond with a default if nothing else responds
if (method === 'options') {
if (req.method === 'OPTIONS') {
done = wrap(done, function(old, err) {
if (err || options.length === 0) return old(err);
@@ -151,35 +151,38 @@ proto.handle = function(req, res, done) {
});
}
// setup basic req values
req.baseUrl = parentUrl;
req.originalUrl = req.originalUrl || req.url;
next();
function next(err) {
if (err === 'route') {
err = undefined;
}
var layerError = err === 'route'
? null
: err;
var layer = stack[idx++];
var layerPath;
if (!layer) {
return done(err);
}
if (slashAdded) {
req.url = req.url.substr(1);
slashAdded = false;
}
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
req.originalUrl = req.originalUrl || req.url;
removed = '';
if (removed.length !== 0) {
req.baseUrl = parentUrl;
req.url = protohost + removed + req.url.substr(protohost.length);
removed = '';
}
try {
var path = parseUrl(req).pathname;
if (undefined == path) path = '/';
if (!layer) {
return done(layerError);
}
if (!layer.match(path)) return next(err);
self.match_layer(layer, req, res, function (err, path) {
if (err || path === undefined) {
return next(layerError || err);
}
// route object and not middleware
var route = layer.route;
@@ -187,46 +190,56 @@ proto.handle = function(req, res, done) {
// if final route, then we support options
if (route) {
// we don't run any routes with error first
if (err) {
return next(err);
if (layerError) {
return next(layerError);
}
req.route = route;
var method = req.method;
var has_method = route._handles_method(method);
// we can now dispatch to the route
if (method === 'options' && !route.methods['options']) {
// build up automatic options response
if (!has_method && method === 'OPTIONS') {
options.push.apply(options, route._options());
}
// don't even bother
if (!has_method && method !== 'HEAD') {
return next();
}
// we can now dispatch to the route
req.route = route;
}
// Capture one-time layer values
req.params = layer.params;
layerPath = layer.path;
req.params = self.mergeParams
? mergeParams(layer.params, parentParams)
: layer.params;
var layerPath = layer.path;
// this should be done for the layer
return self.process_params(layer, paramcalled, req, res, function(err) {
self.process_params(layer, paramcalled, req, res, function (err) {
if (err) {
return next(err);
return next(layerError || err);
}
if (route) {
return layer.handle(req, res, next);
return layer.handle_request(req, res, next);
}
trim_prefix();
trim_prefix(layer, layerError, layerPath, path);
});
});
}
} catch (err) {
next(err);
}
function trim_prefix(layer, layerError, layerPath, path) {
var c = path[layerPath.length];
if (c && '/' !== c && '.' !== c) return next(layerError);
function trim_prefix() {
var c = path[layerPath.length];
if (c && '/' != c && '.' != c) return next(err);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
debug('trim prefix (%s) from url %s', removed, req.url);
// Trim off the part of the url that matches the route
// middleware (.use stuff) needs to have the path stripped
if (layerPath.length !== 0) {
debug('trim prefix (%s) from url %s', layerPath, req.url);
removed = layerPath;
req.url = protohost + req.url.substr(protohost.length + removed.length);
@@ -237,34 +250,42 @@ proto.handle = function(req, res, done) {
}
// Setup base URL (no trailing slash)
if (removed.length && removed.substr(-1) === '/') {
req.baseUrl = parentUrl + removed.substring(0, removed.length - 1);
} else {
req.baseUrl = parentUrl + removed;
}
req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
? removed.substring(0, removed.length - 1)
: removed);
}
debug('%s %s : %s', layer.handle.name || 'anonymous', layerPath, req.originalUrl);
var arity = layer.handle.length;
try {
if (err && arity === 4) {
layer.handle(err, req, res, next);
} else if (!err && arity < 4) {
layer.handle(req, res, next);
} else {
next(err);
}
} catch (err) {
next(err);
}
debug('%s %s : %s', layer.name, layerPath, req.originalUrl);
if (layerError) {
layer.handle_error(layerError, req, res, next);
} else {
layer.handle_request(req, res, next);
}
}
};
function wrap(old, fn) {
return function () {
var args = [old].concat(slice.call(arguments));
fn.apply(this, args);
};
/**
* Match request to a layer.
*
* @api private
*/
proto.match_layer = function match_layer(layer, req, res, done) {
var error = null;
var path;
try {
path = parseUrl(req).pathname;
if (!layer.match(path)) {
path = undefined;
}
} catch (err) {
error = err;
}
done(error, path);
};
/**
@@ -334,11 +355,7 @@ proto.process_params = function(layer, called, req, res, done) {
value: paramVal
};
try {
return paramCallback();
} catch (err) {
return done(err);
}
paramCallback();
}
// single param callbacks
@@ -357,7 +374,11 @@ proto.process_params = function(layer, called, req, res, done) {
if (!fn) return param();
fn(req, res, paramCallback, paramVal, key.name);
try {
fn(req, res, paramCallback, paramVal, key.name);
} catch (e) {
paramCallback(e);
}
}
param();
@@ -375,40 +396,47 @@ proto.process_params = function(layer, called, req, res, done) {
* handlers can operate without any code changes regardless of the "prefix"
* pathname.
*
* @param {String|Function} route
* @param {Function} fn
* @return {app} for chaining
* @api public
*/
proto.use = function(route, fn){
// default route to '/'
if ('string' != typeof route) {
fn = route;
route = '/';
}
proto.use = function use(fn) {
var offset = 0;
var path = '/';
var self = this;
// default path to '/'
if (typeof fn !== 'function') {
var type = {}.toString.call(fn);
var msg = 'Router.use() requires callback functions but got a ' + type;
throw new Error(msg);
offset = 1;
path = fn;
}
// strip trailing slash
if ('/' == route[route.length - 1]) {
route = route.slice(0, -1);
var callbacks = utils.flatten(slice.call(arguments, offset));
if (callbacks.length === 0) {
throw new TypeError('Router.use() requires callback function');
}
var layer = new Layer(route, {
sensitive: this.caseSensitive,
strict: this.strict,
end: false
}, fn);
callbacks.forEach(function (fn) {
if (typeof fn !== 'function') {
var type = toString.call(fn);
var msg = 'Router.use() requires callback function but got a ' + type;
throw new TypeError(msg);
}
// add the middleware
debug('use %s %s', route || '/', fn.name || 'anonymous');
// add the middleware
debug('use %s %s', path, fn.name || '<anonymous>');
var layer = new Layer(path, {
sensitive: self.caseSensitive,
strict: false,
end: false
}, fn);
layer.route = undefined;
self.stack.push(layer);
});
this.stack.push(layer);
return this;
};
@@ -444,7 +472,77 @@ proto.route = function(path){
methods.concat('all').forEach(function(method){
proto[method] = function(path){
var route = this.route(path)
route[method].apply(route, [].slice.call(arguments, 1));
route[method].apply(route, slice.call(arguments, 1));
return this;
};
});
// merge params with parent params
function mergeParams(params, parent) {
if (typeof parent !== 'object' || !parent) {
return params;
}
// make copy of parent for base
var obj = mixin({}, parent);
// simple non-numeric merging
if (!(0 in params) || !(0 in parent)) {
return mixin(obj, params);
}
var i = 0;
var o = 0;
// determine numeric gaps
while (i === o || o in parent) {
if (i in params) i++;
if (o in parent) o++;
}
// offset numeric indices in params before merge
for (i--; i >= 0; i--) {
params[i + o] = params[i];
// create holes for the merge when necessary
if (i < o) {
delete params[i];
}
}
return mixin(parent, params);
}
// restore obj props after function
function restore(fn, obj) {
var props = new Array(arguments.length - 2);
var vals = new Array(arguments.length - 2);
for (var i = 0; i < props.length; i++) {
props[i] = arguments[i + 2];
vals[i] = obj[props[i]];
}
return function(err){
// restore vals
for (var i = 0; i < props.length; i++) {
obj[props[i]] = vals[i];
}
return fn.apply(this, arguments);
};
}
// wrap a function
function wrap(old, fn) {
return function proxy() {
var args = new Array(arguments.length + 1);
args[0] = old;
for (var i = 0, len = arguments.length; i < len; i++) {
args[i + 1] = arguments[i];
}
fn.apply(this, args);
};
}

View File

@@ -18,10 +18,67 @@ function Layer(path, options, fn) {
debug('new %s', path);
options = options || {};
this.regexp = pathRegexp(path, this.keys = [], options);
this.handle = fn;
this.name = fn.name || '<anonymous>';
this.params = undefined;
this.path = undefined;
this.regexp = pathRegexp(path, this.keys = [], options);
if (path === '/' && options.end === false) {
this.regexp.fast_slash = true;
}
}
/**
* Handle the error for the layer.
*
* @param {Error} error
* @param {Request} req
* @param {Response} res
* @param {function} next
* @api private
*/
Layer.prototype.handle_error = function handle_error(error, req, res, next) {
var fn = this.handle;
if (fn.length !== 4) {
// not a standard error handler
return next(error);
}
try {
fn(error, req, res, next);
} catch (err) {
next(err);
}
};
/**
* Handle the request for the layer.
*
* @param {Request} req
* @param {Response} res
* @param {function} next
* @api private
*/
Layer.prototype.handle_request = function handle(req, res, next) {
var fn = this.handle;
if (fn.length > 3) {
// not a standard request handler
return next();
}
try {
fn(req, res, next);
} catch (err) {
next(err);
}
};
/**
* Check if this route matches `path`, if so
* populate `.params`.
@@ -31,30 +88,35 @@ function Layer(path, options, fn) {
* @api private
*/
Layer.prototype.match = function(path){
var keys = this.keys;
var params = this.params = {};
Layer.prototype.match = function match(path) {
if (this.regexp.fast_slash) {
// fast path non-ending match for / (everything matches)
this.params = {};
this.path = '';
return true;
}
var m = this.regexp.exec(path);
if (!m) {
this.params = undefined;
this.path = undefined;
return false;
}
// store values
this.params = {};
this.path = m[0];
var keys = this.keys;
var params = this.params;
var n = 0;
var key;
var val;
if (!m) return false;
this.path = m[0];
for (var i = 1, len = m.length; i < len; ++i) {
key = keys[i - 1];
try {
val = 'string' == typeof m[i]
? decodeURIComponent(m[i])
: m[i];
} catch(e) {
var err = new Error("Failed to decode param '" + m[i] + "'");
err.status = 400;
throw err;
}
val = decode_param(m[i]);
if (key) {
params[key.name] = val;
@@ -65,3 +127,25 @@ Layer.prototype.match = function(path){
return true;
};
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @api private
*/
function decode_param(val){
if (typeof val !== 'string') {
return val;
}
try {
return decodeURIComponent(val);
} catch (e) {
var err = new TypeError("Failed to decode param '" + val + "'");
err.status = 400;
throw err;
}
}

61
lib/router/match.js Normal file
View File

@@ -0,0 +1,61 @@
/**
* Module dependencies.
*/
var pathRegexp = require('path-to-regexp');
/**
* Expose `Layer`.
*/
module.exports = Match;
function Match(layer, path, params) {
this.layer = layer;
this.params = {};
this.path = path || '';
if (!params) {
return this;
}
var keys = layer.keys;
var n = 0;
var prop;
var key;
var val;
for (var i = 0; i < params.length; i++) {
key = keys[i];
val = decode_param(params[i]);
prop = key
? key.name
: n++;
this.params[prop] = val;
}
return this;
};
/**
* Decode param value.
*
* @param {string} val
* @return {string}
* @api private
*/
function decode_param(val){
if (typeof val !== 'string') {
return val;
}
try {
return decodeURIComponent(val);
} catch (e) {
var err = new TypeError("Failed to decode param '" + val + "'");
err.status = 400;
throw err;
}
}

View File

@@ -3,6 +3,7 @@
*/
var debug = require('debug')('express:router:route');
var Layer = require('./layer');
var methods = require('methods');
var utils = require('../utils');
@@ -22,12 +23,30 @@ module.exports = Route;
function Route(path) {
debug('new %s', path);
this.path = path;
this.stack = undefined;
this.stack = [];
// route handlers for various http methods
this.methods = {};
}
/**
* @api private
*/
Route.prototype._handles_method = function _handles_method(method) {
if (this.methods._all) {
return true;
}
method = method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
return Boolean(this.methods[method]);
};
/**
* @return {Array} supported HTTP methods
* @api private
@@ -46,28 +65,22 @@ Route.prototype._options = function(){
*/
Route.prototype.dispatch = function(req, res, done){
var self = this;
var method = req.method.toLowerCase();
var idx = 0;
var stack = this.stack;
if (stack.length === 0) {
return done();
}
var method = req.method.toLowerCase();
if (method === 'head' && !this.methods['head']) {
method = 'get';
}
req.route = self;
req.route = this;
// single middleware route case
if (typeof this.stack === 'function') {
this.stack(req, res, done);
return;
}
next();
var stack = self.stack;
if (!stack) {
return done();
}
var idx = 0;
(function next_layer(err) {
function next(err) {
if (err && err === 'route') {
return done();
}
@@ -78,33 +91,15 @@ Route.prototype.dispatch = function(req, res, done){
}
if (layer.method && layer.method !== method) {
return next_layer(err);
return next(err);
}
var arity = layer.handle.length;
if (err) {
if (arity < 4) {
return next_layer(err);
}
try {
layer.handle(err, req, res, next_layer);
} catch (err) {
next_layer(err);
}
return;
layer.handle_error(err, req, res, next);
} else {
layer.handle_request(req, res, next);
}
if (arity > 3) {
return next_layer();
}
try {
layer.handle(req, res, next_layer);
} catch (err) {
next_layer(err);
}
})();
}
};
/**
@@ -145,15 +140,11 @@ Route.prototype.all = function(){
throw new Error(msg);
}
if (!self.stack) {
self.stack = fn;
}
else if (typeof self.stack === 'function') {
self.stack = [{ handle: self.stack }, { handle: fn }];
}
else {
self.stack.push({ handle: fn });
}
var layer = Layer('/', {}, fn);
layer.method = undefined;
self.methods._all = true;
self.stack.push(layer);
});
return self;
@@ -173,18 +164,11 @@ methods.forEach(function(method){
debug('%s %s', method, self.path);
if (!self.methods[method]) {
self.methods[method] = true;
}
var layer = Layer('/', {}, fn);
layer.method = method;
if (!self.stack) {
self.stack = [];
}
else if (typeof self.stack === 'function') {
self.stack = [{ handle: self.stack }];
}
self.stack.push({ method: method, handle: fn });
self.methods[method] = true;
self.stack.push(layer);
});
return self;
};

View File

@@ -6,37 +6,10 @@ var mime = require('send').mime;
var crc32 = require('buffer-crc32');
var crypto = require('crypto');
var basename = require('path').basename;
var deprecate = require('util').deprecate;
var proxyaddr = require('proxy-addr');
/**
* Simple detection of charset parameter in content-type
*/
var charsetRegExp = /;\s*charset\s*=/;
/**
* Deprecate function, like core `util.deprecate`,
* but with NODE_ENV and color support.
*
* @param {Function} fn
* @param {String} msg
* @return {Function}
* @api private
*/
exports.deprecate = function(fn, msg){
if (process.env.NODE_ENV === 'test') return fn;
// prepend module name
msg = 'express: ' + msg;
if (process.stderr.isTTY) {
// colorize
msg = '\x1b[31;1m' + msg + '\x1b[0m';
}
return deprecate(fn, msg);
};
var qs = require('qs');
var querystring = require('querystring');
var typer = require('media-typer');
/**
* Return strong ETag for `body`.
@@ -164,7 +137,7 @@ exports.contentDisposition = function(filename){
filename = basename(filename);
// if filename contains non-ascii characters, add a utf-8 version ala RFC 5987
ret = /[^\040-\176]/.test(filename)
? 'attachment; filename=' + encodeURI(filename) + '; filename*=UTF-8\'\'' + encodeURI(filename)
? 'attachment; filename="' + encodeURI(filename) + '"; filename*=UTF-8\'\'' + encodeURI(filename)
: 'attachment; filename="' + filename + '"';
}
@@ -231,6 +204,41 @@ exports.compileETag = function(val) {
return fn;
}
/**
* Compile "query parser" value to function.
*
* @param {String|Function} val
* @return {Function}
* @api private
*/
exports.compileQueryParser = function compileQueryParser(val) {
var fn;
if (typeof val === 'function') {
return val;
}
switch (val) {
case true:
fn = querystring.parse;
break;
case false:
fn = newObject;
break;
case 'extended':
fn = qs.parse;
break;
case 'simple':
fn = querystring.parse;
break;
default:
throw new TypeError('unknown value for query parser function: ' + val);
}
return fn;
}
/**
* Compile "proxy trust" value to function.
*
@@ -272,21 +280,23 @@ exports.compileTrust = function(val) {
exports.setCharset = function(type, charset){
if (!type || !charset) return type;
var exists = charsetRegExp.test(type);
// parse type
var parsed = typer.parse(type);
// removing existing charset
if (exists) {
var parts = type.split(';');
// set charset
parsed.parameters.charset = charset;
for (var i = 1; i < parts.length; i++) {
if (charsetRegExp.test(';' + parts[i])) {
parts.splice(i, 1);
break;
}
}
type = parts.join(';');
}
return type + '; charset=' + charset;
// format type
return typer.format(parsed);
};
/**
* Return new empty objet.
*
* @return {Object}
* @api private
*/
function newObject() {
return {};
}

View File

@@ -1,7 +1,7 @@
{
"name": "express",
"description": "Sinatra inspired web development framework",
"version": "4.4.3",
"description": "Fast, unopinionated, minimalist web framework",
"version": "4.7.3",
"author": "TJ Holowaychuk <tj@vision-media.ca>",
"contributors": [
"Aaron Heckmann <aaron.heckmann+github@gmail.com>",
@@ -9,7 +9,8 @@
"Douglas Christopher Wilson <doug@somethingdoug.com>",
"Guillermo Rauch <rauchg@gmail.com>",
"Jonathan Ong <me@jongleberry.com>",
"Roman Shtylman <shtylman+expressjs@gmail.com"
"Roman Shtylman <shtylman+expressjs@gmail.com>",
"Young Jae Sim <hanul@hanul.me>"
],
"keywords": [
"express",
@@ -25,43 +26,46 @@
"repository": "visionmedia/express",
"license": "MIT",
"dependencies": {
"accepts": "1.0.3",
"buffer-crc32": "0.2.1",
"debug": "1.0.2",
"accepts": "~1.0.7",
"buffer-crc32": "0.2.3",
"debug": "1.0.4",
"depd": "0.4.4",
"escape-html": "1.0.1",
"methods": "1.0.1",
"parseurl": "1.0.1",
"finalhandler": "0.1.0",
"media-typer": "0.2.0",
"methods": "1.1.0",
"parseurl": "~1.2.0",
"path-to-regexp": "0.1.3",
"proxy-addr": "1.0.1",
"range-parser": "1.0.0",
"send": "0.4.3",
"serve-static": "1.2.3",
"type-is": "1.2.1",
"send": "0.7.3",
"serve-static": "~1.4.3",
"type-is": "~1.3.2",
"vary": "0.1.0",
"cookie": "0.1.2",
"fresh": "0.2.2",
"cookie-signature": "1.0.3",
"cookie-signature": "1.0.4",
"merge-descriptors": "0.0.2",
"utils-merge": "1.0.0",
"qs": "0.6.6",
"path-to-regexp": "0.1.2"
"utils-merge": "1.0.0"
},
"devDependencies": {
"after": "0.8.1",
"istanbul": "0.2.10",
"mocha": "~1.20.1",
"istanbul": "0.3.0",
"mocha": "~1.21.0",
"should": "~4.0.4",
"supertest": "~0.13.0",
"connect-redis": "~2.0.0",
"ejs": "~1.0.0",
"jade": "~1.3.1",
"marked": "0.3.2",
"multiparty": "~3.2.4",
"hjs": "~0.0.6",
"body-parser": "1.3.0",
"cookie-parser": "1.1.0",
"express-session": "1.2.1",
"method-override": "2.0.2",
"morgan": "1.1.1",
"body-parser": "~1.5.2",
"cookie-parser": "~1.3.1",
"express-session": "~1.7.2",
"jade": "~1.5.0",
"method-override": "~2.1.1",
"morgan": "~1.2.2",
"multiparty": "~3.3.1",
"vhost": "2.0.0"
},
"engines": {
@@ -69,7 +73,7 @@
},
"scripts": {
"prepublish": "npm prune",
"test": "mocha --require test/support/env --reporter dot --check-leaks test/ test/acceptance/",
"test": "mocha --require test/support/env --reporter spec --bail --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-travis": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- --require test/support/env --reporter spec --check-leaks test/ test/acceptance/"
}

View File

@@ -1,4 +1,6 @@
var after = require('after');
var should = require('should');
var express = require('../')
, Route = express.Route
, methods = require('methods')
@@ -8,167 +10,182 @@ describe('Route', function(){
describe('.all', function(){
it('should add handler', function(done){
var req = { method: 'GET', url: '/' };
var route = new Route('/foo');
route.all(function(req, res, next) {
assert.equal(req.a, 1);
assert.equal(res.b, 2);
req.called = true;
next();
});
route.dispatch({ a:1, method: 'GET' }, { b:2 }, done);
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.ok;
done();
});
})
it('should handle VERBS', function(done) {
var route = new Route('/foo');
var count = 0;
var route = new Route('/foo');
var cb = after(methods.length, function (err) {
if (err) return done(err);
count.should.equal(methods.length);
done();
});
route.all(function(req, res, next) {
count++;
next();
});
methods.forEach(function testMethod(method) {
route.dispatch({ method: method }, {});
var req = { method: method, url: '/' };
route.dispatch(req, {}, cb);
});
assert.equal(count, methods.length);
done();
})
it('should stack', function(done) {
var req = { count: 0, method: 'GET', url: '/' };
var route = new Route('/foo');
var count = 0;
route.all(function(req, res, next) {
count++;
req.count++;
next();
});
route.all(function(req, res, next) {
count++;
req.count++;
next();
});
route.dispatch({ method: 'GET' }, {}, function(err) {
assert.ifError(err);
count++;
route.dispatch(req, {}, function (err) {
if (err) return done(err);
req.count.should.equal(2);
done();
});
assert.equal(count, 3);
done();
})
})
describe('.VERB', function(){
it('should support .get', function(done){
var req = { method: 'GET', url: '/' };
var route = new Route('');
var count = 0;
route.get(function(req, res, next) {
count++;
req.called = true;
next();
})
route.dispatch({ method: 'GET' }, {});
assert(count);
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.ok;
done();
});
})
it('should limit to just .VERB', function(done){
var req = { method: 'POST', url: '/' };
var route = new Route('');
route.get(function(req, res, next) {
assert(false);
done();
throw new Error('not me!');
})
route.post(function(req, res, next) {
assert(true);
req.called = true;
next();
})
route.dispatch({ method: 'post' }, {});
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.called).be.true;
done();
});
})
it('should allow fallthrough', function(done){
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.get(function(req, res, next) {
order += 'a';
req.order += 'a';
next();
})
route.all(function(req, res, next) {
order += 'b';
req.order += 'b';
next();
});
route.get(function(req, res, next) {
order += 'c';
req.order += 'c';
next();
})
route.dispatch({ method: 'get' }, {});
assert.equal(order, 'abc');
done();
route.dispatch(req, {}, function (err) {
if (err) return done(err);
req.order.should.equal('abc');
done();
});
})
})
describe('errors', function(){
it('should handle errors via arity 4 functions', function(done){
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.all(function(req, res, next){
next(new Error('foobar'));
});
route.all(function(req, res, next){
order += '0';
req.order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
req.order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
route.dispatch(req, {}, function (err) {
should(err).be.ok;
should(err.message).equal('foobar');
req.order.should.equal('a');
done();
});
route.dispatch({ method: 'get' }, {});
})
it('should handle throw', function(done) {
var req = { order: '', method: 'GET', url: '/' };
var route = new Route('');
var order = '';
route.all(function(req, res, next){
throw new Error('foobar');
});
route.all(function(req, res, next){
order += '0';
req.order += '0';
next();
});
route.all(function(err, req, res, next){
order += 'a';
req.order += 'a';
next(err);
});
route.all(function(err, req, res, next){
assert.equal(err.message, 'foobar');
assert.equal(order, 'a');
route.dispatch(req, {}, function (err) {
should(err).be.ok;
should(err.message).equal('foobar');
req.order.should.equal('a');
done();
});
route.dispatch({ method: 'get' }, {});
});
it('should handle throwing inside error handlers', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.get(function(req, res, next){
@@ -180,11 +197,42 @@ describe('Route', function(){
});
route.get(function(err, req, res, next){
assert.equal(err.message, 'oops');
done();
req.message = err.message;
next();
});
route.dispatch({ url: '/', method: 'GET' }, {}, done);
route.dispatch(req, {}, function (err) {
if (err) return done(err);
should(req.message).equal('oops');
done();
});
});
it('should handle throw in .all', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.all(function(req, res, next){
throw new Error('boom!');
});
route.dispatch(req, {}, function(err){
should(err).be.ok;
err.message.should.equal('boom!');
done();
});
});
it('should handle single error handler', function(done) {
var req = { method: 'GET', url: '/' };
var route = new Route('');
route.all(function(err, req, res, next){
// this should not execute
true.should.be.false;
});
route.dispatch(req, {}, done);
});
})
})

View File

@@ -174,6 +174,111 @@ describe('Router', function(){
});
})
describe('FQDN', function () {
it('should not obscure FQDNs', function (done) {
var request = { hit: 0, url: 'http://example.com/foo', method: 'GET' };
var router = new Router();
router.use(function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/foo');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should ignore FQDN in search', function (done) {
var request = { hit: 0, url: '/proxy?url=http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/proxy', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, '/?url=http://example.com/blog/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should adjust FQDN req.url', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 1);
done();
});
});
it('should adjust FQDN req.url with multiple handlers', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use(function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/blog/post/1');
next();
});
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 1);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 2);
done();
});
});
it('should adjust FQDN req.url with multiple routed handlers', function (done) {
var request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' };
var router = new Router();
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 0);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.use('/blog', function (req, res, next) {
assert.equal(req.hit++, 1);
assert.equal(req.url, 'http://example.com/post/1');
next();
});
router.use(function (req, res, next) {
assert.equal(req.hit++, 2);
assert.equal(req.url, 'http://example.com/blog/post/1');
next();
});
router.handle(request, {}, function (err) {
if (err) return done(err);
assert.equal(request.hit, 3);
done();
});
});
})
describe('.all', function() {
it('should support using .all to capture all http verbs', function(done){
var router = new Router();

View File

@@ -22,6 +22,7 @@ describe('cookies', function(){
it('should respond to cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
@@ -37,6 +38,7 @@ describe('cookies', function(){
it('should clear cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
if (err) return done(err)
@@ -53,6 +55,7 @@ describe('cookies', function(){
it('should set a cookie', function(done){
request(app)
.post('/')
.type('urlencoded')
.send({ remember: 1 })
.expect(302, function(err, res){
res.headers.should.have.property('set-cookie')

View File

@@ -33,6 +33,7 @@ describe('mvc', function(){
it('should update the pet', function(done){
request(app)
.put('/pet/3')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ pet: { name: 'Boots' } })
.end(function(err, res){
if (err) return done(err);
@@ -102,6 +103,7 @@ describe('mvc', function(){
it('should update the user', function(done){
request(app)
.put('/user/1')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ user: { name: 'Tobo' }})
.end(function(err, res){
if (err) return done(err);
@@ -116,6 +118,7 @@ describe('mvc', function(){
it('should create a pet for user', function(done){
request(app)
.post('/user/2/pet')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send({ pet: { name: 'Snickers' }})
.expect('Location', '/user/2')
.expect(302, function(err, res){

View File

@@ -34,6 +34,8 @@ describe('HEAD', function(){
.get('/tobi')
.expect(200, function(err, res){
if (err) return done(err);
delete headers.date;
delete res.headers.date;
assert.deepEqual(res.headers, headers);
done();
});

View File

@@ -1,6 +1,7 @@
var express = require('../')
, assert = require('assert');
var assert = require('assert')
var express = require('..')
var request = require('supertest')
describe('app', function(){
it('should inherit from event emitter', function(done){
@@ -8,6 +9,17 @@ describe('app', function(){
app.on('foo', done);
app.emit('foo');
})
it('should be callable', function(){
var app = express();
assert(typeof app, 'function');
})
it('should 404 without routes', function(done){
request(express())
.get('/')
.expect(404, done);
})
})
describe('app.parent', function(){
@@ -27,16 +39,19 @@ describe('app.parent', function(){
describe('app.mountpath', function(){
it('should return the mounted path', function(){
var app = express()
, blog = express()
, blogAdmin = express();
var admin = express();
var app = express();
var blog = express();
var fallback = express();
app.use('/blog', blog);
blog.use('/admin', blogAdmin);
app.use(fallback);
blog.use('/admin', admin);
admin.mountpath.should.equal('/admin');
app.mountpath.should.equal('/');
blog.mountpath.should.equal('/blog');
blogAdmin.mountpath.should.equal('/admin');
fallback.mountpath.should.equal('/');
})
})

View File

@@ -16,6 +16,24 @@ describe('OPTIONS', function(){
.expect('Allow', 'GET,PUT', done);
})
it('should not be affected by app.all', function(done){
var app = express();
app.get('/', function(){});
app.get('/users', function(req, res){});
app.put('/users', function(req, res){});
app.all('/users', function(req, res, next){
res.setHeader('x-hit', '1');
next();
});
request(app)
.options('/users')
.expect('x-hit', '1')
.expect('allow', 'GET,PUT')
.expect(200, 'GET,PUT', done);
})
it('should not respond if the path is not defined', function(done){
var app = express();

View File

@@ -177,6 +177,31 @@ describe('app', function(){
.expect('loki', done);
})
it('should not invoke without route handler', function(done) {
var app = express();
app.param('thing', function(req, res, next, thing) {
req.thing = thing;
next();
});
app.param('user', function(req, res, next, user) {
next(new Error('invalid invokation'));
});
app.post('/:user', function(req, res, next) {
res.send(req.params.user);
});
app.get('/:thing', function(req, res, next) {
res.send(req.thing);
});
request(app)
.get('/bob')
.expect(200, 'bob', done);
})
it('should work with encoded values', function(done){
var app = express();
@@ -212,6 +237,27 @@ describe('app', function(){
.expect(500, done);
})
it('should catch thrown secondary error', function(done){
var app = express();
app.param('id', function(req, res, next, val){
process.nextTick(next);
});
app.param('id', function(req, res, next, id){
throw new Error('err!');
});
app.get('/user/:id', function(req, res){
var id = req.params.id;
res.send('' + id);
});
request(app)
.get('/user/123')
.expect(500, done);
})
it('should defer to next route', function(done){
var app = express();

View File

@@ -1,11 +1,39 @@
var after = require('after');
var express = require('../')
, request = require('supertest')
, assert = require('assert')
, methods = require('methods');
describe('app.router', function(){
describe('methods supported', function(){
it('should restore req.params after leaving router', function(done){
var app = express();
var router = new express.Router();
function handler1(req, res, next){
res.setHeader('x-user-id', req.params.id);
next()
}
function handler2(req, res){
res.send(req.params.id);
}
router.use(function(req, res, next){
res.setHeader('x-router', req.params.id);
next();
});
app.get('/user/:id', handler1, router, handler2);
request(app)
.get('/user/1')
.expect('x-router', 'undefined')
.expect('x-user-id', '1')
.expect(200, '1', done);
})
describe('methods', function(){
methods.concat('del').forEach(function(method){
if (method === 'connect') return;
@@ -31,6 +59,35 @@ describe('app.router', function(){
app[method].bind(app, '/', 3).should.throw(/Number/);
})
});
it('should re-route when method is altered', function (done) {
var app = express();
var cb = after(3, done);
app.use(function (req, res, next) {
if (req.method !== 'POST') return next();
req.method = 'DELETE';
res.setHeader('X-Method-Altered', '1');
next();
});
app.delete('/', function (req, res) {
res.end('deleted everything');
});
request(app)
.get('/')
.expect(404, 'Cannot GET /\n', cb);
request(app)
.delete('/')
.expect(200, 'deleted everything', cb);
request(app)
.post('/')
.expect('X-Method-Altered', '1')
.expect(200, 'deleted everything', cb);
});
})
describe('decode querystring', function(){
@@ -183,6 +240,106 @@ describe('app.router', function(){
})
})
describe('params', function(){
it('should overwrite existing req.params by default', function(done){
var app = express();
var router = new express.Router();
router.get('/:action', function(req, res){
res.send(req.params);
});
app.use('/user/:user', router);
request(app)
.get('/user/1/get')
.expect(200, '{"action":"get"}', done);
})
it('should allow merging existing req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:action', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/:user', router);
request(app)
.get('/user/tj/get')
.expect(200, '[["action","get"],["user","tj"]]', done);
})
it('should use params from router', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:thing', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/:thing', router);
request(app)
.get('/user/tj/get')
.expect(200, '[["thing","get"]]', done);
})
it('should merge numeric indices req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/*.*', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/id:(\\d+)', router);
request(app)
.get('/user/id:10/profile.json')
.expect(200, '[["0","10"],["1","profile"],["2","json"]]', done);
})
it('should merge numeric indices req.params when more in parent', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/*', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/id:(\\d+)/name:(\\w+)', router);
request(app)
.get('/user/id:10/name:tj/profile')
.expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done);
})
it('should ignore invalid incoming req.params', function(done){
var app = express();
var router = new express.Router({ mergeParams: true });
router.get('/:name', function(req, res){
var keys = Object.keys(req.params).sort();
res.send(keys.map(function(k){ return [k, req.params[k]] }));
});
app.use('/user/', function (req, res, next) {
req.params = 3; // wat?
router(req, res, next);
});
request(app)
.get('/user/tj')
.expect(200, '[["name","tj"]]', done);
})
})
describe('trailing slashes', function(){
it('should be optional by default', function(done){
var app = express();
@@ -211,6 +368,46 @@ describe('app.router', function(){
.expect('tj', done);
})
it('should pass-though middleware', function(done){
var app = express();
app.enable('strict routing');
app.use(function (req, res, next) {
res.setHeader('x-middleware', 'true');
next();
});
app.get('/user/', function(req, res){
res.end('tj');
});
request(app)
.get('/user/')
.expect('x-middleware', 'true')
.expect(200, 'tj', done);
})
it('should pass-though mounted middleware', function(done){
var app = express();
app.enable('strict routing');
app.use('/user/', function (req, res, next) {
res.setHeader('x-middleware', 'true');
next();
});
app.get('/user/test/', function(req, res){
res.end('tj');
});
request(app)
.get('/user/test/')
.expect('x-middleware', 'true')
.expect(200, 'tj', done);
})
it('should match no slashes', function(done){
var app = express();
@@ -225,6 +422,48 @@ describe('app.router', function(){
.expect('tj', done);
})
it('should match middleware when omitting the trailing slash', function(done){
var app = express();
app.enable('strict routing');
app.use('/user/', function(req, res){
res.end('tj');
});
request(app)
.get('/user')
.expect(200, 'tj', done);
})
it('should match middleware', function(done){
var app = express();
app.enable('strict routing');
app.use('/user', function(req, res){
res.end('tj');
});
request(app)
.get('/user')
.expect(200, 'tj', done);
})
it('should match middleware when adding the trailing slash', function(done){
var app = express();
app.enable('strict routing');
app.use('/user', function(req, res){
res.end('tj');
});
request(app)
.get('/user/')
.expect(200, 'tj', done);
})
it('should fail when omitting the trailing slash', function(done){
var app = express();
@@ -597,6 +836,32 @@ describe('app.router', function(){
done();
})
})
it('should call handler in same route, if exists', function(done){
var app = express();
function fn1(req, res, next) {
next(new Error('boom!'));
}
function fn2(req, res, next) {
res.send('foo here');
}
function fn3(err, req, res, next) {
res.send('route go ' + err.message);
}
app.get('/foo', fn1, fn2, fn3);
app.use(function (err, req, res, next) {
res.end('error!');
})
request(app)
.get('/foo')
.expect('route go boom!', done)
})
})
it('should allow rewriting of the url', function(done){

View File

@@ -1,6 +1,7 @@
var express = require('../')
, request = require('supertest');
var after = require('after');
var express = require('..');
var request = require('supertest');
describe('app', function(){
it('should emit "mount" when mounted', function(done){
@@ -15,9 +16,9 @@ describe('app', function(){
app.use(blog);
})
it('should reject numbers', function(){
it('should reject missing functions', function(){
var app = express();
app.use.bind(app, 3).should.throw(/Number/);
app.use.bind(app, 3).should.throw(/requires callback function/);
})
describe('.use(app)', function(){
@@ -84,4 +85,181 @@ describe('app', function(){
.expect('success', done);
})
})
describe('.use(middleware)', function(){
it('should accept multiple arguments', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
app.use(fn1, fn2, function fn3(req, res) {
res.setHeader('x-fn-3', 'hit');
res.end();
});
request(app)
.get('/')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should invoke middleware for all requests', function (done) {
var app = express();
var cb = after(3, done);
app.use(function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/')
.expect(200, 'saw GET /', cb);
request(app)
.options('/')
.expect(200, 'saw OPTIONS /', cb);
request(app)
.post('/foo')
.expect(200, 'saw POST /foo', cb);
})
})
describe('.use(path, middleware)', function(){
it('should strip path from req.url', function (done) {
var app = express();
app.use('/foo', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/foo/bar')
.expect(200, 'saw GET /bar', done);
})
it('should accept multiple arguments', function (done) {
var app = express();
function fn1(req, res, next) {
res.setHeader('x-fn-1', 'hit');
next();
}
function fn2(req, res, next) {
res.setHeader('x-fn-2', 'hit');
next();
}
app.use('/foo', fn1, fn2, function fn3(req, res) {
res.setHeader('x-fn-3', 'hit');
res.end();
});
request(app)
.get('/foo')
.expect('x-fn-1', 'hit')
.expect('x-fn-2', 'hit')
.expect('x-fn-3', 'hit')
.expect(200, done);
})
it('should invoke middleware for all requests starting with path', function (done) {
var app = express();
var cb = after(3, done);
app.use('/foo', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.post('/foo')
.expect(200, 'saw POST /', cb);
request(app)
.post('/foo/bar')
.expect(200, 'saw POST /bar', cb);
})
it('should work if path has trailing slash', function (done) {
var app = express();
var cb = after(3, done);
app.use('/foo/', function (req, res) {
res.send('saw ' + req.method + ' ' + req.url);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.post('/foo')
.expect(200, 'saw POST /', cb);
request(app)
.post('/foo/bar')
.expect(200, 'saw POST /bar', cb);
})
it('should support array of paths', function (done) {
var app = express();
var cb = after(3, done);
app.use(['/foo/', '/bar'], function (req, res) {
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.get('/foo')
.expect(200, 'saw GET / through /foo', cb);
request(app)
.get('/bar')
.expect(200, 'saw GET / through /bar', cb);
})
it('should support regexp path', function (done) {
var app = express();
var cb = after(4, done);
app.use(/^\/[a-z]oo/, function (req, res) {
res.send('saw ' + req.method + ' ' + req.url + ' through ' + req.originalUrl);
});
request(app)
.get('/')
.expect(404, cb);
request(app)
.get('/foo')
.expect(200, 'saw GET / through /foo', cb);
request(app)
.get('/zoo/bear')
.expect(200, 'saw GET /bear through /zoo/bear', cb);
request(app)
.get('/get/zoo')
.expect(404, cb);
})
})
})

View File

@@ -9,7 +9,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
@@ -23,7 +23,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)
@@ -36,7 +36,7 @@ describe('req', function(){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no');
});
request(app)

View File

@@ -0,0 +1,49 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsCharsets(type)', function(){
describe('when Accept-Charset is not present', function(){
it('should return true', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
});
request(app)
.get('/')
.expect('yes', done);
})
})
describe('when Accept-Charset is not present', function(){
it('should return true when present', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
});
request(app)
.get('/')
.set('Accept-Charset', 'foo, bar, utf-8')
.expect('yes', done);
})
it('should return false otherwise', function(done){
var app = express();
app.use(function(req, res, next){
res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no');
});
request(app)
.get('/')
.set('Accept-Charset', 'foo, bar')
.expect('no', done);
})
})
})
})

View File

@@ -3,7 +3,7 @@ var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsEncodings', function(){
describe('.acceptsEncoding', function(){
it('should be true if encoding accpeted', function(done){
var app = express();

View File

@@ -0,0 +1,36 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsEncodingss', function(){
it('should be true if encoding accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsEncodings('gzip').should.be.ok;
req.acceptsEncodings('deflate').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
it('should be false if encoding not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsEncodings('bogus').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Encoding', ' gzip, deflate')
.expect(200, done);
})
})
})

View File

@@ -0,0 +1,53 @@
var express = require('../')
, request = require('supertest');
describe('req', function(){
describe('.acceptsLanguages', function(){
it('should be true if language accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguages('en-us').should.be.ok;
req.acceptsLanguages('en').should.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
it('should be false if language not accpeted', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguages('es').should.not.be.ok;
res.end();
});
request(app)
.get('/')
.set('Accept-Language', 'en;q=.5, en-us')
.expect(200, done);
})
describe('when Accept-Language is not present', function(){
it('should always return true', function(done){
var app = express();
app.use(function(req, res){
req.acceptsLanguages('en').should.be.ok;
req.acceptsLanguages('es').should.be.ok;
req.acceptsLanguages('jp').should.be.ok;
res.end();
});
request(app)
.get('/')
.expect(200, done);
})
})
})
})

138
test/req.hostname.js Normal file
View File

@@ -0,0 +1,138 @@
var express = require('../')
, request = require('supertest')
, assert = require('assert');
describe('req', function(){
describe('.hostname', function(){
it('should return the Host when present', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', 'example.com')
.expect('example.com', done);
})
it('should strip port number', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', 'example.com:3000')
.expect('example.com', done);
})
it('should return undefined otherwise', function(done){
var app = express();
app.use(function(req, res){
req.headers.host = null;
res.end(String(req.hostname));
});
request(app)
.post('/')
.expect('undefined', done);
})
it('should work with IPv6 Host', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', '[::1]')
.expect('[::1]', done);
})
it('should work with IPv6 Host and port', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.post('/')
.set('Host', '[::1]:3000')
.expect('[::1]', done);
})
describe('when "trust proxy" is enabled', function(){
it('should respect X-Forwarded-Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com:3000')
.expect('example.com', done);
})
it('should ignore X-Forwarded-Host if socket addr not trusted', function(done){
var app = express();
app.set('trust proxy', '10.0.0.1');
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'example.com')
.expect('localhost', done);
})
it('should default to Host', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'example.com')
.expect('example.com', done);
})
})
describe('when "trust proxy" is disabled', function(){
it('should ignore X-Forwarded-Host', function(done){
var app = express();
app.use(function(req, res){
res.end(req.hostname);
});
request(app)
.get('/')
.set('Host', 'localhost')
.set('X-Forwarded-Host', 'evil')
.expect('localhost', done);
})
})
})
})

View File

@@ -34,7 +34,7 @@ describe('req', function(){
it('should check req.body', function(done){
var app = express();
app.use(bodyParser());
app.use(bodyParser.json());
app.use(function(req, res){
res.end(req.param('name'));

View File

@@ -32,6 +32,21 @@ describe('req', function(){
.expect('https', done);
})
it('should default to the socket addr if X-Forwarded-Proto not present', function(done){
var app = express();
app.enable('trust proxy');
app.use(function(req, res){
req.connection.encrypted = true;
res.end(req.protocol);
});
request(app)
.get('/')
.expect('https', done);
})
it('should ignore X-Forwarded-Proto if socket addr not trusted', function(done){
var app = express();

View File

@@ -5,33 +5,91 @@ var express = require('../')
describe('req', function(){
describe('.query', function(){
it('should default to {}', function(done){
var app = express();
app.use(function(req, res){
req.query.should.eql({});
res.end();
});
var app = createApp();
request(app)
.get('/')
.end(function(res){
done();
});
})
.expect(200, '{}', done);
});
it('should contain the parsed query-string', function(done){
var app = express();
app.use(function(req, res){
req.query.should.eql({ user: { name: 'tj' }});
res.end();
});
it('should default to parse complex keys', function (done) {
var app = createApp();
request(app)
.get('/?user[name]=tj')
.end(function(res){
done();
.expect(200, '{"user":{"name":"tj"}}', done);
});
describe('when "query parser" is extended', function () {
it('should parse complex keys', function (done) {
var app = createApp('extended');
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user":{"name":"tj"}}', done);
});
})
});
describe('when "query parser" is simple', function () {
it('should not parse complex keys', function (done) {
var app = createApp('simple');
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
});
describe('when "query parser" is a function', function () {
it('should parse using function', function (done) {
var app = createApp(function (str) {
return {'length': (str || '').length};
});
request(app)
.get('/?user[name]=tj')
.expect(200, '{"length":17}', done);
});
});
describe('when "query parser" disabled', function () {
it('should not parse query', function (done) {
var app = createApp(false);
request(app)
.get('/?user[name]=tj')
.expect(200, '{}', done);
});
});
describe('when "query parser" disabled', function () {
it('should not parse complex keys', function (done) {
var app = createApp(true);
request(app)
.get('/?user[name]=tj')
.expect(200, '{"user[name]":"tj"}', done);
});
});
describe('when "query parser" an unknown value', function () {
it('should throw', function () {
createApp.bind(null, 'bogus').should.throw(/unknown value.*query parser/);
});
});
})
})
function createApp(setting) {
var app = express();
if (setting !== undefined) {
app.set('query parser', setting);
}
app.use(function (req, res) {
res.send(req.query);
});
return app;
}

View File

@@ -57,7 +57,7 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Disposition', 'attachment;' +
' filename=%E6%97%A5%E6%9C%AC%E8%AA%9E.txt;' +
' filename="%E6%97%A5%E6%9C%AC%E8%AA%9E.txt";' +
' filename*=UTF-8\'\'%E6%97%A5%E6%9C%AC%E8%AA%9E.txt',
done);
})

View File

@@ -14,11 +14,8 @@ describe('res', function(){
request(app)
.get('/?callback=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /something\(\{"count":1\}\);/, done);
})
it('should use first callback parameter with jsonp', function(done){
@@ -29,12 +26,9 @@ describe('res', function(){
});
request(app)
.get('/?callback=something&callback=somethingelse')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
done();
})
.get('/?callback=something&callback=somethingelse')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /something\(\{"count":1\}\);/, done);
})
it('should ignore object callback parameter with jsonp', function(done){
@@ -61,11 +55,8 @@ describe('res', function(){
request(app)
.get('/?clb=something')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof something === \'function\' && something({"count":1});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /something\(\{"count":1\}\);/, done);
})
it('should allow []', function(done){
@@ -77,11 +68,8 @@ describe('res', function(){
request(app)
.get('/?callback=callbacks[123]')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof callbacks[123] === \'function\' && callbacks[123]({"count":1});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /callbacks\[123\]\(\{"count":1\}\);/, done);
})
it('should disallow arbitrary js', function(done){
@@ -93,11 +81,8 @@ describe('res', function(){
request(app)
.get('/?callback=foo;bar()')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof foobar === \'function\' && foobar({});');
done();
})
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /foobar\(\{\}\);/, done);
})
it('should escape utf whitespace', function(done){
@@ -109,13 +94,37 @@ describe('res', function(){
request(app)
.get('/?callback=foo')
.end(function(err, res){
res.headers.should.have.property('content-type', 'text/javascript; charset=utf-8');
res.text.should.equal('typeof foo === \'function\' && foo({"str":"\\u2028 \\u2029 woot"});');
done();
});
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect(200, /foo\(\{"str":"\\u2028 \\u2029 woot"\}\);/, done);
});
it('should not escape utf whitespace for json fallback', function(done){
var app = express();
app.use(function(req, res){
res.jsonp({ str: '\u2028 \u2029 woot' });
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '{"str":"\u2028 \u2029 woot"}', done);
});
it('should include security header and prologue', function (done) {
var app = express();
app.use(function(req, res){
res.jsonp({ count: 1 });
});
request(app)
.get('/?callback=something')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect('X-Content-Type-Options', 'nosniff')
.expect(200, /^\/\*\*\//, done);
})
it('should not override previous Content-Types with no callback', function(done){
var app = express();
@@ -127,7 +136,11 @@ describe('res', function(){
request(app)
.get('/')
.expect('Content-Type', 'application/vnd.example+json; charset=utf-8')
.expect(200, '{"hello":"world"}', done);
.expect(200, '{"hello":"world"}', function (err, res) {
if (err) return done(err);
res.headers.should.not.have.property('x-content-type-options');
done();
});
})
it('should override previous Content-Types with callback', function(done){
@@ -141,6 +154,7 @@ describe('res', function(){
request(app)
.get('/?callback=cb')
.expect('Content-Type', 'text/javascript; charset=utf-8')
.expect('X-Content-Type-Options', 'nosniff')
.expect(200, /cb\(\{"hello":"world"\}\);$/, done);
})

View File

@@ -81,6 +81,21 @@ describe('res', function(){
})
})
describe('.send(code, number)', function(){
it('should send number as json', function(done){
var app = express();
app.use(function(req, res){
res.send(200, 0.123);
});
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
.expect(200, '0.123', done);
})
})
describe('.send(String)', function(){
it('should send as html', function(done){
var app = express();
@@ -497,8 +512,10 @@ describe('res', function(){
var app = express()
app.set('etag', function(body, encoding){
body.should.equal('hello, world!')
encoding.should.equal('utf8')
var chunk = !Buffer.isBuffer(body)
? new Buffer(body, encoding)
: body;
chunk.toString().should.equal('hello, world!')
return '"custom"'
});

View File

@@ -106,7 +106,7 @@ describe('res', function(){
})
describe('.sendfile(path)', function(){
it('should not serve hidden files', function(done){
it('should not serve dotfiles', function(done){
var app = express();
app.use(function(req, res){
@@ -118,11 +118,11 @@ describe('res', function(){
.expect(404, done);
})
it('should accept hidden option', function(done){
it('should accept dotfiles option', function(done){
var app = express();
app.use(function(req, res){
res.sendfile('test/fixtures/.name', { hidden: true });
res.sendfile('test/fixtures/.name', { dotfiles: 'allow' });
});
request(app)
@@ -130,6 +130,41 @@ describe('res', function(){
.expect(200, 'tobi', done);
})
it('should accept headers option', function(done){
var app = express();
var headers = {
'x-success': 'sent',
'x-other': 'done'
};
app.use(function(req, res){
res.sendfile('test/fixtures/user.html', { headers: headers });
});
request(app)
.get('/')
.expect('x-success', 'sent')
.expect('x-other', 'done')
.expect(200, done);
})
it('should ignore headers option on 404', function(done){
var app = express();
var headers = { 'x-success': 'sent' };
app.use(function(req, res){
res.sendfile('test/fixtures/user.nothing', { headers: headers });
});
request(app)
.get('/')
.expect(404, function (err, res) {
if (err) return done(err);
res.headers.should.not.have.property('x-success');
done();
});
})
describe('with an absolute path', function(){
it('should transfer the file', function(done){
var app = express();

View File

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

View File

@@ -2,28 +2,6 @@
var utils = require('../lib/utils')
, assert = require('assert');
describe('utils.deprecate(fn, msg)', function(){
var env
before(function(){
env = process.env.NODE_ENV
})
after(function(){
process.env.NODE_ENV = env
})
it('should pass-through fn in test environment', function(){
var fn = function(){}
process.env.NODE_ENV = 'test'
utils.deprecate(fn).should.equal(fn)
})
it('should return new fn in other environment', function(){
var fn = function(){}
process.env.NODE_ENV = ''
utils.deprecate(fn).should.not.equal(fn)
})
})
describe('utils.etag(body, encoding)', function(){
it('should support strings', function(){
utils.etag('express!')