diff --git a/History.md b/History.md
index 932932d7614..5380daf09c0 100644
--- a/History.md
+++ b/History.md
@@ -1,91 +1,166 @@
-5.0.0 / 2023-12-11
-===================
-
- * deps: removed safe-buffer
- - engine: node >= v18.19.0
- - Use Node Buffer instead of `safe-buffer`
- * deps: path-to-regexp@6.2.1
- - BREAKING CHANGE: upgraded from 0.1.7. Route mapping changes e.g. /*, /foo/ handling /foo, no longer work
- - See https://github.com/pillarjs/path-to-regexp for documentation
- * deps: removed array-flatten
- - Replaced with Array.prototype.flat
- - Removed `utils.flatten`
- * deps: connect-redis@7.1.0
- * deps: marked@11.0.0
- * deps: removed mocha
- - Replaced mocha with Node Test Runner
- - Removed `nyc`
- * Use ES Import for tests
- - Renamed tests with `.mjs` file extension
- - Renamed tests with Pascal Casing
- * Fix routing requests without method
- * deps: body-parser@1.20.2
- - Fix strict json error message on Node.js 19+
- - deps: content-type@~1.0.5
- - deps: raw-body@2.5.2
-
-4.18.2 / 2022-10-08
-===================
-
- * Fix regression routing a large stack in a single route
- * deps: body-parser@1.20.1
- - deps: qs@6.11.0
- - perf: remove unnecessary object clone
- * deps: qs@6.11.0
-
-4.18.1 / 2022-04-29
-===================
-
- * Fix hanging on large stack of sync routes
-
-4.18.0 / 2022-04-25
-===================
-
- * Add "root" option to `res.download`
- * Allow `options` without `filename` in `res.download`
- * Deprecate string and non-integer arguments to `res.status`
- * Fix behavior of `null`/`undefined` as `maxAge` in `res.cookie`
- * Fix handling very large stacks of sync middleware
- * Ignore `Object.prototype` values in settings through `app.set`/`app.get`
- * Invoke `default` with same arguments as types in `res.format`
- * Support proper 205 responses using `res.send`
- * Use `http-errors` for `res.format` error
- * deps: body-parser@1.20.0
- - Fix error message for json parse whitespace in `strict`
- - Fix internal error when inflated body exceeds limit
- - Prevent loss of async hooks context
- - Prevent hanging when request already read
- - deps: depd@2.0.0
- - deps: http-errors@2.0.0
- - deps: on-finished@2.4.1
- - deps: qs@6.10.3
- - deps: raw-body@2.5.1
- * deps: cookie@0.5.0
- - Add `priority` option
- - Fix `expires` option to reject invalid dates
- * deps: depd@2.0.0
- - Replace internal `eval` usage with `Function` constructor
- - Use instance methods on `process` to check for listeners
- * deps: finalhandler@1.2.0
- - Remove set content headers that break response
- - deps: on-finished@2.4.1
- - deps: statuses@2.0.1
- * deps: on-finished@2.4.1
- - Prevent loss of async hooks context
- * deps: qs@6.10.3
- * deps: send@0.18.0
- - Fix emitted 416 error missing headers property
- - Limit the headers removed for 304 response
- - deps: depd@2.0.0
- - deps: destroy@1.2.0
- - deps: http-errors@2.0.0
- - deps: on-finished@2.4.1
- - deps: statuses@2.0.1
- * deps: serve-static@1.15.0
- - deps: send@0.18.0
- * deps: statuses@2.0.1
- - Remove code 306
- - Rename `425 Unordered Collection` to standard `425 Too Early`
+5.x
+===
+
+This incorporates all changes after 4.17.2 up to 4.17.3.
+
+5.0.0-beta.1 / 2022-02-14
+=========================
+
+This is the first Express 5.0 beta release, based off 4.17.2 and includes
+changes from 5.0.0-alpha.8.
+
+ * change:
+ - Default "query parser" setting to `'simple'`
+ - Requires Node.js 4+
+ - Use `mime-types` for file to content type mapping
+ * deps: array-flatten@3.0.0
+ * deps: body-parser@2.0.0-beta.1
+ - `req.body` is no longer always initialized to `{}`
+ - `urlencoded` parser now defaults `extended` to `false`
+ - Use `on-finished` to determine when body read
+ * deps: router@2.0.0-beta.1
+ - Add new `?`, `*`, and `+` parameter modifiers
+ - Internalize private `router.process_params` method
+ - Matching group expressions are only RegExp syntax
+ - Named matching groups no longer available by position in `req.params`
+ - Regular expressions can only be used in a matching group
+ - Remove `debug` dependency
+ - Special `*` path segment behavior removed
+ - deps: array-flatten@3.0.0
+ - deps: parseurl@~1.3.3
+ - deps: path-to-regexp@3.2.0
+ - deps: setprototypeof@1.2.0
+ * deps: send@1.0.0-beta.1
+ - Change `dotfiles` option default to `'ignore'`
+ - Remove `hidden` option; use `dotfiles` option instead
+ - Use `mime-types` for file to content type mapping
+ - deps: debug@3.1.0
+ * deps: serve-static@2.0.0-beta.1
+ - Change `dotfiles` option default to `'ignore'`
+ - Remove `hidden` option; use `dotfiles` option instead
+ - Use `mime-types` for file to content type mapping
+ - deps: send@1.0.0-beta.1
+
+5.0.0-alpha.8 / 2020-03-25
+==========================
+
+This is the eighth Express 5.0 alpha release, based off 4.17.1 and includes
+changes from 5.0.0-alpha.7.
+
+5.0.0-alpha.7 / 2018-10-26
+==========================
+
+This is the seventh Express 5.0 alpha release, based off 4.16.4 and includes
+changes from 5.0.0-alpha.6.
+
+The major change with this alpha is the basic support for returned, rejected
+Promises in the router.
+
+ * remove:
+ - `path-to-regexp` dependency
+ * deps: debug@3.1.0
+ - Add `DEBUG_HIDE_DATE` environment variable
+ - Change timer to per-namespace instead of global
+ - Change non-TTY date format
+ - Remove `DEBUG_FD` environment variable support
+ - Support 256 namespace colors
+ * deps: router@2.0.0-alpha.1
+ - Add basic support for returned, rejected Promises
+ - Fix JSDoc for `Router` constructor
+ - deps: debug@3.1.0
+ - deps: parseurl@~1.3.2
+ - deps: setprototypeof@1.1.0
+ - deps: utils-merge@1.0.1
+
+5.0.0-alpha.6 / 2017-09-24
+==========================
+
+This is the sixth Express 5.0 alpha release, based off 4.15.5 and includes
+changes from 5.0.0-alpha.5.
+
+ * remove:
+ - `res.redirect(url, status)` signature - use `res.redirect(status, url)`
+ - `res.send(status, body)` signature - use `res.status(status).send(body)`
+ * deps: router@~1.3.1
+ - deps: debug@2.6.8
+
+5.0.0-alpha.5 / 2017-03-06
+==========================
+
+This is the fifth Express 5.0 alpha release, based off 4.15.2 and includes
+changes from 5.0.0-alpha.4.
+
+5.0.0-alpha.4 / 2017-03-01
+==========================
+
+This is the fourth Express 5.0 alpha release, based off 4.15.0 and includes
+changes from 5.0.0-alpha.3.
+
+ * remove:
+ - Remove Express 3.x middleware error stubs
+ * deps: router@~1.3.0
+ - Add `next("router")` to exit from router
+ - Fix case where `router.use` skipped requests routes did not
+ - Skip routing when `req.url` is not set
+ - Use `%o` in path debug to tell types apart
+ - deps: debug@2.6.1
+ - deps: setprototypeof@1.0.3
+ - perf: add fast match path for `*` route
+
+5.0.0-alpha.3 / 2017-01-28
+==========================
+
+This is the third Express 5.0 alpha release, based off 4.14.1 and includes
+changes from 5.0.0-alpha.2.
+
+ * remove:
+ - `res.json(status, obj)` signature - use `res.status(status).json(obj)`
+ - `res.jsonp(status, obj)` signature - use `res.status(status).jsonp(obj)`
+ - `res.vary()` (no arguments) -- provide a field name as an argument
+ * deps: array-flatten@2.1.1
+ * deps: path-is-absolute@1.0.1
+ * deps: router@~1.1.5
+ - deps: array-flatten@2.0.1
+ - deps: methods@~1.1.2
+ - deps: parseurl@~1.3.1
+ - deps: setprototypeof@1.0.2
+
+5.0.0-alpha.2 / 2015-07-06
+==========================
+
+This is the second Express 5.0 alpha release, based off 4.13.1 and includes
+changes from 5.0.0-alpha.1.
+
+ * remove:
+ - `app.param(fn)`
+ - `req.param()` -- use `req.params`, `req.body`, or `req.query` instead
+ * change:
+ - `res.render` callback is always async, even for sync view engines
+ - The leading `:` character in `name` for `app.param(name, fn)` is no longer removed
+ - Use `router` module for routing
+ - Use `path-is-absolute` module for absolute path detection
+
+5.0.0-alpha.1 / 2014-11-06
+==========================
+
+This is the first Express 5.0 alpha release, based off 4.10.1.
+
+ * remove:
+ - `app.del` - use `app.delete`
+ - `req.acceptsCharset` - use `req.acceptsCharsets`
+ - `req.acceptsEncoding` - use `req.acceptsEncodings`
+ - `req.acceptsLanguage` - use `req.acceptsLanguages`
+ - `res.json(obj, status)` signature - use `res.json(status, obj)`
+ - `res.jsonp(obj, status)` signature - use `res.jsonp(status, obj)`
+ - `res.send(body, status)` signature - use `res.send(status, body)`
+ - `res.send(status)` signature - use `res.sendStatus(status)`
+ - `res.sendfile` - use `res.sendFile` instead
+ - `express.query` middleware
+ * change:
+ - `req.host` now returns host (`hostname:port`) - use `req.hostname` for only hostname
+ - `req.query` is now a getter instead of a plain property
+ * add:
+ - `app.router` is a reference to the base router
4.17.3 / 2022-02-16
===================
@@ -2137,7 +2212,7 @@
* 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 get -> append -> set pattern
+ - 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
@@ -2967,7 +3042,7 @@ Closes #805
* Added route `Collection`, ex: `app.get('/user/:id').remove();`
* Added support for `app.param(fn)` to define param logic
* Removed `app.param()` support for callback with return value
- * Removed require.main check from express(1) generated app. Closes #670
+ * Removed module.parent check from express(1) generated app. Closes #670
* Refactored router. Closes #639
2.3.6 / 2011-05-20
@@ -3348,8 +3423,8 @@ Shaw]
* Added node v0.1.97 compatibility
* Added support for deleting cookies via Request#cookie('key', null)
* Updated haml submodule
- * Fixed not-found page, now using charset utf-8
- * Fixed show-exceptions page, now using charset utf-8
+ * Fixed not-found page, now using using charset utf-8
+ * Fixed show-exceptions page, now using using charset utf-8
* Fixed view support due to fs.readFile Buffers
* Changed; mime.type() no longer accepts ".type" due to node extname() changes
@@ -3384,7 +3459,7 @@ Shaw]
==================
* Added charset support via Request#charset (automatically assigned to 'UTF-8' when respond()'s
- encoding is set to 'utf8' or 'utf-8').
+ encoding is set to 'utf8' or 'utf-8'.
* Added "encoding" option to Request#render(). Closes #299
* Added "dump exceptions" setting, which is enabled by default.
* Added simple ejs template engine support
@@ -3423,7 +3498,7 @@ Shaw]
* Added [haml.js](http://github.com/visionmedia/haml.js) submodule; removed haml-js
* Added callback function support to Request#halt() as 3rd/4th arg
* Added preprocessing of route param wildcards using param(). Closes #251
- * Added view partial support (with collections etc.)
+ * Added view partial support (with collections etc)
* Fixed bug preventing falsey params (such as ?page=0). Closes #286
* Fixed setting of multiple cookies. Closes #199
* Changed; view naming convention is now NAME.TYPE.ENGINE (for example page.html.haml)
diff --git a/Readme.md b/Readme.md
index d0f3cf56e6d..b60d588c413 100644
--- a/Readme.md
+++ b/Readme.md
@@ -1,10 +1,12 @@
[](http://expressjs.com/)
- Fast, unopinionated, minimalist web framework for [Node.js](http://nodejs.org).
+ Fast, unopinionated, minimalist web framework for [node](http://nodejs.org).
- [![NPM Version][npm-version-image]][npm-url]
- [![NPM Install Size][npm-install-size-image]][npm-install-size-url]
- [![NPM Downloads][npm-downloads-image]][npm-downloads-url]
+ [![NPM Version][npm-image]][npm-url]
+ [![NPM Downloads][downloads-image]][downloads-url]
+ [![Linux Build][ci-image]][ci-url]
+ [![Windows Build][appveyor-image]][appveyor-url]
+ [![Test Coverage][coveralls-image]][coveralls-url]
```js
const express = require('express')
@@ -31,7 +33,7 @@ the [`npm init` command](https://docs.npmjs.com/creating-a-package-json-file).
Installation is done using the
[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally):
-```console
+```bash
$ npm install express
```
@@ -51,7 +53,7 @@ for more information.
## Docs & Community
* [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/expressjs/expressjs.com)]
- * [#express](https://web.libera.chat/#express) on [Libera Chat](https://libera.chat) IRC
+ * [#express](https://webchat.freenode.net/?channels=express) on freenode IRC
* [GitHub Organization](https://github.com/expressjs) for Official Middleware & Modules
* Visit the [Wiki](https://github.com/expressjs/express/wiki)
* [Google Group](https://groups.google.com/group/express-js) for discussion
@@ -59,31 +61,35 @@ for more information.
**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/expressjs/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/expressjs/express/wiki/New-features-in-4.x).
+### Security Issues
+
+If you discover a security vulnerability in Express, please see [Security Policies and Procedures](Security.md).
+
## Quick Start
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:
Install the executable. The executable's major version will match Express's:
-```console
+```bash
$ npm install -g express-generator@4
```
Create the app:
-```console
+```bash
$ express /tmp/foo && cd /tmp/foo
```
Install dependencies:
-```console
+```bash
$ npm install
```
Start the server:
-```console
+```bash
$ npm start
```
@@ -103,43 +109,31 @@ $ npm start
To view the examples, clone the Express repo and install the dependencies:
-```console
-$ git clone https://github.com/expressjs/express.git --depth 1
+```bash
+$ git clone git://github.com/expressjs/express.git --depth 1
$ cd express
$ npm install
```
Then run whichever example you want:
-```console
+```bash
$ node examples/content-negotiation
```
-## Contributing
-
- [![Linux Build][github-actions-ci-image]][github-actions-ci-url]
- [![Windows Build][appveyor-image]][appveyor-url]
- [![Test Coverage][coveralls-image]][coveralls-url]
-
-The Express.js project welcomes all constructive contributions. Contributions take many forms,
-from code for bug fixes and enhancements, to additions and fixes to documentation, additional
-tests, triaging incoming pull requests and issues, and more!
+## Tests
-See the [Contributing Guide](Contributing.md) for more technical details on contributing.
+ To run the test suite, first install the dependencies, then run `npm test`:
-### Security Issues
-
-If you discover a security vulnerability in Express, please see [Security Policies and Procedures](Security.md).
-
-### Running Tests
-
-To run the test suite, first install the dependencies, then run `npm test`:
-
-```console
+```bash
$ npm install
$ npm test
```
+## Contributing
+
+[Contributing Guide](Contributing.md)
+
## People
The original author of Express is [TJ Holowaychuk](https://github.com/tj)
@@ -152,15 +146,13 @@ The current lead maintainer is [Douglas Christopher Wilson](https://github.com/d
[MIT](LICENSE)
-[appveyor-image]: https://badgen.net/appveyor/ci/dougwilson/express/master?label=windows
+[ci-image]: https://img.shields.io/github/workflow/status/expressjs/express/ci/master.svg?label=linux
+[ci-url]: https://github.com/expressjs/express/actions?query=workflow%3Aci
+[npm-image]: https://img.shields.io/npm/v/express.svg
+[npm-url]: https://npmjs.org/package/express
+[downloads-image]: https://img.shields.io/npm/dm/express.svg
+[downloads-url]: https://npmcharts.com/compare/express?minimal=true
+[appveyor-image]: https://img.shields.io/appveyor/ci/dougwilson/express/master.svg?label=windows
[appveyor-url]: https://ci.appveyor.com/project/dougwilson/express
-[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/express/master
+[coveralls-image]: https://img.shields.io/coveralls/expressjs/express/master.svg
[coveralls-url]: https://coveralls.io/r/expressjs/express?branch=master
-[github-actions-ci-image]: https://badgen.net/github/checks/expressjs/express/master?label=linux
-[github-actions-ci-url]: https://github.com/expressjs/express/actions/workflows/ci.yml
-[npm-downloads-image]: https://badgen.net/npm/dm/express
-[npm-downloads-url]: https://npmcharts.com/compare/express?minimal=true
-[npm-install-size-image]: https://badgen.net/packagephobia/install/express
-[npm-install-size-url]: https://packagephobia.com/result?p=express
-[npm-url]: https://npmjs.org/package/express
-[npm-version-image]: https://badgen.net/npm/v/express
diff --git a/Security.md b/Security.md
index cdcd7a6e0aa..858dfffc5bc 100644
--- a/Security.md
+++ b/Security.md
@@ -27,7 +27,8 @@ endeavor to keep you informed of the progress towards a fix and full
announcement, and may ask for additional information or guidance.
Report security bugs in third-party modules to the person or team maintaining
-the module.
+the module. You can also report a vulnerability through the
+[Node Security Project](https://nodesecurity.io/report).
## Disclosure Policy
diff --git a/examples/cookies/index.js b/examples/cookies/index.js
index d2a4ea62201..fecda79d7a4 100644
--- a/examples/cookies/index.js
+++ b/examples/cookies/index.js
@@ -38,7 +38,7 @@ app.get('/forget', function(req, res){
app.post('/', function(req, res){
var minute = 60000;
- if (req.body.remember) res.cookie('remember', 1, { maxAge: minute });
+ if (req.body?.remember) res.cookie('remember', 1, { maxAge: minute });
res.redirect('back');
});
diff --git a/examples/downloads/index.js b/examples/downloads/index.js
index c5609766213..49f424fbf85 100644
--- a/examples/downloads/index.js
+++ b/examples/downloads/index.js
@@ -12,7 +12,7 @@ var app = module.exports = express();
// path to where the files are stored on disk
var FILES_DIR = path.join(__dirname, 'files')
-app.get('/', function(req, res){
+app.get('/', (req, res) => {
res.send('
' +
'- Download notes/groceries.txt.
' +
'- Download amazing.txt.
' +
@@ -23,8 +23,17 @@ app.get('/', function(req, res){
// /files/* is accessed via req.params[0]
// but here we name it :file
-app.get('/files/:file(.*)', function(req, res, next){
- res.download(req.params.file, { root: FILES_DIR }, function (err) {
+app.get('/files/:file(.*)', (req, res, next) => {
+ // don't let them sneak out of the FILES_DIR
+ // Background: The code previously relied on resolve-path to do this, but it was removed.
+ var filePath = path.join(FILES_DIR, req.params.file);
+ var normalizedPath = path.normalize(filePath);
+
+ if (!normalizedPath.startsWith(FILES_DIR)) {
+ return res.status(403).send('Forbidden');
+ }
+
+ res.download(req.params.file, { root: FILES_DIR }, err => {
if (!err) return; // file sent
if (err.status !== 404) return next(err); // non-404 error
// file for download not found
diff --git a/lib/application.js b/lib/application.js
index ce7a3e6e10c..6bdf30727f5 100644
--- a/lib/application.js
+++ b/lib/application.js
@@ -14,27 +14,17 @@
*/
var finalhandler = require('finalhandler');
-var Router = require('./router');
+var Router = require('router');
var methods = require('methods');
-var middleware = require('./middleware/init');
-var query = require('./middleware/query');
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('depd')('express');
var merge = require('utils-merge');
var resolve = require('path').resolve;
var setPrototypeOf = require('setprototypeof')
-
-/**
- * Module variables.
- * @private
- */
-
-var hasOwnProperty = Object.prototype.hasOwnProperty
var slice = Array.prototype.slice;
/**
@@ -61,11 +51,29 @@ var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default';
*/
app.init = function init() {
+ var router = null;
+
this.cache = {};
this.engines = {};
this.settings = {};
this.defaultConfiguration();
+
+ // Setup getting to lazily add base router
+ Object.defineProperty(this, 'router', {
+ configurable: true,
+ enumerable: true,
+ get: function getrouter() {
+ if (router === null) {
+ router = new Router({
+ caseSensitive: this.enabled('case sensitive routing'),
+ strict: this.enabled('strict routing')
+ });
+ }
+
+ return router;
+ }
+ });
};
/**
@@ -80,7 +88,7 @@ app.defaultConfiguration = function defaultConfiguration() {
this.enable('x-powered-by');
this.set('etag', 'weak');
this.set('env', env);
- this.set('query parser', 'extended');
+ this.set('query parser', 'simple')
this.set('subdomain offset', 2);
this.set('trust proxy', false);
@@ -124,32 +132,6 @@ app.defaultConfiguration = function defaultConfiguration() {
if (env === 'production') {
this.enable('view cache');
}
-
- Object.defineProperty(this, 'router', {
- get: function() {
- throw new Error('\'app.router\' is deprecated!\nPlease see the 3.x to 4.x migration guide for details on how to update your app.');
- }
- });
-};
-
-/**
- * lazily adds the base router if it has not yet been added.
- *
- * We cannot add the base router in the defaultConfiguration because
- * it reads app settings which might be set after that has run.
- *
- * @private
- */
-app.lazyrouter = function lazyrouter() {
- if (!this._router) {
- this._router = new Router({
- caseSensitive: this.enabled('case sensitive routing'),
- strict: this.enabled('strict routing')
- });
-
- this._router.use(query(this.get('query parser fn')));
- this._router.use(middleware.init(this));
- }
};
/**
@@ -162,22 +144,31 @@ app.lazyrouter = function lazyrouter() {
*/
app.handle = function handle(req, res, callback) {
- var router = this._router;
-
// final handler
var done = callback || finalhandler(req, res, {
env: this.get('env'),
onerror: logerror.bind(this)
});
- // no routes
- if (!router) {
- debug('no routes defined on app');
- done();
- return;
+ // set powered by header
+ if (this.enabled('x-powered-by')) {
+ res.setHeader('X-Powered-By', 'Express');
+ }
+
+ // set circular references
+ req.res = res;
+ res.req = req;
+
+ // alter the prototypes
+ setPrototypeOf(req, this.request)
+ setPrototypeOf(res, this.response)
+
+ // setup locals
+ if (!res.locals) {
+ res.locals = Object.create(null);
}
- router.handle(req, res, done);
+ this.router.handle(req, res, done);
};
/**
@@ -216,9 +207,8 @@ app.use = function use(fn) {
throw new TypeError('app.use() requires a middleware function')
}
- // setup router
- this.lazyrouter();
- var router = this._router;
+ // get router
+ var router = this.router;
fns.forEach(function (fn) {
// non-express app
@@ -258,8 +248,7 @@ app.use = function use(fn) {
*/
app.route = function route(path) {
- this.lazyrouter();
- return this._router.route(path);
+ return this.router.route(path);
};
/**
@@ -325,8 +314,6 @@ app.engine = function engine(ext, fn) {
*/
app.param = function param(name, fn) {
- this.lazyrouter();
-
if (Array.isArray(name)) {
for (var i = 0; i < name.length; i++) {
this.param(name[i], fn);
@@ -335,7 +322,7 @@ app.param = function param(name, fn) {
return this;
}
- this._router.param(name, fn);
+ this.router.param(name, fn);
return this;
};
@@ -358,17 +345,7 @@ app.param = function param(name, fn) {
app.set = function set(setting, val) {
if (arguments.length === 1) {
// app.get(setting)
- var settings = this.settings
-
- while (settings && settings !== Object.prototype) {
- if (hasOwnProperty.call(settings, setting)) {
- return settings[setting]
- }
-
- settings = Object.getPrototypeOf(settings)
- }
-
- return undefined
+ return this.settings[setting];
}
debug('set "%s" to %o', setting, val);
@@ -492,9 +469,7 @@ methods.forEach(function(method){
return this.set(path);
}
- this.lazyrouter();
-
- var route = this._router.route(path);
+ var route = this.route(path);
route[method].apply(route, slice.call(arguments, 1));
return this;
};
@@ -511,9 +486,7 @@ methods.forEach(function(method){
*/
app.all = function all(path) {
- this.lazyrouter();
-
- var route = this._router.route(path);
+ var route = this.route(path);
var args = slice.call(arguments, 1);
for (var i = 0; i < methods.length; i++) {
@@ -523,10 +496,6 @@ app.all = function all(path) {
return this;
};
-// del -> delete alias
-
-app.del = deprecate.function(app.delete, 'app.del: Use app.delete instead');
-
/**
* Render the given view `name` name with `options`
* and a callback accepting an error and the
diff --git a/lib/express.js b/lib/express.js
index d188a16db70..b4ef2996360 100644
--- a/lib/express.js
+++ b/lib/express.js
@@ -16,8 +16,7 @@ var bodyParser = require('body-parser')
var EventEmitter = require('events').EventEmitter;
var mixin = require('merge-descriptors');
var proto = require('./application');
-var Route = require('./router/route');
-var Router = require('./router');
+var Router = require('router');
var req = require('./request');
var res = require('./response');
@@ -68,7 +67,7 @@ exports.response = res;
* Expose constructors.
*/
-exports.Route = Route;
+exports.Route = Router.Route;
exports.Router = Router;
/**
@@ -76,41 +75,7 @@ exports.Router = Router;
*/
exports.json = bodyParser.json
-exports.query = require('./middleware/query');
exports.raw = bodyParser.raw
exports.static = require('serve-static');
exports.text = bodyParser.text
exports.urlencoded = bodyParser.urlencoded
-
-/**
- * Replace removed middleware with an appropriate error message.
- */
-
-var removedMiddlewares = [
- 'bodyParser',
- 'compress',
- 'cookieSession',
- 'session',
- 'logger',
- 'cookieParser',
- 'favicon',
- 'responseTime',
- 'errorHandler',
- 'timeout',
- 'methodOverride',
- 'vhost',
- 'csrf',
- 'directory',
- 'limit',
- 'multipart',
- 'staticCache'
-]
-
-removedMiddlewares.forEach(function (name) {
- Object.defineProperty(exports, name, {
- get: function () {
- throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.');
- },
- configurable: true
- });
-});
diff --git a/lib/middleware/init.js b/lib/middleware/init.js
deleted file mode 100644
index dfd042747bd..00000000000
--- a/lib/middleware/init.js
+++ /dev/null
@@ -1,43 +0,0 @@
-/*!
- * express
- * Copyright(c) 2009-2013 TJ Holowaychuk
- * Copyright(c) 2013 Roman Shtylman
- * Copyright(c) 2014-2015 Douglas Christopher Wilson
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Module dependencies.
- * @private
- */
-
-var setPrototypeOf = require('setprototypeof')
-
-/**
- * Initialization middleware, exposing the
- * request and response to each other, as well
- * as defaulting the X-Powered-By header field.
- *
- * @param {Function} app
- * @return {Function}
- * @api private
- */
-
-exports.init = function(app){
- return function expressInit(req, res, next){
- if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express');
- req.res = res;
- res.req = req;
- req.next = next;
-
- setPrototypeOf(req, app.request)
- setPrototypeOf(res, app.response)
-
- res.locals = res.locals || Object.create(null);
-
- next();
- };
-};
-
diff --git a/lib/middleware/query.js b/lib/middleware/query.js
deleted file mode 100644
index 7e9166947af..00000000000
--- a/lib/middleware/query.js
+++ /dev/null
@@ -1,47 +0,0 @@
-/*!
- * express
- * Copyright(c) 2009-2013 TJ Holowaychuk
- * Copyright(c) 2013 Roman Shtylman
- * Copyright(c) 2014-2015 Douglas Christopher Wilson
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Module dependencies.
- */
-
-var merge = require('utils-merge')
-var parseUrl = require('parseurl');
-var qs = require('qs');
-
-/**
- * @param {Object} options
- * @return {Function}
- * @api public
- */
-
-module.exports = function query(options) {
- var opts = merge({}, options)
- var queryparse = qs.parse;
-
- if (typeof options === 'function') {
- queryparse = options;
- opts = undefined;
- }
-
- if (opts !== undefined && opts.allowPrototypes === undefined) {
- // back-compat for qs module
- opts.allowPrototypes = true;
- }
-
- return function query(req, res, next){
- if (!req.query) {
- var val = parseUrl(req).query;
- req.query = queryparse(val, opts);
- }
-
- next();
- };
-};
diff --git a/lib/request.js b/lib/request.js
index 3f1eeca6c1a..c528186aa13 100644
--- a/lib/request.js
+++ b/lib/request.js
@@ -14,7 +14,6 @@
*/
var accepts = require('accepts');
-var deprecate = require('depd')('express');
var isIP = require('net').isIP;
var typeis = require('type-is');
var http = require('http');
@@ -147,9 +146,6 @@ req.acceptsEncodings = function(){
return accept.encodings.apply(accept, arguments);
};
-req.acceptsEncoding = deprecate.function(req.acceptsEncodings,
- 'req.acceptsEncoding: Use acceptsEncodings instead');
-
/**
* Check if the given `charset`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
@@ -164,9 +160,6 @@ req.acceptsCharsets = function(){
return accept.charsets.apply(accept, arguments);
};
-req.acceptsCharset = deprecate.function(req.acceptsCharsets,
- 'req.acceptsCharset: Use acceptsCharsets instead');
-
/**
* Check if the given `lang`s are acceptable,
* otherwise you should respond with 406 "Not Acceptable".
@@ -181,9 +174,6 @@ req.acceptsLanguages = function(){
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`.
*
@@ -216,38 +206,27 @@ req.range = function range(size, options) {
};
/**
- * Return the value of param `name` when present or `defaultValue`.
- *
- * - Checks route placeholders, ex: _/user/:id_
- * - Checks body params, ex: id=12, {"id":12}
- * - Checks query string params, ex: ?id=12
+ * Parse the query string of `req.url`.
*
- * To utilize request bodies, `req.body`
- * should be an object. This can be done by using
- * the `bodyParser()` middleware.
+ * This uses the "query parser" setting to parse the raw
+ * string into an object.
*
- * @param {String} name
- * @param {Mixed} [defaultValue]
* @return {String}
- * @public
+ * @api public
*/
-req.param = function param(name, defaultValue) {
- var params = this.params || {};
- var body = this.body || {};
- var query = this.query || {};
+defineGetter(req, 'query', function query(){
+ var queryparse = this.app.get('query parser fn');
- var args = arguments.length === 1
- ? 'name'
- : 'name, default';
- deprecate('req.param(' + args + '): Use req.params, req.body, or req.query instead');
+ if (!queryparse) {
+ // parsing is disabled
+ return Object.create(null);
+ }
- if (null != params[name] && params.hasOwnProperty(name)) return params[name];
- if (null != body[name]) return body[name];
- if (null != query[name]) return query[name];
+ var querystring = parse(this).query;
- return defaultValue;
-};
+ return queryparse(querystring);
+});
/**
* Check if the incoming request contains the "Content-Type"
@@ -414,7 +393,7 @@ defineGetter(req, 'path', function path() {
});
/**
- * Parse the "Host" header field to a hostname.
+ * Parse the "Host" header field to a host.
*
* When the "trust proxy" setting trusts the socket
* address, the "X-Forwarded-Host" header field will
@@ -424,18 +403,35 @@ defineGetter(req, 'path', function path() {
* @public
*/
-defineGetter(req, 'hostname', function hostname(){
+defineGetter(req, 'host', function host(){
var trust = this.app.get('trust proxy fn');
- var host = this.get('X-Forwarded-Host');
+ var val = this.get('X-Forwarded-Host');
- if (!host || !trust(this.connection.remoteAddress, 0)) {
- host = this.get('Host');
- } else if (host.indexOf(',') !== -1) {
+ if (!val || !trust(this.connection.remoteAddress, 0)) {
+ val = this.get('Host');
+ } else if (val.indexOf(',') !== -1) {
// Note: X-Forwarded-Host is normally only ever a
// single value, but this is to be safe.
- host = host.substring(0, host.indexOf(',')).trimRight()
+ val = val.substring(0, val.indexOf(',')).trimRight()
}
+ return val || undefined;
+});
+
+/**
+ * Parse the "Host" header field to a hostname.
+ *
+ * When the "trust proxy" setting trusts the socket
+ * address, the "X-Forwarded-Host" header field will
+ * be trusted.
+ *
+ * @return {String}
+ * @api public
+ */
+
+defineGetter(req, 'hostname', function hostname(){
+ var host = this.host;
+
if (!host) return;
// IPv6 literal support
@@ -449,12 +445,6 @@ defineGetter(req, 'hostname', function hostname(){
: 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
diff --git a/lib/response.js b/lib/response.js
index 7fc9b6e5fe0..16d9f483b81 100644
--- a/lib/response.js
+++ b/lib/response.js
@@ -12,15 +12,15 @@
* @private
*/
+var Buffer = require('safe-buffer').Buffer
var contentDisposition = require('content-disposition');
-var createError = require('http-errors')
-var deprecate = require('depd')('express');
var encodeUrl = require('encodeurl');
var escapeHtml = require('escape-html');
var http = require('http');
-var isAbsolute = require('./utils').isAbsolute;
var onFinished = require('on-finished');
+var mime = require('mime-types')
var path = require('path');
+var pathIsAbsolute = require('path-is-absolute');
var statuses = require('statuses')
var merge = require('utils-merge');
var sign = require('cookie-signature').sign;
@@ -30,7 +30,6 @@ var setCharset = require('./utils').setCharset;
var cookie = require('cookie');
var send = require('send');
var extname = path.extname;
-var mime = send.mime;
var resolve = path.resolve;
var vary = require('vary');
@@ -48,13 +47,6 @@ var res = Object.create(http.ServerResponse.prototype)
module.exports = res
-/**
- * Module variables.
- * @private
- */
-
-var charsetRegExp = /;\s*charset\s*=/;
-
/**
* Set status `code`.
*
@@ -64,9 +56,6 @@ var charsetRegExp = /;\s*charset\s*=/;
*/
res.status = function status(code) {
- if ((typeof code === 'string' || Math.floor(code) !== code) && code > 99 && code < 1000) {
- deprecate('res.status(' + JSON.stringify(code) + '): use res.status(' + Math.floor(code) + ') instead')
- }
this.statusCode = code;
return this;
};
@@ -116,31 +105,6 @@ res.send = function send(body) {
// settings
var app = this.app;
- // allow status / body
- if (arguments.length === 2) {
- // res.send(body, status) backwards compat
- 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 {
- deprecate('res.send(status, body): Use res.status(status).send(body) instead');
- this.statusCode = arguments[0];
- chunk = arguments[1];
- }
- }
-
- // 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.sendStatus(status) instead');
- this.statusCode = chunk;
- chunk = statuses.message[chunk]
- }
-
switch (typeof chunk) {
// string defaulting to html
case 'string':
@@ -216,13 +180,6 @@ res.send = function send(body) {
chunk = '';
}
- // alter headers for 205
- if (this.statusCode === 205) {
- this.set('Content-Length', '0')
- this.removeHeader('Transfer-Encoding')
- chunk = ''
- }
-
if (req.method === 'HEAD') {
// skip body for HEAD
this.end();
@@ -247,27 +204,12 @@ res.send = function send(body) {
*/
res.json = function json(obj) {
- var val = obj;
-
- // allow status / body
- if (arguments.length === 2) {
- // res.json(body, status) backwards compat
- if (typeof arguments[1] === 'number') {
- deprecate('res.json(obj, status): Use res.status(status).json(obj) instead');
- this.statusCode = arguments[1];
- } else {
- deprecate('res.json(status, obj): Use res.status(status).json(obj) instead');
- this.statusCode = arguments[0];
- val = arguments[1];
- }
- }
-
// settings
var app = this.app;
var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(val, replacer, spaces, escape)
+ var body = stringify(obj, replacer, spaces, escape)
// content-type
if (!this.get('Content-Type')) {
@@ -290,27 +232,12 @@ res.json = function json(obj) {
*/
res.jsonp = function jsonp(obj) {
- var val = obj;
-
- // allow status / body
- if (arguments.length === 2) {
- // res.jsonp(body, status) backwards compat
- if (typeof arguments[1] === 'number') {
- deprecate('res.jsonp(obj, status): Use res.status(status).jsonp(obj) instead');
- this.statusCode = arguments[1];
- } else {
- deprecate('res.jsonp(status, obj): Use res.status(status).jsonp(obj) instead');
- this.statusCode = arguments[0];
- val = arguments[1];
- }
- }
-
// settings
var app = this.app;
var escape = app.get('json escape')
var replacer = app.get('json replacer');
var spaces = app.get('json spaces');
- var body = stringify(val, replacer, spaces, escape)
+ var body = stringify(obj, replacer, spaces, escape)
var callback = this.req.query[app.get('jsonp callback name')];
// content-type
@@ -366,7 +293,7 @@ res.jsonp = function jsonp(obj) {
*/
res.sendStatus = function sendStatus(statusCode) {
- var body = statuses.message[statusCode] || String(statusCode)
+ var body = statuses.message[statusCode] || String(statusCode);
this.statusCode = statusCode;
this.type('txt');
@@ -436,98 +363,36 @@ res.sendFile = function sendFile(path, options, callback) {
opts = {};
}
- if (!opts.root && !isAbsolute(path)) {
+ if (!opts.root && !pathIsAbsolute(path)) {
throw new TypeError('path must be absolute or specify root to res.sendFile');
}
// create file stream
var pathname = encodeURI(path);
var file = send(req, pathname, opts);
-
- // transfer
- sendfile(res, file, opts, function (err) {
- if (done) return done(err);
- if (err && err.code === 'EISDIR') return next();
-
- // next() all but write errors
- if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
- next(err);
+ if (opts.headers) {
+ for (var name in opts.headers) {
+ res.setHeader(name, opts.headers[name]);
}
- });
-};
-
-/**
- * Transfer the file at the given `path`.
- *
- * Automatically sets the _Content-Type_ response header field.
- * The callback `callback(err)` is invoked when the transfer is complete
- * or when an error occurs. Be sure to check `res.headersSent`
- * if you wish to attempt responding, as the header and some data
- * may have already been transferred.
- *
- * Options:
- *
- * - `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`.
- *
- * Examples:
- *
- * The following example illustrates how `res.sendfile()` may
- * be used as an alternative for the `static()` middleware for
- * dynamic situations. The code backing `res.sendfile()` is actually
- * the same code, so HTTP cache support etc is identical.
- *
- * app.get('/user/:uid/photos/:file', function(req, res){
- * var uid = req.params.uid
- * , file = req.params.file;
- *
- * req.user.mayViewFilesFrom(uid, function(yes){
- * if (yes) {
- * res.sendfile('/uploads/' + uid + '/' + file);
- * } else {
- * res.send(403, 'Sorry! you cant see that.');
- * }
- * });
- * });
- *
- * @public
- */
-
-res.sendfile = function (path, options, callback) {
- var done = callback;
- var req = this.req;
- var res = this;
- var next = req.next;
- var opts = options || {};
-
- // support function as second arg
- if (typeof options === 'function') {
- done = options;
- opts = {};
}
- // create file stream
- var file = send(req, path, opts);
-
// transfer
sendfile(res, file, opts, function (err) {
+ if(err) {
+ for (var name in res._headers) {
+ res.removeHeader(name);
+ }
+ }
if (done) return done(err);
if (err && err.code === 'EISDIR') return next();
// next() all but write errors
if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') {
- next(err);
+ return next(err);
}
});
};
-res.sendfile = deprecate.function(res.sendfile,
- 'res.sendfile: Use res.sendFile instead');
-
/**
* Transfer the file at the given `path` as an attachment.
*
@@ -549,23 +414,23 @@ res.sendfile = deprecate.function(res.sendfile,
res.download = function download (path, filename, options, callback) {
var done = callback;
var name = filename;
- var opts = options || null
+ var opts = options || null;
// support function as second or third arg
if (typeof filename === 'function') {
done = filename;
name = null;
- opts = null
+ opts = null;
} else if (typeof options === 'function') {
- done = options
- opts = null
+ done = options;
+ opts = null;
}
// support optional filename, where options may be in it's place
if (typeof filename === 'object' &&
(typeof options === 'function' || options === undefined)) {
- name = null
- opts = filename
+ name = null;
+ opts = filename;
}
// set Content-Disposition when file is sent
@@ -575,31 +440,33 @@ res.download = function download (path, filename, options, callback) {
// merge user-provided headers
if (opts && opts.headers) {
- var keys = Object.keys(opts.headers)
+ var keys = Object.keys(opts.headers);
for (var i = 0; i < keys.length; i++) {
- var key = keys[i]
+ var key = keys[i];
if (key.toLowerCase() !== 'content-disposition') {
- headers[key] = opts.headers[key]
+ headers[key] = opts.headers[key];
}
}
}
// merge user-provided options
- opts = Object.create(opts)
- opts.headers = headers
+ opts = Object.assign({}, opts);
+ opts.headers = headers;
// Resolve the full path for sendFile
var fullPath = !opts.root
? resolve(path)
- : path
+ : path;
// send file
- return this.sendFile(fullPath, opts, done)
+ return this.sendFile(fullPath, opts, done);
};
/**
- * Set _Content-Type_ response header with `type` through `mime.lookup()`
+ * Set _Content-Type_ response header with `type` through `mime.contentType()`
* when it does not contain "/", or set the Content-Type to `type` otherwise.
+ * When no mapping is found though `mime.contentType()`, the type is set to
+ * "application/octet-stream".
*
* Examples:
*
@@ -617,7 +484,7 @@ res.download = function download (path, filename, options, callback) {
res.contentType =
res.type = function contentType(type) {
var ct = type.indexOf('/') === -1
- ? mime.lookup(type)
+ ? (mime.contentType(type) || 'application/octet-stream')
: type;
return this.set('Content-Type', ct);
@@ -684,8 +551,9 @@ res.format = function(obj){
var req = this.req;
var next = req.next;
- var keys = Object.keys(obj)
- .filter(function (v) { return v !== 'default' })
+ var fn = obj.default;
+ if (fn) delete obj.default;
+ var keys = Object.keys(obj);
var key = keys.length > 0
? req.accepts(keys)
@@ -696,12 +564,13 @@ res.format = function(obj){
if (key) {
this.set('Content-Type', normalizeType(key).value);
obj[key](req, this, next);
- } else if (obj.default) {
- obj.default(req, this, next)
+ } else if (fn) {
+ fn();
} else {
- next(createError(406, {
- types: normalizeTypes(keys).map(function (o) { return o.value })
- }))
+ var err = new Error('Not Acceptable');
+ err.status = err.statusCode = 406;
+ err.types = normalizeTypes(keys).map(function(o){ return o.value });
+ next(err);
}
return this;
@@ -766,6 +635,9 @@ res.append = function append(field, val) {
*
* Aliased as `res.header()`.
*
+ * When the set header is "Content-Type", the type is expanded to include
+ * the charset if not present using `mime.contentType()`.
+ *
* @param {String|Object} field
* @param {String|Array} val
* @return {ServerResponse} for chaining
@@ -784,10 +656,7 @@ res.header = function header(field, val) {
if (Array.isArray(value)) {
throw new TypeError('Content-Type cannot be set to an Array');
}
- if (!charsetRegExp.test(value)) {
- var charset = mime.charsets.lookup(value.split(';')[0]);
- if (charset) value += '; charset=' + charset.toLowerCase();
- }
+ value = mime.contentType(value)
}
this.setHeader(field, value);
@@ -867,13 +736,9 @@ res.cookie = function (name, value, options) {
val = 's:' + sign(val, secret);
}
- if (opts.maxAge != null) {
- var maxAge = opts.maxAge - 0
-
- if (!isNaN(maxAge)) {
- opts.expires = new Date(Date.now() + maxAge)
- opts.maxAge = Math.floor(maxAge / 1000)
- }
+ if ('maxAge' in opts) {
+ opts.expires = new Date(Date.now() + opts.maxAge);
+ opts.maxAge /= 1000;
}
if (opts.path == null) {
@@ -939,13 +804,8 @@ res.redirect = function redirect(url) {
// allow status / url
if (arguments.length === 2) {
- if (typeof arguments[0] === 'number') {
- status = arguments[0];
- address = arguments[1];
- } else {
- deprecate('res.redirect(url, status): Use res.redirect(status, url) instead');
- status = arguments[1];
- }
+ status = arguments[0]
+ address = arguments[1]
}
// Set location header
@@ -988,12 +848,6 @@ res.redirect = function redirect(url) {
*/
res.vary = function(field){
- // checks for back-compat
- if (!field || (Array.isArray(field) && !field.length)) {
- deprecate('res.vary(): Provide a field name');
- return this;
- }
-
vary(this, field);
return this;
@@ -1134,7 +988,7 @@ function sendfile(res, file, options, callback) {
* ability to escape characters that can trigger HTML sniffing.
*
* @param {*} value
- * @param {function} replacer
+ * @param {function} replaces
* @param {number} spaces
* @param {boolean} escape
* @returns {string}
diff --git a/lib/router/index.js b/lib/router/index.js
deleted file mode 100644
index b17be4aee4d..00000000000
--- a/lib/router/index.js
+++ /dev/null
@@ -1,672 +0,0 @@
-/*!
- * express
- * Copyright(c) 2009-2013 TJ Holowaychuk
- * Copyright(c) 2013 Roman Shtylman
- * Copyright(c) 2014-2015 Douglas Christopher Wilson
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Module dependencies.
- * @private
- */
-
-var Route = require('./route');
-var Layer = require('./layer');
-var methods = require('methods');
-var mixin = require('utils-merge');
-var debug = require('debug')('express:router');
-var deprecate = require('depd')('express');
-var parseUrl = require('parseurl');
-var setPrototypeOf = require('setprototypeof')
-
-/**
- * Module variables.
- * @private
- */
-
-var objectRegExp = /^\[object (\S+)\]$/;
-var slice = Array.prototype.slice;
-var toString = Object.prototype.toString;
-
-/**
- * Initialize a new `Router` with the given `options`.
- *
- * @param {Object} [options]
- * @return {Router} which is a callable function
- * @public
- */
-
-var proto = module.exports = function(options) {
- var opts = options || {};
-
- function router(req, res, next) {
- router.handle(req, res, next);
- }
-
- // mixin Router class functions
- setPrototypeOf(router, proto)
-
- router.params = {};
- router._params = [];
- router.caseSensitive = opts.caseSensitive;
- router.mergeParams = opts.mergeParams;
- router.strict = opts.strict;
- router.stack = [];
-
- return router;
-};
-
-/**
- * Map the given param placeholder `name`(s) to the given callback.
- *
- * Parameter mapping is used to provide pre-conditions to routes
- * which use normalized placeholders. For example a _:user_id_ parameter
- * could automatically load a user's information from the database without
- * any additional code,
- *
- * The callback uses the same signature as middleware, the only difference
- * being that the value of the placeholder is passed, in this case the _id_
- * of the user. Once the `next()` function is invoked, just like middleware
- * it will continue on to execute the route, or subsequent parameter functions.
- *
- * Just like in middleware, you must either respond to the request or call next
- * to avoid stalling the request.
- *
- * app.param('user_id', function(req, res, next, id){
- * User.find(id, function(err, user){
- * if (err) {
- * return next(err);
- * } else if (!user) {
- * return next(new Error('failed to load user'));
- * }
- * req.user = user;
- * next();
- * });
- * });
- *
- * @param {String} name
- * @param {Function} fn
- * @return {app} for chaining
- * @public
- */
-
-proto.param = function param(name, fn) {
- // param logic
- if (typeof name === 'function') {
- deprecate('router.param(fn): Refactor to use path params');
- this._params.push(name);
- return;
- }
-
- // apply param functions
- var params = this._params;
- var len = params.length;
- var ret;
-
- if (name[0] === ':') {
- deprecate('router.param(' + JSON.stringify(name) + ', fn): Use router.param(' + JSON.stringify(name.slice(1)) + ', fn) instead')
- name = name.slice(1)
- }
-
- for (var i = 0; i < len; ++i) {
- if (ret = params[i](name, fn)) {
- fn = ret;
- }
- }
-
- // ensure we end up with a
- // middleware function
- if ('function' !== typeof fn) {
- throw new Error('invalid param() call for ' + name + ', got ' + fn);
- }
-
- (this.params[name] = this.params[name] || []).push(fn);
- return this;
-};
-
-/**
- * Dispatch a req, res into the router.
- * @private
- */
-
-proto.handle = function handle(req, res, out) {
- var self = this;
-
- debug('dispatching %s %s', req.method, req.url);
-
- var idx = 0;
- var protohost = getProtohost(req.url) || ''
- var removed = '';
- var slashAdded = false;
- var sync = 0
- var paramcalled = {};
-
- // store options for OPTIONS request
- // only used if OPTIONS request
- var options = [];
-
- // middleware and routes
- var stack = self.stack;
-
- // manage inter-router variables
- var parentParams = req.params;
- var parentUrl = req.baseUrl || '';
- var done = restore(out, req, 'baseUrl', 'next', 'params');
-
- // setup next layer
- req.next = next;
-
- // for options requests, respond with a default if nothing else responds
- if (req.method === 'OPTIONS') {
- done = wrap(done, function(old, err) {
- if (err || options.length === 0) return old(err);
- sendOptionsResponse(res, options, old);
- });
- }
-
- // setup basic req values
- req.baseUrl = parentUrl;
- req.originalUrl = req.originalUrl || req.url;
-
- next();
-
- function next(err) {
- var layerError = err === 'route'
- ? null
- : err;
-
- // remove added slash
- if (slashAdded) {
- req.url = req.url.slice(1)
- slashAdded = false;
- }
-
- // restore altered req.url
- if (removed.length !== 0) {
- req.baseUrl = parentUrl;
- req.url = protohost + removed + req.url.slice(protohost.length)
- removed = '';
- }
-
- // signal to exit router
- if (layerError === 'router') {
- setImmediate(done, null)
- return
- }
-
- // no more matching layers
- if (idx >= stack.length) {
- setImmediate(done, layerError);
- return;
- }
-
- // max sync stack
- if (++sync > 100) {
- return setImmediate(next, err)
- }
-
- // get pathname of request
- var path = getPathname(req);
-
- if (path == null) {
- return done(layerError);
- }
-
- // find next matching layer
- var layer;
- var match;
- var route;
-
- while (match !== true && idx < stack.length) {
- layer = stack[idx++];
- match = matchLayer(layer, path);
- route = layer.route;
-
- if (typeof match !== 'boolean') {
- // hold on to layerError
- layerError = layerError || match;
- }
-
- if (match !== true) {
- continue;
- }
-
- if (!route) {
- // process non-route handlers normally
- continue;
- }
-
- if (layerError) {
- // routes do not match with a pending error
- match = false;
- continue;
- }
-
- var method = req.method;
- var has_method = route._handles_method(method);
-
- // build up automatic options response
- if (!has_method && method === 'OPTIONS') {
- appendMethods(options, route._options());
- }
-
- // don't even bother matching route
- if (!has_method && method !== 'HEAD') {
- match = false;
- }
- }
-
- // no match
- if (match !== true) {
- return done(layerError);
- }
-
- // store route for dispatch on change
- if (route) {
- req.route = route;
- }
-
- // Capture one-time layer values
- req.params = self.mergeParams
- ? mergeParams(layer.params, parentParams)
- : layer.params;
- var layerPath = layer.path;
-
- // this should be done for the layer
- self.process_params(layer, paramcalled, req, res, function (err) {
- if (err) {
- next(layerError || err)
- } else if (route) {
- layer.handle_request(req, res, next)
- } else {
- trim_prefix(layer, layerError, layerPath, path)
- }
-
- sync = 0
- });
- }
-
- function trim_prefix(layer, layerError, layerPath, path) {
- if (layerPath.length !== 0) {
- // Validate path is a prefix match
- if (layerPath !== path.slice(0, layerPath.length)) {
- next(layerError)
- return
- }
-
- // Validate path breaks on a path separator
- var c = path[layerPath.length]
- if (c && c !== '/' && c !== '.') return next(layerError)
-
- // 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', layerPath, req.url);
- removed = layerPath;
- req.url = protohost + req.url.slice(protohost.length + removed.length)
-
- // Ensure leading slash
- if (!protohost && req.url[0] !== '/') {
- req.url = '/' + req.url;
- slashAdded = true;
- }
-
- // Setup base URL (no trailing slash)
- req.baseUrl = parentUrl + (removed[removed.length - 1] === '/'
- ? removed.substring(0, removed.length - 1)
- : removed);
- }
-
- 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);
- }
- }
-};
-
-/**
- * Process any parameters for the layer.
- * @private
- */
-
-proto.process_params = function process_params(layer, called, req, res, done) {
- var params = this.params;
-
- // captured parameters from the layer, keys and values
- var keys = layer.keys;
-
- // fast track
- if (!keys || keys.length === 0) {
- return done();
- }
-
- var i = 0;
- var name;
- var paramIndex = 0;
- var key;
- var paramVal;
- var paramCallbacks;
- var paramCalled;
-
- // process params in order
- // param callbacks can be async
- function param(err) {
- if (err) {
- return done(err);
- }
-
- if (i >= keys.length ) {
- return done();
- }
-
- paramIndex = 0;
- key = keys[i++];
- name = key.name;
- paramVal = req.params[name];
- paramCallbacks = params[name];
- paramCalled = called[name];
-
- if (paramVal === undefined || !paramCallbacks) {
- return param();
- }
-
- // param previously called with same value or error occurred
- if (paramCalled && (paramCalled.match === paramVal
- || (paramCalled.error && paramCalled.error !== 'route'))) {
- // restore value
- req.params[name] = paramCalled.value;
-
- // next param
- return param(paramCalled.error);
- }
-
- called[name] = paramCalled = {
- error: null,
- match: paramVal,
- value: paramVal
- };
-
- paramCallback();
- }
-
- // single param callbacks
- function paramCallback(err) {
- var fn = paramCallbacks[paramIndex++];
-
- // store updated value
- paramCalled.value = req.params[key.name];
-
- if (err) {
- // store error
- paramCalled.error = err;
- param(err);
- return;
- }
-
- if (!fn) return param();
-
- try {
- fn(req, res, paramCallback, paramVal, key.name);
- } catch (e) {
- paramCallback(e);
- }
- }
-
- param();
-};
-
-/**
- * Use the given middleware function, with optional path, defaulting to "/".
- *
- * Use (like `.all`) will run for any http METHOD, but it will not add
- * handlers for those methods so OPTIONS requests will not consider `.use`
- * functions even if they could respond.
- *
- * The other difference is that _route_ path is stripped and not visible
- * to the handler function. The main effect of this feature is that mounted
- * handlers can operate without any code changes regardless of the "prefix"
- * pathname.
- *
- * @public
- */
-
-proto.use = function use(fn) {
- var offset = 0;
- var path = '/';
-
- // default path to '/'
- // disambiguate router.use([fn])
- if (typeof fn !== 'function') {
- var arg = fn;
-
- while (Array.isArray(arg) && arg.length !== 0) {
- arg = arg[0];
- }
-
- // first arg is the path
- if (typeof arg !== 'function') {
- offset = 1;
- path = fn;
- }
- }
-
- var callbacks = Array.from(arguments).slice(offset).flat(Infinity);
-
- if (callbacks.length === 0) {
- throw new TypeError('Router.use() requires a middleware function')
- }
-
- for (var i = 0; i < callbacks.length; i++) {
- var fn = callbacks[i];
-
- if (typeof fn !== 'function') {
- throw new TypeError('Router.use() requires a middleware function but got a ' + gettype(fn))
- }
-
- // add the middleware
- debug('use %o %s', path, fn.name || '')
-
- var layer = new Layer(path, {
- sensitive: this.caseSensitive,
- strict: false,
- end: false
- }, fn);
-
- layer.route = undefined;
-
- this.stack.push(layer);
- }
-
- return this;
-};
-
-/**
- * Create a new Route for the given path.
- *
- * Each route contains a separate middleware stack and VERB handlers.
- *
- * See the Route api documentation for details on adding handlers
- * and middleware to routes.
- *
- * @param {String} path
- * @return {Route}
- * @public
- */
-
-proto.route = function route(path) {
- var route = new Route(path);
-
- var layer = new Layer(path, {
- sensitive: this.caseSensitive,
- strict: this.strict,
- end: true
- }, route.dispatch.bind(route));
-
- layer.route = route;
-
- this.stack.push(layer);
- return route;
-};
-
-// create Router#VERB functions
-methods.concat('all').forEach(function(method){
- proto[method] = function(path){
- var route = this.route(path)
- route[method].apply(route, slice.call(arguments, 1));
- return this;
- };
-});
-
-// append methods to a list of methods
-function appendMethods(list, addition) {
- for (var i = 0; i < addition.length; i++) {
- var method = addition[i];
- if (list.indexOf(method) === -1) {
- list.push(method);
- }
- }
-}
-
-// get pathname of request
-function getPathname(req) {
- try {
- return parseUrl(req).pathname;
- } catch (err) {
- return undefined;
- }
-}
-
-// Get get protocol + host for a URL
-function getProtohost(url) {
- if (typeof url !== 'string' || url.length === 0 || url[0] === '/') {
- return undefined
- }
-
- var searchIndex = url.indexOf('?')
- var pathLength = searchIndex !== -1
- ? searchIndex
- : url.length
- var fqdnIndex = url.slice(0, pathLength).indexOf('://')
-
- return fqdnIndex !== -1
- ? url.substring(0, url.indexOf('/', 3 + fqdnIndex))
- : undefined
-}
-
-// get type for error message
-function gettype(obj) {
- var type = typeof obj;
-
- if (type !== 'object') {
- return type;
- }
-
- // inspect [[Class]] for objects
- return toString.call(obj)
- .replace(objectRegExp, '$1');
-}
-
-/**
- * Match path to a layer.
- *
- * @param {Layer} layer
- * @param {string} path
- * @private
- */
-
-function matchLayer(layer, path) {
- try {
- return layer.match(path);
- } catch (err) {
- return err;
- }
-}
-
-// 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 in params) {
- i++;
- }
-
- while (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(obj, 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 () {
- // restore vals
- for (var i = 0; i < props.length; i++) {
- obj[props[i]] = vals[i];
- }
-
- return fn.apply(this, arguments);
- };
-}
-
-// send an OPTIONS response
-function sendOptionsResponse(res, options, next) {
- try {
- var body = options.join(',');
- res.set('Allow', body);
- res.send(body);
- } catch (err) {
- next(err);
- }
-}
-
-// 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);
- };
-}
diff --git a/lib/router/layer.js b/lib/router/layer.js
deleted file mode 100644
index 483c02870ce..00000000000
--- a/lib/router/layer.js
+++ /dev/null
@@ -1,181 +0,0 @@
-/*!
- * express
- * Copyright(c) 2009-2013 TJ Holowaychuk
- * Copyright(c) 2013 Roman Shtylman
- * Copyright(c) 2014-2015 Douglas Christopher Wilson
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Module dependencies.
- * @private
- */
-
-var { pathToRegexp } = require('path-to-regexp');
-var debug = require('debug')('express:router:layer');
-
-/**
- * Module variables.
- * @private
- */
-
-var hasOwnProperty = Object.prototype.hasOwnProperty;
-
-/**
- * Module exports.
- * @public
- */
-
-module.exports = Layer;
-
-function Layer(path, options, fn) {
- if (!(this instanceof Layer)) {
- return new Layer(path, options, fn);
- }
-
- debug('new %o', path)
- var opts = options || {};
-
- this.handle = fn;
- this.name = fn.name || '';
- this.params = undefined;
- this.path = undefined;
- this.regexp = pathToRegexp(path, this.keys = [], opts);
-
- // set fast path flags
- this.regexp.fast_star = path === '*';
- this.regexp.fast_slash = path === '/' && opts.end === false;
-}
-
-/**
- * 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`.
- *
- * @param {String} path
- * @return {Boolean}
- * @api private
- */
-
-Layer.prototype.match = function match(path) {
- var match
-
- if (path != null) {
- // fast path non-ending match for / (any path matches)
- if (this.regexp.fast_slash) {
- this.params = {}
- this.path = ''
- return true
- }
-
- // fast path for * (everything matched in a param)
- if (this.regexp.fast_star) {
- this.params = {'0': decode_param(path)}
- this.path = path
- return true
- }
-
- // match the path
- match = this.regexp.exec(path)
- }
-
- if (!match) {
- this.params = undefined;
- this.path = undefined;
- return false;
- }
-
- // store values
- this.params = {};
- this.path = match[0]
-
- var keys = this.keys;
- var params = this.params;
-
- for (var i = 1; i < match.length; i++) {
- var key = keys[i - 1];
- var prop = key.name;
- var val = decode_param(match[i])
-
- if (val !== undefined || !(hasOwnProperty.call(params, prop))) {
- params[prop] = val;
- }
- }
-
- return true;
-};
-
-/**
- * Decode param value.
- *
- * @param {string} val
- * @return {string}
- * @private
- */
-
-function decode_param(val) {
- if (typeof val !== 'string' || val.length === 0) {
- return val;
- }
-
- try {
- return decodeURIComponent(val);
- } catch (err) {
- if (err instanceof URIError) {
- err.message = 'Failed to decode param \'' + val + '\'';
- err.status = err.statusCode = 400;
- }
-
- throw err;
- }
-}
diff --git a/lib/router/route.js b/lib/router/route.js
deleted file mode 100644
index bd4faf65330..00000000000
--- a/lib/router/route.js
+++ /dev/null
@@ -1,228 +0,0 @@
-/*!
- * express
- * Copyright(c) 2009-2013 TJ Holowaychuk
- * Copyright(c) 2013 Roman Shtylman
- * Copyright(c) 2014-2015 Douglas Christopher Wilson
- * MIT Licensed
- */
-
-'use strict';
-
-/**
- * Module dependencies.
- * @private
- */
-
-var debug = require('debug')('express:router:route');
-var Layer = require('./layer');
-var methods = require('methods');
-
-/**
- * Module variables.
- * @private
- */
-
-var toString = Object.prototype.toString;
-
-/**
- * Module exports.
- * @public
- */
-
-module.exports = Route;
-
-/**
- * Initialize `Route` with the given `path`,
- *
- * @param {String} path
- * @public
- */
-
-function Route(path) {
- this.path = path;
- this.stack = [];
-
- debug('new %o', path)
-
- // route handlers for various http methods
- this.methods = {};
-}
-
-/**
- * Determine if the route handles a given method.
- * @private
- */
-
-Route.prototype._handles_method = function _handles_method(method) {
- if (this.methods._all) {
- return true;
- }
-
- // normalize name
- var name = typeof method === 'string'
- ? method.toLowerCase()
- : method
-
- if (name === 'head' && !this.methods['head']) {
- name = 'get';
- }
-
- return Boolean(this.methods[name]);
-};
-
-/**
- * @return {Array} supported HTTP methods
- * @private
- */
-
-Route.prototype._options = function _options() {
- var methods = Object.keys(this.methods);
-
- // append automatic head
- if (this.methods.get && !this.methods.head) {
- methods.push('head');
- }
-
- for (var i = 0; i < methods.length; i++) {
- // make upper case
- methods[i] = methods[i].toUpperCase();
- }
-
- return methods;
-};
-
-/**
- * dispatch req, res into this route
- * @private
- */
-
-Route.prototype.dispatch = function dispatch(req, res, done) {
- var idx = 0;
- var stack = this.stack;
- var sync = 0
-
- if (stack.length === 0) {
- return done();
- }
- var method = typeof req.method === 'string'
- ? req.method.toLowerCase()
- : req.method
-
- if (method === 'head' && !this.methods['head']) {
- method = 'get';
- }
-
- req.route = this;
-
- next();
-
- function next(err) {
- // signal to exit route
- if (err && err === 'route') {
- return done();
- }
-
- // signal to exit router
- if (err && err === 'router') {
- return done(err)
- }
-
- // max sync stack
- if (++sync > 100) {
- return setImmediate(next, err)
- }
-
- var layer = stack[idx++]
-
- // end of layers
- if (!layer) {
- return done(err)
- }
-
- if (layer.method && layer.method !== method) {
- next(err)
- } else if (err) {
- layer.handle_error(err, req, res, next);
- } else {
- layer.handle_request(req, res, next);
- }
-
- sync = 0
- }
-};
-
-/**
- * Add a handler for all HTTP verbs to this route.
- *
- * Behaves just like middleware and can respond or call `next`
- * to continue processing.
- *
- * You can use multiple `.all` call to add multiple handlers.
- *
- * function check_something(req, res, next){
- * next();
- * };
- *
- * function validate_user(req, res, next){
- * next();
- * };
- *
- * route
- * .all(validate_user)
- * .all(check_something)
- * .get(function(req, res, next){
- * res.send('hello world');
- * });
- *
- * @param {function} handler
- * @return {Route} for chaining
- * @api public
- */
-
-Route.prototype.all = function all() {
- var handles = Array.from(arguments).slice();
-
- for (var i = 0; i < handles.length; i++) {
- var handle = handles[i];
-
- if (typeof handle !== 'function') {
- var type = toString.call(handle);
- var msg = 'Route.all() requires a callback function but got a ' + type
- throw new TypeError(msg);
- }
-
- var layer = Layer('/', {}, handle);
- layer.method = undefined;
-
- this.methods._all = true;
- this.stack.push(layer);
- }
-
- return this;
-};
-
-methods.forEach(function(method){
- Route.prototype[method] = function(){
- var handles = Array.from(arguments).slice().flat(Infinity);
-
- for (var i = 0; i < handles.length; i++) {
- var handle = handles[i];
-
- if (typeof handle !== 'function') {
- var type = toString.call(handle);
- var msg = 'Route.' + method + '() requires a callback function but got a ' + type
- throw new Error(msg);
- }
-
- debug('%s %o', method, this.path)
-
- var layer = Layer('/', {}, handle);
- layer.method = method;
-
- this.methods[method] = true;
- this.stack.push(layer);
- }
-
- return this;
- };
-});
diff --git a/lib/utils.js b/lib/utils.js
index b71b09ce46a..df3335bee22 100644
--- a/lib/utils.js
+++ b/lib/utils.js
@@ -12,11 +12,10 @@
* @api private
*/
-var contentDisposition = require('content-disposition');
+var Buffer = require('safe-buffer').Buffer
var contentType = require('content-type');
-var deprecate = require('depd')('express');
-var mime = require('send').mime;
var etag = require('etag');
+var mime = require('mime-types')
var proxyaddr = require('proxy-addr');
var qs = require('qs');
var querystring = require('querystring');
@@ -43,20 +42,6 @@ exports.etag = createETagGenerator({ weak: false })
exports.wetag = createETagGenerator({ weak: true })
-/**
- * Check if `path` looks absolute.
- *
- * @param {String} path
- * @return {Boolean}
- * @api private
- */
-
-exports.isAbsolute = function(path){
- if ('/' === path[0]) return true;
- if (':' === path[1] && ('\\' === path[2] || '/' === path[2])) return true; // Windows device path
- if ('\\\\' === path.substring(0, 2)) return true; // Microsoft Azure absolute path
-};
-
/**
* Normalize the given `type`, for example "html" becomes "text/html".
*
@@ -68,7 +53,7 @@ exports.isAbsolute = function(path){
exports.normalizeType = function(type){
return ~type.indexOf('/')
? acceptParams(type)
- : { value: mime.lookup(type), params: {} };
+ : { value: (mime.lookup(type) || 'application/octet-stream'), params: {} }
};
/**
@@ -89,30 +74,19 @@ exports.normalizeTypes = function(types){
return ret;
};
-/**
- * Generate Content-Disposition header appropriate for the filename.
- * non-ascii filenames are urlencoded and a filename* parameter is added
- *
- * @param {String} filename
- * @return {String}
- * @api private
- */
-
-exports.contentDisposition = deprecate.function(contentDisposition,
- 'utils.contentDisposition: use content-disposition npm module instead');
-
/**
* Parse accept params `str` returning an
* object with `.value`, `.quality` and `.params`.
+ * also includes `.originalIndex` for stable sorting
*
* @param {String} str
* @return {Object}
* @api private
*/
-function acceptParams (str) {
+function acceptParams(str, index) {
var parts = str.split(/ *; */);
- var ret = { value: parts[0], quality: 1, params: {} }
+ var ret = { value: parts[0], quality: 1, params: {}, originalIndex: index };
for (var i = 1; i < parts.length; ++i) {
var pms = parts[i].split(/ *= */);
@@ -179,7 +153,6 @@ exports.compileQueryParser = function compileQueryParser(val) {
fn = querystring.parse;
break;
case false:
- fn = newObject;
break;
case 'extended':
fn = parseExtendedQueryString;
@@ -267,7 +240,6 @@ function createETagGenerator (options) {
/**
* Parse an extended query string with qs.
*
- * @param {String} str
* @return {Object}
* @private
*/
@@ -277,14 +249,3 @@ function parseExtendedQueryString(str) {
allowPrototypes: true
});
}
-
-/**
- * Return new empty object.
- *
- * @return {Object}
- * @api private
- */
-
-function newObject() {
- return {};
-}
diff --git a/lib/view.js b/lib/view.js
index c08ab4d8d52..1dca84e1143 100644
--- a/lib/view.js
+++ b/lib/view.js
@@ -74,7 +74,7 @@ function View(name, options) {
if (!opts.engines[this.ext]) {
// load engine
- var mod = this.ext.slice(1)
+ var mod = this.ext.substr(1)
debug('require "%s"', mod)
// default engine export
@@ -131,8 +131,31 @@ View.prototype.lookup = function lookup(name) {
*/
View.prototype.render = function render(options, callback) {
+ var sync = true;
+
debug('render "%s"', this.path);
- this.engine(this.path, options, callback);
+
+ // render, normalizing sync callbacks
+ this.engine(this.path, options, function onRender() {
+ if (!sync) {
+ return callback.apply(this, arguments);
+ }
+
+ // copy arguments
+ var args = new Array(arguments.length);
+ var cntx = this;
+
+ for (var i = 0; i < arguments.length; i++) {
+ args[i] = arguments[i];
+ }
+
+ // force callback to be async
+ return process.nextTick(function renderTick() {
+ return callback.apply(cntx, args);
+ });
+ });
+
+ sync = false;
};
/**
diff --git a/package-lock.json b/package-lock.json
index f0756793f71..a35099e3570 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,16 +1,16 @@
{
- "name": "express",
+ "name": "@hubot-friends/express",
"version": "5.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
- "name": "express",
+ "name": "@hubot-friends/express",
"version": "5.0.0",
"license": "MIT",
"dependencies": {
"accepts": "^1.3.8",
- "body-parser": "^1.20.2",
+ "body-parser": "^2.0.0-beta.2",
"content-disposition": "^0.5.4",
"content-type": "^1.0.5",
"cookie": "^0.6.0",
@@ -19,21 +19,22 @@
"depd": "^2.0.0",
"encodeurl": "^1.0.2",
"escape-html": "^1.0.3",
- "eslint": "^8.55.0",
"etag": "^1.8.1",
"finalhandler": "^1.2.0",
"fresh": "^0.5.2",
- "http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"methods": "^1.1.2",
+ "mime-types": "^2.1.34",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
- "path-to-regexp": "^6.2.1",
+ "path-is-absolute": "1.0.1",
"proxy-addr": "^2.0.7",
"qs": "^6.11.2",
"range-parser": "^1.2.1",
- "send": "^0.18.0",
- "serve-static": "^1.15.0",
+ "router": "2.0.0-beta.1",
+ "safe-buffer": "5.2.1",
+ "send": "^1.0.0-beta.1",
+ "serve-static": "2.0.0-beta.1",
"setprototypeof": "^1.2.0",
"statuses": "^2.0.1",
"type-is": "^1.6.18",
@@ -46,6 +47,7 @@
"cookie-parser": "^1.4.6",
"cookie-session": "^2.0.0",
"ejs": "^3.1.9",
+ "eslint": "^8.55.0",
"express-session": "^1.17.3",
"hbs": "^4.2.0",
"marked": "^11.0.0",
@@ -64,6 +66,7 @@
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
"integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -72,6 +75,7 @@
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
"integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
"dependencies": {
"eslint-visitor-keys": "^3.3.0"
},
@@ -86,6 +90,7 @@
"version": "4.10.0",
"resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
"integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+ "dev": true,
"engines": {
"node": "^12.0.0 || ^14.0.0 || >=16.0.0"
}
@@ -94,6 +99,7 @@
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
"integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
"dependencies": {
"ajv": "^6.12.4",
"debug": "^4.3.2",
@@ -112,10 +118,29 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/@eslint/eslintrc/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/@eslint/eslintrc/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/@eslint/js": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.55.0.tgz",
- "integrity": "sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.56.0.tgz",
+ "integrity": "sha512-gMsVel9D7f2HLkBma9VbtzZRehRogVRfbr++f06nL2vnCGCNlzOD+/MUov/F4p8myyAHspEhVobgjpX64q5m6A==",
+ "dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
@@ -124,6 +149,7 @@
"version": "0.11.13",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.13.tgz",
"integrity": "sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==",
+ "dev": true,
"dependencies": {
"@humanwhocodes/object-schema": "^2.0.1",
"debug": "^4.1.1",
@@ -137,6 +163,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
"integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
"engines": {
"node": ">=12.22"
},
@@ -148,12 +175,14 @@
"node_modules/@humanwhocodes/object-schema": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.1.tgz",
- "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw=="
+ "integrity": "sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==",
+ "dev": true
},
"node_modules/@nodelib/fs.scandir": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
"integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
"dependencies": {
"@nodelib/fs.stat": "2.0.5",
"run-parallel": "^1.1.9"
@@ -166,6 +195,7 @@
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
"integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
"engines": {
"node": ">= 8"
}
@@ -174,6 +204,7 @@
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
"integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
"dependencies": {
"@nodelib/fs.scandir": "2.1.5",
"fastq": "^1.6.0"
@@ -185,7 +216,8 @@
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
- "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ=="
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
},
"node_modules/accepts": {
"version": "1.3.8",
@@ -203,6 +235,7 @@
"version": "8.11.2",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
+ "dev": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -214,6 +247,7 @@
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
"integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
"peerDependencies": {
"acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
}
@@ -228,6 +262,7 @@
"version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
"dependencies": {
"fast-deep-equal": "^3.1.1",
"fast-json-stable-stringify": "^2.0.0",
@@ -243,6 +278,7 @@
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -251,6 +287,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
@@ -261,10 +298,10 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
- "node_modules/argparse": {
- "version": "2.0.1",
- "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
- "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ "node_modules/array-flatten": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-3.0.0.tgz",
+ "integrity": "sha512-zPMVc3ZYlGLNk4mpK1NzP2wg0ml9t7fUgDsayR5Y5rSzxQilzR9FGu/EH2jQOcKSAeAfWeylyW8juy3OkWRvNA=="
},
"node_modules/asap": {
"version": "2.0.6",
@@ -287,7 +324,8 @@
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
- "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
},
"node_modules/basic-auth": {
"version": "2.0.1",
@@ -308,40 +346,48 @@
"dev": true
},
"node_modules/body-parser": {
- "version": "1.20.2",
- "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.2.tgz",
- "integrity": "sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==",
+ "version": "2.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.0.0-beta.2.tgz",
+ "integrity": "sha512-oxdqeGYQcO5ovwwkC1A89R0Mf0v3+7smTVh0chGfzDeiK37bg5bYNtXDy3Nmzn6CShoIYk5+nHTyBoSZIWwnCA==",
"dependencies": {
"bytes": "3.1.2",
"content-type": "~1.0.5",
- "debug": "2.6.9",
- "depd": "2.0.0",
+ "debug": "3.1.0",
"destroy": "1.2.0",
"http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
+ "iconv-lite": "0.5.2",
"on-finished": "2.4.1",
"qs": "6.11.0",
- "raw-body": "2.5.2",
+ "raw-body": "3.0.0-beta.1",
"type-is": "~1.6.18",
"unpipe": "1.0.0"
},
"engines": {
- "node": ">= 0.8",
- "npm": "1.2.8000 || >= 1.4.16"
+ "node": ">= 0.10"
}
},
"node_modules/body-parser/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dependencies": {
"ms": "2.0.0"
}
},
- "node_modules/body-parser/node_modules/ms": {
+ "node_modules/body-parser/node_modules/http-errors": {
"version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
},
"node_modules/body-parser/node_modules/qs": {
"version": "6.11.0",
@@ -361,6 +407,7 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
@@ -391,6 +438,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
"engines": {
"node": ">=6"
}
@@ -399,6 +447,7 @@
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
"integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
"dependencies": {
"ansi-styles": "^4.1.0",
"supports-color": "^7.1.0"
@@ -414,6 +463,7 @@
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
@@ -424,7 +474,8 @@
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
- "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
},
"node_modules/combined-stream": {
"version": "1.0.8",
@@ -450,7 +501,8 @@
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
- "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
},
"node_modules/connect-redis": {
"version": "7.1.0",
@@ -543,6 +595,12 @@
"ms": "^2.1.1"
}
},
+ "node_modules/cookie-session/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
"node_modules/cookie-signature": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.1.tgz",
@@ -574,6 +632,7 @@
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
@@ -599,10 +658,16 @@
}
}
},
+ "node_modules/debug/node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
"node_modules/deep-is": {
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
- "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
},
"node_modules/define-data-property": {
"version": "1.1.1",
@@ -657,6 +722,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
"integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
"dependencies": {
"esutils": "^2.0.2"
},
@@ -701,6 +767,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
"engines": {
"node": ">=10"
},
@@ -709,14 +776,15 @@
}
},
"node_modules/eslint": {
- "version": "8.55.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.55.0.tgz",
- "integrity": "sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==",
+ "version": "8.56.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.56.0.tgz",
+ "integrity": "sha512-Go19xM6T9puCOWntie1/P997aXxFsOi37JIHRWI514Hc6ZnaHGKY9xFhrU65RT6CcBEzZoGG1e6Nq+DT04ZtZQ==",
+ "dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.4",
- "@eslint/js": "8.55.0",
+ "@eslint/js": "8.56.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -766,6 +834,7 @@
"version": "7.2.2",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
"integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
"dependencies": {
"esrecurse": "^4.3.0",
"estraverse": "^5.2.0"
@@ -781,6 +850,7 @@
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
"integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
},
@@ -788,10 +858,41 @@
"url": "https://opencollective.com/eslint"
}
},
+ "node_modules/eslint/node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true
+ },
+ "node_modules/eslint/node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/eslint/node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dev": true,
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
"node_modules/espree": {
"version": "9.6.1",
"resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
"integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
"dependencies": {
"acorn": "^8.9.0",
"acorn-jsx": "^5.3.2",
@@ -808,6 +909,7 @@
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
"integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
"dependencies": {
"estraverse": "^5.1.0"
},
@@ -819,6 +921,7 @@
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
"integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
"dependencies": {
"estraverse": "^5.2.0"
},
@@ -830,6 +933,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
"integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
"engines": {
"node": ">=4.0"
}
@@ -838,6 +942,7 @@
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
"integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -893,26 +998,23 @@
"ms": "2.0.0"
}
},
- "node_modules/express-session/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
- "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
},
"node_modules/fast-json-stable-stringify": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
- "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
},
"node_modules/fast-levenshtein": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
- "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
@@ -933,9 +1035,10 @@
}
},
"node_modules/fastq": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
- "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
+ "integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
+ "dev": true,
"dependencies": {
"reusify": "^1.0.4"
}
@@ -944,6 +1047,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
"integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
"dependencies": {
"flat-cache": "^3.0.4"
},
@@ -1006,15 +1110,11 @@
"ms": "2.0.0"
}
},
- "node_modules/finalhandler/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
- },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
"integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
"dependencies": {
"locate-path": "^6.0.0",
"path-exists": "^4.0.0"
@@ -1030,6 +1130,7 @@
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
"integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
"dependencies": {
"flatted": "^3.2.9",
"keyv": "^4.5.3",
@@ -1042,7 +1143,8 @@
"node_modules/flatted": {
"version": "3.2.9",
"resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.9.tgz",
- "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ=="
+ "integrity": "sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==",
+ "dev": true
},
"node_modules/foreachasync": {
"version": "3.0.0",
@@ -1098,7 +1200,8 @@
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
- "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
},
"node_modules/function-bind": {
"version": "1.1.2",
@@ -1123,14 +1226,15 @@
}
},
"node_modules/glob": {
- "version": "7.2.3",
- "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
- "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dev": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
- "minimatch": "^3.1.1",
+ "minimatch": "^3.0.4",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
@@ -1141,21 +1245,11 @@
"url": "https://github.com/sponsors/isaacs"
}
},
- "node_modules/glob-parent": {
- "version": "6.0.2",
- "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
- "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
- "dependencies": {
- "is-glob": "^4.0.3"
- },
- "engines": {
- "node": ">=10.13.0"
- }
- },
"node_modules/globals": {
"version": "13.24.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
"integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
"dependencies": {
"type-fest": "^0.20.2"
},
@@ -1180,7 +1274,8 @@
"node_modules/graphemer": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
- "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag=="
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
},
"node_modules/handlebars": {
"version": "4.7.7",
@@ -1207,6 +1302,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
"integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -1279,24 +1375,40 @@
}
},
"node_modules/http-errors": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
- "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "version": "1.8.1",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
+ "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"dependencies": {
- "depd": "2.0.0",
+ "depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
- "statuses": "2.0.1",
+ "statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.1"
},
"engines": {
- "node": ">= 0.8"
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-errors/node_modules/depd": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
+ "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/http-errors/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "engines": {
+ "node": ">= 0.6"
}
},
"node_modules/iconv-lite": {
- "version": "0.4.24",
- "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
- "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+ "version": "0.5.2",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.5.2.tgz",
+ "integrity": "sha512-kERHXvpSaB4aU3eANwidg79K8FlrN77m8G9V+0vOR3HYaRifrlwMEpT7ZBJqLSEIHnEgJTHcWK82wwLwwKwtag==",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3"
},
@@ -1308,6 +1420,7 @@
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.0.tgz",
"integrity": "sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==",
+ "dev": true,
"engines": {
"node": ">= 4"
}
@@ -1316,6 +1429,7 @@
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
"integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
"dependencies": {
"parent-module": "^1.0.0",
"resolve-from": "^4.0.0"
@@ -1331,6 +1445,7 @@
"version": "0.1.4",
"resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
"integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
"engines": {
"node": ">=0.8.19"
}
@@ -1339,6 +1454,7 @@
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
@@ -1361,6 +1477,7 @@
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
"engines": {
"node": ">=0.10.0"
}
@@ -1369,6 +1486,7 @@
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
"dependencies": {
"is-extglob": "^2.1.1"
},
@@ -1380,6 +1498,7 @@
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
"integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -1387,7 +1506,8 @@
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
- "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
},
"node_modules/jake": {
"version": "10.8.7",
@@ -1407,31 +1527,23 @@
"node": ">=10"
}
},
- "node_modules/js-yaml": {
- "version": "4.1.0",
- "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
- "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
- "dependencies": {
- "argparse": "^2.0.1"
- },
- "bin": {
- "js-yaml": "bin/js-yaml.js"
- }
- },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
- "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ=="
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
},
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
- "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
},
"node_modules/json-stable-stringify-without-jsonify": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
- "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
},
"node_modules/keygrip": {
"version": "1.1.0",
@@ -1449,6 +1561,7 @@
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
"integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
"dependencies": {
"json-buffer": "3.0.1"
}
@@ -1457,6 +1570,7 @@
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
"integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
"dependencies": {
"prelude-ls": "^1.2.1",
"type-check": "~0.4.0"
@@ -1469,6 +1583,7 @@
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
"integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
"dependencies": {
"p-locate": "^5.0.0"
},
@@ -1482,24 +1597,13 @@
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
- "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
- },
- "node_modules/lru-cache": {
- "version": "6.0.0",
- "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
- "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
- "dev": true,
- "dependencies": {
- "yallist": "^4.0.0"
- },
- "engines": {
- "node": ">=10"
- }
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
},
"node_modules/marked": {
- "version": "11.0.0",
- "resolved": "https://registry.npmjs.org/marked/-/marked-11.0.0.tgz",
- "integrity": "sha512-2GsW34uXaFEGTQ/+3rCnNC6vUYTAgFuDLGl70v/aWinA5mIJtTrrFAmfbLOfVvgPyxXuDVL9He/7reCK+6j3Sw==",
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-11.1.0.tgz",
+ "integrity": "sha512-fvKJWAPEafVj1dwGwcPI5mBB/0pvViL6NlCbNDG1HOIRwwAU/jeMoFxfbRLuirO1wRH7m4yPvBqD/O1wyWvayw==",
"dev": true,
"bin": {
"marked": "bin/marked.js"
@@ -1551,12 +1655,6 @@
"ms": "2.0.0"
}
},
- "node_modules/method-override/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
@@ -1566,14 +1664,15 @@
}
},
"node_modules/mime": {
- "version": "1.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz",
- "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==",
+ "version": "2.6.0",
+ "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
+ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
+ "dev": true,
"bin": {
"mime": "cli.js"
},
"engines": {
- "node": ">=4"
+ "node": ">=4.0.0"
}
},
"node_modules/mime-db": {
@@ -1599,6 +1698,7 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
@@ -1640,12 +1740,6 @@
"ms": "2.0.0"
}
},
- "node_modules/morgan/node_modules/ms": {
- "version": "2.0.0",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
- "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
- "dev": true
- },
"node_modules/morgan/node_modules/on-finished": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
@@ -1659,9 +1753,9 @@
}
},
"node_modules/ms": {
- "version": "2.1.2",
- "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
- "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
"node_modules/multiparty": {
"version": "4.2.3",
@@ -1677,44 +1771,11 @@
"node": ">= 0.10"
}
},
- "node_modules/multiparty/node_modules/depd": {
- "version": "1.1.2",
- "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
- "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/multiparty/node_modules/http-errors": {
- "version": "1.8.1",
- "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
- "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
- "dev": true,
- "dependencies": {
- "depd": "~1.1.2",
- "inherits": "2.0.4",
- "setprototypeof": "1.2.0",
- "statuses": ">= 1.5.0 < 2",
- "toidentifier": "1.0.1"
- },
- "engines": {
- "node": ">= 0.6"
- }
- },
- "node_modules/multiparty/node_modules/statuses": {
- "version": "1.5.0",
- "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
- "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
- "dev": true,
- "engines": {
- "node": ">= 0.6"
- }
- },
"node_modules/natural-compare": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
- "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
},
"node_modules/negotiator": {
"version": "0.6.3",
@@ -1762,6 +1823,7 @@
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
"dependencies": {
"wrappy": "1"
}
@@ -1770,6 +1832,7 @@
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
"integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "dev": true,
"dependencies": {
"@aashutoshrathi/word-wrap": "^1.2.3",
"deep-is": "^0.1.3",
@@ -1786,6 +1849,7 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
"integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
"dependencies": {
"yocto-queue": "^0.1.0"
},
@@ -1800,6 +1864,7 @@
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
"integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
"dependencies": {
"p-limit": "^3.0.2"
},
@@ -1814,6 +1879,7 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
"dependencies": {
"callsites": "^3.0.0"
},
@@ -1833,6 +1899,7 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
"integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -1849,14 +1916,15 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/path-to-regexp": {
- "version": "6.2.1",
- "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz",
- "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw=="
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-3.2.0.tgz",
+ "integrity": "sha512-jczvQbCUS7XmS7o+y1aEO9OBVFeZBQ1MDSEqmO7xSoPgOPoowY/SxLpZ6Vh97/8qHZOteiCKb7gkG9gA2ZUxJA=="
},
"node_modules/pbkdf2-password": {
"version": "1.2.1",
@@ -1871,6 +1939,7 @@
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
"integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
"engines": {
"node": ">= 0.8.0"
}
@@ -1891,6 +1960,7 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
"engines": {
"node": ">=6"
}
@@ -1913,6 +1983,7 @@
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
"integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -1946,23 +2017,39 @@
}
},
"node_modules/raw-body": {
- "version": "2.5.2",
- "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.2.tgz",
- "integrity": "sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA==",
+ "version": "3.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0-beta.1.tgz",
+ "integrity": "sha512-XlSTHr67bCjSo5aOfAnN3x507zGvi3unF65BW57limYkc2ws/XB0mLUtJvvP7JGFeSPsYrlCv1ZrPGh0cwDxPQ==",
"dependencies": {
"bytes": "3.1.2",
"http-errors": "2.0.0",
- "iconv-lite": "0.4.24",
+ "iconv-lite": "0.5.2",
"unpipe": "1.0.0"
},
"engines": {
"node": ">= 0.8"
}
},
+ "node_modules/raw-body/node_modules/http-errors": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
+ "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==",
+ "dependencies": {
+ "depd": "2.0.0",
+ "inherits": "2.0.4",
+ "setprototypeof": "1.2.0",
+ "statuses": "2.0.1",
+ "toidentifier": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
"engines": {
"node": ">=4"
}
@@ -1971,6 +2058,7 @@
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
"integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
"engines": {
"iojs": ">=1.0.0",
"node": ">=0.10.0"
@@ -1980,6 +2068,7 @@
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
"dependencies": {
"glob": "^7.1.3"
},
@@ -1990,10 +2079,27 @@
"url": "https://github.com/sponsors/isaacs"
}
},
+ "node_modules/router": {
+ "version": "2.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/router/-/router-2.0.0-beta.1.tgz",
+ "integrity": "sha512-GLoYgkhAGAiwVda5nt6Qd4+5RAPuQ4WIYLlZ+mxfYICI+22gnIB3eCfmhgV8+uJNPS1/39DOYi/vdrrz0/ouKA==",
+ "dependencies": {
+ "array-flatten": "3.0.0",
+ "methods": "~1.1.2",
+ "parseurl": "~1.3.3",
+ "path-to-regexp": "3.2.0",
+ "setprototypeof": "1.2.0",
+ "utils-merge": "1.0.1"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
"node_modules/run-parallel": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
"integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
"funding": [
{
"type": "github",
@@ -2051,33 +2157,50 @@
"node": ">=10"
}
},
+ "node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
"node_modules/send": {
- "version": "0.18.0",
- "resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
- "integrity": "sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==",
+ "version": "1.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/send/-/send-1.0.0-beta.1.tgz",
+ "integrity": "sha512-OKTRokcl/oo34O8+6aUpj8Jf2Bjw2D0tZzmX0/RvyfVC9ZOZW+HPAWAlhS817IsRaCnzYX1z++h2kHFr2/KNRg==",
"dependencies": {
- "debug": "2.6.9",
- "depd": "2.0.0",
- "destroy": "1.2.0",
+ "debug": "3.1.0",
+ "destroy": "~1.0.4",
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"etag": "~1.8.1",
"fresh": "0.5.2",
- "http-errors": "2.0.0",
- "mime": "1.6.0",
+ "http-errors": "1.8.1",
+ "mime-types": "~2.1.34",
"ms": "2.1.3",
- "on-finished": "2.4.1",
+ "on-finished": "~2.3.0",
"range-parser": "~1.2.1",
- "statuses": "2.0.1"
+ "statuses": "~1.5.0"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">= 0.10"
}
},
"node_modules/send/node_modules/debug": {
- "version": "2.6.9",
- "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
- "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
"dependencies": {
"ms": "2.0.0"
}
@@ -2087,23 +2210,47 @@
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="
},
+ "node_modules/send/node_modules/destroy": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz",
+ "integrity": "sha512-3NdhDuEXnfun/z7x9GOElY49LoqVHoGScmOKwmxhsS8N5Y+Z8KyPPDnaSzqWgYt/ji4mqwfTS34Htrk0zPIXVg=="
+ },
"node_modules/send/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
+ "node_modules/send/node_modules/on-finished": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz",
+ "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==",
+ "dependencies": {
+ "ee-first": "1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/send/node_modules/statuses": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
+ "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/serve-static": {
- "version": "1.15.0",
- "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.15.0.tgz",
- "integrity": "sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==",
+ "version": "2.0.0-beta.1",
+ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.0.0-beta.1.tgz",
+ "integrity": "sha512-DEJ9on/tQeFO2Omj7ovT02lCp1YgP4Kb8W2lv2o/4keTFAbgc8HtH3yPd47++2wv9lvQeqiA7FHFDe5+8c4XpA==",
"dependencies": {
"encodeurl": "~1.0.2",
"escape-html": "~1.0.3",
"parseurl": "~1.3.3",
- "send": "0.18.0"
+ "send": "1.0.0-beta.1"
},
"engines": {
- "node": ">= 0.8.0"
+ "node": ">= 0.10"
}
},
"node_modules/set-function-length": {
@@ -2129,6 +2276,7 @@
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
@@ -2140,6 +2288,7 @@
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
"engines": {
"node": ">=8"
}
@@ -2178,6 +2327,7 @@
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
@@ -2189,6 +2339,7 @@
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
"integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
"engines": {
"node": ">=8"
},
@@ -2217,18 +2368,6 @@
"node": ">=6.4.0 <13 || >=14"
}
},
- "node_modules/superagent/node_modules/mime": {
- "version": "2.6.0",
- "resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
- "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
- "dev": true,
- "bin": {
- "mime": "cli.js"
- },
- "engines": {
- "node": ">=4.0.0"
- }
- },
"node_modules/supertest": {
"version": "6.3.3",
"resolved": "https://registry.npmjs.org/supertest/-/supertest-6.3.3.tgz",
@@ -2246,6 +2385,7 @@
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
"integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
"dependencies": {
"has-flag": "^4.0.0"
},
@@ -2256,7 +2396,8 @@
"node_modules/text-table": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
- "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
},
"node_modules/toidentifier": {
"version": "1.0.1",
@@ -2279,6 +2420,7 @@
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
"integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
"dependencies": {
"prelude-ls": "^1.2.1"
},
@@ -2290,6 +2432,7 @@
"version": "0.20.2",
"resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
"integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
"engines": {
"node": ">=10"
},
@@ -2346,6 +2489,7 @@
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
"integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
"dependencies": {
"punycode": "^2.1.0"
}
@@ -2388,6 +2532,7 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
@@ -2407,18 +2552,14 @@
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
- "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
- },
- "node_modules/yallist": {
- "version": "4.0.0",
- "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
- "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"dev": true
},
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
"integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
"engines": {
"node": ">=10"
},
diff --git a/package.json b/package.json
index e4fec9e5cd7..2570ebd754e 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
],
"dependencies": {
"accepts": "^1.3.8",
- "body-parser": "^1.20.2",
+ "body-parser": "^2.0.0-beta.2",
"content-disposition": "^0.5.4",
"content-type": "^1.0.5",
"cookie": "^0.6.0",
@@ -41,21 +41,22 @@
"depd": "^2.0.0",
"encodeurl": "^1.0.2",
"escape-html": "^1.0.3",
- "eslint": "^8.55.0",
"etag": "^1.8.1",
"finalhandler": "^1.2.0",
"fresh": "^0.5.2",
- "http-errors": "^2.0.0",
"merge-descriptors": "^2.0.0",
"methods": "^1.1.2",
+ "mime-types": "^2.1.34",
"on-finished": "^2.4.1",
"parseurl": "^1.3.3",
- "path-to-regexp": "^6.2.1",
+ "path-is-absolute": "1.0.1",
"proxy-addr": "^2.0.7",
"qs": "^6.11.2",
"range-parser": "^1.2.1",
- "send": "^0.18.0",
- "serve-static": "^1.15.0",
+ "router": "2.0.0-beta.1",
+ "safe-buffer": "5.2.1",
+ "send": "^1.0.0-beta.1",
+ "serve-static": "2.0.0-beta.1",
"setprototypeof": "^1.2.0",
"statuses": "^2.0.1",
"type-is": "^1.6.18",
@@ -68,6 +69,7 @@
"cookie-parser": "^1.4.6",
"cookie-session": "^2.0.0",
"ejs": "^3.1.9",
+ "eslint": "^8.55.0",
"express-session": "^1.17.3",
"hbs": "^4.2.0",
"marked": "^11.0.0",
diff --git a/test/App.mjs b/test/App.mjs
index e6b3fe7c9d5..3cacbafa79c 100644
--- a/test/App.mjs
+++ b/test/App.mjs
@@ -57,18 +57,6 @@ describe('app.mountpath', () => {
})
})
-describe('app.router', () => {
- it('should throw with notice', (t, done) => {
- const app = express()
-
- try {
- app.router
- } catch(err) {
- done()
- }
- })
-})
-
describe('app.path()', () => {
it('should return the canonical', () => {
const app = express()
diff --git a/test/AppDel.mjs b/test/AppDel.mjs
deleted file mode 100644
index 9342499712e..00000000000
--- a/test/AppDel.mjs
+++ /dev/null
@@ -1,19 +0,0 @@
-'use strict'
-
-import { describe, it } from 'node:test'
-import express from '../lib/express.js'
-import request from 'supertest'
-
-describe('app.del()', () => {
- it('should alias app.delete()', (t, done) => {
- const app = express()
-
- app.del('/tobi', (req, res) => {
- res.end('deleted tobi!')
- })
-
- request(app)
- .del('/tobi')
- .expect('deleted tobi!', done)
- })
-})
diff --git a/test/AppOptions.mjs b/test/AppOptions.mjs
index ba897558561..deaa6c528ac 100644
--- a/test/AppOptions.mjs
+++ b/test/AppOptions.mjs
@@ -8,28 +8,28 @@ describe('OPTIONS', () => {
it('should default to the routes defined', (t, done) => {
const app = express()
- app.del('/', () => {})
+ app.delete('/', () => {})
app.get('/users', (req, res) => {})
app.put('/users', (req, res) => {})
request(app)
.options('/users')
- .expect('Allow', 'GET,HEAD,PUT')
- .expect(200, 'GET,HEAD,PUT', done)
+ .expect('Allow', 'GET, HEAD, PUT')
+ .expect(200, 'GET, HEAD, PUT', done)
})
it('should only include each method once', (t, done) => {
const app = express()
- app.del('/', () => {})
+ app.delete('/', () => {})
app.get('/users', (req, res) => {})
app.put('/users', (req, res) => {})
app.get('/users', (req, res) => {})
request(app)
.options('/users')
- .expect('Allow', 'GET,HEAD,PUT')
- .expect(200, 'GET,HEAD,PUT', done)
+ .expect('Allow', 'GET, HEAD, PUT')
+ .expect(200, 'GET, HEAD, PUT', done)
})
it('should not be affected by app.all', (t, done) => {
@@ -46,8 +46,8 @@ describe('OPTIONS', () => {
request(app)
.options('/users')
.expect('x-hit', '1')
- .expect('Allow', 'GET,HEAD,PUT')
- .expect(200, 'GET,HEAD,PUT', done)
+ .expect('Allow', 'GET, HEAD, PUT')
+ .expect(200, 'GET, HEAD, PUT', done)
})
it('should not respond if the path is not defined', (t, done) => {
@@ -70,8 +70,8 @@ describe('OPTIONS', () => {
request(app)
.options('/other')
- .expect('Allow', 'GET,HEAD')
- .expect(200, 'GET,HEAD', done)
+ .expect('Allow', 'GET, HEAD')
+ .expect(200, 'GET, HEAD', done)
})
describe('when error occurs in response handler', () => {
diff --git a/test/AppParam.mjs b/test/AppParam.mjs
index 171b97729c0..ad6eb6645df 100644
--- a/test/AppParam.mjs
+++ b/test/AppParam.mjs
@@ -6,47 +6,6 @@ import assert from 'node:assert'
import request from 'supertest'
describe('app', () => {
- describe('.param(fn)', () => {
- it('should map app.param(name, ...) logic', (t, done) => {
- const app = express()
-
- app.param((name, regexp) => {
- if (Object.prototype.toString.call(regexp) === '[object RegExp]') { // See #1557
- return function(req, res, next, val){
- var captures
- if (captures = regexp.exec(String(val))) {
- req.params[name] = captures[1]
- next()
- } else {
- next('route')
- }
- }
- }
- })
-
- app.param(':name', /^([a-zA-Z]+)$/)
-
- app.get('/user/:name', (req, res) => {
- res.send(req.params.name)
- })
-
- request(app)
- .get('/user/tj')
- .expect(200, 'tj', err => {
- if (err) return done(err)
- request(app)
- .get('/user/123')
- .expect(404, done)
- })
-
- })
-
- it('should fail if not given fn', () => {
- const app = express()
- assert.throws(app.param.bind(app, ':name', 'bob'))
- })
- })
-
describe('.param(names, fn)', () => {
it('should map the array', (t, done) => {
const app = express()
diff --git a/test/AppRouter.mjs b/test/AppRouter.mjs
index 2cf3b2dfeff..97557f5bde5 100644
--- a/test/AppRouter.mjs
+++ b/test/AppRouter.mjs
@@ -7,6 +7,8 @@ import request from 'supertest'
import after from 'after'
import methods from 'methods'
+const describePromises = global.Promise ? describe : describe.skip
+
describe('app.router', () => {
it('should restore req.params after leaving router', (t, done) => {
const app = express()
@@ -36,7 +38,7 @@ describe('app.router', () => {
})
describe('methods', () => {
- methods.concat('del').forEach(method => {
+ methods.forEach(method => {
if (method === 'connect') return
it('should include ' + method.toUpperCase(), (t, done) => {
@@ -53,7 +55,7 @@ describe('app.router', () => {
it('should reject numbers for app.' + method, () => {
const app = express()
- assert.throws(app[method].bind(app, '/', 3), /Number/)
+ assert.throws(app[method].bind(app, '/', 3), /argument handler must be a function/)
})
})
@@ -320,7 +322,7 @@ describe('app.router', () => {
res.send(keys.map(k => [k, req.params[k]] ))
})
- app.use('/user/id\\:(\\d+)', router)
+ app.use('/user/id:(\\d+)', router)
request(app)
.get('/user/id:10/profile.json')
@@ -336,7 +338,7 @@ describe('app.router', () => {
res.send(keys.map(k => [k, req.params[k]] ))
})
- app.use('/user/id\\:(\\d+)/name\\:(\\w+)', router)
+ app.use('/user/id:(\\d+)/name:(\\w+)', router)
request(app)
.get('/user/id:10/name:tj/profile')
@@ -347,12 +349,12 @@ describe('app.router', () => {
const app = express()
const router = new express.Router({ mergeParams: true })
- router.get('/name\\:(\\w+)', (req, res) => {
+ router.get('/name:(\\w+)', (req, res) => {
const keys = Object.keys(req.params).sort()
res.send(keys.map(k => [k, req.params[k]] ))
})
- app.use('/user/id\\:(\\d+)', router)
+ app.use('/user/id:(\\d+)', router)
request(app)
.get('/user/id:10/name:tj')
@@ -368,7 +370,7 @@ describe('app.router', () => {
res.send(keys.map(k => [k, req.params[k]] ))
})
- app.use('/user', (req, res, next) => {
+ app.use('/user/', (req, res, next) => {
req.params = 3; // wat?
router(req, res, next)
})
@@ -382,11 +384,11 @@ describe('app.router', () => {
const app = express()
const router = new express.Router({ mergeParams: true })
- router.get('/user\\:(\\w+)/(.*)', (req, res, next) => {
+ router.get('/user:(\\w+)/*', (req, res, next) => {
next()
})
- app.use('/user/id\\:(\\d+)', (req, res, next) => {
+ app.use('/user/id:(\\d+)', (req, res, next) => {
router(req, res, err => {
const keys = Object.keys(req.params).sort()
res.send(keys.map(k => [k, req.params[k]] ))
@@ -452,7 +454,7 @@ describe('app.router', () => {
app.enable('strict routing')
- app.use('/user', (req, res, next) => {
+ app.use('/user/', (req, res, next) => {
res.setHeader('x-middleware', 'true')
next()
})
@@ -481,7 +483,7 @@ describe('app.router', () => {
.expect('tj', done)
})
- it('should not match middleware when omitting the trailing slash', (t, done) => {
+ it('should match middleware when omitting the trailing slash', (t, done) => {
const app = express()
app.enable('strict routing')
@@ -492,7 +494,7 @@ describe('app.router', () => {
request(app)
.get('/user')
- .expect(404, done)
+ .expect(200, done)
})
it('should match middleware', (t, done) => {
@@ -553,23 +555,6 @@ describe('app.router', () => {
})
})
- it('should allow escaped regexp', (t, done) => {
- const app = express()
-
- app.get('/user/(\\d+)', (req, res) => {
- res.end('woot')
- })
-
- request(app)
- .get('/user/10')
- .expect(200, err => {
- if (err) return done(err)
- request(app)
- .get('/user/tj')
- .expect(404, done)
- })
- })
-
it('should allow literal "."', (t, done) => {
const app = express()
@@ -585,172 +570,6 @@ describe('app.router', () => {
.expect('users from 1 to 50', done)
})
- describe('*', () => {
- it('should capture everything', (t, done) => {
- const app = express()
-
- app.get('(.*)', (req, res) => {
- res.end(req.params[0])
- })
-
- request(app)
- .get('/user/tobi.json')
- .expect('/user/tobi.json', done)
- })
-
- it('should decode the capture', (t, done) => {
- const app = express()
-
- app.get('(.*)', (req, res) => {
- res.end(req.params[0])
- })
-
- request(app)
- .get('/user/tobi%20and%20loki.json')
- .expect('/user/tobi and loki.json', done)
- })
-
- it('should denote a greedy capture group', (t, done) => {
- const app = express()
-
- app.get('/user/(.*).json', (req, res) => {
- res.end(req.params[0])
- })
-
- request(app)
- .get('/user/tj.json')
- .expect('tj', done)
- })
-
- it('should work with several', (t, done) => {
- const app = express()
-
- app.get('/api/(.*).(.*)', (req, res) => {
- const resource = req.params[0]
- , format = req.params[1]
- res.end(resource + ' as ' + format)
- })
-
- request(app)
- .get('/api/users/foo.bar.json')
- .expect('users/foo.bar as json', done)
- })
-
- it('should work cross-segment', (t, done) => {
- const app = express()
- const cb = after(2, done)
-
- app.get('/api(.*)', (req, res) => {
- res.send(req.params[0])
- })
-
- request(app)
- .get('/api')
- .expect(200, '', cb)
-
- request(app)
- .get('/api/hey')
- .expect(200, '/hey', cb)
- })
-
- it('should allow naming', (t, done) => {
- const app = express()
-
- app.get('/api/:resource(.*)', (req, res) => {
- const resource = req.params.resource
- res.end(resource)
- })
-
- request(app)
- .get('/api/users/0.json')
- .expect('users/0.json', done)
- })
-
- it('should not be greedy immediately after param', (t, done) => {
- const app = express()
-
- app.get('/user/:user*', (req, res) => {
- res.end(req.params.user)
- })
-
- request(app)
- .get('/user/122')
- .expect('122', done)
- })
-
- it('should eat everything after /', (t, done) => {
- const app = express()
-
- app.get('/user/:user/:splat*', (req, res) => {
- res.end(req.params.user)
- })
-
- request(app)
- .get('/user/122/aaa')
- .expect('122', done)
- })
-
- it('should span multiple segments', (t, done) => {
- const app = express()
-
- app.get('/file/(.*)', (req, res) => {
- res.end(req.params[0])
- })
-
- request(app)
- .get('/file/javascripts/jquery.js')
- .expect('javascripts/jquery.js', done)
- })
-
- it('should be optional', (t, done) => {
- const app = express()
-
- app.get('/file/:splat*', (req, res) => {
- res.end(req.params[0])
- })
-
- request(app)
- .get('/file/')
- .expect('', done)
- })
-
- it('should require a preceding /', (t, done) => {
- const app = express()
-
- app.get('/file/(.*)', (req, res) => {
- res.end(req.params[0])
- })
-
- request(app)
- .get('/file')
- .expect(404, done)
- })
-
- it('should keep correct parameter indexes', (t, done) => {
- const app = express()
-
- app.get('/(.*)/user/:id', (req, res) => {
- res.send(req.params)
- })
-
- request(app)
- .get('/1/user/2')
- .expect(200, '{"0":"1","id":"2"}', done)
- })
-
- it('should work within arrays', (t, done) => {
- const app = express()
-
- app.get(['/user/:id', '/foo/:splat*', '/:bar'], (req, res) => {
- res.send(req.params.bar)
- })
-
- request(app)
- .get('/test')
- .expect(200, 'test', done)
- })
- })
-
describe(':name', () => {
it('should denote a capture group', (t, done) => {
const app = express()
@@ -792,7 +611,7 @@ describe('app.router', () => {
const app = express()
const cb = after(2, done)
- app.get('/user(s)?/:user/:op', (req, res) => {
+ app.get('/user(s?)/:user/:op', (req, res) => {
res.end(req.params.op + 'ing ' + req.params.user + (req.params[0] ? ' (old)' : ''))
})
@@ -863,6 +682,82 @@ describe('app.router', () => {
})
})
+ describe(':name*', () => {
+ it('should match one segment', (t, done) => {
+ const app = express()
+
+ app.get('/user/:user*', (req, res) => {
+ res.end(req.params.user)
+ })
+
+ request(app)
+ .get('/user/122')
+ .expect('122', done)
+ })
+
+ it('should match many segments', (t, done) => {
+ const app = express()
+
+ app.get('/user/:user*', (req, res) => {
+ res.end(req.params.user)
+ })
+
+ request(app)
+ .get('/user/1/2/3/4')
+ .expect('1/2/3/4', done)
+ })
+
+ it('should match zero segments', (t, done) => {
+ const app = express()
+
+ app.get('/user/:user*', (req, res) => {
+ res.end(req.params.user)
+ })
+
+ request(app)
+ .get('/user')
+ .expect('', done)
+ })
+ })
+
+ describe(':name+', () => {
+ it('should match one segment', (t, done) => {
+ const app = express()
+
+ app.get('/user/:user+', (req, res) => {
+ res.end(req.params.user)
+ })
+
+ request(app)
+ .get('/user/122')
+ .expect(200, '122', done)
+ })
+
+ it('should match many segments', (t, done) => {
+ const app = express()
+
+ app.get('/user/:user+', (req, res) => {
+ res.end(req.params.user)
+ })
+
+ request(app)
+ .get('/user/1/2/3/4')
+ .expect(200, '1/2/3/4', done)
+ })
+
+ it('should not match zero segments', (t, done) => {
+ const app = express()
+
+ app.get('/user/:user+', (req, res) => {
+ res.end(req.params.user)
+ })
+
+ request(app)
+ .get('/user')
+ .expect(404, done)
+ })
+ })
+
describe('.:name', () => {
it('should denote a format', (t, done) => {
const app = express()
@@ -911,7 +806,7 @@ describe('app.router', () => {
next()
})
- app.get('/bar', () => {
+ app.get('/bar', (req, res) => {
assert(0)
})
@@ -920,7 +815,7 @@ describe('app.router', () => {
next()
})
- app.get('/foo', (req, res) => {
+ app.get('/foo', (req, res, next) => {
calls.push('/foo 2')
res.json(calls)
})
@@ -940,7 +835,7 @@ describe('app.router', () => {
next('route')
}
- app.get('/foo', fn, (req, res) => {
+ app.get('/foo', fn, (req, res, next) => {
res.end('failure')
})
@@ -965,11 +860,11 @@ describe('app.router', () => {
next('router')
}
- router.get('/foo', fn, (req, res) => {
+ router.get('/foo', fn, (req, res, next) => {
res.end('failure')
})
- router.get('/foo', (req, res) => {
+ router.get('/foo', (req, res, next) => {
res.end('failure')
})
@@ -996,7 +891,7 @@ describe('app.router', () => {
next()
})
- app.get('/bar', () => {
+ app.get('/bar', (req, res) => {
assert(0)
})
@@ -1005,11 +900,11 @@ describe('app.router', () => {
next(new Error('fail'))
})
- app.get('/foo', () => {
+ app.get('/foo', (req, res, next) => {
assert(0)
})
- app.use((err, req, res, next) => {
+ app.use(function(err, req, res, next){
res.json({
calls: calls,
error: err.message
@@ -1048,6 +943,138 @@ describe('app.router', () => {
})
})
+ describePromises('promise support', () => {
+ it('should pass rejected promise value', (t, done) => {
+ const app = express()
+ const router = new express.Router()
+
+ router.use(function createError (req, res, next) {
+ return Promise.reject(new Error('boom!'))
+ })
+
+ router.use(function sawError (err, req, res, next) {
+ res.send('saw ' + err.name + ': ' + err.message)
+ })
+
+ app.use(router)
+
+ request(app)
+ .get('/')
+ .expect(200, 'saw Error: boom!', done)
+ })
+
+ it('should pass rejected promise without value', (t, done) => {
+ const app = express()
+ const router = new express.Router()
+
+ router.use(function createError (req, res, next) {
+ return Promise.reject()
+ })
+
+ router.use(function sawError (err, req, res, next) {
+ res.send('saw ' + err.name + ': ' + err.message)
+ })
+
+ app.use(router)
+
+ request(app)
+ .get('/')
+ .expect(200, 'saw Error: Rejected promise', done)
+ })
+
+ it('should ignore resolved promise', (t, done) => {
+ const app = express()
+ const router = new express.Router()
+
+ router.use(function createError (req, res, next) {
+ res.send('saw GET /foo')
+ return Promise.resolve('foo')
+ })
+
+ router.use(() => {
+ done(new Error('Unexpected middleware invoke'))
+ })
+
+ app.use(router)
+
+ request(app)
+ .get('/foo')
+ .expect(200, 'saw GET /foo', done)
+ })
+
+ describe('error handling', () => {
+ it('should pass rejected promise value', (t, done) => {
+ const app = express()
+ const router = new express.Router()
+
+ router.use(function createError (req, res, next) {
+ return Promise.reject(new Error('boom!'))
+ })
+
+ router.use(function handleError (err, req, res, next) {
+ return Promise.reject(new Error('caught: ' + err.message))
+ })
+
+ router.use(function sawError (err, req, res, next) {
+ res.send('saw ' + err.name + ': ' + err.message)
+ })
+
+ app.use(router)
+
+ request(app)
+ .get('/')
+ .expect(200, 'saw Error: caught: boom!', done)
+ })
+
+ it('should pass rejected promise without value', (t, done) => {
+ const app = express()
+ const router = new express.Router()
+
+ router.use(function createError (req, res, next) {
+ return Promise.reject()
+ })
+
+ router.use(function handleError (err, req, res, next) {
+ return Promise.reject(new Error('caught: ' + err.message))
+ })
+
+ router.use(function sawError (err, req, res, next) {
+ res.send('saw ' + err.name + ': ' + err.message)
+ })
+
+ app.use(router)
+
+ request(app)
+ .get('/')
+ .expect(200, 'saw Error: caught: Rejected promise', done)
+ })
+
+ it('should ignore resolved promise', (t, done) => {
+ const app = express()
+ const router = new express.Router()
+
+ router.use(function createError (req, res, next) {
+ return Promise.reject(new Error('boom!'))
+ })
+
+ router.use(function handleError (err, req, res, next) {
+ res.send('saw ' + err.name + ': ' + err.message)
+ return Promise.resolve('foo')
+ })
+
+ router.use(() => {
+ done(new Error('Unexpected middleware invoke'))
+ })
+
+ app.use(router)
+
+ request(app)
+ .get('/foo')
+ .expect(200, 'saw Error: boom!', done)
+ })
+ })
+ })
+
it('should allow rewriting of the url', (t, done) => {
const app = express()
@@ -1070,7 +1097,7 @@ describe('app.router', () => {
const app = express()
const path = []
- app.get('/:splat*', (req, res, next) => {
+ app.get('/:path+', (req, res, next) => {
path.push(0)
next()
})
@@ -1090,7 +1117,7 @@ describe('app.router', () => {
next()
})
- app.get('/:splat*', (req, res, next) => {
+ app.get('/(.*)', (req, res, next) => {
path.push(4)
next()
})
diff --git a/test/AppRoutesError.mjs b/test/AppRoutesError.mjs
index 20022364b1d..d8b0998dc2a 100644
--- a/test/AppRoutesError.mjs
+++ b/test/AppRoutesError.mjs
@@ -52,7 +52,7 @@ describe('app', () => {
assert.ok(b)
assert.ok(c)
assert.ok(!d)
- res.send(204)
+ res.sendStatus(204)
})
request(app)
diff --git a/test/AppUse.mjs b/test/AppUse.mjs
index 87828fcdd9c..4ca026d2bf8 100644
--- a/test/AppUse.mjs
+++ b/test/AppUse.mjs
@@ -264,22 +264,22 @@ describe('app', () => {
it('should reject string as middleware', () => {
const app = express()
- assert.throws(() => { app.use('/', 'foo') }, /requires a middleware function but got a string/)
+ assert.throws(() => { app.use('/', 'foo') }, /argument handler must be a function/)
})
it('should reject number as middleware', () => {
const app = express()
- assert.throws(() => { app.use('/', 42) }, /requires a middleware function but got a number/)
+ assert.throws(() => { app.use('/', 42) }, /argument handler must be a function/)
})
it('should reject null as middleware', () => {
const app = express()
- assert.throws(() => { app.use('/', null) }, /requires a middleware function but got a Null/)
+ assert.throws(() => { app.use('/', null) }, /argument handler must be a function/)
})
it('should reject Date as middleware', () => {
const app = express()
- assert.throws(() => { app.use('/', new Date()) }, /requires a middleware function but got a Date/)
+ assert.throws(() => { app.use('/', new Date()) }, /argument handler must be a function/)
})
it('should strip path from req.url', (t, done) => {
diff --git a/test/Config.mjs b/test/Config.mjs
index 46fbff6ea3b..c6664cb3999 100644
--- a/test/Config.mjs
+++ b/test/Config.mjs
@@ -12,12 +12,6 @@ describe('config', () => {
assert.equal(app.get('foo'), 'bar')
})
- it('should set prototype values', () => {
- const app = express()
- app.set('hasOwnProperty', 42)
- assert.strictEqual(app.get('hasOwnProperty'), 42)
- })
-
it('should return the app', () => {
const app = express()
assert.equal(app.set('foo', 'bar'), app)
@@ -28,17 +22,6 @@ describe('config', () => {
assert.equal(app.set('foo', undefined), app)
})
- it('should return set value', () => {
- const app = express()
- app.set('foo', 'bar')
- assert.strictEqual(app.set('foo'), 'bar')
- })
-
- it('should return undefined for prototype values', () => {
- const app = express()
- assert.strictEqual(app.set('hasOwnProperty'), undefined)
- })
-
describe('"etag"', () => {
it('should throw on bad value', () => {
const app = express()
@@ -69,11 +52,6 @@ describe('config', () => {
assert.strictEqual(app.get('foo'), undefined)
})
- it('should return undefined for prototype values', () => {
- const app = express()
- assert.strictEqual(app.get('hasOwnProperty'), undefined)
- })
-
it('should otherwise return the value', () => {
const app = express()
app.set('foo', 'bar')
@@ -148,12 +126,6 @@ describe('config', () => {
assert.equal(app.enable('tobi'), app)
assert.strictEqual(app.get('tobi'), true)
})
-
- it('should set prototype values', () => {
- const app = express()
- app.enable('hasOwnProperty')
- assert.strictEqual(app.get('hasOwnProperty'), true)
- })
})
describe('.disable()', () => {
@@ -162,12 +134,6 @@ describe('config', () => {
assert.equal(app.disable('tobi'), app)
assert.strictEqual(app.get('tobi'), false)
})
-
- it('should set prototype values', () => {
- const app = express()
- app.disable('hasOwnProperty')
- assert.strictEqual(app.get('hasOwnProperty'), false)
- })
})
describe('.enabled()', () => {
@@ -181,11 +147,6 @@ describe('config', () => {
app.set('foo', 'bar')
assert.strictEqual(app.enabled('foo'), true)
})
-
- it('should default to false for prototype values', () => {
- const app = express()
- assert.strictEqual(app.enabled('hasOwnProperty'), false)
- })
})
describe('.disabled()', () => {
@@ -199,10 +160,5 @@ describe('config', () => {
app.set('foo', 'bar')
assert.strictEqual(app.disabled('foo'), false)
})
-
- it('should default to true for prototype values', () => {
- const app = express()
- assert.strictEqual(app.disabled('hasOwnProperty'), true)
- })
})
})
diff --git a/test/Exports.mjs b/test/Exports.mjs
index ff9c9b289da..e34999c9ed9 100644
--- a/test/Exports.mjs
+++ b/test/Exports.mjs
@@ -80,9 +80,4 @@ describe('exports', () => {
.get('/')
.expect('bar', done)
})
-
- it('should throw on old middlewares', () => {
- assert.throws(() => { express.bodyParser() }, /Error:.*middleware.*bodyParser/)
- assert.throws(() => { express.limit() }, /Error:.*middleware.*limit/)
- })
})
diff --git a/test/ExpressJson.mjs b/test/ExpressJson.mjs
index 42afc57df12..0b04a2e757a 100644
--- a/test/ExpressJson.mjs
+++ b/test/ExpressJson.mjs
@@ -6,12 +6,6 @@ import request from 'supertest'
import assert from 'node:assert'
import tryImport from './support/TryImport.mjs'
-const asyncHooks = tryImport('async_hooks')
-
-const describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
- ? describe
- : describe.skip
-
describe('express.json()', () => {
it('should parse JSON', (t, done) => {
request(createApp())
@@ -45,14 +39,6 @@ describe('express.json()', () => {
.expect(200, '{}', done)
})
- it('should 400 when only whitespace', (t, done) => {
- request(createApp())
- .post('/')
- .set('Content-Type', 'application/json')
- .send(' \n')
- .expect(400, '[entity.parse.failed] ' + parseError(' '), done)
- })
-
it('should 400 when invalid content-length', (t, done) => {
const app = express()
@@ -74,32 +60,6 @@ describe('express.json()', () => {
.expect(400, /content length/, done)
})
- it('should 500 if stream not readable', (t, done) => {
- const app = express()
-
- app.use((req, res, next) => {
- req.on('end', next)
- req.resume()
- })
-
- app.use(express.json())
-
- app.use((err, req, res, next) => {
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- res.json(req.body)
- })
-
- request(app)
- .post('/')
- .set('Content-Type', 'application/json')
- .send('{"user":"tobi"}')
- .expect(500, '[stream.not.readable] stream is not readable', done)
- })
-
it('should handle duplicated middleware', (t, done) => {
const app = express()
@@ -128,7 +88,7 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send('{:')
- .expect(400, '[entity.parse.failed] ' + parseError('{:'), done)
+ .expect(400, parseError('{:'), done)
})
it('should 400 for incomplete', (t, done) => {
@@ -136,7 +96,16 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send('{"user"')
- .expect(400, '[entity.parse.failed] ' + parseError('{"user"'), done)
+ .expect(400, parseError('{"user"'), done)
+ })
+
+ it('should error with type = "entity.parse.failed"', (t, done) => {
+ request(app)
+ .post('/')
+ .set('Content-Type', 'application/json')
+ .set('X-Error-Property', 'type')
+ .send(' {"user"')
+ .expect(400, 'entity.parse.failed', done)
})
it('should include original body on error object', (t, done) => {
@@ -157,13 +126,24 @@ describe('express.json()', () => {
.set('Content-Type', 'application/json')
.set('Content-Length', '1034')
.send(JSON.stringify({ str: buf.toString() }))
- .expect(413, '[entity.too.large] request entity too large', done)
+ .expect(413, done)
+ })
+
+ it('should error with type = "entity.too.large"', (t, done) => {
+ const buf = Buffer.alloc(1024, '.')
+ request(createApp({ limit: '1kb' }))
+ .post('/')
+ .set('Content-Type', 'application/json')
+ .set('Content-Length', '1034')
+ .set('X-Error-Property', 'type')
+ .send(JSON.stringify({ str: buf.toString() }))
+ .expect(413, 'entity.too.large', done)
})
it('should 413 when over limit with chunked encoding', (t, done) => {
- const app = createApp({ limit: '1kb' })
const buf = Buffer.alloc(1024, '.')
- const test = request(app).post('/')
+ const server = createApp({ limit: '1kb' })
+ const test = request(server).post('/')
test.set('Content-Type', 'application/json')
test.set('Transfer-Encoding', 'chunked')
test.write('{"str":')
@@ -171,15 +151,6 @@ describe('express.json()', () => {
test.expect(413, done)
})
- it('should 413 when inflated body over limit', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/json')
- test.write(Buffer.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a040000', 'hex'))
- test.expect(413, done)
- })
-
it('should accept number of bytes', (t, done) => {
const buf = Buffer.alloc(1024, '.')
request(createApp({ limit: 1024 }))
@@ -192,11 +163,11 @@ describe('express.json()', () => {
it('should not change when options altered', (t, done) => {
const buf = Buffer.alloc(1024, '.')
const options = { limit: '1kb' }
- const app = createApp(options)
+ const server = createApp(options)
options.limit = '100kb'
- request(app)
+ request(server)
.post('/')
.set('Content-Type', 'application/json')
.send(JSON.stringify({ str: buf.toString() }))
@@ -205,23 +176,14 @@ describe('express.json()', () => {
it('should not hang response', (t, done) => {
const buf = Buffer.alloc(10240, '.')
- const app = createApp({ limit: '8kb' })
- const test = request(app).post('/')
+ const server = createApp({ limit: '8kb' })
+ const test = request(server).post('/')
test.set('Content-Type', 'application/json')
test.write(buf)
test.write(buf)
test.write(buf)
test.expect(413, done)
})
-
- it('should not error when inflating', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/json')
- test.write(Buffer.from('1f8b080000000000000aab562a2e2952b252d21b05a360148c58a0540b0066f7ce1e0a0400', 'hex'))
- test.expect(413, done)
- })
})
describe('with inflate option', () => {
@@ -236,7 +198,7 @@ describe('express.json()', () => {
test.set('Content-Encoding', 'gzip')
test.set('Content-Type', 'application/json')
test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
- test.expect(415, '[encoding.unsupported] content encoding unsupported', done)
+ test.expect(415, 'content encoding unsupported', done)
})
})
@@ -268,7 +230,7 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send('true')
- .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace(/#/g, 't'), done)
+ .expect(400, parseError('#rue').replaceAll('#', 't'), done)
})
})
@@ -298,7 +260,7 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send('true')
- .expect(400, '[entity.parse.failed] ' + parseError('#rue').replace(/#/g, 't'), done)
+ .expect(400, parseError('#rue').replaceAll('#', 't'), done)
})
it('should not parse primitives with leading whitespaces', (t, done) => {
@@ -306,7 +268,7 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send(' true')
- .expect(400, '[entity.parse.failed] ' + parseError(' #rue').replace(/#/g, 't'), done)
+ .expect(400, parseError(' #rue').replaceAll('#', 't'), done)
})
it('should allow leading whitespaces in JSON', (t, done) => {
@@ -317,6 +279,15 @@ describe('express.json()', () => {
.expect(200, '{"user":"tobi"}', done)
})
+ it('should error with type = "entity.parse.failed"', (t, done) => {
+ request(app)
+ .post('/')
+ .set('Content-Type', 'application/json')
+ .set('X-Error-Property', 'type')
+ .send('true')
+ .expect(400, 'entity.parse.failed', done)
+ })
+
it('should include correct message in stack trace', (t, done) => {
request(app)
.post('/')
@@ -324,7 +295,7 @@ describe('express.json()', () => {
.set('X-Error-Property', 'stack')
.send('true')
.expect(400)
- .expect(shouldContainInBody(parseError('#rue').replace(/#/g, 't')))
+ .expect(shouldContainInBody(parseError('#rue').replaceAll('#', 't')))
.end(done)
})
})
@@ -350,7 +321,7 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send('{"user":"tobi"}')
- .expect(200, '{}', done)
+ .expect(200, '', done)
})
})
@@ -383,7 +354,7 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/x-json')
.send('{"user":"tobi"}')
- .expect(200, '{}', done)
+ .expect(200, '', done)
})
})
@@ -391,7 +362,7 @@ describe('express.json()', () => {
it('should parse when truthy value returned', (t, done) => {
const app = createApp({ type: accept })
- function accept (req) {
+ function accept(req) {
return req.headers['content-type'] === 'application/vnd.api+json'
}
@@ -405,7 +376,7 @@ describe('express.json()', () => {
it('should work without content-type', (t, done) => {
const app = createApp({ type: accept })
- function accept (req) {
+ function accept(req) {
return true
}
@@ -417,7 +388,7 @@ describe('express.json()', () => {
it('should not invoke without a body', (t, done) => {
const app = createApp({ type: accept })
- function accept (req) {
+ function accept(req) {
throw new Error('oops!')
}
@@ -436,22 +407,37 @@ describe('express.json()', () => {
it('should error from verify', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
- if (buff[0] === 0x5b) throw new Error('no arrays')
+ verify: function (req, res, buf) {
+ if (buf[0] === 0x5b) throw new Error('no arrays')
+ }
+ })
+
+ request(app)
+ .post('/')
+ .set('Content-Type', 'application/json')
+ .send('["tobi"]')
+ .expect(403, 'no arrays', done)
+ })
+
+ it('should error with type = "entity.verify.failed"', (t, done) => {
+ const app = createApp({
+ verify: function (req, res, buf) {
+ if (buf[0] === 0x5b) throw new Error('no arrays')
}
})
request(app)
.post('/')
.set('Content-Type', 'application/json')
+ .set('X-Error-Property', 'type')
.send('["tobi"]')
- .expect(403, '[entity.verify.failed] no arrays', done)
+ .expect(403, 'entity.verify.failed', done)
})
it('should allow custom codes', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
- if (buff[0] !== 0x5b) return
+ verify: function (req, res, buf) {
+ if (buf[0] !== 0x5b) return
const err = new Error('no arrays')
err.status = 400
throw err
@@ -462,13 +448,13 @@ describe('express.json()', () => {
.post('/')
.set('Content-Type', 'application/json')
.send('["tobi"]')
- .expect(400, '[entity.verify.failed] no arrays', done)
+ .expect(400, 'no arrays', done)
})
it('should allow custom type', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
- if (buff[0] !== 0x5b) return
+ verify: function (req, res, buf) {
+ if (buf[0] !== 0x5b) return
const err = new Error('no arrays')
err.type = 'foo.bar'
throw err
@@ -478,14 +464,15 @@ describe('express.json()', () => {
request(app)
.post('/')
.set('Content-Type', 'application/json')
+ .set('X-Error-Property', 'type')
.send('["tobi"]')
- .expect(403, '[foo.bar] no arrays', done)
+ .expect(403, 'foo.bar', done)
})
it('should include original body on error object', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
- if (buff[0] === 0x5b) throw new Error('no arrays')
+ verify: function (req, res, buf) {
+ if (buf[0] === 0x5b) throw new Error('no arrays')
}
})
@@ -499,8 +486,8 @@ describe('express.json()', () => {
it('should allow pass-through', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
- if (buff[0] === 0x5b) throw new Error('no arrays')
+ verify: function (req, res, buf) {
+ if (buf[0] === 0x5b) throw new Error('no arrays')
}
})
@@ -513,8 +500,8 @@ describe('express.json()', () => {
it('should work with different charsets', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
- if (buff[0] === 0x5b) throw new Error('no arrays')
+ verify: function (req, res, buf) {
+ if (buf[0] === 0x5b) throw new Error('no arrays')
}
})
@@ -526,7 +513,7 @@ describe('express.json()', () => {
it('should 415 on unknown charset prior to verify', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
+ verify: function (req, res, buf) {
throw new Error('unexpected verify call')
}
})
@@ -534,111 +521,7 @@ describe('express.json()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/json; charset=x-bogus')
test.write(Buffer.from('00000000', 'hex'))
- test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done)
- })
- })
-
- describeAsyncHooks('async local storage', () => {
- before(() => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use(express.json())
-
- app.use((req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- next()
- })
-
- app.use((err, req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- res.json(req.body)
- })
-
- app = app
- })
-
- it('should presist store', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/json')
- .send('{"user":"tobi"}')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('{"user":"tobi"}')
- .end(done)
- })
-
- it('should presist store when unmatched content-type', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/fizzbuzz')
- .send('buzz')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('{}')
- .end(done)
- })
-
- it('should presist store when inflated', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/json')
- test.write(Buffer.from('1f8b080000000000000bab56ca4bcc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
- test.expect(200)
- test.expect('x-store-foo', 'bar')
- test.expect('{"name":"论"}')
- test.end(done)
- })
-
- it('should presist store when inflate error', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/json')
- test.write(Buffer.from('1f8b080000000000000bab56cc4d55b2527ab16e97522d00515be1cc0e000000', 'hex'))
- test.expect(400)
- test.expect('x-store-foo', 'bar')
- test.end(done)
- })
-
- it('should presist store when parse error', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/json')
- .send('{"user":')
- .expect(400)
- .expect('x-store-foo', 'bar')
- .end(done)
- })
-
- it('should presist store when limit exceeded', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/json')
- .send('{"user":"' + Buffer.alloc(1024 * 100, '.').toString() + '"}')
- .expect(413)
- .expect('x-store-foo', 'bar')
- .end(done)
+ test.expect(415, 'unsupported charset "X-BOGUS"', done)
})
})
@@ -681,7 +564,15 @@ describe('express.json()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/json; charset=koi8-r')
test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
- test.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done)
+ test.expect(415, 'unsupported charset "KOI8-R"', done)
+ })
+
+ it('should error with type = "charset.unsupported"', (t, done) => {
+ const test = request(app).post('/')
+ test.set('Content-Type', 'application/json; charset=koi8-r')
+ test.set('X-Error-Property', 'type')
+ test.write(Buffer.from('7b226e616d65223a22cec5d4227d', 'hex'))
+ test.expect(415, 'charset.unsupported', done)
})
})
@@ -735,7 +626,16 @@ describe('express.json()', () => {
test.set('Content-Encoding', 'nulls')
test.set('Content-Type', 'application/json')
test.write(Buffer.from('000000000000', 'hex'))
- test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done)
+ test.expect(415, 'unsupported content encoding "nulls"', done)
+ })
+
+ it('should error with type = "encoding.unsupported"', (t, done) => {
+ const test = request(app).post('/')
+ test.set('Content-Encoding', 'nulls')
+ test.set('Content-Type', 'application/json')
+ test.set('X-Error-Property', 'type')
+ test.write(Buffer.from('000000000000', 'hex'))
+ test.expect(415, 'encoding.unsupported', done)
})
it('should 400 on malformed encoding', (t, done) => {
@@ -759,16 +659,14 @@ describe('express.json()', () => {
})
})
-function createApp (options) {
+function createApp(options) {
const app = express()
app.use(express.json(options))
- app.use((err, req, res, next) => {
+ app.use(function (err, req, res, next) {
res.status(err.status || 500)
- res.send(String(req.headers['x-error-property']
- ? err[req.headers['x-error-property']]
- : ('[' + err.type + '] ' + err.message)))
+ res.send(String(err[req.headers['x-error-property'] || 'message']))
})
app.post('/', (req, res) => {
@@ -778,25 +676,18 @@ function createApp (options) {
return app
}
-function parseError (str) {
+function parseError(str) {
try {
- JSON.parse(str); throw new SyntaxError('strict violation')
+ JSON.parse(str)
+ throw new SyntaxError('strict violation')
} catch (e) {
return e.message
}
}
-function shouldContainInBody (str) {
+function shouldContainInBody(str) {
return function (res) {
assert.ok(res.text.indexOf(str) !== -1,
'expected \'' + res.text + '\' to contain \'' + str + '\'')
}
}
-
-function tryRequire (name) {
- try {
- return require(name)
- } catch (e) {
- return {}
- }
-}
diff --git a/test/ExpressRaw.mjs b/test/ExpressRaw.mjs
index e013616d2b2..4bfd5b1e824 100644
--- a/test/ExpressRaw.mjs
+++ b/test/ExpressRaw.mjs
@@ -5,20 +5,6 @@ import express from '../lib/express.js'
import request from 'supertest'
import assert from 'node:assert'
-async function tryImport (name) {
- try {
- return await import(name)
- } catch (e) {
- return {}
- }
-}
-
-const asyncHooks = tryImport('async_hooks')
-
-const describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
- ? describe
- : describe.skip
-
describe('express.raw()', () => {
let app = null
before(() => {
@@ -75,36 +61,6 @@ describe('express.raw()', () => {
.expect(200, { buf: '' }, done)
})
- it('should 500 if stream not readable', (t, done) => {
- const app = express()
-
- app.use((req, res, next) => {
- req.on('end', next)
- req.resume()
- })
-
- app.use(express.raw())
-
- app.use((err, req, res, next) => {
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- if (Buffer.isBuffer(req.body)) {
- res.json({ buf: req.body.toString('hex') })
- } else {
- res.json(req.body)
- }
- })
-
- request(app)
- .post('/')
- .set('Content-Type', 'application/octet-stream')
- .send('the user is tobi')
- .expect(500, '[stream.not.readable] stream is not readable', done)
- })
-
it('should handle duplicated middleware', (t, done) => {
const app = express()
@@ -147,15 +103,6 @@ describe('express.raw()', () => {
test.expect(413, done)
})
- it('should 413 when inflated body over limit', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/octet-stream')
- test.write(Buffer.from('1f8b080000000000000ad3d31b05a360148c64000087e5a14704040000', 'hex'))
- test.expect(413, done)
- })
-
it('should accept number of bytes', (t, done) => {
const buf = Buffer.alloc(1028, '.')
const app = createApp({ limit: 1024 })
@@ -188,15 +135,6 @@ describe('express.raw()', () => {
test.write(buf)
test.expect(413, done)
})
-
- it('should not error when inflating', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/octet-stream')
- test.write(Buffer.from('1f8b080000000000000ad3d31b05a360148c64000087e5a147040400', 'hex'))
- test.expect(413, done)
- })
})
describe('with inflate option', () => {
@@ -211,7 +149,7 @@ describe('express.raw()', () => {
test.set('Content-Encoding', 'gzip')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
- test.expect(415, '[encoding.unsupported] content encoding unsupported', done)
+ test.expect(415, 'content encoding unsupported', done)
})
})
@@ -249,7 +187,7 @@ describe('express.raw()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('000102', 'hex'))
- test.expect(200, '{}', done)
+ test.expect(200, '', done)
})
})
@@ -279,7 +217,7 @@ describe('express.raw()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/x-foo')
test.write(Buffer.from('000102', 'hex'))
- test.expect(200, '{}', done)
+ test.expect(200, '', done)
})
})
@@ -339,7 +277,7 @@ describe('express.raw()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('000102', 'hex'))
- test.expect(403, '[entity.verify.failed] no leading null', done)
+ test.expect(403, 'no leading null', done)
})
it('should allow custom codes', (t, done) => {
@@ -355,7 +293,7 @@ describe('express.raw()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('000102', 'hex'))
- test.expect(400, '[entity.verify.failed] no leading null', done)
+ test.expect(400, 'no leading null', done)
})
it('should allow pass-through', (t, done) => {
@@ -372,104 +310,6 @@ describe('express.raw()', () => {
})
})
- describeAsyncHooks('async local storage', () => {
- before(() => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use(express.raw())
-
- app.use((req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- next()
- })
-
- app.use((err, req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- if (Buffer.isBuffer(req.body)) {
- res.json({ buf: req.body.toString('hex') })
- } else {
- res.json(req.body)
- }
- })
-
- app = app
- })
-
- it('should presist store', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/octet-stream')
- .send('the user is tobi')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect({ buf: '746865207573657220697320746f6269' })
- .end(done)
- })
-
- it('should presist store when unmatched content-type', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/fizzbuzz')
- .send('buzz')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('{}')
- .end(done)
- })
-
- it('should presist store when inflated', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/octet-stream')
- test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
- test.expect(200)
- test.expect('x-store-foo', 'bar')
- test.expect({ buf: '6e616d653de8aeba' })
- test.end(done)
- })
-
- it('should presist store when inflate error', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/octet-stream')
- test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad6080000', 'hex'))
- test.expect(400)
- test.expect('x-store-foo', 'bar')
- test.end(done)
- })
-
- it('should presist store when limit exceeded', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/octet-stream')
- .send('the user is ' + Buffer.alloc(1024 * 100, '.').toString())
- .expect(413)
- .expect('x-store-foo', 'bar')
- .end(done)
- })
- })
-
describe('charset', () => {
let app = null
before(() => {
@@ -534,7 +374,7 @@ describe('express.raw()', () => {
test.set('Content-Encoding', 'nulls')
test.set('Content-Type', 'application/octet-stream')
test.write(Buffer.from('000000000000', 'hex'))
- test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done)
+ test.expect(415, 'unsupported content encoding "nulls"', done)
})
})
})
@@ -546,9 +386,7 @@ function createApp (options) {
app.use((err, req, res, next) => {
res.status(err.status || 500)
- res.send(String(req.headers['x-error-property']
- ? err[req.headers['x-error-property']]
- : ('[' + err.type + '] ' + err.message)))
+ res.send(String(err[req.headers['x-error-property'] || 'message']))
})
app.post('/', (req, res) => {
@@ -561,11 +399,3 @@ function createApp (options) {
return app
}
-
-function tryRequire (name) {
- try {
- return require(name)
- } catch (e) {
- return {}
- }
-}
diff --git a/test/ExpressStatic.mjs b/test/ExpressStatic.mjs
index 88d7c6116db..f2828a92c95 100644
--- a/test/ExpressStatic.mjs
+++ b/test/ExpressStatic.mjs
@@ -3,7 +3,7 @@
import { describe, it, before } from 'node:test'
import express from '../lib/express.js'
import request from 'supertest'
-import assert from 'node:assert'
+import assert from 'node:assert/strict'
import utils from './support/Utils.mjs'
import path from 'node:path'
@@ -46,7 +46,7 @@ describe('express.static()', () => {
it('should set Content-Type', (t, done) => {
request(app)
.get('/todo.txt')
- .expect('Content-Type', 'text/plain; charset=UTF-8')
+ .expect('Content-Type', 'text/plain; charset=utf-8')
.expect(200, done)
})
@@ -685,12 +685,18 @@ describe('express.static()', () => {
.expect(416, done)
})
- it('should include a Content-Range header of complete length', (t, done) => {
+ it('should not include a Content-Range header for Range Not Satisfiable', (t, done) => {
request(app)
.get('/nums.txt')
.set('Range', 'bytes=9-50')
- .expect('Content-Range', 'bytes */9')
- .expect(416, done)
+ .expect(res => {
+ assert.deepEqual(res.headers['content-range'], undefined)
+ assert.deepEqual(res.statusCode, 416)
+ assert.match(res.text, /Range Not Satisfiable/)
+ })
+ .end(err => {
+ done(err)
+ })
})
})
diff --git a/test/ExpressText.mjs b/test/ExpressText.mjs
index 64292b3bb85..ddf3277527c 100644
--- a/test/ExpressText.mjs
+++ b/test/ExpressText.mjs
@@ -5,20 +5,6 @@ import express from '../lib/express.js'
import request from 'supertest'
import assert from 'node:assert'
-async function tryImport (name) {
- try {
- return await import(name)
- } catch (e) {
- return {}
- }
-}
-
-const asyncHooks = tryImport('async_hooks')
-
-const describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
- ? describe
- : describe.skip
-
describe('express.text()', () => {
it('should parse text/plain', (t, done) => {
const app = createApp()
@@ -67,32 +53,6 @@ describe('express.text()', () => {
.expect(200, '""', done)
})
- it('should 500 if stream not readable', (t, done) => {
- const app = express()
-
- app.use((req, res, next) => {
- req.on('end', next)
- req.resume()
- })
-
- app.use(express.text())
-
- app.use((err, req, res, next) => {
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- res.json(req.body)
- })
-
- request(app)
- .post('/')
- .set('Content-Type', 'text/plain')
- .send('user is tobi')
- .expect(500, '[stream.not.readable] stream is not readable', done)
- })
-
it('should handle duplicated middleware', (t, done) => {
const app = express()
@@ -149,15 +109,6 @@ describe('express.text()', () => {
test.expect(413, done)
})
- it('should 413 when inflated body over limit', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'text/plain')
- test.write(Buffer.from('1f8b080000000000000ad3d31b05a360148c64000087e5a14704040000', 'hex'))
- test.expect(413, done)
- })
-
it('should accept number of bytes', (t, done) => {
const buf = Buffer.alloc(1028, '.')
request(createApp({ limit: 1024 }))
@@ -191,17 +142,6 @@ describe('express.text()', () => {
test.write(buf)
test.expect(413, done)
})
-
- it('should not error when inflating', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'text/plain')
- test.write(Buffer.from('1f8b080000000000000ad3d31b05a360148c64000087e5a1470404', 'hex'))
- setTimeout(() => {
- test.expect(413, done)
- }, 100)
- })
})
describe('with inflate option', () => {
@@ -216,7 +156,7 @@ describe('express.text()', () => {
test.set('Content-Encoding', 'gzip')
test.set('Content-Type', 'text/plain')
test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b000000', 'hex'))
- test.expect(415, '[encoding.unsupported] content encoding unsupported', done)
+ test.expect(415, 'content encoding unsupported', done)
})
})
@@ -256,7 +196,7 @@ describe('express.text()', () => {
.post('/')
.set('Content-Type', 'text/plain')
.send('user is tobi')
- .expect(200, '{}', done)
+ .expect(200, '', done)
})
})
@@ -287,7 +227,7 @@ describe('express.text()', () => {
.post('/')
.set('Content-Type', 'text/xml')
.send('tobi')
- .expect(200, '{}', done)
+ .expect(200, '', done)
})
})
@@ -347,7 +287,7 @@ describe('express.text()', () => {
.post('/')
.set('Content-Type', 'text/plain')
.send(' user is tobi')
- .expect(403, '[entity.verify.failed] no leading space', done)
+ .expect(403, 'no leading space', done)
})
it('should allow custom codes', (t, done) => {
@@ -362,7 +302,7 @@ describe('express.text()', () => {
.post('/')
.set('Content-Type', 'text/plain')
.send(' user is tobi')
- .expect(400, '[entity.verify.failed] no leading space', done)
+ .expect(400, 'no leading space', done)
})
it('should allow pass-through', (t, done) => {
@@ -389,101 +329,7 @@ describe('express.text()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'text/plain; charset=x-bogus')
test.write(Buffer.from('00000000', 'hex'))
- test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done)
- })
- })
-
- describeAsyncHooks('async local storage', () => {
- before(() => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use(express.text())
-
- app.use((req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- next()
- })
-
- app.use((err, req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- res.json(req.body)
- })
-
- app = app
- })
-
- it('should presist store', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'text/plain')
- .send('user is tobi')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('"user is tobi"')
- .end(done)
- })
-
- it('should presist store when unmatched content-type', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/fizzbuzz')
- .send('buzz')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('{}')
- .end(done)
- })
-
- it('should presist store when inflated', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'text/plain')
- test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b000000', 'hex'))
- test.expect(200)
- test.expect('x-store-foo', 'bar')
- test.expect('"name is 论"')
- test.end(done)
- })
-
- it('should presist store when inflate error', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'text/plain')
- test.write(Buffer.from('1f8b080000000000000bcb4bcc4d55c82c5678b16e170072b3e0200b0000', 'hex'))
- test.expect(400)
- test.expect('x-store-foo', 'bar')
- test.end(done)
- })
-
- it('should presist store when limit exceeded', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'text/plain')
- .send('user is ' + Buffer.alloc(1024 * 100, '.').toString())
- .expect(413)
- .expect('x-store-foo', 'bar')
- .end(done)
+ test.expect(415, 'unsupported charset "X-BOGUS"', done)
})
})
@@ -526,7 +372,7 @@ describe('express.text()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'text/plain; charset=x-bogus')
test.write(Buffer.from('00000000', 'hex'))
- test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done)
+ test.expect(415, 'unsupported charset "X-BOGUS"', done)
})
})
@@ -580,7 +426,7 @@ describe('express.text()', () => {
test.set('Content-Encoding', 'nulls')
test.set('Content-Type', 'text/plain')
test.write(Buffer.from('000000000000', 'hex'))
- test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done)
+ test.expect(415, 'unsupported content encoding "nulls"', done)
})
})
})
@@ -592,9 +438,7 @@ function createApp (options) {
app.use((err, req, res, next) => {
res.status(err.status || 500)
- res.send(String(req.headers['x-error-property']
- ? err[req.headers['x-error-property']]
- : ('[' + err.type + '] ' + err.message)))
+ res.send(err.message)
})
app.post('/', (req, res) => {
diff --git a/test/ExpressUrlencoded.mjs b/test/ExpressUrlencoded.mjs
index bd3519f5774..f053e958129 100644
--- a/test/ExpressUrlencoded.mjs
+++ b/test/ExpressUrlencoded.mjs
@@ -5,23 +5,10 @@ import express from '../lib/express.js'
import request from 'supertest'
import assert from 'node:assert'
-async function tryImport (name) {
- try {
- return await import(name)
- } catch (e) {
- return {}
- }
-}
-
-const asyncHooks = tryImport('async_hooks')
-const describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
- ? describe
- : describe.skip
-
describe('express.urlencoded()', () => {
let app = null
before(() => {
- app = createApp()
+ app = createApp({extended: true})
})
it('should parse x-www-form-urlencoded', (t, done) => {
@@ -71,32 +58,6 @@ describe('express.urlencoded()', () => {
.expect(200, '{}', done)
})
- it('should 500 if stream not readable', (t, done) => {
- const app = express()
-
- app.use((req, res, next) => {
- req.on('end', next)
- req.resume()
- })
-
- app.use(express.urlencoded())
-
- app.use((err, req, res, next) => {
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- res.json(req.body)
- })
-
- request(app)
- .post('/')
- .set('Content-Type', 'application/x-www-form-urlencoded')
- .send('user=tobi')
- .expect(500, '[stream.not.readable] stream is not readable', done)
- })
-
it('should handle duplicated middleware', (t, done) => {
const app = express()
@@ -257,7 +218,7 @@ describe('express.urlencoded()', () => {
test.set('Content-Encoding', 'gzip')
test.set('Content-Type', 'application/x-www-form-urlencoded')
test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
- test.expect(415, '[encoding.unsupported] content encoding unsupported', done)
+ test.expect(415, 'content encoding unsupported', done)
})
})
@@ -298,15 +259,6 @@ describe('express.urlencoded()', () => {
test.expect(413, done)
})
- it('should 413 when inflated body over limit', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/x-www-form-urlencoded')
- test.write(Buffer.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f9204040000', 'hex'))
- test.expect(413, done)
- })
-
it('should accept number of bytes', (t, done) => {
const buff = Buffer.alloc(1024, '.')
request(createApp({ limit: 1024 }))
@@ -340,15 +292,6 @@ describe('express.urlencoded()', () => {
test.write(buff)
test.expect(413, done)
})
-
- it('should not error when inflating', (t, done) => {
- const app = createApp({ limit: '1kb' })
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/x-www-form-urlencoded')
- test.write(Buffer.from('1f8b080000000000000a2b2e29b2d51b05a360148c580000a0351f92040400', 'hex'))
- test.expect(413, done)
- })
})
describe('with parameterLimit option', () => {
@@ -368,7 +311,16 @@ describe('express.urlencoded()', () => {
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send(createManyParams(11))
- .expect(413, '[parameters.too.many] too many parameters', done)
+ .expect(413, /too many parameters/, done)
+ })
+
+ it('should error with type = "parameters.too.many"', (t, done) => {
+ request(createApp({ extended: false, parameterLimit: 10 }))
+ .post('/')
+ .set('Content-Type', 'application/x-www-form-urlencoded')
+ .set('X-Error-Property', 'type')
+ .send(createManyParams(11))
+ .expect(413, 'parameters.too.many', done)
})
it('should work when at the limit', (t, done) => {
@@ -423,7 +375,16 @@ describe('express.urlencoded()', () => {
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send(createManyParams(11))
- .expect(413, '[parameters.too.many] too many parameters', done)
+ .expect(413, /too many parameters/, done)
+ })
+
+ it('should error with type = "parameters.too.many"', (t, done) => {
+ request(createApp({ extended: true, parameterLimit: 10 }))
+ .post('/')
+ .set('Content-Type', 'application/x-www-form-urlencoded')
+ .set('X-Error-Property', 'type')
+ .send(createManyParams(11))
+ .expect(413, 'parameters.too.many', done)
})
it('should work when at the limit', (t, done) => {
@@ -482,7 +443,7 @@ describe('express.urlencoded()', () => {
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send('user=tobi')
- .expect(200, '{}', done)
+ .expect(200, '', done)
})
})
@@ -514,7 +475,7 @@ describe('express.urlencoded()', () => {
.post('/')
.set('Content-Type', 'application/x-foo')
.send('user=tobi')
- .expect(200, '{}', done)
+ .expect(200, '', done)
})
})
@@ -576,31 +537,30 @@ describe('express.urlencoded()', () => {
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
.send(' user=tobi')
- .expect(403, '[entity.verify.failed] no leading space', done)
+ .expect(403, 'no leading space', done)
})
- it('should allow custom codes', (t, done) => {
- const app = createApp({
- verify(req, res, buff) {
- if (buff[0] !== 0x20) return
- const err = new Error('no leading space')
- err.status = 400
- throw err
- }
+ it('should error with type = "entity.verify.failed"', (t, done) => {
+ const app = createApp(
+ {
+ verify (req, res, buff) {
+ if (buff[0] === 0x20) throw new Error('no leading space')
+ }
})
request(app)
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
+ .set('X-Error-Property', 'type')
.send(' user=tobi')
- .expect(400, '[entity.verify.failed] no leading space', done)
+ .expect(403, 'entity.verify.failed', done)
})
it('should allow custom type', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
+ verify (req, res, buff) {
if (buff[0] !== 0x20) return
- const err = new Error('no leading space')
+ var err = new Error('no leading space')
err.type = 'foo.bar'
throw err
}
@@ -609,13 +569,14 @@ describe('express.urlencoded()', () => {
request(app)
.post('/')
.set('Content-Type', 'application/x-www-form-urlencoded')
+ .set('X-Error-Property', 'type')
.send(' user=tobi')
- .expect(403, '[foo.bar] no leading space', done)
+ .expect(403, 'foo.bar', done)
})
it('should allow pass-through', (t, done) => {
const app = createApp({
- verify(req, res, buff) {
+ verify (req, res, buff) {
if (buff[0] === 0x5b) throw new Error('no arrays')
}
})
@@ -637,101 +598,7 @@ describe('express.urlencoded()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/x-www-form-urlencoded; charset=x-bogus')
test.write(Buffer.from('00000000', 'hex'))
- test.expect(415, '[charset.unsupported] unsupported charset "X-BOGUS"', done)
- })
- })
-
- describeAsyncHooks('async local storage', () => {
- before(() => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use(express.urlencoded())
-
- app.use((req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- next()
- })
-
- app.use((err, req, res, next) => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.status(err.status || 500)
- res.send('[' + err.type + '] ' + err.message)
- })
-
- app.post('/', (req, res) => {
- res.json(req.body)
- })
-
- app = app
- })
-
- it('should presist store', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/x-www-form-urlencoded')
- .send('user=tobi')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('{"user":"tobi"}')
- .end(done)
- })
-
- it('should presist store when unmatched content-type', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/fizzbuzz')
- .send('buzz')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('{}')
- .end(done)
- })
-
- it('should presist store when inflated', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/x-www-form-urlencoded')
- test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad608000000', 'hex'))
- test.expect(200)
- test.expect('x-store-foo', 'bar')
- test.expect('{"name":"论"}')
- test.end(done)
- })
-
- it('should presist store when inflate error', (t, done) => {
- const test = request(app).post('/')
- test.set('Content-Encoding', 'gzip')
- test.set('Content-Type', 'application/x-www-form-urlencoded')
- test.write(Buffer.from('1f8b080000000000000bcb4bcc4db57db16e170099a4bad6080000', 'hex'))
- test.expect(400)
- test.expect('x-store-foo', 'bar')
- test.end(done)
- })
-
- it('should presist store when limit exceeded', (t, done) => {
- request(app)
- .post('/')
- .set('Content-Type', 'application/x-www-form-urlencoded')
- .send('user=' + Buffer.alloc(1024 * 100, '.').toString())
- .expect(413)
- .expect('x-store-foo', 'bar')
- .end(done)
+ test.expect(415, 'unsupported charset "X-BOGUS"', done)
})
})
@@ -766,7 +633,7 @@ describe('express.urlencoded()', () => {
const test = request(app).post('/')
test.set('Content-Type', 'application/x-www-form-urlencoded; charset=koi8-r')
test.write(Buffer.from('6e616d653dcec5d4', 'hex'))
- test.expect(415, '[charset.unsupported] unsupported charset "KOI8-R"', done)
+ test.expect(415, 'unsupported charset "KOI8-R"', done)
})
})
@@ -814,12 +681,12 @@ describe('express.urlencoded()', () => {
test.expect(200, '{"name":"论"}', done)
})
- it('should 415 on unknown encoding', (t, done) => {
+ it('should fail on unknown encoding', (t, done) => {
const test = request(app).post('/')
test.set('Content-Encoding', 'nulls')
test.set('Content-Type', 'application/x-www-form-urlencoded')
test.write(Buffer.from('000000000000', 'hex'))
- test.expect(415, '[encoding.unsupported] unsupported content encoding "nulls"', done)
+ test.expect(415, 'unsupported content encoding "nulls"', done)
})
})
})
@@ -848,9 +715,7 @@ function createApp (options) {
app.use((err, req, res, next) => {
res.status(err.status || 500)
- res.send(String(req.headers['x-error-property']
- ? err[req.headers['x-error-property']]
- : ('[' + err.type + '] ' + err.message)))
+ res.send(String(err[req.headers['x-error-property'] || 'message']))
})
app.post('/', (req, res) => {
diff --git a/test/ReqAcceptsCharset.mjs b/test/ReqAcceptsCharset.mjs
index 15ab1b09083..b04f19ab8cc 100644
--- a/test/ReqAcceptsCharset.mjs
+++ b/test/ReqAcceptsCharset.mjs
@@ -5,13 +5,13 @@ import express from '../lib/express.js'
import request from 'supertest'
describe('req', () => {
- describe('.acceptsCharset(type)', () => {
+ describe('.acceptsCharsets(type)', () => {
describe('when Accept-Charset is not present', () => {
it('should return true', (t, done) => {
const app = express()
app.use((req, res, next) => {
- res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no')
+ res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no')
})
request(app)
@@ -25,7 +25,7 @@ describe('req', () => {
const app = express()
app.use((req, res, next) => {
- res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no')
+ res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no')
})
request(app)
@@ -38,7 +38,7 @@ describe('req', () => {
const app = express()
app.use((req, res, next) => {
- res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no')
+ res.end(req.acceptsCharsets('utf-8') ? 'yes' : 'no')
})
request(app)
diff --git a/test/ReqAcceptsEncoding.mjs b/test/ReqAcceptsEncoding.mjs
index 0e07f009c8c..b99083ca0d4 100644
--- a/test/ReqAcceptsEncoding.mjs
+++ b/test/ReqAcceptsEncoding.mjs
@@ -11,8 +11,8 @@ describe('req', () => {
app.get('/', (req, res) => {
res.send({
- gzip: req.acceptsEncoding('gzip'),
- deflate: req.acceptsEncoding('deflate')
+ gzip: req.acceptsEncodings('gzip'),
+ deflate: req.acceptsEncodings('deflate')
})
})
@@ -27,7 +27,7 @@ describe('req', () => {
app.get('/', (req, res) => {
res.send({
- bogus: req.acceptsEncoding('bogus')
+ bogus: req.acceptsEncodings('bogus')
})
})
diff --git a/test/ReqAcceptsLanguage.mjs b/test/ReqAcceptsLanguage.mjs
index 1ca783ff8fd..f5703adf062 100644
--- a/test/ReqAcceptsLanguage.mjs
+++ b/test/ReqAcceptsLanguage.mjs
@@ -5,14 +5,14 @@ import express from '../lib/express.js'
import request from 'supertest'
describe('req', () => {
- describe('.acceptsLanguage', () => {
+ describe('.acceptsLanguages', () => {
it('should return language if accepted', (t, done) => {
const app = express()
app.get('/', (req, res) => {
res.send({
- 'en-us': req.acceptsLanguage('en-us'),
- en: req.acceptsLanguage('en')
+ 'en-us': req.acceptsLanguages('en-us'),
+ en: req.acceptsLanguages('en')
})
})
@@ -27,7 +27,7 @@ describe('req', () => {
app.get('/', (req, res) => {
res.send({
- es: req.acceptsLanguage('es')
+ es: req.acceptsLanguages('es')
})
})
@@ -43,9 +43,9 @@ describe('req', () => {
app.get('/', (req, res) => {
res.send({
- en: req.acceptsLanguage('en'),
- es: req.acceptsLanguage('es'),
- jp: req.acceptsLanguage('jp')
+ en: req.acceptsLanguages('en'),
+ es: req.acceptsLanguages('es'),
+ jp: req.acceptsLanguages('jp')
})
})
diff --git a/test/ReqHost.mjs b/test/ReqHost.mjs
index f6e871a97fc..ace9bccc69d 100644
--- a/test/ReqHost.mjs
+++ b/test/ReqHost.mjs
@@ -29,7 +29,7 @@ describe('req', () => {
request(app)
.post('/')
.set('Host', 'example.com:3000')
- .expect('example.com', done)
+ .expect('example.com:3000', done)
})
it('should return undefined otherwise', (t, done) => {
@@ -68,7 +68,7 @@ describe('req', () => {
request(app)
.post('/')
.set('Host', '[::1]:3000')
- .expect('[::1]', done)
+ .expect('[::1]:3000', done)
})
describe('when "trust proxy" is enabled', () => {
diff --git a/test/ReqIs.mjs b/test/ReqIs.mjs
index 81c9ba5b8ae..bcc9db72175 100644
--- a/test/ReqIs.mjs
+++ b/test/ReqIs.mjs
@@ -43,7 +43,7 @@ describe('req.is()', () => {
request(app)
.post('/')
- .type('application/json; charset=UTF-8')
+ .type('application/json; charset=utf-8')
.send('{}')
.expect(200, '"application/json"', done)
})
@@ -118,7 +118,7 @@ describe('req.is()', () => {
request(app)
.post('/')
- .type('application/json; charset=UTF-8')
+ .type('application/json; charset=utf-8')
.send('{}')
.expect(200, '"application/json"', done)
})
@@ -162,7 +162,7 @@ describe('req.is()', () => {
request(app)
.post('/')
- .type('application/json; charset=UTF-8')
+ .type('application/json; charset=utf-8')
.send('{}')
.expect(200, '"application/json"', done)
})
diff --git a/test/ReqParam.mjs b/test/ReqParam.mjs
deleted file mode 100644
index 1fadcdbbc98..00000000000
--- a/test/ReqParam.mjs
+++ /dev/null
@@ -1,62 +0,0 @@
-'use strict'
-
-import { describe, it } from 'node:test'
-import express from '../lib/express.js'
-import request from 'supertest'
-
-describe('req', () => {
- describe('.param(name, default)', () => {
- it('should use the default value unless defined', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.end(req.param('name', 'tj'))
- })
-
- request(app)
- .get('/')
- .expect('tj', done)
- })
- })
-
- describe('.param(name)', () => {
- it('should check req.query', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.end(req.param('name'))
- })
-
- request(app)
- .get('/?name=tj')
- .expect('tj', done)
- })
-
- it('should check req.body', (t, done) => {
- const app = express()
-
- app.use(express.json())
-
- app.use((req, res) => {
- res.end(req.param('name'))
- })
-
- request(app)
- .post('/')
- .send({ name: 'tj' })
- .expect('tj', done)
- })
-
- it('should check req.params', (t, done) => {
- const app = express()
-
- app.get('/user/:name', (req, res) => {
- res.end(req.param('filter') + req.param('name'))
- })
-
- request(app)
- .get('/user/tj')
- .expect('undefinedtj', done)
- })
- })
-})
diff --git a/test/ReqQuery.mjs b/test/ReqQuery.mjs
index 7a8b41064b9..b8a0b87f882 100644
--- a/test/ReqQuery.mjs
+++ b/test/ReqQuery.mjs
@@ -15,17 +15,17 @@ describe('req', () => {
.expect(200, '{}', done)
})
- it('should default to parse complex keys', (t, done) => {
+ it('should default to parse simple keys', (t, done) => {
const app = createApp()
request(app)
.get('/?user[name]=tj')
- .expect(200, '{"user":{"name":"tj"}}', done)
+ .expect(200, '{"user[name]":"tj"}', done)
})
describe('when "query parser" is extended', () => {
it('should parse complex keys', (t, done) => {
- const app = createApp('extended')
+ const app = createApp('extended');
request(app)
.get('/?foo[0][bar]=baz&foo[0][fizz]=buzz&foo[]=done!')
@@ -83,23 +83,6 @@ describe('req', () => {
})
})
- describe('when "query parser fn" is missing', () => {
- it('should act like "extended"', (t, done) => {
- const app = express()
-
- delete app.settings['query parser']
- delete app.settings['query parser fn']
-
- app.use((req, res) => {
- res.send(req.query)
- })
-
- request(app)
- .get('/?user[name]=tj&user.name=tj')
- .expect(200, '{"user":{"name":"tj"},"user.name":"tj"}', done)
- })
- })
-
describe('when "query parser" an unknown value', () => {
it('should throw', () => {
assert.throws(createApp.bind(null, 'bogus'),
diff --git a/test/ResCookie.mjs b/test/ResCookie.mjs
index 5d7c6eb91bc..479b2e3db6a 100644
--- a/test/ResCookie.mjs
+++ b/test/ResCookie.mjs
@@ -68,21 +68,6 @@ describe('res', () => {
.expect(200, done)
})
- describe('expires', () => {
- it('should throw on invalid date', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { expires: new Date(NaN) })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect(500, /option expires is invalid/, done)
- })
- })
-
describe('maxAge', () => {
it('should set relative expires', (t, done) => {
const app = express()
@@ -127,36 +112,6 @@ describe('res', () => {
.expect(200, optionsCopy, done)
})
- it('should not throw on null', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { maxAge: null })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Set-Cookie', 'name=tobi; Path=/')
- .end(done)
- })
-
- it('should not throw on undefined', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { maxAge: undefined })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Set-Cookie', 'name=tobi; Path=/')
- .end(done)
- })
-
it('should throw an error with invalid maxAge', (t, done) => {
const app = express()
@@ -171,63 +126,6 @@ describe('res', () => {
})
})
- describe('priority', () => {
- it('should set low priority', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { priority: 'low' })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect('Set-Cookie', /Priority=Low/)
- .expect(200, done)
- })
-
- it('should set medium priority', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { priority: 'medium' })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect('Set-Cookie', /Priority=Medium/)
- .expect(200, done)
- })
-
- it('should set high priority', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { priority: 'high' })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect('Set-Cookie', /Priority=High/)
- .expect(200, done)
- })
-
- it('should throw with invalid priority', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.cookie('name', 'tobi', { priority: 'foobar' })
- res.end()
- })
-
- request(app)
- .get('/')
- .expect(500, /option priority is invalid/, done)
- })
- })
-
describe('signed', () => {
it('should generate a signed JSON cookie', (t, done) => {
const app = express()
diff --git a/test/ResDownload.mjs b/test/ResDownload.mjs
index 08d1c9226b0..f8cf42c056d 100644
--- a/test/ResDownload.mjs
+++ b/test/ResDownload.mjs
@@ -1,32 +1,11 @@
'use strict'
import { describe, it } from 'node:test'
-import path from 'node:path'
import express from '../lib/express.js'
import request from 'supertest'
import assert from 'node:assert'
import after from 'after'
-import utils from './support/Utils.mjs'
-const __dirname = path.dirname(new URL(import.meta.url).pathname)
-
-async function tryImport (name) {
- try {
- return await import(name)
- } catch (e) {
- return {}
- }
-}
-
-const asyncHooks = tryImport('async_hooks')
-
-const FIXTURES_PATH = path.join(__dirname, 'fixtures')
-
-const describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
- ? describe
- : describe.skip
-
-
describe('res', () => {
describe('.download(path)', () => {
it('should transfer as an attachment', (t, done) => {
@@ -38,7 +17,7 @@ describe('res', () => {
request(app)
.get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
+ .expect('Content-Type', 'text/html; charset=utf-8')
.expect('Content-Disposition', 'attachment; filename="user.html"')
.expect(200, '{{user.name}}
', done)
})
@@ -81,7 +60,7 @@ describe('res', () => {
request(app)
.get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
+ .expect('Content-Type', 'text/html; charset=utf-8')
.expect('Content-Disposition', 'attachment; filename="document"')
.expect(200, done)
})
@@ -98,276 +77,10 @@ describe('res', () => {
request(app)
.get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
+ .expect('Content-Type', 'text/html; charset=utf-8')
.expect('Content-Disposition', 'attachment; filename="user.html"')
.expect(200, cb)
})
-
- describeAsyncHooks('async local storage', () => {
- it('should presist store', (t, done) => {
- const app = express()
- const cb = after(2, done)
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use((req, res) => {
- res.download('test/fixtures/name.txt', err => {
- if (err) return cb(err)
-
- const local = req.asyncLocalStorage.getStore()
-
- assert.strictEqual(local.foo, 'bar')
- cb()
- })
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/plain; charset=UTF-8')
- .expect('Content-Disposition', 'attachment; filename="name.txt"')
- .expect(200, 'tobi', cb)
- })
-
- it('should presist store on error', (t, done) => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use((req, res) => {
- res.download('test/fixtures/does-not-exist', err => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.send(err ? 'got ' + err.status + ' error' : 'no error')
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('got 404 error')
- .end(done)
- })
- })
- })
-
- describe('.download(path, options)', () => {
- it('should allow options to res.sendFile()', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/.name', {
- dotfiles: 'allow',
- maxAge: '4h'
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Disposition', 'attachment; filename=".name"')
- .expect('Cache-Control', 'public, max-age=14400')
- .expect(utils.shouldHaveBody(Buffer.from('tobi')))
- .end(done)
- })
-
- describe('with "headers" option', () => {
- it('should set headers on response', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/user.html', {
- headers: {
- 'X-Foo': 'Bar',
- 'X-Bar': 'Foo'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('X-Foo', 'Bar')
- .expect('X-Bar', 'Foo')
- .end(done)
- })
-
- it('should use last header when duplicated', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/user.html', {
- headers: {
- 'X-Foo': 'Bar',
- 'x-foo': 'bar'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('X-Foo', 'bar')
- .end(done)
- })
-
- it('should override Content-Type', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/user.html', {
- headers: {
- 'Content-Type': 'text/x-custom'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Type', 'text/x-custom')
- .end(done)
- })
-
- it('should not set headers on 404', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/does-not-exist', {
- headers: {
- 'X-Foo': 'Bar'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(404)
- .expect(utils.shouldNotHaveHeader('X-Foo'))
- .end(done)
- })
-
- describe('when headers contains Content-Disposition', () => {
- it('should be ignored', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/user.html', {
- headers: {
- 'Content-Disposition': 'inline'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Disposition', 'attachment; filename="user.html"')
- .end(done)
- })
-
- it('should be ignored case-insensitively', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('test/fixtures/user.html', {
- headers: {
- 'content-disposition': 'inline'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Disposition', 'attachment; filename="user.html"')
- .end(done)
- })
- })
- })
-
- describe('with "root" option', () => {
- it('should allow relative path', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('name.txt', {
- root: FIXTURES_PATH
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Disposition', 'attachment; filename="name.txt"')
- .expect(utils.shouldHaveBody(Buffer.from('tobi')))
- .end(done)
- })
-
- it('should allow up within root', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('fake/../name.txt', {
- root: FIXTURES_PATH
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Disposition', 'attachment; filename="name.txt"')
- .expect(utils.shouldHaveBody(Buffer.from('tobi')))
- .end(done)
- })
-
- it('should reject up outside root', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- const p = '..' + path.sep +
- path.relative(path.dirname(FIXTURES_PATH), path.join(FIXTURES_PATH, 'name.txt'))
-
- res.download(p, {
- root: FIXTURES_PATH
- })
- })
-
- request(app)
- .get('/')
- .expect(403)
- .expect(utils.shouldNotHaveHeader('Content-Disposition'))
- .end(done)
- })
-
- it('should reject reading outside root', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.download('../name.txt', {
- root: FIXTURES_PATH
- })
- })
-
- request(app)
- .get('/')
- .expect(403)
- .expect(utils.shouldNotHaveHeader('Content-Disposition'))
- .end(done)
- })
- })
})
describe('.download(path, filename, fn)', () => {
@@ -381,7 +94,7 @@ describe('res', () => {
request(app)
.get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
+ .expect('Content-Type', 'text/html; charset=utf-8')
.expect('Content-Disposition', 'attachment; filename="document"')
.expect(200, cb)
})
@@ -400,7 +113,7 @@ describe('res', () => {
request(app)
.get('/')
.expect(200)
- .expect('Content-Type', 'text/html; charset=UTF-8')
+ .expect('Content-Type', 'text/html; charset=utf-8')
.expect('Content-Disposition', 'attachment; filename="document"')
.end(cb)
})
@@ -420,7 +133,7 @@ describe('res', () => {
.expect(200)
.expect('Content-Disposition', 'attachment; filename="document"')
.expect('Cache-Control', 'public, max-age=14400')
- .expect(utils.shouldHaveBody(Buffer.from('tobi')))
+ .expect(shouldHaveBody(Buffer.from('tobi')))
.end(done)
})
@@ -487,7 +200,7 @@ describe('res', () => {
const app = express()
app.use((req, res, next) => {
- res.download('test/fixtures/foobar.html', function(err){
+ res.download('test/fixtures/foobar.html', err => {
if (!err) return next(new Error('expected error'))
res.end('failed')
})
@@ -495,8 +208,24 @@ describe('res', () => {
request(app)
.get('/')
- .expect(utils.shouldNotHaveHeader('Content-Disposition'))
+ .expect(shouldNotHaveHeader('Content-Disposition'))
.expect(200, 'failed', done)
})
})
})
+
+function shouldHaveBody (buf) {
+ return res => {
+ var body = !Buffer.isBuffer(res.body)
+ ? Buffer.from(res.text)
+ : res.body
+ assert.ok(body, 'response has body')
+ assert.strictEqual(body.toString('hex'), buf.toString('hex'))
+ }
+}
+
+function shouldNotHaveHeader(header) {
+ return res => {
+ assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header)
+ }
+}
diff --git a/test/ResFormat.mjs b/test/ResFormat.mjs
index cb264c255fd..217ba903132 100644
--- a/test/ResFormat.mjs
+++ b/test/ResFormat.mjs
@@ -29,7 +29,8 @@ app1.use((req, res, next) => {
app1.use((err, req, res, next) => {
if (!err.types) throw err
- res.send(err.status, 'Supports: ' + err.types.join(', '))
+ res.status(err.status)
+ res.send('Supports: ' + err.types.join(', '))
})
const app2 = express()
@@ -43,7 +44,8 @@ app2.use((req, res, next) => {
})
app2.use((err, req, res, next) => {
- res.send(err.status, 'Supports: ' + err.types.join(', '))
+ res.status(err.status)
+ res.send('Supports: ' + err.types.join(', '))
})
const app3 = express()
@@ -51,12 +53,7 @@ const app3 = express()
app3.use((req, res, next) => {
res.format({
text: () => { res.send('hey') },
- default: function (a, b, c) {
- assert(req === a)
- assert(res === b)
- assert(next === c)
- res.send('default')
- }
+ default: () => { res.send('default') }
})
})
@@ -71,7 +68,8 @@ app4.get('/', (req, res) => {
})
app4.use((err, req, res, next) => {
- res.send(err.status, 'Supports: ' + err.types.join(', '))
+ res.status(err.status)
+ res.send('Supports: ' + err.types.join(', '))
})
const app5 = express()
@@ -104,7 +102,8 @@ describe('res', () => {
})
app.use((err, req, res, next) => {
- res.send(err.status, 'Supports: ' + err.types.join(', '))
+ res.status(err.status)
+ res.send('Supports: ' + err.types.join(', '))
})
test(app)
@@ -124,28 +123,6 @@ describe('res', () => {
.set('Accept', '*/*')
.expect('hey', done)
})
-
- it('should be able to invoke other formatter', (t, done) => {
- const app = express()
-
- app.use((req, res, next) => {
- res.format({
- json () { res.send('json') },
- default () {
- res.header('x-default', '1')
- this.json()
- }
- })
- })
-
- request(app)
- .get('/')
- .set('Accept', 'text/plain')
- .expect(200)
- .expect('x-default', '1')
- .expect('json')
- .end(done)
- })
})
describe('in router', () => {
@@ -156,7 +133,7 @@ describe('res', () => {
const app = express()
const router = express.Router()
- router.get('/', (req, res) => {
+ router.get('/', (req, res, next) => {
res.format({
text: () => { res.send('hey') },
html: () => { res.send('hey
') },
@@ -165,7 +142,8 @@ describe('res', () => {
})
router.use((err, req, res, next) => {
- res.send(err.status, 'Supports: ' + err.types.join(', '))
+ res.status(err.status)
+ res.send('Supports: ' + err.types.join(', '))
})
app.use(router)
diff --git a/test/ResJson.mjs b/test/ResJson.mjs
index 138723ed8fd..ba4989d1bff 100644
--- a/test/ResJson.mjs
+++ b/test/ResJson.mjs
@@ -184,47 +184,4 @@ describe('res', () => {
})
})
})
-
- describe('.json(status, object)', () => {
- it('should respond with json and set the .statusCode', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.json(201, { id: 1 })
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'application/json; charset=utf-8')
- .expect(201, '{"id":1}', done)
- })
- })
-
- describe('.json(object, status)', () => {
- it('should respond with json and set the .statusCode for backwards compat', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.json({ id: 1 }, 201)
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'application/json; charset=utf-8')
- .expect(201, '{"id":1}', done)
- })
-
- it('should use status as second number for backwards compat', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.json(200, 201)
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'application/json; charset=utf-8')
- .expect(201, '200', done)
- })
- })
})
diff --git a/test/ResJsonp.mjs b/test/ResJsonp.mjs
index d1b563cd050..3c6f94972f1 100644
--- a/test/ResJsonp.mjs
+++ b/test/ResJsonp.mjs
@@ -329,49 +329,6 @@ describe('res', () => {
})
})
- describe('.jsonp(status, object)', () => {
- it('should respond with json and set the .statusCode', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.jsonp(201, { id: 1 })
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'application/json; charset=utf-8')
- .expect(201, '{"id":1}', done)
- })
- })
-
- describe('.jsonp(object, status)', () => {
- it('should respond with json and set the .statusCode for backwards compat', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.jsonp({ id: 1 }, 201)
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'application/json; charset=utf-8')
- .expect(201, '{"id":1}', done)
- })
-
- it('should use status as second number for backwards compat', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.jsonp(200, 201)
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'application/json; charset=utf-8')
- .expect(201, '200', done)
- })
- })
-
it('should not override previous Content-Types', (t, done) => {
const app = express()
diff --git a/test/ResRedirect.mjs b/test/ResRedirect.mjs
index debe7d5eec8..c976301d684 100644
--- a/test/ResRedirect.mjs
+++ b/test/ResRedirect.mjs
@@ -1,6 +1,7 @@
'use strict'
import { describe, it } from 'node:test'
+import assert from 'node:assert/strict'
import express from '../lib/express.js'
import request from 'supertest'
import utils from './support/Utils.mjs'
@@ -62,21 +63,6 @@ describe('res', () => {
})
})
- describe('.redirect(url, status)', () => {
- it('should set the response status', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.redirect('http://google.com', 303)
- })
-
- request(app)
- .get('/')
- .expect('Location', 'http://google.com')
- .expect(303, done)
- })
- })
-
describe('when the request method is HEAD', () => {
it('should ignore the body', (t, done) => {
const app = express()
@@ -86,11 +72,11 @@ describe('res', () => {
})
request(app)
- .head('/')
- .expect(302)
- .expect('Location', 'http://google.com')
- .expect(utils.shouldNotHaveBody())
- .end(done)
+ .head('/')
+ .expect(302)
+ .expect('Location', 'http://google.com')
+ .expect(shouldNotHaveBody())
+ .end(done)
})
})
@@ -199,14 +185,20 @@ describe('res', () => {
})
request(app)
- .get('/')
- .set('Accept', 'application/octet-stream')
- .expect(302)
- .expect('location', 'http://google.com')
- .expect('content-length', '0')
- .expect(utils.shouldNotHaveHeader('Content-Type'))
- .expect(utils.shouldNotHaveBody())
- .end(done)
+ .get('/')
+ .set('Accept', 'application/octet-stream')
+ .expect(302)
+ .expect('location', 'http://google.com')
+ .expect('content-length', '0')
+ .expect(utils.shouldNotHaveHeader('Content-Type'))
+ .expect(shouldNotHaveBody())
+ .end(done)
})
})
})
+
+function shouldNotHaveBody () {
+ return res => {
+ assert.ok(res.text === '' || res.text === undefined)
+ }
+}
diff --git a/test/ResSend.mjs b/test/ResSend.mjs
index 842fb05f0f4..691ef23aa12 100644
--- a/test/ResSend.mjs
+++ b/test/ResSend.mjs
@@ -1,6 +1,6 @@
'use strict'
import { describe, it } from 'node:test'
-import assert from 'node:assert'
+import assert from 'node:assert/strict'
import express from '../lib/express.js'
import request from 'supertest'
import methods from 'methods'
@@ -50,63 +50,18 @@ describe('res', () => {
})
})
- describe('.send(code)', () => {
- it('should set .statusCode', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.send(201)
- })
-
- request(app)
- .get('/')
- .expect('Created')
- .expect(201, done)
- })
- })
-
- describe('.send(code, body)', () => {
- it('should set .statusCode and body', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.send(201, 'Created :)')
- })
-
- request(app)
- .get('/')
- .expect('Created :)')
- .expect(201, done)
- })
- })
-
- describe('.send(body, code)', () => {
- it('should be supported for backwards compat', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.send('Bad!', 400)
- })
-
- request(app)
- .get('/')
- .expect('Bad!')
- .expect(400, done)
- })
- })
-
- describe('.send(code, number)', () => {
- it('should send number as json', (t, done) => {
+ describe('.send(Number)', () => {
+ it('should send as application/json', (t, done) => {
const app = express()
app.use((req, res) => {
- res.send(200, 0.123)
+ res.send(1000)
})
request(app)
.get('/')
.expect('Content-Type', 'application/json; charset=utf-8')
- .expect(200, '0.123', done)
+ .expect(200, '1000', done)
})
})
@@ -190,7 +145,7 @@ describe('res', () => {
.get('/')
.expect(200)
.expect('Content-Type', 'application/octet-stream')
- .expect(utils.shouldHaveBody(Buffer.from('hello')))
+ .expect(shouldHaveBody(Buffer.from('hello')))
.end(done)
})
@@ -260,7 +215,7 @@ describe('res', () => {
request(app)
.head('/')
.expect(200)
- .expect(utils.shouldNotHaveBody())
+ .expect(shouldNotHaveBody())
.end(done)
})
})
@@ -282,22 +237,6 @@ describe('res', () => {
})
})
- describe('when .statusCode is 205', () => {
- it('should strip Transfer-Encoding field and body, set Content-Length', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.status(205).set('Transfer-Encoding', 'chunked').send('foo')
- })
-
- request(app)
- .get('/')
- .expect(utils.shouldNotHaveHeader('Transfer-Encoding'))
- .expect('Content-Length', '0')
- .expect(205, '', done)
- })
- })
-
describe('when .statusCode is 304', () => {
it('should strip Content-* fields, Transfer-Encoding field, and body', (t, done) => {
const app = express()
@@ -508,7 +447,7 @@ describe('res', () => {
app.use((req, res) => {
res.set('etag', '"asdf"')
- res.send(200)
+ res.send('hello!')
})
request(app)
@@ -593,3 +532,19 @@ describe('res', () => {
})
})
})
+
+function shouldHaveBody (buf) {
+ return res => {
+ var body = !Buffer.isBuffer(res.body)
+ ? Buffer.from(res.text)
+ : res.body
+ assert.ok(body, 'response has body')
+ assert.strictEqual(body.toString('hex'), buf.toString('hex'))
+ }
+}
+
+function shouldNotHaveBody () {
+ return res => {
+ assert.ok(res.text === '' || res.text === undefined)
+ }
+}
diff --git a/test/ResSendFile.mjs b/test/ResSendFile.mjs
index 471b22c0a27..bf5d4825b82 100644
--- a/test/ResSendFile.mjs
+++ b/test/ResSendFile.mjs
@@ -9,31 +9,8 @@ import path from 'node:path'
import utils from './support/Utils.mjs'
const __dirname = path.dirname(new URL(import.meta.url).pathname)
-
-async function tryImport (name) {
- try {
- return await import(name)
- } catch (e) {
- return {}
- }
-}
-
-function createApp(path, options, fn) {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path, options, fn)
- })
-
- return app
-}
-const asyncHooks = await tryImport('async_hooks')
const fixtures = path.join(__dirname, 'fixtures')
-const describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function'
- ? describe
- : describe.skip
-
describe('res', () => {
describe('.sendFile(path)', () => {
it('should error missing path', (t, done) => {
@@ -52,14 +29,6 @@ describe('res', () => {
.expect(500, /TypeError: path must be a string to res.sendFile/, done)
})
- it('should error for non-absolute path', (t, done) => {
- const app = createApp('name.txt')
-
- request(app)
- .get('/')
- .expect(500, /TypeError: path must be absolute/, done)
- })
-
it('should transfer a file', (t, done) => {
const app = createApp(path.resolve(fixtures, 'name.txt'))
@@ -117,23 +86,6 @@ describe('res', () => {
res.send('no!')
})
- request(app)
- .get('/')
- .expect(404, done)
- })
-
- it('should send cache-control by default', (t, done) => {
- const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'))
-
- request(app)
- .get('/')
- .expect('Cache-Control', 'public, max-age=0')
- .expect(200, done)
- })
-
- it('should not serve dotfiles by default', (t, done) => {
- const app = createApp(path.resolve(__dirname, 'fixtures/.name'))
-
request(app)
.get('/')
.expect(404, done)
@@ -180,6 +132,136 @@ describe('res', () => {
server.close(cb)
})
})
+
+ describe('with "cacheControl" option', () => {
+ it('should enable cacheControl by default', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'))
+
+ request(app)
+ .get('/')
+ .expect('Cache-Control', 'public, max-age=0')
+ .expect(200, done)
+ })
+
+ it('should accept cacheControl option', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { cacheControl: false })
+
+ request(app)
+ .get('/')
+ .expect(utils.shouldNotHaveHeader('Cache-Control'))
+ .expect(200, done)
+ })
+ })
+
+ describe('with "dotfiles" option', () => {
+ it('should not serve dotfiles by default', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/.name'))
+
+ request(app)
+ .get('/')
+ .expect(404, done)
+ });
+
+ it('should accept dotfiles option', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/.name'), { dotfiles: 'allow' })
+
+ request(app)
+ .get('/')
+ .expect(200)
+ .expect(shouldHaveBody(Buffer.from('tobi')))
+ .end(done)
+ })
+ })
+
+ describe('with "headers" option', () => {
+ it('should accept headers option', (t, done) => {
+ const headers = {
+ 'x-success': 'sent',
+ 'x-other': 'done'
+ }
+ const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), { headers: headers })
+
+ request(app)
+ .get('/')
+ .expect('x-success', 'sent')
+ .expect('x-other', 'done')
+ .expect(200, done)
+ })
+
+ it('should ignore headers option on 404', (t, done) => {
+ const headers = { 'x-success': 'sent' }
+ const app = createApp(path.resolve(__dirname, 'fixtures/does-not-exist'), { headers: headers })
+
+ request(app)
+ .get('/')
+ .expect(utils.shouldNotHaveHeader('X-Success'))
+ .expect(404, done)
+ })
+ })
+
+ describe('with "immutable" option', () => {
+ it('should add immutable cache-control directive', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
+ immutable: true,
+ maxAge: '4h'
+ })
+
+ request(app)
+ .get('/')
+ .expect('Cache-Control', 'public, max-age=14400, immutable')
+ .expect(200, done)
+ })
+ })
+
+ describe('with "maxAge" option', () => {
+ it('should set cache-control max-age from number', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
+ maxAge: 14400000
+ })
+
+ request(app)
+ .get('/')
+ .expect('Cache-Control', 'public, max-age=14400')
+ .expect(200, done)
+ })
+
+ it('should set cache-control max-age from string', (t, done) => {
+ const app = createApp(path.resolve(__dirname, 'fixtures/name.txt'), {
+ maxAge: '4h'
+ })
+
+ request(app)
+ .get('/')
+ .expect('Cache-Control', 'public, max-age=14400')
+ .expect(200, done)
+ })
+ })
+
+ describe('with "root" option', () => {
+ it('should not transfer relative with without', (t, done) => {
+ const app = createApp('test/fixtures/name.txt')
+
+ request(app)
+ .get('/')
+ .expect(500, /must be absolute/, done)
+ })
+
+ it('should serve relative to "root"', (t, done) => {
+ const app = createApp('name.txt', {root: fixtures})
+
+ request(app)
+ .get('/')
+ .expect(200, 'tobi', done)
+ })
+
+ it('should disallow requesting out of "root"', (t, done) => {
+ const app = createApp('foo/../../user.html', {root: fixtures})
+
+ request(app)
+ .get('/')
+ .expect(403, done)
+ })
+ })
})
describe('.sendFile(path, fn)', () => {
@@ -188,8 +270,8 @@ describe('res', () => {
const app = createApp(path.resolve(fixtures, 'name.txt'), cb)
request(app)
- .get('/')
- .expect(200, cb)
+ .get('/')
+ .expect(200, cb)
})
it('should invoke the callback when client aborts', (t, done) => {
@@ -247,9 +329,9 @@ describe('res', () => {
})
request(app)
- .head('/')
- .expect(200, cb)
- })
+ .head('/')
+ .expect(200, cb)
+ });
it('should invoke the callback without error when 304', (t, done) => {
const app = express()
@@ -260,16 +342,16 @@ describe('res', () => {
})
request(app)
- .get('/')
- .expect('ETag', /^(?:W\/)?"[^"]+"$/)
- .expect(200, 'tobi', (err, res) => {
- if (err) return cb(err)
- const etag = res.headers.etag
- request(app)
.get('/')
- .set('If-None-Match', etag)
- .expect(304, cb)
- })
+ .expect('ETag', /^(?:W\/)?"[^"]+"$/)
+ .expect(200, 'tobi', (err, res) => {
+ if (err) return cb(err)
+ const etag = res.headers.etag
+ request(app)
+ .get('/')
+ .set('If-None-Match', etag)
+ .expect(304, cb)
+ })
})
it('should invoke the callback on 404', (t, done) => {
@@ -277,1137 +359,43 @@ describe('res', () => {
app.use((req, res) => {
res.sendFile(path.resolve(fixtures, 'does-not-exist'), err => {
- res.send(err ? 'got ' + err.status + ' error' : 'no error')
+ assert.ok(err)
+ assert.strictEqual(err.status, 404)
+ res.send('got it')
})
})
request(app)
.get('/')
- .expect(200, 'got 404 error', done)
- })
-
- describeAsyncHooks('async local storage', () => {
- it('should presist store', (t, done) => {
- const app = express()
- const cb = after(2, done)
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'name.txt'), err => {
- if (err) return cb(err)
-
- const local = req.asyncLocalStorage.getStore()
-
- assert.strictEqual(local.foo, 'bar')
- cb()
- })
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/plain; charset=UTF-8')
- .expect(200, 'tobi', cb)
- })
-
- it('should persist store on error', (t, done) => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'does-not-exist'), err => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.send(err ? 'got ' + err.status + ' error' : 'no error')
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('got 404 error')
- .end(done)
- })
+ .expect(200, 'got it', done)
})
})
describe('.sendFile(path, options)', () => {
it('should pass options to send module', (t, done) => {
request(createApp(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }))
- .get('/')
- .expect(200, 'to', done)
- })
-
- describe('with "acceptRanges" option', () => {
- describe('when true', () => {
- it('should advertise byte range accepted', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'nums.txt'), {
- acceptRanges: true
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Accept-Ranges', 'bytes')
- .expect('123456789')
- .end(done)
- })
-
- it('should respond to range request', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'nums.txt'), {
- acceptRanges: true
- })
- })
-
- request(app)
- .get('/')
- .set('Range', 'bytes=0-4')
- .expect(206, '12345', done)
- })
- })
-
- describe('when false', () => {
- it('should not advertise accept-ranges', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'nums.txt'), {
- acceptRanges: false
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldNotHaveHeader('Accept-Ranges'))
- .end(done)
- })
-
- it('should not honor range requests', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'nums.txt'), {
- acceptRanges: false
- })
- })
-
- request(app)
- .get('/')
- .set('Range', 'bytes=0-4')
- .expect(200, '123456789', done)
- })
- })
- })
-
- describe('with "cacheControl" option', () => {
- describe('when true', () => {
- it('should send cache-control header', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- cacheControl: true
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=0')
- .end(done)
- })
- })
-
- describe('when false', () => {
- it('should not send cache-control header', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- cacheControl: false
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldNotHaveHeader('Cache-Control'))
- .end(done)
- })
- })
- })
-
- describe('with "dotfiles" option', () => {
- describe('when "allow"', () => {
- it('should allow dotfiles', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, '.name'), {
- dotfiles: 'allow'
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldHaveBody(Buffer.from('tobi')))
- .end(done)
- })
- })
-
- describe('when "deny"', () => {
- it('should deny dotfiles', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, '.name'), {
- dotfiles: 'deny'
- })
- })
-
- request(app)
- .get('/')
- .expect(403)
- .expect(/Forbidden/)
- .end(done)
- })
- })
-
- describe('when "ignore"', () => {
- it('should ignore dotfiles', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, '.name'), {
- dotfiles: 'ignore'
- })
- })
-
- request(app)
- .get('/')
- .expect(404)
- .expect(/Not Found/)
- .end(done)
- })
- })
+ .get('/')
+ .expect(200, 'to', done)
})
+ })
+})
- describe('with "headers" option', () => {
- it('should set headers on response', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- headers: {
- 'X-Foo': 'Bar',
- 'X-Bar': 'Foo'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('X-Foo', 'Bar')
- .expect('X-Bar', 'Foo')
- .end(done)
- })
-
- it('should use last header when duplicated', (t, done) => {
- const app = express()
+function createApp(path, options, fn) {
+ const app = express()
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- headers: {
- 'X-Foo': 'Bar',
- 'x-foo': 'bar'
- }
- })
- })
+ app.use((req, res) => {
+ res.sendFile(path, options, fn)
+ })
- request(app)
- .get('/')
- .expect(200)
- .expect('X-Foo', 'bar')
- .end(done)
- })
+ return app
+}
- it('should override Content-Type', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- headers: {
- 'Content-Type': 'text/x-custom'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Content-Type', 'text/x-custom')
- .end(done)
- })
-
- it('should not set headers on 404', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'does-not-exist'), {
- headers: {
- 'X-Foo': 'Bar'
- }
- })
- })
-
- request(app)
- .get('/')
- .expect(404)
- .expect(utils.shouldNotHaveHeader('X-Foo'))
- .end(done)
- })
- })
-
- describe('with "immutable" option', () => {
- describe('when true', () => {
- it('should send cache-control header with immutable', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- immutable: true
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=0, immutable')
- .end(done)
- })
- })
-
- describe('when false', () => {
- it('should not send cache-control header with immutable', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- immutable: false
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=0')
- .end(done)
- })
- })
- })
-
- describe('with "lastModified" option', () => {
- describe('when true', () => {
- it('should send last-modified header', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- lastModified: true
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldHaveHeader('Last-Modified'))
- .end(done)
- })
-
- it('should conditionally respond with if-modified-since', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- lastModified: true
- })
- })
-
- request(app)
- .get('/')
- .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))
- .expect(304, done)
- })
- })
-
- describe('when false', () => {
- it('should not have last-modified header', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- lastModified: false
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldNotHaveHeader('Last-Modified'))
- .end(done)
- })
-
- it('should not honor if-modified-since', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- lastModified: false
- })
- })
-
- request(app)
- .get('/')
- .set('If-Modified-Since', (new Date(Date.now() + 99999).toUTCString()))
- .expect(200)
- .expect(utils.shouldNotHaveHeader('Last-Modified'))
- .end(done)
- })
- })
- })
-
- describe('with "maxAge" option', () => {
- it('should set cache-control max-age to milliseconds', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: 20000
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=20')
- .end(done)
- })
-
- it('should cap cache-control max-age to 1 year', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: 99999999999
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=31536000')
- .end(done)
- })
-
- it('should min cache-control max-age to 0', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: -20000
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=0')
- .end(done)
- })
-
- it('should floor cache-control max-age', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: 21911.23
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=21')
- .end(done)
- })
-
- describe('when cacheControl: false', () => {
- it('should not send cache-control', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- cacheControl: false,
- maxAge: 20000
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldNotHaveHeader('Cache-Control'))
- .end(done)
- })
- })
-
- describe('when string', () => {
- it('should accept plain number as milliseconds', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: '20000'
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=20')
- .end(done)
- })
-
- it('should accept suffix "s" for seconds', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: '20s'
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=20')
- .end(done)
- })
-
- it('should accept suffix "m" for minutes', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: '20m'
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=1200')
- .end(done)
- })
-
- it('should accept suffix "d" for days', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile(path.resolve(fixtures, 'user.html'), {
- maxAge: '20d'
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('Cache-Control', 'public, max-age=1728000')
- .end(done)
- })
- })
- })
-
- describe('with "root" option', () => {
- it('should allow relative path', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile('name.txt', {
- root: fixtures
- })
- })
-
- request(app)
- .get('/')
- .expect(200, 'tobi', done)
- })
-
- it('should allow up within root', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile('fake/../name.txt', {
- root: fixtures
- })
- })
-
- request(app)
- .get('/')
- .expect(200, 'tobi', done)
- })
-
- it('should reject up outside root', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile('..' + path.sep + path.relative(path.dirname(fixtures), path.join(fixtures, 'name.txt')), {
- root: fixtures
- })
- })
-
- request(app)
- .get('/')
- .expect(403, done)
- })
-
- it('should reject reading outside root', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendFile('../name.txt', {
- root: fixtures
- })
- })
-
- request(app)
- .get('/')
- .expect(403, done)
- })
- })
- })
-
- describe('.sendfile(path, fn)', () => {
- it('should invoke the callback when complete', (t, done) => {
- const app = express()
- const cb = after(2, done)
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/user.html', cb)
- })
-
- request(app)
- .get('/')
- .expect(200, cb)
- })
-
- it('should utilize the same options as express.static()', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/user.html', { maxAge: 60000 })
- })
-
- request(app)
- .get('/')
- .expect('Cache-Control', 'public, max-age=60')
- .end(done)
- })
-
- it('should invoke the callback when client aborts', (t, done) => {
- const cb = after(2, done)
- const app = express()
-
- app.use((req, res) => {
- setImmediate(() => {
- res.sendfile('test/fixtures/name.txt', err => {
- assert.ok(err)
- assert.strictEqual(err.code, 'ECONNABORTED')
- cb()
- })
- })
- test.req.abort()
- })
-
- const server = app.listen()
- const test = request(server).get('/')
- test.end(err => {
- assert.ok(err)
- server.close(cb)
- })
- })
-
- it('should invoke the callback when client already aborted', (t, done) => {
- const cb = after(2, done)
- const app = express()
-
- app.use((req, res) => {
- onFinished(res, () => {
- res.sendfile('test/fixtures/name.txt', err => {
- assert.ok(err)
- assert.strictEqual(err.code, 'ECONNABORTED')
- cb()
- })
- })
- test.req.abort()
- })
-
- const server = app.listen()
- const test = request(server).get('/')
- test.end(err => {
- assert.ok(err)
- server.close(cb)
- })
- })
-
- it('should invoke the callback without error when HEAD', (t, done) => {
- const app = express()
- const cb = after(2, done)
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/name.txt', cb)
- })
-
- request(app)
- .head('/')
- .expect(200, cb)
- })
-
- it('should invoke the callback without error when 304', (t, done) => {
- const app = express()
- const cb = after(3, done)
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/name.txt', cb)
- })
-
- request(app)
- .get('/')
- .expect('ETag', /^(?:W\/)?"[^"]+"$/)
- .expect(200, 'tobi', (err, res) => {
- if (err) return cb(err)
- const etag = res.headers.etag
- request(app)
- .get('/')
- .set('If-None-Match', etag)
- .expect(304, cb)
- })
- })
-
- it('should invoke the callback on 404', (t, done) => {
- const app = express()
- var calls = 0
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/nope.html', err => {
- assert.equal(calls++, 0)
- assert(!res.headersSent)
- res.send(err.message)
- })
- })
-
- request(app)
- .get('/')
- .expect(200, /^ENOENT.*?, stat/, done)
- })
-
- it('should not override manual content-types', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.contentType('txt')
- res.sendfile('test/fixtures/user.html')
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/plain; charset=utf-8')
- .end(done)
- })
-
- it('should invoke the callback on 403', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/foo/../user.html', err => {
- assert(!res.headersSent)
- res.send(err.message)
- })
- })
-
- request(app)
- .get('/')
- .expect('Forbidden')
- .expect(200, done)
- })
-
- it('should invoke the callback on socket error', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/user.html', err => {
- assert.ok(err)
- assert.ok(!res.headersSent)
- assert.strictEqual(err.message, 'broken!')
- done()
- })
-
- req.socket.destroy(new Error('broken!'))
- })
-
- request(app)
- .get('/')
- .end(() => {})
- })
-
- describeAsyncHooks('async local storage', () => {
- it('should presist store', (t, done) => {
- const app = express()
- const cb = after(2, done)
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/name.txt', err => {
- if (err) return cb(err)
-
- const local = req.asyncLocalStorage.getStore()
-
- assert.strictEqual(local.foo, 'bar')
- cb()
- })
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/plain; charset=UTF-8')
- .expect(200, 'tobi', cb)
- })
-
- it('should presist store on error', (t, done) => {
- const app = express()
- const store = { foo: 'bar' }
-
- app.use((req, res, next) => {
- req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage()
- req.asyncLocalStorage.run(store, next)
- })
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/does-not-exist', err => {
- const local = req.asyncLocalStorage.getStore()
-
- if (local) {
- res.setHeader('x-store-foo', String(local.foo))
- }
-
- res.send(err ? 'got ' + err.status + ' error' : 'no error')
- })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect('x-store-foo', 'bar')
- .expect('got 404 error')
- .end(done)
- })
- })
- })
-
- describe('.sendfile(path)', () => {
- it('should not serve dotfiles', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/.name')
- })
-
- request(app)
- .get('/')
- .expect(404, done)
- })
-
- it('should accept dotfiles option', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/.name', { dotfiles: 'allow' })
- })
-
- request(app)
- .get('/')
- .expect(200)
- .expect(utils.shouldHaveBody(Buffer.from('tobi')))
- .end(done)
- })
-
- it('should accept headers option', (t, done) => {
- const app = express()
- var headers = {
- 'x-success': 'sent',
- 'x-other': 'done'
- }
-
- app.use((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', (t, done) => {
- const app = express()
- var headers = { 'x-success': 'sent' }
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/user.nothing', { headers: headers })
- })
-
- request(app)
- .get('/')
- .expect(utils.shouldNotHaveHeader('X-Success'))
- .expect(404, done)
- })
-
- it('should transfer a file', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/name.txt')
- })
-
- request(app)
- .get('/')
- .expect(200, 'tobi', done)
- })
-
- it('should transfer a directory index file', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/blog/')
- })
-
- request(app)
- .get('/')
- .expect(200, 'index', done)
- })
-
- it('should 404 for directory without trailing slash', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/blog')
- })
-
- request(app)
- .get('/')
- .expect(404, done)
- })
-
- it('should transfer a file with urlencoded name', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/%25%20of%20dogs.txt')
- })
-
- request(app)
- .get('/')
- .expect(200, '20%', done)
- })
-
- it('should not error if the client aborts', (t, done) => {
- const app = express()
- const cb = after(2, done)
- var error = null
-
- app.use((req, res) => {
- setImmediate(() => {
- res.sendfile(path.resolve(fixtures, 'name.txt'))
- setTimeout(() => {
- cb(error)
- }, 10)
- })
- test.req.abort()
- })
-
- app.use((err, req, res, next) => {
- error = err
- next(err)
- })
-
- const server = app.listen()
- const test = request(server).get('/')
- test.end(err => {
- assert.ok(err)
- server.close(cb)
- })
- })
-
- describe('with an absolute path', () => {
- it('should transfer the file', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile(path.join(__dirname, '/fixtures/user.html'))
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
- .expect(200, '{{user.name}}
', done)
- })
- })
-
- describe('with a relative path', () => {
- it('should transfer the file', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/user.html')
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
- .expect(200, '{{user.name}}
', done)
- })
-
- it('should serve relative to "root"', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('user.html', { root: 'test/fixtures/' })
- })
-
- request(app)
- .get('/')
- .expect('Content-Type', 'text/html; charset=UTF-8')
- .expect(200, '{{user.name}}
', done)
- })
-
- it('should consider ../ malicious when "root" is not set', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('test/fixtures/foo/../user.html')
- })
-
- request(app)
- .get('/')
- .expect(403, done)
- })
-
- it('should allow ../ when "root" is set', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('foo/../user.html', { root: 'test/fixtures' })
- })
-
- request(app)
- .get('/')
- .expect(200, done)
- })
-
- it('should disallow requesting out of "root"', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile('foo/../../user.html', { root: 'test/fixtures' })
- })
-
- request(app)
- .get('/')
- .expect(403, done)
- })
-
- it('should next(404) when not found', (t, done) => {
- const app = express()
- let calls = 0
-
- app.use((req, res) => {
- res.sendfile('user.html')
- })
-
- app.use((req, res) => {
- assert(0, 'this should not be called')
- })
-
- app.use((err, req, res, next) => {
- ++calls
- next(err)
- })
-
- request(app)
- .get('/')
- .expect(404, err => {
- if (err) return done(err)
- assert.strictEqual(calls, 1)
- done()
- })
- })
-
- describe('with non-GET', () => {
- it('should still serve', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile(path.join(__dirname, '/fixtures/name.txt'))
- })
-
- request(app)
- .get('/')
- .expect('tobi', done)
- })
- })
- })
- })
-
- describe('.sendfile(path, options)', () => {
- it('should pass options to send module', (t, done) => {
- const app = express()
-
- app.use((req, res) => {
- res.sendfile(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 })
- })
-
- request(app)
- .get('/')
- .expect(200, 'to', done)
- })
- })
-})
+function shouldHaveBody (buff) {
+ return function (res) {
+ var body = !Buffer.isBuffer(res.body)
+ ? Buffer.from(res.text)
+ : res.body
+ assert.ok(body, 'response has body')
+ assert.strictEqual(body.toString('hex'), buff.toString('hex'))
+ }
+}
diff --git a/test/ResVary.mjs b/test/ResVary.mjs
index 538a056340a..42b5514ca6b 100644
--- a/test/ResVary.mjs
+++ b/test/ResVary.mjs
@@ -6,7 +6,7 @@ import utils from './support/Utils.mjs'
describe('res.vary()', () => {
describe('with no arguments', () => {
- it('should not set Vary', (t, done) => {
+ it('should throw error', (t, done) => {
const app = express()
app.use((req, res) => {
@@ -16,8 +16,7 @@ describe('res.vary()', () => {
request(app)
.get('/')
- .expect(utils.shouldNotHaveHeader('Vary'))
- .expect(200, done)
+ .expect(500, /field.*required/, done)
})
})
diff --git a/test/Route.mjs b/test/Route.mjs
index 28ee385c04a..a6d89e95530 100644
--- a/test/Route.mjs
+++ b/test/Route.mjs
@@ -1,7 +1,7 @@
'use strict'
import after from 'after'
import { describe, it } from 'node:test'
-import assert from 'node:assert'
+import assert from 'node:assert/strict'
import { Route } from '../lib/express.js'
import methods from 'methods'
@@ -12,37 +12,6 @@ describe('Route', () => {
route.dispatch(req, {}, done)
})
- it('should not stack overflow with a large sync stack', (t, done) => {
- t.timeout = 5000 // long-running test
-
- const req = { method: 'GET', url: '/' }
- const route = new Route('/foo')
-
- route.get((req, res, next) => {
- req.counter = 0
- next()
- })
-
- for (let i = 0; i < 6000; i++) {
- route.all((req, res, next) => {
- req.counter++
- next()
- })
- }
-
- route.get((req, res, next) => {
- req.called = true
- next()
- })
-
- route.dispatch(req, {}, err => {
- if (err) return done(err)
- assert.ok(req.called)
- assert.strictEqual(req.counter, 6000)
- done()
- })
- })
-
describe('.all', () => {
it('should add handler', (t, done) =>{
const req = { method: 'GET', url: '/' }
diff --git a/test/Router.mjs b/test/Router.mjs
index dec1ed1d24a..0cc9450def4 100644
--- a/test/Router.mjs
+++ b/test/Router.mjs
@@ -15,7 +15,7 @@ describe('Router', () => {
assert(typeof router.use === 'function')
})
- it('should support .use of other routers', function (t, done) {
+ it('should support .use of other routers', (t, done) => {
const router = new Router()
const another = new Router()
@@ -24,10 +24,10 @@ describe('Router', () => {
})
router.use('/foo', another)
- router.handle({ url: '/foo/bar', method: 'GET' }, { end: done })
+ router.handle({ url: '/foo/bar', method: 'GET' }, { end: done}, done)
})
- it('should support dynamic routes', function (t, done) {
+ it('should support dynamic routes', (t, done) => {
const router = new Router()
const another = new Router()
@@ -37,10 +37,10 @@ describe('Router', () => {
})
router.use('/:foo', another)
- router.handle({ url: '/test/route', method: 'GET' }, { end: done })
+ router.handle({ url: '/test/route', method: 'GET' }, { end: done}, done)
})
- it('should handle blank URL', function (t, done) {
+ it('should handle blank URL', (t, done) => {
const router = new Router()
router.use((req, res) => {
@@ -50,7 +50,7 @@ describe('Router', () => {
router.handle({ url: '', method: 'GET' }, {}, done)
})
- it('should handle missing URL',function (t, done) {
+ it('should handle missing URL',(t, done) => {
const router = new Router()
router.use((req, res) => {
@@ -60,36 +60,7 @@ describe('Router', () => {
router.handle({ method: 'GET' }, {}, done)
})
- it('handle missing method',function (t, done) {
- let all = false
- const router = new Router()
- const route = router.route('/foo')
- let use = false
-
- route.post((req, res, next) => { next(new Error('should not run')) })
- route.all((req, res, next) => {
- all = true
- next()
- })
- route.get((req, res, next) => { next(new Error('should not run')) })
-
- router.get('/foo', (req, res, next) => { next(new Error('should not run')) })
- router.use((req, res, next) => {
- use = true
- next()
- })
-
- router.handle({ url: '/foo' }, {}, err => {
- if (err) return done(err)
- assert.ok(all)
- assert.ok(use)
- done()
- })
- })
-
- it('should not stack overflow with many registered routes', function (t, done) {
- t.timeout = 5000 // long-running test
-
+ it('should not stack overflow with many registered routes', (t, done) => {
const handler = (req, res) => { res.end(new Error('wrong handler')) }
const router = new Router()
@@ -101,61 +72,11 @@ describe('Router', () => {
res.end()
})
- router.handle({ url: '/', method: 'GET' }, { end: done })
- })
-
- it('should not stack overflow with a large sync route stack',function (t, done) {
- t.timeout = 5000 // long-running test
-
- const router = new Router()
-
- router.get('/foo', (req, res, next) => {
- req.counter = 0
- next()
- })
-
- for (let i = 0; i < 6000; i++) {
- router.get('/foo', (req, res, next) => {
- req.counter++
- next()
- })
- }
-
- router.get('/foo', (req, res) => {
- assert.strictEqual(req.counter, 6000)
- res.end()
- })
-
- router.handle({ url: '/foo', method: 'GET' }, { end: done })
- })
-
- it('should not stack overflow with a large sync middleware stack',function (t, done) {
- t.timeout = 5000 // long-running test
-
- const router = new Router()
-
- router.use((req, res, next) => {
- req.counter = 0
- next()
- })
-
- for (let i = 0; i < 6000; i++) {
- router.use((req, res, next) => {
- req.counter++
- next()
- })
- }
-
- router.use((req, res) => {
- assert.strictEqual(req.counter, 6000)
- res.end()
- })
-
- router.handle({ url: '/', method: 'GET' }, { end: done })
+ router.handle({ url: '/', method: 'GET' }, { end: done}, done)
})
describe('.handle', () => {
- it('should dispatch', function (t, done) {
+ it('should dispatch', (t, done) => {
const router = new Router()
router.route('/foo').get((req, res) => {
@@ -168,7 +89,7 @@ describe('Router', () => {
done()
}
}
- router.handle({ url: '/foo', method: 'GET' }, res)
+ router.handle({ url: '/foo', method: 'GET' }, res, done)
})
})
@@ -201,7 +122,7 @@ describe('Router', () => {
})
describe('error', () => {
- it('should skip non error middleware', function (t, done) {
+ it('should skip non error middleware', (t, done) => {
const router = new Router()
router.get('/foo', (req, res, next) => {
@@ -224,7 +145,7 @@ describe('Router', () => {
router.handle({ url: '/foo', method: 'GET' }, {}, done)
})
- it('should handle throwing inside routes with params', function (t, done) {
+ it('should handle throwing inside routes with params', (t, done) => {
const router = new Router()
router.get('/foo/:id', () => {
@@ -243,7 +164,7 @@ describe('Router', () => {
router.handle({ url: '/foo/2', method: 'GET' }, {}, () => {})
})
- it('should handle throwing in handler after async param', function (t, done) {
+ it('should handle throwing in handler after async param', (t, done) => {
const router = new Router()
router.param('user', function(req, res, next, val){
@@ -265,7 +186,7 @@ describe('Router', () => {
router.handle({ url: '/bob', method: 'GET' }, {}, () => {})
})
- it('should handle throwing inside error handlers', function (t, done) {
+ it('should handle throwing inside error handlers', (t, done) => {
const router = new Router()
router.use((req, res, next) => {
@@ -286,7 +207,7 @@ describe('Router', () => {
})
describe('FQDN', () => {
- it('should not obscure FQDNs',function (t, done) {
+ it('should not obscure FQDNs',(t, done) => {
const request = { hit: 0, url: 'http://example.com/foo', method: 'GET' }
const router = new Router()
@@ -303,7 +224,7 @@ describe('Router', () => {
})
})
- it('should ignore FQDN in search',function (t, done) {
+ it('should ignore FQDN in search',(t, done) => {
const request = { hit: 0, url: '/proxy?url=http://example.com/blog/post/1', method: 'GET' }
const router = new Router()
@@ -320,7 +241,7 @@ describe('Router', () => {
})
})
- it('should ignore FQDN in path',function (t, done) {
+ it('should ignore FQDN in path',(t, done) => {
const request = { hit: 0, url: '/proxy/http://example.com/blog/post/1', method: 'GET' }
const router = new Router()
@@ -337,7 +258,7 @@ describe('Router', () => {
})
})
- it('should adjust FQDN req.url',function (t, done) {
+ it('should adjust FQDN req.url',(t, done) => {
const request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' }
const router = new Router()
@@ -354,7 +275,7 @@ describe('Router', () => {
})
})
- it('should adjust FQDN req.url with multiple handlers',function (t, done) {
+ it('should adjust FQDN req.url with multiple handlers',(t, done) => {
const request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' }
const router = new Router()
@@ -377,7 +298,7 @@ describe('Router', () => {
})
})
- it('should adjust FQDN req.url with multiple routed handlers',function (t, done) {
+ it('should adjust FQDN req.url with multiple routed handlers',(t, done) => {
const request = { hit: 0, url: 'http://example.com/blog/post/1', method: 'GET' }
const router = new Router()
@@ -408,7 +329,7 @@ describe('Router', () => {
})
describe('.all', () => {
- it('should support using .all to capture all http verbs', function (t, done) {
+ it('should support using .all to capture all http verbs', (t, done) => {
const router = new Router()
let count = 0
@@ -423,53 +344,35 @@ describe('Router', () => {
assert.equal(count, methods.length)
done()
})
-
- it('should be called for any URL when "(.*)"',function (t, done) {
- const cb = after(4, done)
- const router = new Router()
-
- function no () {
- throw new Error('should not be called')
- }
-
- router.all('(.*)', (req, res) => {
- res.end()
- })
-
- router.handle({ url: '/', method: 'GET' }, { end: cb }, no)
- router.handle({ url: '/foo', method: 'GET' }, { end: cb }, no)
- router.handle({ url: 'foo', method: 'GET' }, { end: cb }, no)
- router.handle({ url: '*', method: 'GET' }, { end: cb }, no)
- })
})
- describe('.use', () => {
+ describe('.use', () => {
it('should require middleware', () => {
const router = new Router()
- assert.throws(() => { router.use('/') }, /requires a middleware function/)
+ assert.throws(() => { router.use('/') }, /argument handler is required/)
})
it('should reject string as middleware', () => {
const router = new Router()
- assert.throws(() => { router.use('/', 'foo') }, /requires a middleware function but got a string/)
+ assert.throws(() => { router.use('/', 'foo') }, /argument handler must be a function/)
})
it('should reject number as middleware', () => {
const router = new Router()
- assert.throws(() => { router.use('/', 42) }, /requires a middleware function but got a number/)
+ assert.throws(() => { router.use('/', 42) }, /argument handler must be a function/)
})
it('should reject null as middleware', () => {
const router = new Router()
- assert.throws(() => { router.use('/', null) }, /requires a middleware function but got a Null/)
+ assert.throws(() => { router.use('/', null) }, /argument handler must be a function/)
})
it('should reject Date as middleware', () => {
const router = new Router()
- assert.throws(() => { router.use('/', new Date()) }, /requires a middleware function but got a Date/)
+ assert.throws(() => { router.use('/', new Date()) }, /argument handler must be a function/)
})
- it('should be called for any URL',function (t, done) {
+ it('should be called for any URL', (t, done) => {
const cb = after(4, done)
const router = new Router()
@@ -487,7 +390,7 @@ describe('Router', () => {
router.handle({ url: '*', method: 'GET' }, { end: cb }, no)
})
- it('should accept array of middleware', function (t, done) {
+ it('should accept array of middleware', (t, done) => {
let count = 0
const router = new Router()
@@ -510,11 +413,21 @@ describe('Router', () => {
})
})
- describe('.param', () => {
- it('should call param function when routing VERBS', function (t, done) {
+ describe('.param', () => {
+ it('should require function', () => {
+ const router = new Router()
+ assert.throws(router.param.bind(router, 'id'), /argument fn is required/)
+ })
+
+ it('should reject non-function', () => {
+ const router = new Router()
+ assert.throws(router.param.bind(router, 'id', 42), /argument fn must be a function/)
+ })
+
+ it('should call param function when routing VERBS', (t, done) => {
const router = new Router()
- router.param('id', function(req, res, next, id) {
+ router.param('id', (req, res, next, id) => {
assert.equal(id, '123')
next()
})
@@ -527,10 +440,10 @@ describe('Router', () => {
router.handle({ url: '/foo/123/bar', method: 'get' }, {}, done)
})
- it('should call param function when routing middleware', function (t, done) {
+ it('should call param function when routing middleware', (t, done) => {
const router = new Router()
- router.param('id', function(req, res, next, id) {
+ router.param('id', (req, res, next, id) => {
assert.equal(id, '123')
next()
})
@@ -544,7 +457,7 @@ describe('Router', () => {
router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done)
})
- it('should only call once per request', function (t, done) {
+ it('should only call once per request', (t, done) => {
let count = 0
const req = { url: '/foo/bob/bar', method: 'get' }
const router = new Router()
@@ -554,7 +467,7 @@ describe('Router', () => {
next()
})
- router.param('user', function(req, res, next, user) {
+ router.param('user', (req, res, next, user) => {
count++
req.user = user
next()
@@ -563,7 +476,7 @@ describe('Router', () => {
router.use('/foo/:user/', new Router())
router.use('/foo/:user/', sub)
- router.handle(req, {}, function(err) {
+ router.handle(req, {}, err => {
if (err) return done(err)
assert.equal(count, 1)
assert.equal(req.user, 'bob')
@@ -571,7 +484,7 @@ describe('Router', () => {
})
})
- it('should call when values differ', function (t, done) {
+ it('should call when values differ', (t, done) => {
let count = 0
const req = { url: '/foo/bob/bar', method: 'get' }
const router = new Router()
@@ -581,7 +494,7 @@ describe('Router', () => {
next()
})
- router.param('user', function(req, res, next, user) {
+ router.param('user', (req, res, next, user) => {
count++
req.user = user
next()
@@ -590,7 +503,7 @@ describe('Router', () => {
router.use('/foo/:user/', new Router())
router.use('/:user/bob/', sub)
- router.handle(req, {}, function(err) {
+ router.handle(req, {}, err => {
if (err) return done(err)
assert.equal(count, 2)
assert.equal(req.user, 'foo')
@@ -599,8 +512,8 @@ describe('Router', () => {
})
})
- describe('parallel requests', () => {
- it('should not mix requests', function (t, done) {
+ describe('parallel requests', () => {
+ it('should not mix requests', (t, done) => {
const req1 = { url: '/foo/50/bar', method: 'get' }
const req2 = { url: '/foo/10/bar', method: 'get' }
const router = new Router()
diff --git a/test/Utils.mjs b/test/Utils.mjs
index 3b3e511ab5c..a97057cc2a9 100644
--- a/test/Utils.mjs
+++ b/test/Utils.mjs
@@ -69,34 +69,3 @@ describe('utils.wetag(body, encoding)', () => {
'W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"')
})
})
-
-describe('utils.isAbsolute()', () => {
- it('should support windows', () => {
- assert(utils.isAbsolute('c:\\'))
- assert(utils.isAbsolute('c:/'))
- assert(!utils.isAbsolute(':\\'))
- })
-
- it('should support windows unc', () => {
- assert(utils.isAbsolute('\\\\foo\\bar'))
- })
-
- it('should support unices', () => {
- assert(utils.isAbsolute('/foo/bar'))
- assert(!utils.isAbsolute('foo/bar'))
- })
-})
-
-describe('deprecated: utils.flatten(arr) - example using Array.prototype.flat', () => {
- it('should flatten an array', () => {
- const arr = ['one', ['two', ['three', 'four'], 'five']]
- const flat = arr.flat(Infinity)
- assert.strictEqual(flat.length, 5)
- assert.strictEqual(flat[0], 'one')
- assert.strictEqual(flat[1], 'two')
- assert.strictEqual(flat[2], 'three')
- assert.strictEqual(flat[3], 'four')
- assert.strictEqual(flat[4], 'five')
- assert.ok(flat.every(v => { return typeof v === 'string' }))
- })
-})
diff --git a/test/acceptance/Cookies.mjs b/test/acceptance/Cookies.mjs
index 9014a53c443..43dfca8ef61 100644
--- a/test/acceptance/Cookies.mjs
+++ b/test/acceptance/Cookies.mjs
@@ -27,7 +27,7 @@ describe('cookies', () => {
.post('/')
.type('urlencoded')
.send({ remember: 1 })
- .expect(302, function(err, res){
+ .expect(302, (err, res) => {
if (err) return done(err)
request(app)
.get('/')
@@ -43,7 +43,7 @@ describe('cookies', () => {
.post('/')
.type('urlencoded')
.send({ remember: 1 })
- .expect(302, function(err, res){
+ .expect(302, (err, res) => {
if (err) return done(err)
request(app)
.get('/forget')
@@ -64,7 +64,7 @@ describe('cookies', () => {
.expect(302, done)
})
- it('should no set cookie w/o reminder', (t, done) => {
+ it('should not set cookie w/o remember', (t, done) => {
request(app)
.post('/')
.send({})