diff --git a/.eslintrc.yml b/.eslintrc.yml index 587169c533..70bc9a6e7e 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,5 +1,7 @@ root: true - +env: + es2022: true + node: true rules: eol-last: error eqeqeq: [error, allow-null] diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3ddd6b23a..e73fbce8ca 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,6 +7,7 @@ on: - develop - '4.x' - '5.x' + - '5.0' paths-ignore: - '*.md' pull_request: @@ -38,77 +39,14 @@ jobs: run: npm run lint test: - name: Run tests strategy: fail-fast: false matrix: os: [ubuntu-latest, windows-latest] - node-version: - - "0.10" - - "0.12" - - "4" - - "5" - - "6" - - "7" - - "8" - - "9" - - "10" - - "11" - - "12" - - "13" - - "14" - - "15" - - "16" - - "17" - - "18" - - "19" - - "20" - - "21" - - "22" - # Use supported versions of our testing tools under older versions of Node - # Install npm in some specific cases where we need to - include: - - node-version: "0.10" - npm-i: "mocha@3.5.3 nyc@10.3.2 supertest@2.0.0" - # Npm isn't being installed on windows w/ setup-node for - # 0.10 and 0.12, which will end up choking when npm uses es6 - npm-version: "npm@2.15.1" - - - node-version: "0.12" - npm-i: "mocha@3.5.3 nyc@10.3.2 supertest@2.0.0" - npm-version: "npm@2.15.11" - - - node-version: "4" - npm-i: "mocha@5.2.0 nyc@11.9.0 supertest@3.4.2" - - - node-version: "5" - npm-i: "mocha@5.2.0 nyc@11.9.0 supertest@3.4.2" - # fixes https://github.com/npm/cli/issues/681 - npm-version: "npm@3.10.10" - - - node-version: "6" - npm-i: "mocha@6.2.2 nyc@14.1.1 supertest@3.4.2" - - - node-version: "7" - npm-i: "mocha@6.2.2 nyc@14.1.1 supertest@6.1.6" - - - node-version: "8" - npm-i: "mocha@7.2.0 nyc@14.1.1" - - - node-version: "9" - npm-i: "mocha@7.2.0 nyc@14.1.1" - - - node-version: "10" - npm-i: "mocha@8.4.0" - - - node-version: "11" - npm-i: "mocha@8.4.0" - - - node-version: "12" - npm-i: "mocha@9.2.2" - - - node-version: "13" - npm-i: "mocha@9.2.2" + node-version: [18, 19, 20, 21, 22] + # Node.js release schedule: https://nodejs.org/en/about/releases/ + + name: Node.js ${{ matrix.node-version }} - ${{matrix.os}} runs-on: ${{ matrix.os }} steps: @@ -121,10 +59,6 @@ jobs: with: node-version: ${{ matrix.node-version }} - - name: Npm version fixes - if: ${{matrix.npm-version != ''}} - run: npm install -g ${{ matrix.npm-version }} - - name: Configure npm loglevel run: | npm config set loglevel error @@ -133,13 +67,6 @@ jobs: - name: Install dependencies run: npm install - - name: Install Node version specific dev deps - if: ${{ matrix.npm-i != '' }} - run: npm install --save-dev ${{ matrix.npm-i }} - - - name: Remove non-test dependencies - run: npm rm --silent --save-dev connect-redis - - name: Output Node and NPM versions run: | echo "Node.js version: $(node -v)" diff --git a/.github/workflows/iojs.yml b/.github/workflows/iojs.yml deleted file mode 100644 index c1268abd68..0000000000 --- a/.github/workflows/iojs.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: iojs-ci - -on: - push: - branches: - - master - - '4.x' - paths-ignore: - - '*.md' - pull_request: - paths-ignore: - - '*.md' - -concurrency: - group: "${{ github.workflow }} ✨ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" - cancel-in-progress: true - -jobs: - test: - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - node-version: ["1.8", "2.5", "3.3"] - include: - - node-version: "1.8" - npm-i: "mocha@3.5.3 nyc@10.3.2 supertest@2.0.0" - - node-version: "2.5" - npm-i: "mocha@3.5.3 nyc@10.3.2 supertest@2.0.0" - - node-version: "3.3" - npm-i: "mocha@3.5.3 nyc@10.3.2 supertest@2.0.0" - - steps: - - uses: actions/checkout@v4 - - - name: Install iojs ${{ matrix.node-version }} - shell: bash -eo pipefail -l {0} - run: | - nvm install --default ${{ matrix.node-version }} - dirname "$(nvm which ${{ matrix.node-version }})" >> "$GITHUB_PATH" - - - name: Configure npm - run: | - npm config set loglevel error - npm config set shrinkwrap false - - - name: Install npm module(s) ${{ matrix.npm-i }} - run: npm install --save-dev ${{ matrix.npm-i }} - if: matrix.npm-i != '' - - - name: Remove non-test dependencies - run: npm rm --silent --save-dev connect-redis - - - name: Install Node.js dependencies - run: npm install - - - name: List environment - id: list_env - shell: bash - run: | - echo "node@$(node -v)" - echo "npm@$(npm -v)" - npm -s ls ||: - (npm -s ls --depth=0 ||:) | awk -F'[ @]' 'NR>1 && $2 { print $2 "=" $3 }' >> "$GITHUB_OUTPUT" - - - name: Run tests - shell: bash - run: npm run test - diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml new file mode 100644 index 0000000000..d26d6df34d --- /dev/null +++ b/.github/workflows/legacy.yml @@ -0,0 +1,100 @@ +name: legacy + +on: + push: + branches: + - master + - develop + - '4.x' + - '5.x' + - '5.0' + paths-ignore: + - '*.md' + pull_request: + paths-ignore: + - '*.md' + +# Cancel in progress workflows +# in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run +concurrency: + group: "${{ github.workflow }} ✨ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true + +jobs: + test: + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, windows-latest] + node-version: [16, 17] + # Node.js release schedule: https://nodejs.org/en/about/releases/ + + name: Node.js ${{ matrix.node-version }} - ${{matrix.os}} + + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Configure npm loglevel + run: | + npm config set loglevel error + shell: bash + + - name: Install dependencies + run: npm install + + - name: Output Node and NPM versions + run: | + echo "Node.js version: $(node -v)" + echo "NPM version: $(npm -v)" + + - name: Run tests + shell: bash + run: | + npm run test-ci + cp coverage/lcov.info "coverage/${{ matrix.node-version }}.lcov" + + - name: Collect code coverage + run: | + mv ./coverage "./${{ matrix.node-version }}" + mkdir ./coverage + mv "./${{ matrix.node-version }}" "./coverage/${{ matrix.node-version }}" + + - name: Upload code coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: ./coverage + retention-days: 1 + + coverage: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install lcov + shell: bash + run: sudo apt-get -y install lcov + + - name: Collect coverage reports + uses: actions/download-artifact@v3 + with: + name: coverage + path: ./coverage + + - name: Merge coverage reports + shell: bash + run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./coverage/lcov.info + + - name: Upload coverage report + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/History.md b/History.md index 887a38f182..2592c976bf 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,199 @@ +5.0.0 / 2024-09-10 +========================= +* remove: + - `path-is-absolute` dependency - use `path.isAbsolute` instead +* breaking: + * `res.status()` accepts only integers, and input must be greater than 99 and less than 1000 + * will throw a `RangeError: Invalid status code: ${code}. Status code must be greater than 99 and less than 1000.` for inputs outside this range + * will throw a `TypeError: Invalid status code: ${code}. Status code must be an integer.` for non integer inputs + * deps: send@1.0.0 + * `res.redirect('back')` and `res.location('back')` is no longer a supported magic string, explicitly use `req.get('Referrer') || '/'`. +* change: + - `res.clearCookie` will ignore user provided `maxAge` and `expires` options +* deps: cookie-signature@^1.2.1 +* deps: debug@4.3.6 +* deps: merge-descriptors@^2.0.0 +* deps: serve-static@^2.1.0 +* deps: qs@6.13.0 +* deps: accepts@^2.0.0 +* deps: mime-types@^3.0.0 + - `application/javascript` => `text/javascript` +* deps: type-is@^2.0.0 +* deps: content-disposition@^1.0.0 +* deps: finalhandler@^2.0.0 +* deps: fresh@^2.0.0 +* deps: body-parser@^2.0.1 +* deps: send@^1.1.0 + +5.0.0-beta.3 / 2024-03-25 +========================= + +This incorporates all changes after 4.19.1 up to 4.19.2. + +5.0.0-beta.2 / 2024-03-20 +========================= + +This incorporates all changes after 4.17.2 up to 4.19.1. + +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.20.0 / 2024-09-10 ========== * deps: serve-static@0.16.0 diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 629499bf37..0000000000 --- a/appveyor.yml +++ /dev/null @@ -1,113 +0,0 @@ -environment: - matrix: - - nodejs_version: "0.10" - - nodejs_version: "0.12" - - nodejs_version: "1.8" - - nodejs_version: "2.5" - - nodejs_version: "3.3" - - nodejs_version: "4.9" - - nodejs_version: "5.12" - - nodejs_version: "6.17" - - nodejs_version: "7.10" - - nodejs_version: "8.17" - - nodejs_version: "9.11" - - nodejs_version: "10.24" - - nodejs_version: "11.15" - - nodejs_version: "12.22" - - nodejs_version: "13.14" - - nodejs_version: "14.20" - - nodejs_version: "15.14" - - nodejs_version: "16.20" - - nodejs_version: "17.9" - - nodejs_version: "18.19" - - nodejs_version: "19.9" - - nodejs_version: "20.11" - - nodejs_version: "21.6" - - nodejs_version: "22.0" -cache: - - node_modules -install: - # Install Node.js - - ps: >- - try { Install-Product node $env:nodejs_version -ErrorAction Stop } - catch { Update-NodeJsInstallation (Get-NodeJsLatestBuild $env:nodejs_version) x64 } - # Configure npm - - ps: | - npm config set loglevel error - if ((npm config get package-lock) -eq "true") { - npm config set package-lock false - } else { - npm config set shrinkwrap false - } - # Remove all non-test dependencies - - ps: | - # Remove example dependencies - npm rm --silent --save-dev connect-redis - # Remove lint dependencies - cmd.exe /c "node -pe `"Object.keys(require('./package').devDependencies).join('\n')`"" | ` - sls "^eslint(-|$)" | ` - %{ npm rm --silent --save-dev $_ } - # Setup Node.js version-specific dependencies - - ps: | - # mocha for testing - # - use 3.x for Node.js < 4 - # - use 5.x for Node.js < 6 - # - use 6.x for Node.js < 8 - # - use 7.x for Node.js < 10 - # - use 8.x for Node.js < 12 - # - use 9.x for Node.js < 14 - if ([int]$env:nodejs_version.split(".")[0] -lt 4) { - npm install --silent --save-dev mocha@3.5.3 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 6) { - npm install --silent --save-dev mocha@5.2.0 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 8) { - npm install --silent --save-dev mocha@6.2.2 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 10) { - npm install --silent --save-dev mocha@7.2.0 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 12) { - npm install --silent --save-dev mocha@8.4.0 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 14) { - npm install --silent --save-dev mocha@9.2.2 - } - - ps: | - # nyc for test coverage - # - use 10.3.2 for Node.js < 4 - # - use 11.9.0 for Node.js < 6 - # - use 14.1.1 for Node.js < 10 - if ([int]$env:nodejs_version.split(".")[0] -lt 4) { - npm install --silent --save-dev nyc@10.3.2 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 6) { - npm install --silent --save-dev nyc@11.9.0 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 10) { - npm install --silent --save-dev nyc@14.1.1 - } - - ps: | - # supertest for http calls - # - use 2.0.0 for Node.js < 4 - # - use 3.4.2 for Node.js < 7 - # - use 6.1.6 for Node.js < 8 - if ([int]$env:nodejs_version.split(".")[0] -lt 4) { - npm install --silent --save-dev supertest@2.0.0 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 7) { - npm install --silent --save-dev supertest@3.4.2 - } elseif ([int]$env:nodejs_version.split(".")[0] -lt 8) { - npm install --silent --save-dev supertest@6.1.6 - } - # Update Node.js modules - - ps: | - # Prune & rebuild node_modules - if (Test-Path -Path node_modules) { - npm prune - npm rebuild - } - # Install Node.js modules - - npm install -build: off -test_script: - # Output version data - - ps: | - node --version - npm --version - # Run test script - - npm run test-ci -version: "{build}" diff --git a/examples/auth/index.js b/examples/auth/index.js index 36205d0f99..2884ca4e17 100644 --- a/examples/auth/index.js +++ b/examples/auth/index.js @@ -18,7 +18,7 @@ app.set('views', path.join(__dirname, 'views')); // middleware -app.use(express.urlencoded({ extended: false })) +app.use(express.urlencoded()) app.use(session({ resave: false, // don't save session if unmodified saveUninitialized: false, // don't create session until something stored @@ -102,6 +102,7 @@ app.get('/login', function(req, res){ }); app.post('/login', function (req, res, next) { + if (!req.body) return res.sendStatus(400) authenticate(req.body.username, req.body.password, function(err, user){ if (err) return next(err) if (user) { @@ -115,7 +116,7 @@ app.post('/login', function (req, res, next) { req.session.success = 'Authenticated as ' + user.name + ' click to logout. ' + ' You may now access /restricted.'; - res.redirect('back'); + res.redirect(req.get('Referrer') || '/'); }); } else { req.session.error = 'Authentication failed, please check your ' diff --git a/examples/cookies/index.js b/examples/cookies/index.js index 04093591f7..0620cb40e4 100644 --- a/examples/cookies/index.js +++ b/examples/cookies/index.js @@ -19,7 +19,7 @@ if (process.env.NODE_ENV !== 'test') app.use(logger(':method :url')) app.use(cookieParser('my secret here')); // parses x-www-form-urlencoded -app.use(express.urlencoded({ extended: false })) +app.use(express.urlencoded()) app.get('/', function(req, res){ if (req.cookies.remember) { @@ -33,13 +33,17 @@ app.get('/', function(req, res){ app.get('/forget', function(req, res){ res.clearCookie('remember'); - res.redirect('back'); + res.redirect(req.get('Referrer') || '/'); }); app.post('/', function(req, res){ var minute = 60000; - if (req.body.remember) res.cookie('remember', 1, { maxAge: minute }); - res.redirect('back'); + + if (req.body && req.body.remember) { + res.cookie('remember', 1, { maxAge: minute }) + } + + res.redirect(req.get('Referrer') || '/'); }); /* istanbul ignore next */ diff --git a/examples/downloads/index.js b/examples/downloads/index.js index 6b67e0c886..c47dddd738 100644 --- a/examples/downloads/index.js +++ b/examples/downloads/index.js @@ -23,8 +23,8 @@ 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', function (req, res, next) { + res.download(req.params.file.join('/'), { root: FILES_DIR }, function (err) { if (!err) return; // file sent if (err.status !== 404) return next(err); // non-404 error // file for download not found diff --git a/examples/resource/index.js b/examples/resource/index.js index ff1f6fe11f..627ab24c5a 100644 --- a/examples/resource/index.js +++ b/examples/resource/index.js @@ -12,7 +12,7 @@ var app = module.exports = express(); app.resource = function(path, obj) { this.get(path, obj.index); - this.get(path + '/:a..:b.:format?', function(req, res){ + this.get(path + '/:a..:b{.:format}', function(req, res){ var a = parseInt(req.params.a, 10); var b = parseInt(req.params.b, 10); var format = req.params.format; diff --git a/examples/route-separation/index.js b/examples/route-separation/index.js index 5d48381111..a471a4b091 100644 --- a/examples/route-separation/index.js +++ b/examples/route-separation/index.js @@ -38,7 +38,7 @@ app.get('/', site.index); // User app.get('/users', user.list); -app.all('/user/:id/:op?', user.load); +app.all('/user/:id{/:op}', user.load); app.get('/user/:id', user.view); app.get('/user/:id/view', user.view); app.get('/user/:id/edit', user.edit); diff --git a/examples/route-separation/user.js b/examples/route-separation/user.js index 1c2aec7cd2..bc6fbd7baf 100644 --- a/examples/route-separation/user.js +++ b/examples/route-separation/user.js @@ -43,5 +43,5 @@ exports.update = function(req, res){ var user = req.body.user; req.user.name = user.name; req.user.email = user.email; - res.redirect('back'); + res.redirect(req.get('Referrer') || '/'); }; diff --git a/examples/search/index.js b/examples/search/index.js index 0d19444e52..4b57168987 100644 --- a/examples/search/index.js +++ b/examples/search/index.js @@ -35,10 +35,10 @@ db.sadd('cat', 'luna'); * GET search for :query. */ -app.get('/search/:query?', function(req, res){ +app.get('/search/:query?', function(req, res, next){ var query = req.params.query; db.smembers(query, function(err, vals){ - if (err) return res.send(500); + if (err) return next(err); res.send(vals); }); }); diff --git a/lib/application.js b/lib/application.js index ebb30b51b3..ecfe2186db 100644 --- a/lib/application.js +++ b/lib/application.js @@ -14,20 +14,17 @@ */ var finalhandler = require('finalhandler'); -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 flatten = require('array-flatten'); var merge = require('utils-merge'); var resolve = require('path').resolve; +var once = require('once') +var Router = require('router'); var setPrototypeOf = require('setprototypeof') /** @@ -35,8 +32,8 @@ var setPrototypeOf = require('setprototypeof') * @private */ -var hasOwnProperty = Object.prototype.hasOwnProperty var slice = Array.prototype.slice; +var flatten = Array.prototype.flat; /** * Application prototype. @@ -62,11 +59,29 @@ var trustProxyDefaultSymbol = '@@symbol:trust_proxy_default'; */ app.init = function init() { - this.cache = {}; - this.engines = {}; - this.settings = {}; + var router = null; + + this.cache = Object.create(null); + this.engines = Object.create(null); + this.settings = Object.create(null); 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; + } + }); }; /** @@ -81,7 +96,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); @@ -125,32 +140,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)); - } }; /** @@ -163,22 +152,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); }; /** @@ -211,15 +209,14 @@ app.use = function use(fn) { } } - var fns = flatten(slice.call(arguments, offset)); + var fns = flatten.call(slice.call(arguments, offset), Infinity); if (fns.length === 0) { 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 @@ -259,8 +256,7 @@ app.use = function use(fn) { */ app.route = function route(path) { - this.lazyrouter(); - return this._router.route(path); + return this.router.route(path); }; /** @@ -326,8 +322,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); @@ -336,7 +330,7 @@ app.param = function param(name, fn) { return this; } - this._router.param(name, fn); + this.router.param(name, fn); return this; }; @@ -359,17 +353,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); @@ -493,9 +477,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; }; @@ -512,9 +494,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++) { @@ -524,10 +504,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 @@ -630,10 +606,15 @@ app.render = function render(name, options, callback) { * @public */ -app.listen = function listen() { - var server = http.createServer(this); - return server.listen.apply(server, arguments); -}; +app.listen = function listen () { + var server = http.createServer(this) + var args = Array.prototype.slice.call(arguments) + if (typeof args[args.length - 1] === 'function') { + var done = args[args.length - 1] = once(args[args.length - 1]) + server.once('error', done) + } + return server.listen.apply(server, args) +} /** * Log error using console.error. diff --git a/lib/express.js b/lib/express.js index d188a16db7..b4ef299636 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 dfd042747b..0000000000 --- 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 7e9166947a..0000000000 --- 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 3f1eeca6c1..372a9915e9 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,15 +445,9 @@ 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 + * Last-Modified or the ETag * still match. * * @return {Boolean} diff --git a/lib/response.js b/lib/response.js index 76b6b54a3b..937e985853 100644 --- a/lib/response.js +++ b/lib/response.js @@ -15,13 +15,13 @@ 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').isAbsolute; var statuses = require('statuses') var merge = require('utils-merge'); var sign = require('cookie-signature').sign; @@ -31,7 +31,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'); @@ -50,24 +49,28 @@ var res = Object.create(http.ServerResponse.prototype) module.exports = res /** - * Module variables. - * @private - */ - -var charsetRegExp = /;\s*charset\s*=/; - -/** - * Set status `code`. + * Set the HTTP status code for the response. * - * @param {Number} code - * @return {ServerResponse} + * Expects an integer value between 100 and 999 inclusive. + * Throws an error if the provided status code is not an integer or if it's outside the allowable range. + * + * @param {number} code - The HTTP status code to set. + * @return {ServerResponse} - Returns itself for chaining methods. + * @throws {TypeError} If `code` is not an integer. + * @throws {RangeError} If `code` is outside the range 100 to 999. * @public */ 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') + // Check if the status code is not an integer + if (!Number.isInteger(code)) { + throw new TypeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be an integer.`); } + // Check if the status code is outside of Node's valid range + if (code < 100 || code > 999) { + throw new RangeError(`Invalid status code: ${JSON.stringify(code)}. Status code must be greater than 99 and less than 1000.`); + } + this.statusCode = code; return this; }; @@ -117,31 +120,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': @@ -207,7 +185,7 @@ res.send = function send(body) { } // freshness - if (req.fresh) this.statusCode = 304; + if (req.fresh) this.status(304); // strip irrelevant headers if (204 === this.statusCode || 304 === this.statusCode) { @@ -248,27 +226,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')) { @@ -291,27 +254,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 @@ -369,7 +317,7 @@ res.jsonp = function jsonp(obj) { res.sendStatus = function sendStatus(statusCode) { var body = statuses.message[statusCode] || String(statusCode) - this.statusCode = statusCode; + this.status(statusCode); this.type('txt'); return this.send(body); @@ -437,7 +385,7 @@ 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'); } @@ -457,78 +405,6 @@ res.sendFile = function sendFile(path, options, callback) { }); }; -/** - * 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 (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); - } - }); -}; - -res.sendfile = deprecate.function(res.sendfile, - 'res.sendfile: Use res.sendFile instead'); - /** * Transfer the file at the given `path` as an attachment. * @@ -599,8 +475,10 @@ res.download = function download (path, filename, options, callback) { }; /** - * 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: * @@ -618,7 +496,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); @@ -767,6 +645,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 @@ -785,10 +666,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); @@ -822,15 +700,10 @@ res.get = function(field){ */ res.clearCookie = function clearCookie(name, options) { - if (options) { - if (options.maxAge) { - deprecate('res.clearCookie: Passing "options.maxAge" is deprecated. In v5.0.0 of Express, this option will be ignored, as res.clearCookie will automatically set cookies to expire immediately. Please update your code to omit this option.'); - } - if (options.expires) { - deprecate('res.clearCookie: Passing "options.expires" is deprecated. In v5.0.0 of Express, this option will be ignored, as res.clearCookie will automatically set cookies to expire immediately. Please update your code to omit this option.'); - } - } - var opts = merge({ expires: new Date(1), path: '/' }, options); + // Force cookie expiration by setting expires to the past + const opts = { path: '/', ...options, expires: new Date(1)}; + // ensure maxAge is not passed + delete opts.maxAge return this.cookie(name, '', opts); }; @@ -912,26 +785,13 @@ res.cookie = function (name, value, options) { */ res.location = function location(url) { - var loc; - - // "back" is an alias for the referrer - if (url === 'back') { - loc = this.req.get('Referrer') || '/'; - } else { - loc = String(url); - } - - return this.set('Location', encodeUrl(loc)); + return this.set('Location', encodeUrl(url)); }; /** * Redirect to the given `url` with optional response `status` * defaulting to 302. * - * The resulting `url` is determined by `res.location()`, so - * it will play nicely with mounted apps, relative paths, - * `"back"` etc. - * * Examples: * * res.redirect('/foo/bar'); @@ -949,13 +809,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 @@ -978,7 +833,7 @@ res.redirect = function redirect(url) { }); // Respond - this.statusCode = status; + this.status(status); this.set('Content-Length', Buffer.byteLength(body)); if (this.req.method === 'HEAD') { @@ -998,12 +853,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; diff --git a/lib/router/index.js b/lib/router/index.js deleted file mode 100644 index abb3a6f589..0000000000 --- a/lib/router/index.js +++ /dev/null @@ -1,673 +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 flatten = require('array-flatten'); -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 = flatten(slice.call(arguments, offset)); - - 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 4dc8e86d4f..0000000000 --- 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 pathRegexp = 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 = pathRegexp(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 a65756d6de..0000000000 --- a/lib/router/route.js +++ /dev/null @@ -1,230 +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 flatten = require('array-flatten'); -var Layer = require('./layer'); -var methods = require('methods'); - -/** - * Module variables. - * @private - */ - -var slice = Array.prototype.slice; -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 = flatten(slice.call(arguments)); - - 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 = flatten(slice.call(arguments)); - - 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 56e12b9b54..f66760a17c 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -13,12 +13,9 @@ */ var Buffer = require('safe-buffer').Buffer -var contentDisposition = require('content-disposition'); var contentType = require('content-type'); -var deprecate = require('depd')('express'); -var flatten = require('array-flatten'); -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'); @@ -45,31 +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 -}; - -/** - * Flatten the given `arr`. - * - * @param {Array} arr - * @return {Array} - * @api private - */ - -exports.flatten = deprecate.function(flatten, - 'utils.flatten: use array-flatten npm module instead'); - /** * Normalize the given `type`, for example "html" becomes "text/html". * @@ -81,7 +53,7 @@ exports.flatten = deprecate.function(flatten, exports.normalizeType = function(type){ return ~type.indexOf('/') ? acceptParams(type) - : { value: mime.lookup(type), params: {} }; + : { value: (mime.lookup(type) || 'application/octet-stream'), params: {} } }; /** @@ -102,18 +74,6 @@ 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`. @@ -192,7 +152,6 @@ exports.compileQueryParser = function compileQueryParser(val) { fn = querystring.parse; break; case false: - fn = newObject; break; case 'extended': fn = parseExtendedQueryString; @@ -290,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 c08ab4d8d5..6beffca6e2 100644 --- a/lib/view.js +++ b/lib/view.js @@ -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.json b/package.json index bffa70a6f1..c1eff78d97 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "express", "description": "Fast, unopinionated, minimalist web framework", - "version": "4.20.0", + "version": "5.0.0", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", @@ -28,35 +28,36 @@ "api" ], "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "accepts": "^2.0.0", + "body-parser": "^2.0.1", + "content-disposition": "^1.0.0", "content-type": "~1.0.4", "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", + "cookie-signature": "^1.2.1", + "debug": "4.3.6", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.2.0", - "fresh": "0.5.2", + "finalhandler": "^2.0.0", + "fresh": "2.0.0", "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", + "merge-descriptors": "^2.0.0", "methods": "~1.1.2", + "mime-types": "^3.0.0", "on-finished": "2.4.1", + "once": "1.4.0", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", "proxy-addr": "~2.0.7", - "qs": "6.11.0", + "qs": "6.13.0", "range-parser": "~1.2.1", + "router": "^2.0.0", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.0", + "send": "^1.1.0", + "serve-static": "^2.1.0", "setprototypeof": "1.2.0", "statuses": "2.0.1", - "type-is": "~1.6.18", + "type-is": "^2.0.0", "utils-merge": "1.0.1", "vary": "~1.1.2" }, @@ -79,7 +80,7 @@ "vhost": "~3.0.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "files": [ "LICENSE", diff --git a/test/Router.js b/test/Router.js index b22001a9ff..a1952f445a 100644 --- a/test/Router.js +++ b/test/Router.js @@ -25,7 +25,7 @@ describe('Router', function(){ }); router.use('/foo', another); - router.handle({ url: '/foo/bar', method: 'GET' }, { end: done }); + router.handle({ url: '/foo/bar', method: 'GET' }, { end: done }, function(){}); }); it('should support dynamic routes', function(done){ @@ -38,7 +38,7 @@ describe('Router', function(){ }); router.use('/:foo', another); - router.handle({ url: '/test/route', method: 'GET' }, { end: done }); + router.handle({ url: '/test/route', method: 'GET' }, { end: done }, function(){}); }); it('should handle blank URL', function(done){ @@ -102,7 +102,7 @@ describe('Router', function(){ res.end(); }); - router.handle({ url: '/', method: 'GET' }, { end: done }); + router.handle({ url: '/', method: 'GET' }, { end: done }, function(){}); }); it('should not stack overflow with a large sync route stack', function (done) { @@ -127,7 +127,9 @@ describe('Router', function(){ res.end() }) - router.handle({ url: '/foo', method: 'GET' }, { end: done }) + router.handle({ url: '/foo', method: 'GET' }, { end: done }, function (err) { + assert(!err, err); + }); }) it('should not stack overflow with a large sync middleware stack', function (done) { @@ -152,7 +154,9 @@ describe('Router', function(){ res.end() }) - router.handle({ url: '/', method: 'GET' }, { end: done }) + router.handle({ url: '/', method: 'GET' }, { end: done }, function (err) { + assert(!err, err); + }) }) describe('.handle', function(){ @@ -169,7 +173,7 @@ describe('Router', function(){ done(); } } - router.handle({ url: '/foo', method: 'GET' }, res); + router.handle({ url: '/foo', method: 'GET' }, res, function(){}); }) }) @@ -424,50 +428,32 @@ describe('Router', function(){ assert.equal(count, methods.length); done(); }) - - it('should be called for any URL when "*"', function (done) { - var cb = after(4, done) - var router = new Router() - - function no () { - throw new Error('should not be called') - } - - router.all('*', function (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', function() { it('should require middleware', function () { var router = new Router() - assert.throws(function () { router.use('/') }, /requires a middleware function/) + assert.throws(function () { router.use('/') }, /argument handler is required/) }) it('should reject string as middleware', function () { var router = new Router() - assert.throws(function () { router.use('/', 'foo') }, /requires a middleware function but got a string/) + assert.throws(function () { router.use('/', 'foo') }, /argument handler must be a function/) }) it('should reject number as middleware', function () { var router = new Router() - assert.throws(function () { router.use('/', 42) }, /requires a middleware function but got a number/) + assert.throws(function () { router.use('/', 42) }, /argument handler must be a function/) }) it('should reject null as middleware', function () { var router = new Router() - assert.throws(function () { router.use('/', null) }, /requires a middleware function but got a Null/) + assert.throws(function () { router.use('/', null) }, /argument handler must be a function/) }) it('should reject Date as middleware', function () { var router = new Router() - assert.throws(function () { router.use('/', new Date()) }, /requires a middleware function but got a Date/) + assert.throws(function () { router.use('/', new Date()) }, /argument handler must be a function/) }) it('should be called for any URL', function (done) { @@ -512,6 +498,16 @@ describe('Router', function(){ }) describe('.param', function() { + it('should require function', function () { + var router = new Router(); + assert.throws(router.param.bind(router, 'id'), /argument fn is required/); + }); + + it('should reject non-function', function () { + var 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', function(done) { var router = new Router(); diff --git a/test/app.all.js b/test/app.all.js index 185a8332fe..e4afca7d73 100644 --- a/test/app.all.js +++ b/test/app.all.js @@ -26,7 +26,7 @@ describe('app.all()', function(){ var app = express() , n = 0; - app.all('/*', function(req, res, next){ + app.all('/*splat', function(req, res, next){ if (n++) return done(new Error('DELETE called several times')); next(); }); diff --git a/test/app.del.js b/test/app.del.js deleted file mode 100644 index e9e5769d65..0000000000 --- a/test/app.del.js +++ /dev/null @@ -1,18 +0,0 @@ -'use strict' - -var express = require('../') - , request = require('supertest'); - -describe('app.del()', function(){ - it('should alias app.delete()', function(done){ - var app = express(); - - app.del('/tobi', function(req, res){ - res.end('deleted tobi!'); - }); - - request(app) - .del('/tobi') - .expect('deleted tobi!', done); - }) -}) diff --git a/test/app.js b/test/app.js index 6134717c33..fe7d4c2758 100644 --- a/test/app.js +++ b/test/app.js @@ -56,18 +56,6 @@ describe('app.mountpath', function(){ }) }) -describe('app.router', function(){ - it('should throw with notice', function(done){ - var app = express() - - try { - app.router; - } catch(err) { - done(); - } - }) -}) - describe('app.path()', function(){ it('should return the canonical', function(){ var app = express() diff --git a/test/app.listen.js b/test/app.listen.js index 5b150063b9..7e7e731a3b 100644 --- a/test/app.listen.js +++ b/test/app.listen.js @@ -1,6 +1,7 @@ 'use strict' var express = require('../') +var assert = require('assert') describe('app.listen()', function(){ it('should wrap with an HTTP server', function(done){ @@ -10,4 +11,17 @@ describe('app.listen()', function(){ server.close(done) }); }) + it('should callback on HTTP server errors', function (done) { + var app1 = express() + var app2 = express() + + var server1 = app1.listen(0, function (err) { + assert(!err) + app2.listen(server1.address().port, function (err) { + assert(err.code === 'EADDRINUSE') + server1.close() + done() + }) + }) + }) }) diff --git a/test/app.locals.js b/test/app.locals.js index 657b4b75c7..a4f804fe2a 100644 --- a/test/app.locals.js +++ b/test/app.locals.js @@ -5,10 +5,11 @@ var express = require('../') describe('app', function(){ describe('.locals', function () { - it('should default object', function () { + it('should default object with null prototype', function () { var app = express() assert.ok(app.locals) assert.strictEqual(typeof app.locals, 'object') + assert.strictEqual(Object.getPrototypeOf(app.locals), null) }) describe('.settings', function () { diff --git a/test/app.options.js b/test/app.options.js index fdfd38c8a2..ee4c81631c 100644 --- a/test/app.options.js +++ b/test/app.options.js @@ -7,28 +7,28 @@ describe('OPTIONS', function(){ it('should default to the routes defined', function(done){ var app = express(); - app.del('/', function(){}); + app.post('/', function(){}); app.get('/users', function(req, res){}); app.put('/users', function(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', function(done){ var app = express(); - app.del('/', function(){}); + app.delete('/', function(){}); app.get('/users', function(req, res){}); app.put('/users', function(req, res){}); app.get('/users', function(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', function(done){ @@ -45,8 +45,8 @@ describe('OPTIONS', function(){ 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', function(done){ @@ -69,8 +69,8 @@ describe('OPTIONS', function(){ 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', function () { diff --git a/test/app.param.js b/test/app.param.js index b4ccc8a2d1..5c9a563087 100644 --- a/test/app.param.js +++ b/test/app.param.js @@ -1,51 +1,9 @@ 'use strict' -var assert = require('assert') var express = require('../') , request = require('supertest'); describe('app', function(){ - describe('.param(fn)', function(){ - it('should map app.param(name, ...) logic', function(done){ - var app = express(); - - app.param(function(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', function(req, res){ - res.send(req.params.name); - }); - - request(app) - .get('/user/tj') - .expect(200, 'tj', function (err) { - if (err) return done(err) - request(app) - .get('/user/123') - .expect(404, done); - }); - - }) - - it('should fail if not given fn', function(){ - var app = express(); - assert.throws(app.param.bind(app, ':name', 'bob')) - }) - }) - describe('.param(names, fn)', function(){ it('should map the array', function(done){ var app = express(); diff --git a/test/app.route.js b/test/app.route.js index eaf8a12051..a0c8696e50 100644 --- a/test/app.route.js +++ b/test/app.route.js @@ -3,6 +3,8 @@ var express = require('../'); var request = require('supertest'); +var describePromises = global.Promise ? describe : describe.skip + describe('app.route', function(){ it('should return a new route', function(done){ var app = express(); @@ -61,4 +63,137 @@ describe('app.route', function(){ .get('/test') .expect(404, done); }); + + describePromises('promise support', function () { + it('should pass rejected promise value', function (done) { + var app = express() + var route = app.route('/foo') + + route.all(function createError (req, res, next) { + return Promise.reject(new Error('boom!')) + }) + + route.all(function helloWorld (req, res) { + res.send('hello, world!') + }) + + route.all(function handleError (err, req, res, next) { + res.status(500) + res.send('caught: ' + err.message) + }) + + request(app) + .get('/foo') + .expect(500, 'caught: boom!', done) + }) + + it('should pass rejected promise without value', function (done) { + var app = express() + var route = app.route('/foo') + + route.all(function createError (req, res, next) { + return Promise.reject() + }) + + route.all(function helloWorld (req, res) { + res.send('hello, world!') + }) + + route.all(function handleError (err, req, res, next) { + res.status(500) + res.send('caught: ' + err.message) + }) + + request(app) + .get('/foo') + .expect(500, 'caught: Rejected promise', done) + }) + + it('should ignore resolved promise', function (done) { + var app = express() + var route = app.route('/foo') + + route.all(function createError (req, res, next) { + res.send('saw GET /foo') + return Promise.resolve('foo') + }) + + route.all(function () { + done(new Error('Unexpected route invoke')) + }) + + request(app) + .get('/foo') + .expect(200, 'saw GET /foo', done) + }) + + describe('error handling', function () { + it('should pass rejected promise value', function (done) { + var app = express() + var route = app.route('/foo') + + route.all(function createError (req, res, next) { + return Promise.reject(new Error('boom!')) + }) + + route.all(function handleError (err, req, res, next) { + return Promise.reject(new Error('caught: ' + err.message)) + }) + + route.all(function handleError (err, req, res, next) { + res.status(500) + res.send('caught again: ' + err.message) + }) + + request(app) + .get('/foo') + .expect(500, 'caught again: caught: boom!', done) + }) + + it('should pass rejected promise without value', function (done) { + var app = express() + var route = app.route('/foo') + + route.all(function createError (req, res, next) { + return Promise.reject(new Error('boom!')) + }) + + route.all(function handleError (err, req, res, next) { + return Promise.reject() + }) + + route.all(function handleError (err, req, res, next) { + res.status(500) + res.send('caught again: ' + err.message) + }) + + request(app) + .get('/foo') + .expect(500, 'caught again: Rejected promise', done) + }) + + it('should ignore resolved promise', function (done) { + var app = express() + var route = app.route('/foo') + + route.all(function createError (req, res, next) { + return Promise.reject(new Error('boom!')) + }) + + route.all(function handleError (err, req, res, next) { + res.status(500) + res.send('caught: ' + err.message) + return Promise.resolve('foo') + }) + + route.all(function () { + done(new Error('Unexpected route invoke')) + }) + + request(app) + .get('/foo') + .expect(500, 'caught: boom!', done) + }) + }) + }) }); diff --git a/test/app.router.js b/test/app.router.js index 8e427bd6dc..11742d98a7 100644 --- a/test/app.router.js +++ b/test/app.router.js @@ -6,6 +6,7 @@ var express = require('../') , assert = require('assert') , methods = require('methods'); +var describePromises = global.Promise ? describe : describe.skip var shouldSkipQuery = require('./support/utils').shouldSkipQuery describe('app.router', function(){ @@ -37,7 +38,7 @@ describe('app.router', function(){ }) describe('methods', function(){ - methods.concat('del').forEach(function(method){ + methods.forEach(function(method){ if (method === 'connect') return; it('should include ' + method.toUpperCase(), function(done){ @@ -57,7 +58,7 @@ describe('app.router', function(){ it('should reject numbers for app.' + method, function(){ var app = express(); - assert.throws(app[method].bind(app, '/', 3), /Number/) + assert.throws(app[method].bind(app, '/', 3), /argument handler must be a function/); }) }); @@ -336,12 +337,12 @@ describe('app.router', function(){ var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/*.*', function(req, res){ + router.get(/^\/(.*)\.(.*)/, function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function(k){ return [k, req.params[k]] })); }); - app.use('/user/id:(\\d+)', router); + app.use(/^\/user\/id:(\d+)/, router); request(app) .get('/user/id:10/profile.json') @@ -352,12 +353,12 @@ describe('app.router', function(){ var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/*', function(req, res){ + router.get(/\/(.*)/, function (req, res) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function(k){ return [k, req.params[k]] })); }); - app.use('/user/id:(\\d+)/name:(\\w+)', router); + app.use(/^\/user\/id:(\d+)\/name:(\w+)/, router); request(app) .get('/user/id:10/name:tj/profile') @@ -368,12 +369,12 @@ describe('app.router', function(){ var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/name:(\\w+)', function(req, res){ + router.get(/\/name:(\w+)/, function(req, res){ var keys = Object.keys(req.params).sort(); res.send(keys.map(function(k){ return [k, req.params[k]] })); }); - app.use('/user/id:(\\d+)', router); + app.use(/\/user\/id:(\d+)/, router); request(app) .get('/user/id:10/name:tj') @@ -403,11 +404,11 @@ describe('app.router', function(){ var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/user:(\\w+)/*', function (req, res, next) { + router.get(/\/user:(\w+)\//, function (req, res, next) { next(); }); - app.use('/user/id:(\\d+)', function (req, res, next) { + app.use(/\/user\/id:(\d+)/, function (req, res, next) { router(req, res, function (err) { var keys = Object.keys(req.params).sort(); res.send(keys.map(function(k){ return [k, req.params[k]] })); @@ -574,23 +575,6 @@ describe('app.router', function(){ }) }) - it('should allow escaped regexp', function(done){ - var app = express(); - - app.get('/user/\\d+', function(req, res){ - res.end('woot'); - }); - - request(app) - .get('/user/10') - .expect(200, function (err) { - if (err) return done(err) - request(app) - .get('/user/tj') - .expect(404, done); - }); - }) - it('should allow literal "."', function(done){ var app = express(); @@ -606,172 +590,6 @@ describe('app.router', function(){ .expect('users from 1 to 50', done); }) - describe('*', function(){ - it('should capture everything', function (done) { - var app = express() - - app.get('*', function (req, res) { - res.end(req.params[0]) - }) - - request(app) - .get('/user/tobi.json') - .expect('/user/tobi.json', done) - }) - - it('should decode the capture', function (done) { - var app = express() - - app.get('*', function (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', function(done){ - var app = express(); - - app.get('/user/*.json', function(req, res){ - res.end(req.params[0]); - }); - - request(app) - .get('/user/tj.json') - .expect('tj', done); - }) - - it('should work with several', function(done){ - var app = express(); - - app.get('/api/*.*', function(req, res){ - var 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', function(done){ - var app = express(); - var cb = after(2, done) - - app.get('/api*', function(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', function(done){ - var app = express(); - - app.get('/api/:resource(*)', function(req, res){ - var 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', function(done){ - var app = express(); - - app.get('/user/:user*', function(req, res){ - res.end(req.params.user); - }); - - request(app) - .get('/user/122') - .expect('122', done); - }) - - it('should eat everything after /', function(done){ - var app = express(); - - app.get('/user/:user*', function(req, res){ - res.end(req.params.user); - }); - - request(app) - .get('/user/122/aaa') - .expect('122', done); - }) - - it('should span multiple segments', function(done){ - var app = express(); - - app.get('/file/*', function(req, res){ - res.end(req.params[0]); - }); - - request(app) - .get('/file/javascripts/jquery.js') - .expect('javascripts/jquery.js', done); - }) - - it('should be optional', function(done){ - var app = express(); - - app.get('/file/*', function(req, res){ - res.end(req.params[0]); - }); - - request(app) - .get('/file/') - .expect('', done); - }) - - it('should require a preceding /', function(done){ - var app = express(); - - app.get('/file/*', function(req, res){ - res.end(req.params[0]); - }); - - request(app) - .get('/file') - .expect(404, done); - }) - - it('should keep correct parameter indexes', function(done){ - var app = express(); - - app.get('/*/user/:id', function (req, res) { - res.send(req.params); - }); - - request(app) - .get('/1/user/2') - .expect(200, '{"0":"1","id":"2"}', done); - }) - - it('should work within arrays', function(done){ - var app = express(); - - app.get(['/user/:id', '/foo/*', '/:bar'], function (req, res) { - res.send(req.params.bar); - }); - - request(app) - .get('/test') - .expect(200, 'test', done); - }) - }) - describe(':name', function(){ it('should denote a capture group', function(done){ var app = express(); @@ -813,8 +631,8 @@ describe('app.router', function(){ var app = express(); var cb = after(2, done); - app.get('/user(s)?/:user/:op', function(req, res){ - res.end(req.params.op + 'ing ' + req.params.user + (req.params[0] ? ' (old)' : '')); + app.get('/user{s}/:user/:op', function(req, res){ + res.end(req.params.op + 'ing ' + req.params.user + (req.url.startsWith('/users') ? ' (old)' : '')); }); request(app) @@ -860,7 +678,7 @@ describe('app.router', function(){ it('should denote an optional capture group', function(done){ var app = express(); - app.get('/user/:user/:op?', function(req, res){ + app.get('/user/:user{/:op}', function(req, res){ var op = req.params.op || 'view'; res.end(op + 'ing ' + req.params.user); }); @@ -873,7 +691,7 @@ describe('app.router', function(){ it('should populate the capture group', function(done){ var app = express(); - app.get('/user/:user/:op?', function(req, res){ + app.get('/user/:user{/:op}', function(req, res){ var op = req.params.op || 'view'; res.end(op + 'ing ' + req.params.user); }); @@ -884,6 +702,82 @@ describe('app.router', function(){ }) }) + describe(':name*', function () { + it('should match one segment', function (done) { + var app = express() + + app.get('/user/*user', function (req, res) { + res.end(req.params.user[0]) + }) + + request(app) + .get('/user/122') + .expect('122', done) + }) + + it('should match many segments', function (done) { + var app = express() + + app.get('/user/*user', function (req, res) { + res.end(req.params.user.join('/')) + }) + + request(app) + .get('/user/1/2/3/4') + .expect('1/2/3/4', done) + }) + + it('should match zero segments', function (done) { + var app = express() + + app.get('/user{/*user}', function (req, res) { + res.end(req.params.user) + }) + + request(app) + .get('/user') + .expect('', done) + }) + }) + + describe(':name+', function () { + it('should match one segment', function (done) { + var app = express() + + app.get('/user/*user', function (req, res) { + res.end(req.params.user[0]) + }) + + request(app) + .get('/user/122') + .expect(200, '122', done) + }) + + it('should match many segments', function (done) { + var app = express() + + app.get('/user/*user', function (req, res) { + res.end(req.params.user.join('/')) + }) + + request(app) + .get('/user/1/2/3/4') + .expect(200, '1/2/3/4', done) + }) + + it('should not match zero segments', function (done) { + var app = express() + + app.get('/user/*user', function (req, res) { + res.end(req.params.user) + }) + + request(app) + .get('/user') + .expect(404, done) + }) + }) + describe('.:name', function(){ it('should denote a format', function(done){ var app = express(); @@ -908,7 +802,7 @@ describe('app.router', function(){ var app = express(); var cb = after(2, done) - app.get('/:name.:format?', function(req, res){ + app.get('/:name{.:format}', function(req, res){ res.end(req.params.name + ' as ' + (req.params.format || 'html')); }); @@ -927,7 +821,7 @@ describe('app.router', function(){ var app = express() , calls = []; - app.get('/foo/:bar?', function(req, res, next){ + app.get('/foo{/:bar}', function(req, res, next){ calls.push('/foo/:bar?'); next(); }); @@ -1012,7 +906,7 @@ describe('app.router', function(){ var app = express() , calls = []; - app.get('/foo/:bar?', function(req, res, next){ + app.get('/foo{/:bar}', function(req, res, next){ calls.push('/foo/:bar?'); next(); }); @@ -1069,6 +963,138 @@ describe('app.router', function(){ }) }) + describePromises('promise support', function () { + it('should pass rejected promise value', function (done) { + var app = express() + var 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', function (done) { + var app = express() + var 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', function (done) { + var app = express() + var router = new express.Router() + + router.use(function createError (req, res, next) { + res.send('saw GET /foo') + return Promise.resolve('foo') + }) + + router.use(function () { + done(new Error('Unexpected middleware invoke')) + }) + + app.use(router) + + request(app) + .get('/foo') + .expect(200, 'saw GET /foo', done) + }) + + describe('error handling', function () { + it('should pass rejected promise value', function (done) { + var app = express() + var 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', function (done) { + var app = express() + var 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', function (done) { + var app = express() + var 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(function () { + 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', function(done){ var app = express(); @@ -1091,7 +1117,7 @@ describe('app.router', function(){ var app = express(); var path = []; - app.get('*', function(req, res, next){ + app.get('/*path', function (req, res, next) { path.push(0); next(); }); @@ -1111,7 +1137,7 @@ describe('app.router', function(){ next(); }); - app.get('*', function(req, res, next){ + app.get('/*splat', function (req, res, next) { path.push(4); next(); }); diff --git a/test/app.routes.error.js b/test/app.routes.error.js index 56081b3112..efc0108b0f 100644 --- a/test/app.routes.error.js +++ b/test/app.routes.error.js @@ -51,7 +51,7 @@ describe('app', function(){ assert.ok(b) assert.ok(c) assert.ok(!d) - res.send(204); + res.sendStatus(204); }); request(app) diff --git a/test/app.use.js b/test/app.use.js index 1de3275c8e..a88a2f2c8e 100644 --- a/test/app.use.js +++ b/test/app.use.js @@ -258,27 +258,27 @@ describe('app', function(){ describe('.use(path, middleware)', function(){ it('should require middleware', function () { var app = express() - assert.throws(function () { app.use('/') }, /requires a middleware function/) + assert.throws(function () { app.use('/') }, 'TypeError: app.use() requires a middleware function') }) it('should reject string as middleware', function () { var app = express() - assert.throws(function () { app.use('/', 'foo') }, /requires a middleware function but got a string/) + assert.throws(function () { app.use('/', 'foo') }, /argument handler must be a function/) }) it('should reject number as middleware', function () { var app = express() - assert.throws(function () { app.use('/', 42) }, /requires a middleware function but got a number/) + assert.throws(function () { app.use('/', 42) }, /argument handler must be a function/) }) it('should reject null as middleware', function () { var app = express() - assert.throws(function () { app.use('/', null) }, /requires a middleware function but got a Null/) + assert.throws(function () { app.use('/', null) }, /argument handler must be a function/) }) it('should reject Date as middleware', function () { var app = express() - assert.throws(function () { app.use('/', new Date()) }, /requires a middleware function but got a Date/) + assert.throws(function () { app.use('/', new Date()) }, /argument handler must be a function/) }) it('should strip path from req.url', function (done) { diff --git a/test/exports.js b/test/exports.js index 5ab0f885ce..dc635d1dbc 100644 --- a/test/exports.js +++ b/test/exports.js @@ -79,9 +79,4 @@ describe('exports', function(){ .get('/') .expect('bar', done); }) - - it('should throw on old middlewares', function(){ - assert.throws(function () { express.bodyParser() }, /Error:.*middleware.*bodyParser/) - assert.throws(function () { express.limit() }, /Error:.*middleware.*limit/) - }) }) diff --git a/test/express.json.js b/test/express.json.js index f6f536b15e..859347e1dc 100644 --- a/test/express.json.js +++ b/test/express.json.js @@ -43,12 +43,13 @@ describe('express.json()', function () { .expect(200, '{}', done) }) + // The old node error message modification in body parser is catching this it('should 400 when only whitespace', function (done) { request(createApp()) .post('/') .set('Content-Type', 'application/json') .send(' \n') - .expect(400, '[entity.parse.failed] ' + parseError(' '), done) + .expect(400, '[entity.parse.failed] ' + parseError(' \n'), done) }) it('should 400 when invalid content-length', function (done) { @@ -72,32 +73,6 @@ describe('express.json()', function () { .expect(400, /content length/, done) }) - it('should 500 if stream not readable', function (done) { - var app = express() - - app.use(function (req, res, next) { - req.on('end', next) - req.resume() - }) - - app.use(express.json()) - - app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.send('[' + err.type + '] ' + err.message) - }) - - app.post('/', function (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', function (done) { var app = express() @@ -341,7 +316,7 @@ describe('express.json()', function () { .post('/') .set('Content-Type', 'application/json') .send('{"user":"tobi"}') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -373,7 +348,7 @@ describe('express.json()', function () { .post('/') .set('Content-Type', 'application/x-json') .send('{"user":"tobi"}') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -579,14 +554,14 @@ describe('express.json()', function () { .end(done) }) - it('should presist store when unmatched content-type', function (done) { + it('should persist store when unmatched content-type', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/fizzbuzz') .send('buzz') .expect(200) .expect('x-store-foo', 'bar') - .expect('{}') + .expect('') .end(done) }) @@ -753,6 +728,7 @@ function createApp (options) { app.use(express.json(options)) app.use(function (err, req, res, next) { + // console.log(err) res.status(err.status || 500) res.send(String(req.headers['x-error-property'] ? err[req.headers['x-error-property']] diff --git a/test/express.raw.js b/test/express.raw.js index 4aa62bb85b..f6513a7d48 100644 --- a/test/express.raw.js +++ b/test/express.raw.js @@ -65,36 +65,6 @@ describe('express.raw()', function () { .expect(200, { buf: '' }, done) }) - it('should 500 if stream not readable', function (done) { - var app = express() - - app.use(function (req, res, next) { - req.on('end', next) - req.resume() - }) - - app.use(express.raw()) - - app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.send('[' + err.type + '] ' + err.message) - }) - - app.post('/', function (req, res) { - if (Buffer.isBuffer(req.body)) { - res.json({ buf: req.body.toString('hex') }) - } else { - res.json(req.body) - } - }) - - request(app) - .post('/') - .set('Content-Type', 'application/octet-stream') - .send('the user is tobi') - .expect(500, '[stream.not.readable] stream is not readable', done) - }) - it('should handle duplicated middleware', function (done) { var app = express() @@ -236,7 +206,7 @@ describe('express.raw()', function () { var test = request(this.app).post('/') test.set('Content-Type', 'application/octet-stream') test.write(Buffer.from('000102', 'hex')) - test.expect(200, '{}', done) + test.expect(200, '', done) }) }) @@ -265,7 +235,7 @@ describe('express.raw()', function () { var test = request(this.app).post('/') test.set('Content-Type', 'application/x-foo') test.write(Buffer.from('000102', 'hex')) - test.expect(200, '{}', done) + test.expect(200, '', done) }) }) @@ -420,7 +390,6 @@ describe('express.raw()', function () { .send('buzz') .expect(200) .expect('x-store-foo', 'bar') - .expect('{}') .end(done) }) diff --git a/test/express.static.js b/test/express.static.js index 23e607ed93..e5100e8c8d 100644 --- a/test/express.static.js +++ b/test/express.static.js @@ -41,7 +41,7 @@ describe('express.static()', function () { it('should set Content-Type', function (done) { request(this.app) .get('/todo.txt') - .expect('Content-Type', 'text/plain; charset=UTF-8') + .expect('Content-Type', 'text/plain; charset=utf-8') .expect(200, done) }) diff --git a/test/express.text.js b/test/express.text.js index cb7750a525..ce365fa73c 100644 --- a/test/express.text.js +++ b/test/express.text.js @@ -61,32 +61,6 @@ describe('express.text()', function () { .expect(200, '""', done) }) - it('should 500 if stream not readable', function (done) { - var app = express() - - app.use(function (req, res, next) { - req.on('end', next) - req.resume() - }) - - app.use(express.text()) - - app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.send('[' + err.type + '] ' + err.message) - }) - - app.post('/', function (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', function (done) { var app = express() @@ -247,7 +221,7 @@ describe('express.text()', function () { .post('/') .set('Content-Type', 'text/plain') .send('user is tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -277,7 +251,7 @@ describe('express.text()', function () { .post('/') .set('Content-Type', 'text/xml') .send('tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -445,7 +419,6 @@ describe('express.text()', function () { .send('buzz') .expect(200) .expect('x-store-foo', 'bar') - .expect('{}') .end(done) }) diff --git a/test/express.urlencoded.js b/test/express.urlencoded.js index 537fb797e7..37afb07f38 100644 --- a/test/express.urlencoded.js +++ b/test/express.urlencoded.js @@ -62,32 +62,6 @@ describe('express.urlencoded()', function () { .expect(200, '{}', done) }) - it('should 500 if stream not readable', function (done) { - var app = express() - - app.use(function (req, res, next) { - req.on('end', next) - req.resume() - }) - - app.use(express.urlencoded()) - - app.use(function (err, req, res, next) { - res.status(err.status || 500) - res.send('[' + err.type + '] ' + err.message) - }) - - app.post('/', function (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', function (done) { var app = express() @@ -105,12 +79,12 @@ describe('express.urlencoded()', function () { .expect(200, '{"user":"tobi"}', done) }) - it('should parse extended syntax', function (done) { + it('should not parse extended syntax', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/x-www-form-urlencoded') .send('user[name][first]=Tobi') - .expect(200, '{"user":{"name":{"first":"Tobi"}}}', done) + .expect(200, '{"user[name][first]":"Tobi"}', done) }) describe('with extended option', function () { @@ -473,7 +447,7 @@ describe('express.urlencoded()', function () { .post('/') .set('Content-Type', 'application/x-www-form-urlencoded') .send('user=tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -505,7 +479,7 @@ describe('express.urlencoded()', function () { .post('/') .set('Content-Type', 'application/x-foo') .send('user=tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -690,7 +664,6 @@ describe('express.urlencoded()', function () { .send('buzz') .expect(200) .expect('x-store-foo', 'bar') - .expect('{}') .end(done) }) diff --git a/test/req.acceptsCharset.js b/test/req.acceptsCharset.js deleted file mode 100644 index 6dbab439b7..0000000000 --- a/test/req.acceptsCharset.js +++ /dev/null @@ -1,50 +0,0 @@ -'use strict' - -var express = require('../') - , request = require('supertest'); - -describe('req', function(){ - describe('.acceptsCharset(type)', function(){ - describe('when Accept-Charset is not present', function(){ - it('should return true', function(done){ - var app = express(); - - app.use(function(req, res, next){ - res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no'); - }); - - request(app) - .get('/') - .expect('yes', done); - }) - }) - - describe('when Accept-Charset is present', function () { - it('should return true', function (done) { - var app = express(); - - app.use(function(req, res, next){ - res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no'); - }); - - request(app) - .get('/') - .set('Accept-Charset', 'foo, bar, utf-8') - .expect('yes', done); - }) - - it('should return false otherwise', function(done){ - var app = express(); - - app.use(function(req, res, next){ - res.end(req.acceptsCharset('utf-8') ? 'yes' : 'no'); - }); - - request(app) - .get('/') - .set('Accept-Charset', 'foo, bar') - .expect('no', done); - }) - }) - }) -}) diff --git a/test/req.acceptsEncoding.js b/test/req.acceptsEncoding.js deleted file mode 100644 index bcec2280e6..0000000000 --- a/test/req.acceptsEncoding.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -var express = require('../') - , request = require('supertest'); - -describe('req', function(){ - describe('.acceptsEncoding', function(){ - it('should return encoding if accepted', function (done) { - var app = express(); - - app.get('/', function (req, res) { - res.send({ - gzip: req.acceptsEncoding('gzip'), - deflate: req.acceptsEncoding('deflate') - }) - }) - - request(app) - .get('/') - .set('Accept-Encoding', ' gzip, deflate') - .expect(200, { gzip: 'gzip', deflate: 'deflate' }, done) - }) - - it('should be false if encoding not accepted', function(done){ - var app = express(); - - app.get('/', function (req, res) { - res.send({ - bogus: req.acceptsEncoding('bogus') - }) - }) - - request(app) - .get('/') - .set('Accept-Encoding', ' gzip, deflate') - .expect(200, { bogus: false }, done) - }) - }) -}) diff --git a/test/req.acceptsLanguage.js b/test/req.acceptsLanguage.js deleted file mode 100644 index 39bd73c483..0000000000 --- a/test/req.acceptsLanguage.js +++ /dev/null @@ -1,57 +0,0 @@ -'use strict' - -var express = require('../') - , request = require('supertest'); - -describe('req', function(){ - describe('.acceptsLanguage', function(){ - it('should return language if accepted', function (done) { - var app = express(); - - app.get('/', function (req, res) { - res.send({ - 'en-us': req.acceptsLanguage('en-us'), - en: req.acceptsLanguage('en') - }) - }) - - request(app) - .get('/') - .set('Accept-Language', 'en;q=.5, en-us') - .expect(200, { 'en-us': 'en-us', en: 'en' }, done) - }) - - it('should be false if language not accepted', function(done){ - var app = express(); - - app.get('/', function (req, res) { - res.send({ - es: req.acceptsLanguage('es') - }) - }) - - request(app) - .get('/') - .set('Accept-Language', 'en;q=.5, en-us') - .expect(200, { es: false }, done) - }) - - describe('when Accept-Language is not present', function(){ - it('should always return language', function (done) { - var app = express(); - - app.get('/', function (req, res) { - res.send({ - en: req.acceptsLanguage('en'), - es: req.acceptsLanguage('es'), - jp: req.acceptsLanguage('jp') - }) - }) - - request(app) - .get('/') - .expect(200, { en: 'en', es: 'es', jp: 'jp' }, done) - }) - }) - }) -}) diff --git a/test/req.fresh.js b/test/req.fresh.js index 9160e2caaf..3bf6a1f65a 100644 --- a/test/req.fresh.js +++ b/test/req.fresh.js @@ -46,5 +46,25 @@ describe('req', function(){ .get('/') .expect(200, 'false', done); }) + + it('should ignore "If-Modified-Since" when "If-None-Match" is present', function(done) { + var app = express(); + const etag = '"FooBar"' + const now = Date.now() + + app.disable('x-powered-by') + app.use(function(req, res) { + res.set('Etag', etag) + res.set('Last-Modified', new Date(now).toUTCString()) + res.send(req.fresh); + }); + + request(app) + .get('/') + .set('If-Modified-Since', new Date(now - 1000).toUTCString) + .set('If-None-Match', etag) + .expect(304, done); + }) + }) }) diff --git a/test/req.host.js b/test/req.host.js index 2c051fb979..cdda82eaae 100644 --- a/test/req.host.js +++ b/test/req.host.js @@ -28,7 +28,7 @@ describe('req', function(){ request(app) .post('/') .set('Host', 'example.com:3000') - .expect('example.com', done); + .expect(200, 'example.com:3000', done); }) it('should return undefined otherwise', function(done){ @@ -67,7 +67,7 @@ describe('req', function(){ request(app) .post('/') .set('Host', '[::1]:3000') - .expect('[::1]', done); + .expect(200, '[::1]:3000', done); }) describe('when "trust proxy" is enabled', function(){ diff --git a/test/req.param.js b/test/req.param.js deleted file mode 100644 index b3748c02bc..0000000000 --- a/test/req.param.js +++ /dev/null @@ -1,61 +0,0 @@ -'use strict' - -var express = require('../') - , request = require('supertest') - -describe('req', function(){ - describe('.param(name, default)', function(){ - it('should use the default value unless defined', function(done){ - var app = express(); - - app.use(function(req, res){ - res.end(req.param('name', 'tj')); - }); - - request(app) - .get('/') - .expect('tj', done); - }) - }) - - describe('.param(name)', function(){ - it('should check req.query', function(done){ - var app = express(); - - app.use(function(req, res){ - res.end(req.param('name')); - }); - - request(app) - .get('/?name=tj') - .expect('tj', done); - }) - - it('should check req.body', function(done){ - var app = express(); - - app.use(express.json()) - - app.use(function(req, res){ - res.end(req.param('name')); - }); - - request(app) - .post('/') - .send({ name: 'tj' }) - .expect('tj', done); - }) - - it('should check req.params', function(done){ - var app = express(); - - app.get('/user/:name', function(req, res){ - res.end(req.param('filter') + req.param('name')); - }); - - request(app) - .get('/user/tj') - .expect('undefinedtj', done); - }) - }) -}) diff --git a/test/req.query.js b/test/req.query.js index 6fae592dcc..bc76d4106b 100644 --- a/test/req.query.js +++ b/test/req.query.js @@ -14,12 +14,12 @@ describe('req', function(){ .expect(200, '{}', done); }); - it('should default to parse complex keys', function (done) { + it('should default to parse simple keys', function (done) { var 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', function () { @@ -82,23 +82,6 @@ describe('req', function(){ }); }); - describe('when "query parser fn" is missing', function () { - it('should act like "extended"', function (done) { - var app = express(); - - delete app.settings['query parser']; - delete app.settings['query parser fn']; - - app.use(function (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', function () { it('should throw', function () { assert.throws(createApp.bind(null, 'bogus'), diff --git a/test/req.route.js b/test/req.route.js index 6c17fbb1c8..9bd7ed923b 100644 --- a/test/req.route.js +++ b/test/req.route.js @@ -8,7 +8,7 @@ describe('req', function(){ it('should be the executed Route', function(done){ var app = express(); - app.get('/user/:id/:op?', function(req, res, next){ + app.get('/user/:id{/:op}', function(req, res, next){ res.header('path-1', req.route.path) next(); }); @@ -20,7 +20,7 @@ describe('req', function(){ request(app) .get('/user/12/edit') - .expect('path-1', '/user/:id/:op?') + .expect('path-1', '/user/:id{/:op}') .expect('path-2', '/user/:id/edit') .expect(200, done) }) diff --git a/test/res.clearCookie.js b/test/res.clearCookie.js index 3d8a6a5a81..74a746eb7b 100644 --- a/test/res.clearCookie.js +++ b/test/res.clearCookie.js @@ -33,35 +33,29 @@ describe('res', function(){ .expect(200, done) }) - it('should set expires when passed', function(done) { - var expiresAt = new Date() + it('should ignore maxAge', function(done){ var app = express(); app.use(function(req, res){ - res.clearCookie('sid', { expires: expiresAt }).end(); + res.clearCookie('sid', { path: '/admin', maxAge: 1000 }).end(); }); request(app) .get('/') - .expect('Set-Cookie', 'sid=; Path=/; Expires=' + expiresAt.toUTCString() ) + .expect('Set-Cookie', 'sid=; Path=/admin; Expires=Thu, 01 Jan 1970 00:00:00 GMT') .expect(200, done) }) - it('should set both maxAge and expires when passed', function(done) { - var maxAgeInMs = 10000 - var expiresAt = new Date() - var expectedExpires = new Date(expiresAt.getTime() + maxAgeInMs) + it('should ignore user supplied expires param', function(done){ var app = express(); app.use(function(req, res){ - res.clearCookie('sid', { expires: expiresAt, maxAge: maxAgeInMs }).end(); + res.clearCookie('sid', { path: '/admin', expires: new Date() }).end(); }); request(app) .get('/') - // yes, this is the behavior. When we set a max-age, we also set expires to a date 10 sec ahead of expires - // even if we set max-age only, we will also set an expires 10 sec in the future - .expect('Set-Cookie', 'sid=; Max-Age=10; Path=/; Expires=' + expectedExpires.toUTCString()) + .expect('Set-Cookie', 'sid=; Path=/admin; Expires=Thu, 01 Jan 1970 00:00:00 GMT') .expect(200, done) }) }) diff --git a/test/res.download.js b/test/res.download.js index b52e66803c..f7d795d57c 100644 --- a/test/res.download.js +++ b/test/res.download.js @@ -26,7 +26,7 @@ describe('res', function(){ 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) }) @@ -69,7 +69,7 @@ describe('res', function(){ 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) }) @@ -86,7 +86,7 @@ describe('res', function(){ 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); }) @@ -115,7 +115,7 @@ describe('res', function(){ request(app) .get('/') - .expect('Content-Type', 'text/plain; charset=UTF-8') + .expect('Content-Type', 'text/plain; charset=utf-8') .expect('Content-Disposition', 'attachment; filename="name.txt"') .expect(200, 'tobi', cb) }) @@ -369,7 +369,7 @@ describe('res', function(){ 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); }) @@ -388,7 +388,7 @@ describe('res', function(){ 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) }) diff --git a/test/res.format.js b/test/res.format.js index cba6fe136b..59205bfaf4 100644 --- a/test/res.format.js +++ b/test/res.format.js @@ -28,7 +28,8 @@ app1.use(function(req, res, next){ app1.use(function(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(', ')) }) var app2 = express(); @@ -42,7 +43,8 @@ app2.use(function(req, res, next){ }); app2.use(function(err, req, res, next){ - res.send(err.status, 'Supports: ' + err.types.join(', ')); + res.status(err.status) + res.send('Supports: ' + err.types.join(', ')) }) var app3 = express(); @@ -70,7 +72,8 @@ app4.get('/', function (req, res) { }); app4.use(function(err, req, res, next){ - res.send(err.status, 'Supports: ' + err.types.join(', ')); + res.status(err.status) + res.send('Supports: ' + err.types.join(', ')) }) var app5 = express(); @@ -103,7 +106,8 @@ describe('res', function(){ }); app.use(function(err, req, res, next){ - res.send(err.status, 'Supports: ' + err.types.join(', ')); + res.status(err.status) + res.send('Supports: ' + err.types.join(', ')) }); test(app); @@ -164,7 +168,8 @@ describe('res', function(){ }); router.use(function(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/res.json.js b/test/res.json.js index dcaceae5ca..bef8adafd5 100644 --- a/test/res.json.js +++ b/test/res.json.js @@ -183,47 +183,4 @@ describe('res', function(){ }) }) }) - - describe('.json(status, object)', function(){ - it('should respond with json and set the .statusCode', function(done){ - var app = express(); - - app.use(function(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)', function(){ - it('should respond with json and set the .statusCode for backwards compat', function(done){ - var app = express(); - - app.use(function(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', function(done){ - var app = express(); - - app.use(function(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/res.jsonp.js b/test/res.jsonp.js index 0735d43bd5..e9cc08bc05 100644 --- a/test/res.jsonp.js +++ b/test/res.jsonp.js @@ -328,49 +328,6 @@ describe('res', function(){ }) }) - describe('.jsonp(status, object)', function(){ - it('should respond with json and set the .statusCode', function(done){ - var app = express(); - - app.use(function(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)', function(){ - it('should respond with json and set the .statusCode for backwards compat', function(done){ - var app = express(); - - app.use(function(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', function(done){ - var app = express(); - - app.use(function(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', function(done){ var app = express(); diff --git a/test/res.location.js b/test/res.location.js index 2e88002625..fb03221d7a 100644 --- a/test/res.location.js +++ b/test/res.location.js @@ -46,65 +46,19 @@ describe('res', function(){ .expect(200, done) }) - describe('when url is "back"', function () { - it('should set location from "Referer" header', function (done) { - var app = express() - - app.use(function (req, res) { - res.location('back').end() - }) - - request(app) - .get('/') - .set('Referer', '/some/page.html') - .expect('Location', '/some/page.html') - .expect(200, done) - }) - - it('should set location from "Referrer" header', function (done) { - var app = express() - - app.use(function (req, res) { - res.location('back').end() - }) - - request(app) - .get('/') - .set('Referrer', '/some/page.html') - .expect('Location', '/some/page.html') - .expect(200, done) - }) - - it('should prefer "Referrer" header', function (done) { - var app = express() - - app.use(function (req, res) { - res.location('back').end() - }) - - request(app) - .get('/') - .set('Referer', '/some/page1.html') - .set('Referrer', '/some/page2.html') - .expect('Location', '/some/page2.html') - .expect(200, done) - }) - - it('should set the header to "/" without referrer', function (done) { - var app = express() - - app.use(function (req, res) { - res.location('back').end() - }) + it('should encode data uri1', function (done) { + var app = express() + app.use(function (req, res) { + res.location('data:text/javascript,export default () => { }').end(); + }); - request(app) + request(app) .get('/') - .expect('Location', '/') + .expect('Location', 'data:text/javascript,export%20default%20()%20=%3E%20%7B%20%7D') .expect(200, done) - }) }) - it('should encode data uri', function (done) { + it('should encode data uri2', function (done) { var app = express() app.use(function (req, res) { res.location('data:text/javascript,export default () => { }').end(); diff --git a/test/res.redirect.js b/test/res.redirect.js index f7214d9331..264e0f2b8f 100644 --- a/test/res.redirect.js +++ b/test/res.redirect.js @@ -61,21 +61,6 @@ describe('res', function(){ }) }) - describe('.redirect(url, status)', function(){ - it('should set the response status', function(done){ - var app = express(); - - app.use(function(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', function(){ it('should ignore the body', function(done){ var app = express(); diff --git a/test/res.send.js b/test/res.send.js index b4cf68a7df..bce62c8d40 100644 --- a/test/res.send.js +++ b/test/res.send.js @@ -53,63 +53,18 @@ describe('res', function(){ }) }) - describe('.send(code)', function(){ - it('should set .statusCode', function(done){ - var app = express(); - - app.use(function(req, res){ - res.send(201) - }); - - request(app) - .get('/') - .expect('Created') - .expect(201, done); - }) - }) - - describe('.send(code, body)', function(){ - it('should set .statusCode and body', function(done){ - var app = express(); - - app.use(function(req, res){ - res.send(201, 'Created :)'); - }); - - request(app) - .get('/') - .expect('Created :)') - .expect(201, done); - }) - }) - - describe('.send(body, code)', function(){ - it('should be supported for backwards compat', function(done){ - var app = express(); - - app.use(function(req, res){ - res.send('Bad!', 400); - }); - - request(app) - .get('/') - .expect('Bad!') - .expect(400, done); - }) - }) - - describe('.send(code, number)', function(){ - it('should send number as json', function(done){ + describe('.send(Number)', function(){ + it('should send as application/json', function(done){ var app = express(); app.use(function(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) }) }) @@ -463,7 +418,7 @@ describe('res', function(){ app.use(function (req, res) { res.set('etag', '"asdf"'); - res.send(200); + res.send('hello!'); }); app.enable('etag'); @@ -514,7 +469,7 @@ describe('res', function(){ app.use(function (req, res) { res.set('etag', '"asdf"'); - res.send(200); + res.send('hello!'); }); request(app) diff --git a/test/res.sendFile.js b/test/res.sendFile.js index 4db0a3b6a4..7bba9cd6d1 100644 --- a/test/res.sendFile.js +++ b/test/res.sendFile.js @@ -1,11 +1,11 @@ 'use strict' var after = require('after'); +var assert = require('assert') var asyncHooks = tryRequire('async_hooks') var Buffer = require('safe-buffer').Buffer var express = require('../') , request = require('supertest') - , assert = require('assert'); var onFinished = require('on-finished'); var path = require('path'); var fixtures = path.join(__dirname, 'fixtures'); @@ -291,7 +291,7 @@ describe('res', function(){ request(app) .get('/') - .expect('Content-Type', 'text/plain; charset=UTF-8') + .expect('Content-Type', 'text/plain; charset=utf-8') .expect(200, 'tobi', cb) }) @@ -890,507 +890,6 @@ describe('res', function(){ }) }) }) - - describe('.sendfile(path, fn)', function(){ - it('should invoke the callback when complete', function(done){ - var app = express(); - var cb = after(2, done); - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', cb) - }); - - request(app) - .get('/') - .expect(200, cb); - }) - - it('should utilize the same options as express.static()', function(done){ - var app = express(); - - app.use(function(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', function (done) { - var cb = after(2, done) - var app = express(); - - app.use(function (req, res) { - setImmediate(function () { - res.sendfile('test/fixtures/name.txt', function (err) { - assert.ok(err) - assert.strictEqual(err.code, 'ECONNABORTED') - cb() - }); - }); - test.req.abort() - }); - - var server = app.listen() - var test = request(server).get('/') - test.end(function (err) { - assert.ok(err) - server.close(cb) - }) - }) - - it('should invoke the callback when client already aborted', function (done) { - var cb = after(2, done) - var app = express(); - - app.use(function (req, res) { - onFinished(res, function () { - res.sendfile('test/fixtures/name.txt', function (err) { - assert.ok(err) - assert.strictEqual(err.code, 'ECONNABORTED') - cb() - }); - }); - test.req.abort() - }); - - var server = app.listen() - var test = request(server).get('/') - test.end(function (err) { - assert.ok(err) - server.close(cb) - }) - }) - - it('should invoke the callback without error when HEAD', function (done) { - var app = express(); - var cb = after(2, done); - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt', cb); - }); - - request(app) - .head('/') - .expect(200, cb); - }); - - it('should invoke the callback without error when 304', function (done) { - var app = express(); - var cb = after(3, done); - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt', cb); - }); - - request(app) - .get('/') - .expect('ETag', /^(?:W\/)?"[^"]+"$/) - .expect(200, 'tobi', function (err, res) { - if (err) return cb(err); - var etag = res.headers.etag; - request(app) - .get('/') - .set('If-None-Match', etag) - .expect(304, cb); - }); - }); - - it('should invoke the callback on 404', function(done){ - var app = express(); - var calls = 0; - - app.use(function(req, res){ - res.sendfile('test/fixtures/nope.html', function(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', function(done){ - var app = express(); - - app.use(function(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', function(done){ - var app = express() - - app.use(function(req, res){ - res.sendfile('test/fixtures/foo/../user.html', function(err){ - assert(!res.headersSent); - res.send(err.message); - }); - }); - - request(app) - .get('/') - .expect('Forbidden') - .expect(200, done); - }) - - it('should invoke the callback on socket error', function(done){ - var app = express() - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', function(err){ - assert.ok(err) - assert.ok(!res.headersSent) - assert.strictEqual(err.message, 'broken!') - done(); - }); - - req.socket.destroy(new Error('broken!')) - }); - - request(app) - .get('/') - .end(function(){}); - }) - - describeAsyncHooks('async local storage', function () { - it('should presist store', function (done) { - var app = express() - var cb = after(2, done) - var store = { foo: 'bar' } - - app.use(function (req, res, next) { - req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage() - req.asyncLocalStorage.run(store, next) - }) - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt', function (err) { - if (err) return cb(err) - - var 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', function (done) { - var app = express() - var store = { foo: 'bar' } - - app.use(function (req, res, next) { - req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage() - req.asyncLocalStorage.run(store, next) - }) - - app.use(function (req, res) { - res.sendfile('test/fixtures/does-not-exist', function (err) { - var 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)', function(){ - it('should not serve dotfiles', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/.name'); - }); - - request(app) - .get('/') - .expect(404, done); - }) - - it('should accept dotfiles option', function(done){ - var app = express(); - - app.use(function(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', function(done){ - var app = express(); - var headers = { - 'x-success': 'sent', - 'x-other': 'done' - }; - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.html', { headers: headers }); - }); - - request(app) - .get('/') - .expect('x-success', 'sent') - .expect('x-other', 'done') - .expect(200, done); - }) - - it('should ignore headers option on 404', function(done){ - var app = express(); - var headers = { 'x-success': 'sent' }; - - app.use(function(req, res){ - res.sendfile('test/fixtures/user.nothing', { headers: headers }); - }); - - request(app) - .get('/') - .expect(utils.shouldNotHaveHeader('X-Success')) - .expect(404, done); - }) - - it('should transfer a file', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/name.txt'); - }); - - request(app) - .get('/') - .expect(200, 'tobi', done); - }); - - it('should transfer a directory index file', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/blog/'); - }); - - request(app) - .get('/') - .expect(200, 'index', done); - }); - - it('should 404 for directory without trailing slash', function (done) { - var app = express(); - - app.use(function (req, res) { - res.sendfile('test/fixtures/blog'); - }); - - request(app) - .get('/') - .expect(404, done); - }); - - it('should transfer a file with urlencoded name', function (done) { - var app = express(); - - app.use(function (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', function (done) { - var app = express(); - var cb = after(2, done) - var error = null - - app.use(function (req, res) { - setImmediate(function () { - res.sendfile(path.resolve(fixtures, 'name.txt')); - setTimeout(function () { - cb(error) - }, 10) - }); - test.req.abort() - }); - - app.use(function (err, req, res, next) { - error = err - next(err) - }); - - var server = app.listen() - var test = request(server).get('/') - test.end(function (err) { - assert.ok(err) - server.close(cb) - }) - }) - - describe('with an absolute path', function(){ - it('should transfer the file', function(done){ - var app = express(); - - app.use(function(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', function(){ - it('should transfer the file', function(done){ - var app = express(); - - app.use(function(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"', function(done){ - var app = express(); - - app.use(function(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', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('test/fixtures/foo/../user.html'); - }); - - request(app) - .get('/') - .expect(403, done); - }) - - it('should allow ../ when "root" is set', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('foo/../user.html', { root: 'test/fixtures' }); - }); - - request(app) - .get('/') - .expect(200, done); - }) - - it('should disallow requesting out of "root"', function(done){ - var app = express(); - - app.use(function(req, res){ - res.sendfile('foo/../../user.html', { root: 'test/fixtures' }); - }); - - request(app) - .get('/') - .expect(403, done); - }) - - it('should next(404) when not found', function(done){ - var app = express() - , calls = 0; - - app.use(function(req, res){ - res.sendfile('user.html'); - }); - - app.use(function(req, res){ - assert(0, 'this should not be called'); - }); - - app.use(function(err, req, res, next){ - ++calls; - next(err); - }); - - request(app) - .get('/') - .expect(404, function (err) { - if (err) return done(err) - assert.strictEqual(calls, 1) - done() - }) - }) - - describe('with non-GET', function(){ - it('should still serve', function(done){ - var app = express() - - app.use(function(req, res){ - res.sendfile(path.join(__dirname, '/fixtures/name.txt')) - }); - - request(app) - .get('/') - .expect('tobi', done); - }) - }) - }) - }) - - describe('.sendfile(path, options)', function () { - it('should pass options to send module', function (done) { - var app = express() - - app.use(function (req, res) { - res.sendfile(path.resolve(fixtures, 'name.txt'), { start: 0, end: 1 }) - }) - - request(app) - .get('/') - .expect(200, 'to', done) - }) - }) }) function createApp(path, options, fn) { diff --git a/test/res.sendStatus.js b/test/res.sendStatus.js index 9b1de8385c..b244cf9d17 100644 --- a/test/res.sendStatus.js +++ b/test/res.sendStatus.js @@ -28,5 +28,17 @@ describe('res', function () { .get('/') .expect(599, '599', done); }) + + it('should raise error for invalid status code', function (done) { + var app = express() + + app.use(function (req, res) { + res.sendStatus(undefined).end() + }) + + request(app) + .get('/') + .expect(500, /TypeError: Invalid status code/, done) + }) }) }) diff --git a/test/res.status.js b/test/res.status.js index 1fe08344ea..59c8a57e70 100644 --- a/test/res.status.js +++ b/test/res.status.js @@ -1,55 +1,36 @@ 'use strict' - -var express = require('../') -var request = require('supertest') - -var isIoJs = process.release - ? process.release.name === 'io.js' - : ['v1.', 'v2.', 'v3.'].indexOf(process.version.slice(0, 3)) !== -1 +const express = require('../.'); +const request = require('supertest'); describe('res', function () { describe('.status(code)', function () { - describe('when "code" is undefined', function () { - it('should raise error for invalid status code', function (done) { - var app = express() - app.use(function (req, res) { - res.status(undefined).end() - }) + it('should set the status code when valid', function (done) { + var app = express(); - request(app) - .get('/') - .expect(500, /Invalid status code/, function (err) { - if (isIoJs) { - done(err ? null : new Error('expected error')) - } else { - done(err) - } - }) - }) - }) + app.use(function (req, res) { + res.status(200).end(); + }); - describe('when "code" is null', function () { - it('should raise error for invalid status code', function (done) { + request(app) + .get('/') + .expect(200, done); + }); + + describe('accept valid ranges', function() { + // not testing w/ 100, because that has specific meaning and behavior in Node as Expect: 100-continue + it('should set the response status code to 101', function (done) { var app = express() app.use(function (req, res) { - res.status(null).end() + res.status(101).end() }) request(app) .get('/') - .expect(500, /Invalid status code/, function (err) { - if (isIoJs) { - done(err ? null : new Error('expected error')) - } else { - done(err) - } - }) + .expect(101, done) }) - }) - describe('when "code" is 201', function () { it('should set the response status code to 201', function (done) { var app = express() @@ -61,9 +42,7 @@ describe('res', function () { .get('/') .expect(201, done) }) - }) - describe('when "code" is 302', function () { it('should set the response status code to 302', function (done) { var app = express() @@ -75,9 +54,7 @@ describe('res', function () { .get('/') .expect(302, done) }) - }) - describe('when "code" is 403', function () { it('should set the response status code to 403', function (done) { var app = express() @@ -89,9 +66,7 @@ describe('res', function () { .get('/') .expect(403, done) }) - }) - describe('when "code" is 501', function () { it('should set the response status code to 501', function (done) { var app = express() @@ -103,100 +78,129 @@ describe('res', function () { .get('/') .expect(501, done) }) - }) - describe('when "code" is "410"', function () { - it('should set the response status code to 410', function (done) { + it('should set the response status code to 700', function (done) { var app = express() app.use(function (req, res) { - res.status('410').end() + res.status(700).end() }) request(app) .get('/') - .expect(410, done) + .expect(700, done) }) - }) - describe('when "code" is 410.1', function () { - it('should set the response status code to 410', function (done) { + it('should set the response status code to 800', function (done) { var app = express() app.use(function (req, res) { - res.status(410.1).end() + res.status(800).end() }) request(app) .get('/') - .expect(410, function (err) { - if (isIoJs) { - done(err ? null : new Error('expected error')) - } else { - done(err) - } - }) + .expect(800, done) }) - }) - describe('when "code" is 1000', function () { - it('should raise error for invalid status code', function (done) { + it('should set the response status code to 900', function (done) { var app = express() app.use(function (req, res) { - res.status(1000).end() + res.status(900).end() }) request(app) .get('/') - .expect(500, /Invalid status code/, function (err) { - if (isIoJs) { - done(err ? null : new Error('expected error')) - } else { - done(err) - } - }) + .expect(900, done) }) }) - describe('when "code" is 99', function () { - it('should raise error for invalid status code', function (done) { - var app = express() + describe('invalid status codes', function () { + it('should raise error for status code below 100', function (done) { + var app = express(); app.use(function (req, res) { - res.status(99).end() - }) + res.status(99).end(); + }); request(app) .get('/') - .expect(500, /Invalid status code/, function (err) { - if (isIoJs) { - done(err ? null : new Error('expected error')) - } else { - done(err) - } - }) - }) - }) + .expect(500, /Invalid status code/, done); + }); - describe('when "code" is -401', function () { - it('should raise error for invalid status code', function (done) { - var app = express() + it('should raise error for status code above 999', function (done) { + var app = express(); app.use(function (req, res) { - res.status(-401).end() - }) + res.status(1000).end(); + }); request(app) .get('/') - .expect(500, /Invalid status code/, function (err) { - if (isIoJs) { - done(err ? null : new Error('expected error')) - } else { - done(err) - } - }) - }) - }) - }) -}) + .expect(500, /Invalid status code/, done); + }); + + it('should raise error for non-integer status codes', function (done) { + var app = express(); + + app.use(function (req, res) { + res.status(200.1).end(); + }); + + request(app) + .get('/') + .expect(500, /Invalid status code/, done); + }); + + it('should raise error for undefined status code', function (done) { + var app = express(); + + app.use(function (req, res) { + res.status(undefined).end(); + }); + + request(app) + .get('/') + .expect(500, /Invalid status code/, done); + }); + + it('should raise error for null status code', function (done) { + var app = express(); + + app.use(function (req, res) { + res.status(null).end(); + }); + + request(app) + .get('/') + .expect(500, /Invalid status code/, done); + }); + + it('should raise error for string status code', function (done) { + var app = express(); + + app.use(function (req, res) { + res.status("200").end(); + }); + + request(app) + .get('/') + .expect(500, /Invalid status code/, done); + }); + + it('should raise error for NaN status code', function (done) { + var app = express(); + + app.use(function (req, res) { + res.status(NaN).end(); + }); + + request(app) + .get('/') + .expect(500, /Invalid status code/, done); + }); + }); + }); +}); + diff --git a/test/res.type.js b/test/res.type.js index 980717a6e3..09285af391 100644 --- a/test/res.type.js +++ b/test/res.type.js @@ -14,7 +14,7 @@ describe('res', function(){ request(app) .get('/') - .expect('Content-Type', 'application/javascript; charset=utf-8') + .expect('Content-Type', 'text/javascript; charset=utf-8') .end(done) }) diff --git a/test/res.vary.js b/test/res.vary.js index 1efc20b445..ff3c971652 100644 --- a/test/res.vary.js +++ b/test/res.vary.js @@ -6,7 +6,7 @@ var utils = require('./support/utils'); describe('res.vary()', function(){ describe('with no arguments', function(){ - it('should not set Vary', function (done) { + it('should throw error', function (done) { var app = express(); app.use(function (req, res) { @@ -16,8 +16,7 @@ describe('res.vary()', function(){ request(app) .get('/') - .expect(utils.shouldNotHaveHeader('Vary')) - .expect(200, done); + .expect(500, /field.*required/, done) }) }) diff --git a/test/utils.js b/test/utils.js index 9a38ede656..aff3f03aa3 100644 --- a/test/utils.js +++ b/test/utils.js @@ -69,35 +69,3 @@ describe('utils.wetag(body, encoding)', function(){ 'W/"0-2jmj7l5rSw0yVb/vlWAYkK/YBwk"') }) }) - -describe('utils.isAbsolute()', function(){ - it('should support windows', function(){ - assert(utils.isAbsolute('c:\\')); - assert(utils.isAbsolute('c:/')); - assert(!utils.isAbsolute(':\\')); - }) - - it('should support windows unc', function(){ - assert(utils.isAbsolute('\\\\foo\\bar')) - }) - - it('should support unices', function(){ - assert(utils.isAbsolute('/foo/bar')); - assert(!utils.isAbsolute('foo/bar')); - }) -}) - -describe('utils.flatten(arr)', function(){ - it('should flatten an array', function(){ - var arr = ['one', ['two', ['three', 'four'], 'five']]; - var flat = utils.flatten(arr) - - 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(function (v) { return typeof v === 'string' })) - }) -})