diff --git a/.editorconfig b/.editorconfig index cdb36c1b466..12cf1111232 100644 --- a/.editorconfig +++ b/.editorconfig @@ -1,4 +1,4 @@ -# http://editorconfig.org +# https://editorconfig.org root = true [*] diff --git a/.eslintrc.yml b/.eslintrc.yml index 587169c5330..f9359bf2892 100644 --- a/.eslintrc.yml +++ b/.eslintrc.yml @@ -1,8 +1,14 @@ root: true - +env: + es2022: true + node: true rules: eol-last: error eqeqeq: [error, allow-null] indent: [error, 2, { MemberExpression: "off", SwitchCase: 1 }] no-trailing-spaces: error no-unused-vars: [error, { vars: all, args: none, ignoreRestSiblings: true }] + no-restricted-globals: + - error + - name: Buffer + message: Use `import { Buffer } from "node:buffer"` instead of the global Buffer. diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 00000000000..a6096a49b45 --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,17 @@ +version: 2 +updates: + - package-ecosystem: github-actions + directory: / + schedule: + interval: monthly + + - package-ecosystem: npm + directory: / + schedule: + interval: monthly + time: "23:00" + timezone: Europe/London + open-pull-requests-limit: 10 + ignore: + - dependency-name: "*" + update-types: ["version-update:semver-major"] \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b3ddd6b23ae..3c9ad9cc12e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,11 +7,14 @@ on: - develop - '4.x' - '5.x' + - '5.0' paths-ignore: - '*.md' pull_request: - paths-ignore: - - '*.md' + workflow_dispatch: + +permissions: + contents: read # 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 @@ -24,107 +27,41 @@ jobs: name: Lint runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - name: Setup Node.js {{ matrix.node-version }} - uses: actions/setup-node@v4 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: - node-version: 'lts/*' persist-credentials: false + - name: Setup Node.js + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + with: + node-version: 'lts/*' - name: Install dependencies - run: npm install --ignore-scripts --only=dev + run: npm install --ignore-scripts --include=dev - name: Run lint 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, 23, 24, 25] + # 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 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: persist-credentials: false - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@v4 + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 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 +70,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)" @@ -147,44 +77,41 @@ jobs: - 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 }}" + run: npm run test-ci - name: Upload code coverage - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 with: - name: coverage - path: ./coverage + name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }} + path: ./coverage/lcov.info retention-days: 1 coverage: needs: test runs-on: ubuntu-latest + permissions: + contents: read + checks: write 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 }} + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install lcov + shell: bash + run: sudo apt-get -y install lcov + + - name: Collect coverage reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: ./coverage + pattern: coverage-node-* + + - name: Merge coverage reports + shell: bash + run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info + + - name: Upload coverage report + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 + with: + file: ./lcov.info diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index db4e01aff56..3530bbb0bc9 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -19,6 +19,7 @@ on: branches: ["master"] schedule: - cron: "0 0 * * 1" + workflow_dispatch: permissions: contents: read @@ -31,16 +32,25 @@ jobs: actions: read contents: read security-events: write + strategy: + fail-fast: false + matrix: + language: [javascript, actions] steps: - name: Checkout repository - uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 with: - languages: javascript + languages: ${{ matrix.language }} + config: | + paths-ignore: + - test # If you wish to specify custom queries, you can do so here or in a config file. # By default, queries listed here will override any specified in a config file. # Prefix the list here with "+" to use these queries and those in the config file. @@ -61,6 +71,4 @@ jobs: # ./location_of_script_within_repo/buildscript.sh - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 - with: - category: "/language:javascript" + uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 diff --git a/.github/workflows/iojs.yml b/.github/workflows/iojs.yml deleted file mode 100644 index c1268abd689..00000000000 --- 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 00000000000..145a08178a7 --- /dev/null +++ b/.github/workflows/legacy.yml @@ -0,0 +1,101 @@ +name: legacy + +on: + push: + branches: + - master + - develop + - '4.x' + - '5.x' + - '5.0' + paths-ignore: + - '*.md' + pull_request: + paths-ignore: + - '*.md' + workflow_dispatch: + +permissions: + contents: read + +# 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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0 + 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 + + - name: Upload code coverage + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: coverage-node-${{ matrix.node-version }}-${{ matrix.os }} + path: ./coverage/lcov.info + retention-days: 1 + + coverage: + needs: test + runs-on: ubuntu-latest + permissions: + contents: read + checks: write + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: Install lcov + shell: bash + run: sudo apt-get -y install lcov + + - name: Collect coverage reports + uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1 + with: + path: ./coverage + pattern: coverage-node-* + + - name: Merge coverage reports + shell: bash + run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./lcov.info + + - name: Upload coverage report + uses: coverallsapp/github-action@5cbfd81b66ca5d10c19b062c04de0199c215fb6e # v2.3.7 + with: + file: ./lcov.info diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml new file mode 100644 index 00000000000..3658736ea9d --- /dev/null +++ b/.github/workflows/scorecard.yml @@ -0,0 +1,72 @@ +# This workflow uses actions that are not certified by GitHub. They are provided +# by a third-party and are governed by separate terms of service, privacy +# policy, and support documentation. + +name: Scorecard supply-chain security +on: + # For Branch-Protection check. Only the default branch is supported. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#branch-protection + branch_protection_rule: + # To guarantee Maintained check is occasionally updated. See + # https://github.com/ossf/scorecard/blob/main/docs/checks.md#maintained + schedule: + - cron: '16 21 * * 1' + push: + branches: [ "master" ] + +# Declare default permissions as read only. +permissions: read-all + +jobs: + analysis: + name: Scorecard analysis + runs-on: ubuntu-latest + permissions: + # Needed to upload the results to code-scanning dashboard. + security-events: write + # Needed to publish results and get a badge (see publish_results below). + id-token: write + # Uncomment the permissions below if installing in a private repository. + # contents: read + # actions: read + + steps: + - name: "Checkout code" + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + with: + persist-credentials: false + + - name: "Run analysis" + uses: ossf/scorecard-action@4eaacf0543bb3f2c246792bd56e8cdeffafb205a # v2.4.3 + with: + results_file: results.sarif + results_format: sarif + # (Optional) "write" PAT token. Uncomment the `repo_token` line below if: + # - you want to enable the Branch-Protection check on a *public* repository, or + # - you are installing Scorecard on a *private* repository + # To create the PAT, follow the steps in https://github.com/ossf/scorecard-action#authentication-with-pat. + # repo_token: ${{ secrets.SCORECARD_TOKEN }} + + # Public repositories: + # - Publish results to OpenSSF REST API for easy access by consumers + # - Allows the repository to include the Scorecard badge. + # - See https://github.com/ossf/scorecard-action#publishing-results. + # For private repositories: + # - `publish_results` will always be set to `false`, regardless + # of the value entered here. + publish_results: true + + # Upload the results as artifacts (optional). Commenting out will disable uploads of run results in SARIF + # format to the repository Actions tab. + - name: "Upload artifact" + uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7.0.0 + with: + name: SARIF file + path: results.sarif + retention-days: 5 + + # Upload the results to GitHub's code scanning dashboard. + - name: "Upload to code-scanning" + uses: github/codeql-action/upload-sarif@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1 + with: + sarif_file: results.sarif diff --git a/.gitignore b/.gitignore index 1bd5c02b28b..768368cd652 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,10 @@ npm-shrinkwrap.json *.log *.gz +# Yarn +yarn-error.log +yarn.lock + # Coveralls .nyc_output coverage diff --git a/Charter.md b/Charter.md deleted file mode 100644 index a906e52909a..00000000000 --- a/Charter.md +++ /dev/null @@ -1,92 +0,0 @@ -# Express Charter - -## Section 0: Guiding Principles - -The Express project is part of the OpenJS Foundation which operates -transparently, openly, collaboratively, and ethically. -Project proposals, timelines, and status must not merely be open, but -also easily visible to outsiders. - -## Section 1: Scope - -Express is a HTTP web server framework with a simple and expressive API -which is highly aligned with Node.js core. We aim to be the best in -class for writing performant, spec compliant, and powerful web servers -in Node.js. As one of the oldest and most popular web frameworks in -the ecosystem, we have an important place for new users and experts -alike. - -### 1.1: In-scope - -Express is made of many modules spread between three GitHub Orgs: - -- [expressjs](http://github.com/expressjs/): Top level middleware and - libraries -- [pillarjs](http://github.com/pillarjs/): Components which make up - Express but can also be used for other web frameworks -- [jshttp](http://github.com/jshttp/): Low level HTTP libraries - -### 1.2: Out-of-Scope - -Section Intentionally Left Blank - -## Section 2: Relationship with OpenJS Foundation CPC. - -Technical leadership for the projects within the OpenJS Foundation is -delegated to the projects through their project charters by the OpenJS -Cross Project Council (CPC). In the case of the Express project, it is -delegated to the Express Technical Committee ("TC"). - -This Technical Committee is in charge of both the day-to-day operations -of the project, as well as its technical management. This charter can -be amended by the TC requiring at least two approvals and a minimum two -week comment period for other TC members or CPC members to object. Any -changes the CPC wishes to propose will be considered a priority but -will follow the same process. - -### 2.1 Other Formal Project Relationships - -Section Intentionally Left Blank - -## Section 3: Express Governing Body - -The Express project is managed by the Technical Committee ("TC"). -Members can be added to the TC at any time. Any committer can nominate -another committer to the TC and the TC uses its standard consensus -seeking process to evaluate whether or not to add this new member. -Members who do not participate consistently at the level of a majority -of the other members are expected to resign. - -## Section 4: Roles & Responsibilities - -The Express TC manages all aspects of both the technical and community -parts of the project. Members of the TC should attend the regular -meetings when possible, and be available for discussion of time -sensitive or important issues. - -### Section 4.1 Project Operations & Management - -Section Intentionally Left Blank - -### Section 4.2: Decision-making, Voting, and/or Elections - -The Express TC uses a "consensus seeking" process for issues that are -escalated to the TC. The group tries to find a resolution that has no -open objections among TC members. If a consensus cannot be reached -that has no objections then a majority wins vote is called. It is also -expected that the majority of decisions made by the TC are via a -consensus seeking process and that voting is only used as a last-resort. - -Resolution may involve returning the issue to committers with -suggestions on how to move forward towards a consensus. It is not -expected that a meeting of the TC will resolve all issues on its -agenda during that meeting and may prefer to continue the discussion -happening among the committers. - -### Section 4.3: Other Project Roles - -Section Intentionally Left Blank - -## Section 5: Definitions - -Section Intentionally Left Blank diff --git a/Code-Of-Conduct.md b/Code-Of-Conduct.md deleted file mode 100644 index ca4c6b31468..00000000000 --- a/Code-Of-Conduct.md +++ /dev/null @@ -1,139 +0,0 @@ -# Contributor Covenant Code of Conduct - -As a member of the Open JS Foundation, Express has adopted the -[Contributor Covenant 2.0][cc-20-doc]. - -If an issue arises and you cannot resolve it directly with the parties -involved, you can report it to the Express project TC through the following -email: express-coc@lists.openjsf.org - -In addition, the OpenJS Foundation maintains a Code of Conduct Panel (CoCP). -This is a foundation-wide team established to manage escalation when a reporter -believes that a report to a member project or the CPC has not been properly -handled. In order to escalate to the CoCP send an email to -coc-escalation@lists.openjsf.org. - -## Our Pledge - -We as members, contributors, and leaders pledge to make participation in our -community a harassment-free experience for everyone, regardless of age, body -size, visible or invisible disability, ethnicity, sex characteristics, gender -identity and expression, level of experience, education, socio-economic status, -nationality, personal appearance, race, religion, or sexual identity and -orientation. - -We pledge to act and interact in ways that contribute to an open, welcoming, -diverse, inclusive, and healthy community. - -## Our Standards - -Examples of behavior that contributes to a positive environment for our -community include: - -* Demonstrating empathy and kindness toward other people -* Being respectful of differing opinions, viewpoints, and experiences -* Giving and gracefully accepting constructive feedback -* Accepting responsibility and apologizing to those affected by our mistakes, - and learning from the experience -* Focusing on what is best not just for us as individuals, but for the overall - community - -Examples of unacceptable behavior include: - -* The use of sexualized language or imagery, and sexual attention or advances - of any kind -* Trolling, insulting or derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or email address, - without their explicit permission -* Other conduct which could reasonably be considered inappropriate in a - professional setting - -## Enforcement Responsibilities - -Community leaders are responsible for clarifying and enforcing our standards of -acceptable behavior and will take appropriate and fair corrective action in -response to any behavior that they deem inappropriate, threatening, offensive, -or harmful. - -Community leaders have the right and responsibility to remove, edit, or reject -comments, commits, code, wiki edits, issues, and other contributions that are -not aligned to this Code of Conduct, and will communicate reasons for -moderation decisions when appropriate. - -## Scope - -This Code of Conduct applies within all community spaces, and also applies when -an individual is officially representing the community in public spaces. -Examples of representing our community include using an official e-mail -address, posting via an official social media account, or acting as an -appointed representative at an online or offline event. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be -reported to the community leaders responsible for enforcement at -express-coc@lists.openjsf.org. All complaints will be reviewed and -investigated promptly and fairly. - -All community leaders are obligated to respect the privacy and security of the -reporter of any incident. - -## Enforcement Guidelines - -Community leaders will follow these Community Impact Guidelines in determining -the consequences for any action they deem in violation of this Code of Conduct: - -### 1. Correction - -**Community Impact**: Use of inappropriate language or other behavior deemed -unprofessional or unwelcome in the community. - -**Consequence**: A private, written warning from community leaders, providing -clarity around the nature of the violation and an explanation of why the -behavior was inappropriate. A public apology may be requested. - -### 2. Warning - -**Community Impact**: A violation through a single incident or series of -actions. - -**Consequence**: A warning with consequences for continued behavior. No -interaction with the people involved, including unsolicited interaction with -those enforcing the Code of Conduct, for a specified period of time. This -includes avoiding interactions in community spaces as well as external channels -like social media. Violating these terms may lead to a temporary or permanent -ban. - -### 3. Temporary Ban - -**Community Impact**: A serious violation of community standards, including -sustained inappropriate behavior. - -**Consequence**: A temporary ban from any sort of interaction or public -communication with the community for a specified period of time. No public or -private interaction with the people involved, including unsolicited -interaction with those enforcing the Code of Conduct, is allowed during this -period. Violating these terms may lead to a permanent ban. - -### 4. Permanent Ban - -**Community Impact**: Demonstrating a pattern of violation of community -standards, including sustained inappropriate behavior, harassment of an -individual, or aggression toward or disparagement of classes of individuals. - -**Consequence**: A permanent ban from any sort of public interaction within the -project community. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant, version 2.0][cc-20-doc]. - -Community Impact Guidelines were inspired by -[Mozilla's code of conduct enforcement ladder](https://github.com/mozilla/diversity). - -[cc-20-doc]: https://www.contributor-covenant.org/version/2/0/code_of_conduct/ - -For answers to common questions about this code of conduct, see the FAQ at -https://www.contributor-covenant.org/faq. Translations are available at -https://www.contributor-covenant.org/translations. diff --git a/Collaborator-Guide.md b/Collaborator-Guide.md deleted file mode 100644 index 3c73307d61b..00000000000 --- a/Collaborator-Guide.md +++ /dev/null @@ -1,51 +0,0 @@ -# Express Collaborator Guide - -## Website Issues - -Open issues for the expressjs.com website in https://github.com/expressjs/expressjs.com. - -## PRs and Code contributions - -* Tests must pass. -* Follow the [JavaScript Standard Style](http://standardjs.com/) and `npm run lint`. -* If you fix a bug, add a test. - -## Branches - -Use the `master` branch for bug fixes or minor work that is intended for the -current release stream. - -Use the correspondingly named branch, e.g. `5.0`, for anything intended for -a future release of Express. - -## Steps for contributing - -1. [Create an issue](https://github.com/expressjs/express/issues/new) for the - bug you want to fix or the feature that you want to add. -2. Create your own [fork](https://github.com/expressjs/express) on GitHub, then - checkout your fork. -3. Write your code in your local copy. It's good practice to create a branch for - each new issue you work on, although not compulsory. -4. To run the test suite, first install the dependencies by running `npm install`, - then run `npm test`. -5. Ensure your code is linted by running `npm run lint` -- fix any issue you - see listed. -6. If the tests pass, you can commit your changes to your fork and then create - a pull request from there. Make sure to reference your issue from the pull - request comments by including the issue number e.g. `#123`. - -## Issues which are questions - -We will typically close any vague issues or questions that are specific to some -app you are writing. Please double check the docs and other references before -being trigger happy with posting a question issue. - -Things that will help get your question issue looked at: - -* Full and runnable JS code. -* Clear description of the problem or unexpected behavior. -* Clear description of the expected result. -* Steps you have taken to debug it yourself. - -If you post a question and do not outline the above items or make it easy for -us to understand and reproduce your issue, it will be closed. diff --git a/Contributing.md b/Contributing.md deleted file mode 100644 index 1654cee02f2..00000000000 --- a/Contributing.md +++ /dev/null @@ -1,209 +0,0 @@ -# Express.js Community Contributing Guide 1.0 - -The goal of this document is to create a contribution process that: - -* Encourages new contributions. -* Encourages contributors to remain involved. -* Avoids unnecessary processes and bureaucracy whenever possible. -* Creates a transparent decision making process that makes it clear how -contributors can be involved in decision making. - -## Vocabulary - -* A **Contributor** is any individual creating or commenting on an issue or pull request. -* A **Committer** is a subset of contributors who have been given write access to the repository. -* A **Project Captain** is the lead maintainer of a repository. -* A **TC (Technical Committee)** is a group of committers representing the required technical -expertise to resolve rare disputes. -* A **Triager** is a subset of contributors who have been given triage access to the repository. - -## Logging Issues - -Log an issue for any question or problem you might have. When in doubt, log an issue, and -any additional policies about what to include will be provided in the responses. The only -exception is security disclosures which should be sent privately. - -Committers may direct you to another repository, ask for additional clarifications, and -add appropriate metadata before the issue is addressed. - -Please be courteous and respectful. Every participant is expected to follow the -project's Code of Conduct. - -## Contributions - -Any change to resources in this repository must be through pull requests. This applies to all changes -to documentation, code, binary files, etc. Even long term committers and TC members must use -pull requests. - -No pull request can be merged without being reviewed. - -For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that -contributors in other timezones have time to review. Consideration should also be given to -weekends and other holiday periods to ensure active committers all have reasonable time to -become involved in the discussion and review process if they wish. - -The default for each contribution is that it is accepted once no committer has an objection. -During a review, committers may also request that a specific contributor who is most versed in a -particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off" -process for contributions to land. Once all issues brought by committers are addressed it can -be landed by any committer. - -In the case of an objection being raised in a pull request by another committer, all involved -committers should seek to arrive at a consensus by way of addressing concerns being expressed -by discussion, compromise on the proposed change, or withdrawal of the proposed change. - -If a contribution is controversial and committers cannot agree about how to get it to land -or if it should land then it should be escalated to the TC. TC members should regularly -discuss pending contributions in order to find a resolution. It is expected that only a -small minority of issues be brought to the TC for resolution and that discussion and -compromise among committers be the default resolution mechanism. - -## Becoming a Triager - -Anyone can become a triager! Read more about the process of being a triager in -[the triage process document](Triager-Guide.md). - -Currently, any existing [organization member](https://github.com/orgs/expressjs/people) can nominate -a new triager. If you are interested in becoming a triager, our best advice is to actively participate -in the community by helping triaging issues and pull requests. As well we recommend -to engage in other community activities like attending the TC meetings, and participating in the Slack -discussions. - -You can also reach out to any of the [organization members](https://github.com/orgs/expressjs/people) -if you have questions or need guidance. - -## Becoming a Committer - -All contributors who land a non-trivial contribution should be on-boarded in a timely manner, -and added as a committer, and be given write access to the repository. - -Committers are expected to follow this policy and continue to send pull requests, go through -proper review, and have other committers merge their pull requests. - -## TC Process - -The TC uses a "consensus seeking" process for issues that are escalated to the TC. -The group tries to find a resolution that has no open objections among TC members. -If a consensus cannot be reached that has no objections then a majority wins vote -is called. It is also expected that the majority of decisions made by the TC are via -a consensus seeking process and that voting is only used as a last-resort. - -Resolution may involve returning the issue to project captains with suggestions on -how to move forward towards a consensus. It is not expected that a meeting of the TC -will resolve all issues on its agenda during that meeting and may prefer to continue -the discussion happening among the project captains. - -Members can be added to the TC at any time. Any TC member can nominate another committer -to the TC and the TC uses its standard consensus seeking process to evaluate whether or -not to add this new member. The TC will consist of a minimum of 3 active members and a -maximum of 10. If the TC should drop below 5 members the active TC members should nominate -someone new. If a TC member is stepping down, they are encouraged (but not required) to -nominate someone to take their place. - -TC members will be added as admin's on the Github orgs, npm orgs, and other resources as -necessary to be effective in the role. - -To remain "active" a TC member should have participation within the last 12 months and miss -no more than six consecutive TC meetings. Our goal is to increase participation, not punish -people for any lack of participation, this guideline should be only be used as such -(replace an inactive member with a new active one, for example). Members who do not meet this -are expected to step down. If A TC member does not step down, an issue can be opened in the -discussions repo to move them to inactive status. TC members who step down or are removed due -to inactivity will be moved into inactive status. - -Inactive status members can become active members by self nomination if the TC is not already -larger than the maximum of 10. They will also be given preference if, while at max size, an -active member steps down. - -## Project Captains - -The Express TC can designate captains for individual projects/repos in the -organizations. These captains are responsible for being the primary -day-to-day maintainers of the repo on a technical and community front. -Repo captains are empowered with repo ownership and package publication rights. -When there are conflicts, especially on topics that effect the Express project -at large, captains are responsible to raise it up to the TC and drive -those conflicts to resolution. Captains are also responsible for making sure -community members follow the community guidelines, maintaining the repo -and the published package, as well as in providing user support. - -Like TC members, Repo captains are a subset of committers. - -To become a captain for a project the candidate is expected to participate in that -project for at least 6 months as a committer prior to the request. They should have -helped with code contributions as well as triaging issues. They are also required to -have 2FA enabled on both their GitHub and npm accounts. Any TC member or existing -captain on the repo can nominate another committer to the captain role, submit a PR to -this doc, under `Current Project Captains` section (maintaining the sort order) with -the project, their GitHub handle and npm username (if different). The PR will require -at least 2 approvals from TC members and 2 weeks hold time to allow for comment and/or -dissent. When the PR is merged, a TC member will add them to the proper GitHub/npm groups. - -### Active Projects and Captains - -- `expressjs/badgeboard`: @wesleytodd -- `expressjs/basic-auth-connect`: N/A -- `expressjs/body-parser`: @wesleytodd, @jonchurch -- `expressjs/compression`: N/A -- `expressjs/connect-multiparty`: N/A -- `expressjs/cookie-parser`: @wesleytodd, @UlisesGascon -- `expressjs/cookie-session`: N/A -- `expressjs/cors`: @jonchurch -- `expressjs/discussions`: @wesleytodd -- `expressjs/errorhandler`: N/A -- `expressjs/express-paginate`: N/A -- `expressjs/express`: @wesleytodd -- `expressjs/expressjs.com`: @crandmck, @jonchurch -- `expressjs/flash`: N/A -- `expressjs/generator`: @wesleytodd -- `expressjs/method-override`: N/A -- `expressjs/morgan`: @jonchurch -- `expressjs/multer`: @LinusU -- `expressjs/response-time`: @blakeembrey -- `expressjs/serve-favicon`: N/A -- `expressjs/serve-index`: N/A -- `expressjs/serve-static`: N/A -- `expressjs/session`: N/A -- `expressjs/statusboard`: @wesleytodd -- `expressjs/timeout`: N/A -- `expressjs/vhost`: N/A -- `jshttp/accepts`: @blakeembrey -- `jshttp/basic-auth`: @blakeembrey -- `jshttp/compressible`: @blakeembrey -- `jshttp/content-disposition`: @blakeembrey -- `jshttp/content-type`: @blakeembrey -- `jshttp/cookie`: @wesleytodd -- `jshttp/etag`: @blakeembrey -- `jshttp/forwarded`: @blakeembrey -- `jshttp/fresh`: @blakeembrey -- `jshttp/http-assert`: @wesleytodd, @jonchurch -- `jshttp/http-errors`: @wesleytodd, @jonchurch -- `jshttp/media-typer`: @blakeembrey -- `jshttp/methods`: @blakeembrey -- `jshttp/mime-db`: @blakeembrey, @UlisesGascon -- `jshttp/mime-types`: @blakeembrey, @UlisesGascon -- `jshttp/negotiator`: @blakeembrey -- `jshttp/on-finished`: @wesleytodd -- `jshttp/on-headers`: @blakeembrey -- `jshttp/proxy-addr`: @wesleytodd -- `jshttp/range-parser`: @blakeembrey -- `jshttp/statuses`: @blakeembrey -- `jshttp/type-is`: @blakeembrey -- `jshttp/vary`: @blakeembrey -- `pillarjs/cookies`: @blakeembrey -- `pillarjs/csrf`: N/A -- `pillarjs/encodeurl`: @blakeembrey -- `pillarjs/finalhandler`: @wesleytodd -- `pillarjs/hbs`: N/A -- `pillarjs/multiparty`: @blakeembrey -- `pillarjs/parseurl`: @blakeembrey -- `pillarjs/path-to-regexp`: @blakeembrey -- `pillarjs/request`: @wesleytodd -- `pillarjs/resolve-path`: @blakeembrey -- `pillarjs/router`: @blakeembrey -- `pillarjs/send`: @blakeembrey -- `pillarjs/understanding-csrf`: N/A - -### Current Initiative Captains - -- Triage team [ref](https://github.com/expressjs/discussions/issues/227): @UlisesGascon diff --git a/History.md b/History.md index 887a38f182d..571cd0e47c5 100644 --- a/History.md +++ b/History.md @@ -1,3 +1,257 @@ +# Unreleased Changes + +## 🚀 Improvements + +* Improve HTML structure in `res.redirect()` responses when HTML format is accepted by adding ``, ``, and `<body>` tags for better browser compatibility - by [@Bernice55231](https://github.com/Bernice55231) in [#5167](https://github.com/expressjs/express/pull/5167) + +* When calling `app.render` with options set to null, the locals object is handled correctly, preventing unexpected errors and making the method behave the same as when options is omitted or an empty object is passed - by [AkaHarshit](https://github.com/AkaHarshit) in [#6903](https://github.com/expressjs/express/pull/6903) + + ```js + app.render('index', null, callback); // now works as expected + ``` + +## ⚡ Performance + +* Avoid duplicate Content-Type header processing in `res.send()` when sending string responses without an explicit Content-Type header - by [@bjohansebas](https://github.com/bjohansebas) in [#6991](https://github.com/expressjs/express/pull/6991) + +5.2.1 / 2025-12-01 +======================= + +* Revert security fix for [CVE-2024-51999](https://www.cve.org/CVERecord?id=CVE-2024-51999) ([GHSA-pj86-cfqh-vqx6](https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6)) + * The prior release (5.2.0) included an erroneous breaking change related to the extended query parser. There is no actual security vulnerability associated with this behavior (CVE-2024-51999 has been rejected). The change has been fully reverted in this release. + +5.2.0 / 2025-12-01 +======================== + +* Security fix for [CVE-2024-51999](https://www.cve.org/CVERecord?id=CVE-2024-51999) ([GHSA-pj86-cfqh-vqx6](https://github.com/expressjs/express/security/advisories/GHSA-pj86-cfqh-vqx6)) +* deps: `body-parser@^2.2.1` +* A deprecation warning was added when using `res.redirect` with undefined arguments, Express now emits a warning to help detect calls that pass undefined as the status or URL and make them easier to fix. + +5.1.0 / 2025-03-31 +======================== + +* Add support for `Uint8Array` in `res.send()` +* Add support for ETag option in `res.sendFile()` +* Add support for multiple links with the same rel in `res.links()` +* Add funding field to package.json +* perf: use loop for acceptParams +* refactor: prefix built-in node module imports +* deps: remove `setprototypeof` +* deps: remove `safe-buffer` +* deps: remove `utils-merge` +* deps: remove `methods` +* deps: remove `depd` +* deps: `debug@^4.4.0` +* deps: `body-parser@^2.2.0` +* deps: `router@^2.2.0` +* deps: `content-type@^1.0.5` +* deps: `finalhandler@^2.1.0` +* deps: `qs@^6.14.0` +* deps: `server-static@2.2.0` +* deps: `type-is@2.0.1` + +5.0.1 / 2024-10-08 +========== + +* Update `cookie` semver lock to address [CVE-2024-47764](https://nvd.nist.gov/vuln/detail/CVE-2024-47764) + +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 + - Remove `express.static.mime` export; use `mime-types` package instead + - 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/Readme-Guide.md b/Readme-Guide.md deleted file mode 100644 index 34d4648b9c2..00000000000 --- a/Readme-Guide.md +++ /dev/null @@ -1,125 +0,0 @@ -# README guidelines - -Every module in the expressjs, pillarjs, and jshttp organizations should have -a README file named `README.md`. The purpose of the README is to: - -- Explain the purpose of the module and how to use it. -- Act as a landing page (both on GitHub and npmjs.com) for the module to help - people find it via search. Middleware module READMEs are also incorporated - into https://expressjs.com/en/resources/middleware.html. -- Encourage community contributions and participation. - -Use the [README template](https://github.com/expressjs/express/wiki/README-template) -to quickly create a new README file. - -## Top-level items - -**Badges** (optional): At the very top (with no subheading), include any -applicable badges, such as npm version/downloads, build status, test coverage, -and so on. Badges should resolve properly (not display a broken image). - -Possible badges include: -- npm version: `[![NPM Version][npm-image]][npm-url]` -- npm downloads: `[![NPM Downloads][downloads-image]][downloads-url]` -- Build status: `[![Build Status][travis-image]][travis-url]` -- Test coverage: `[![Test Coverage][coveralls-image]][coveralls-url]` -- Tips: `[![Gratipay][gratipay-image]][gratipay-url]` - -**Summary**: Following badges, provide a one- or two-sentence description of -what the module does. This should be the same as the npmjs.org blurb (which -comes from the description property of `package.json`). Since npm doesn't -handle markdown for the blurb, avoid using markdown in the summary sentence. - -**TOC** (Optional): For longer READMEs, provide a table of contents that has -a relative link to each section. A tool such as -[doctoc](https://www.npmjs.com/package/doctoc) makes it very easy to generate -a TOC. - -## Overview - -Optionally, include a section of one or two paragraphs with more high-level -information on what the module does, what problems it solves, why one would -use it and how. Don't just repeat what's in the summary. - -## Installation - -Required. This section is typically just: - -```sh -$ npm install module-name -``` - -But include any other steps or requirements. - -NOTE: Use the `sh` code block to make the shell command display properly on -the website. - -## Basic use - -- Provide a general description of how to use the module with code sample. - Include any important caveats or restrictions. -- Explain the most common use cases. -- Optional: a simple "hello world" type example (where applicable). This - example is in addition to the more comprehensive [example section](#examples) - later. - -## API - -Provide complete API documentation. - -Formatting conventions: Each function is listed in a 3rd-level heading (`###`), -like this: - -``` -### Function_name(arg, options [, optional_arg] ... ) -``` - -**Options objects** - -For arguments that are objects (for example, options object), describe the -properties in a table, as follows. This matches the formatting used in the -[Express API docs](https://expressjs.com/en/4x/api.html). - -|Property | Description | Type | Default| -|----------|-----------|------------|-------------| -|Name of the property in `monospace`. | Brief description | String, Number, Boolean, etc. | If applicable.| - -If all the properties are required (i.e. there are no defaults), then you -can omit the default column. - -Instead of very lengthy descriptions, link out to subsequent paragraphs for -more detailed explanation of specific cases (e.g. "When this property is set -to 'foobar', xyz happens; see <link to following section >.) - -If there are options properties that are themselves options, use additional -tables. See [`trust proxy` and `etag` properties](https://expressjs.com/en/4x/api.html#app.settings.table). - -## Examples - -Every README should have at least one example; ideally more. For code samples, -be sure to use the `js` code block, for proper display in the website, e.g.: - -```js -var csurf = require('csurf') -... -``` - -## Tests - -What tests are included. - -How to run them. - -The convention for running tests is `npm test`. All our projects should follow -this convention. - -## Contributors - -Names of module "owners" (lead developers) and other developers who have -contributed. - -## License - -Link to the license, with a short description of what it is, e.g. "MIT" or -whatever. Ideally, avoid putting the license text directly in the README; link -to it instead. diff --git a/Readme.md b/Readme.md index bc108d55fc0..3fb257a8d4a 100644 --- a/Readme.md +++ b/Readme.md @@ -1,39 +1,48 @@ -[![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/) +[![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](https://expressjs.com/) -**Fast, unopinionated, minimalist web framework for [Node.js](http://nodejs.org).** +**Fast, unopinionated, minimalist web framework for [Node.js](https://nodejs.org).** -**This project has a [Code of Conduct][].** +**This project has a [Code of Conduct].** ## Table of contents -* [Installation](#Installation) -* [Features](#Features) -* [Docs & Community](#docs--community) -* [Quick Start](#Quick-Start) -* [Running Tests](#Running-Tests) -* [Philosophy](#Philosophy) -* [Examples](#Examples) -* [Contributing to Express](#Contributing) -* [TC (Technical Committee)](#tc-technical-committee) -* [Triagers](#triagers) -* [License](#license) +- [Table of contents](#table-of-contents) +- [Installation](#installation) +- [Features](#features) +- [Docs \& Community](#docs--community) +- [Quick Start](#quick-start) +- [Philosophy](#philosophy) +- [Examples](#examples) +- [Contributing](#contributing) + - [Security Issues](#security-issues) + - [Running Tests](#running-tests) +- [Current project team members](#current-project-team-members) + - [TC (Technical Committee)](#tc-technical-committee) + - [TC emeriti members](#tc-emeriti-members) + - [Triagers](#triagers) + - [Emeritus Triagers](#emeritus-triagers) +- [License](#license) [![NPM Version][npm-version-image]][npm-url] -[![NPM Install Size][npm-install-size-image]][npm-install-size-url] [![NPM Downloads][npm-downloads-image]][npm-downloads-url] +[![Linux Build][github-actions-ci-image]][github-actions-ci-url] +[![Test Coverage][coveralls-image]][coveralls-url] [![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer] ```js -const express = require('express') +import express from 'express' + const app = express() -app.get('/', function (req, res) { +app.get('/', (req, res) => { res.send('Hello World') }) -app.listen(3000) +app.listen(3000, () => { + console.log('Server is running on http://localhost:3000') +}) ``` ## Installation @@ -42,19 +51,19 @@ This is a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/). Before installing, [download and install Node.js](https://nodejs.org/en/download/). -Node.js 0.10 or higher is required. +Node.js 18 or higher is required. If this is a brand new project, make sure to create a `package.json` first with the [`npm init` command](https://docs.npmjs.com/creating-a-package-json-file). Installation is done using the -[`npm install` command](https://docs.npmjs.com/getting-started/installing-npm-packages-locally): +[`npm install` command](https://docs.npmjs.com/downloading-and-installing-packages-locally): -```console -$ npm install express +```bash +npm install express ``` -Follow [our installing guide](http://expressjs.com/en/starter/installing.html) +Follow [our installing guide](https://expressjs.com/en/starter/installing.html) for more information. ## Features @@ -69,14 +78,11 @@ for more information. ## Docs & Community - * [Website and Documentation](http://expressjs.com/) - [[website repo](https://github.com/expressjs/expressjs.com)] - * [#express](https://web.libera.chat/#express) on [Libera Chat](https://libera.chat) IRC + * [Website and Documentation](https://expressjs.com/) - [[website repo](https://github.com/expressjs/expressjs.com)] * [GitHub Organization](https://github.com/expressjs) for Official Middleware & Modules - * Visit the [Wiki](https://github.com/expressjs/express/wiki) - * [Google Group](https://groups.google.com/group/express-js) for discussion - * [Gitter](https://gitter.im/expressjs/express) for support and discussion + * [Github Discussions](https://github.com/expressjs/discussions) for discussion on the development and usage of Express -**PROTIP** Be sure to read [Migrating from 3.x to 4.x](https://github.com/expressjs/express/wiki/Migrating-from-3.x-to-4.x) as well as [New features in 4.x](https://github.com/expressjs/express/wiki/New-features-in-4.x). +**PROTIP** Be sure to read the [migration guide to v5](https://expressjs.com/en/guide/migrating-5) ## Quick Start @@ -84,26 +90,26 @@ for more information. Install the executable. The executable's major version will match Express's: -```console -$ npm install -g express-generator@4 +```bash +npm install -g express-generator@4 ``` Create the app: -```console -$ express /tmp/foo && cd /tmp/foo +```bash +express /tmp/foo && cd /tmp/foo ``` Install dependencies: -```console -$ npm install +```bash +npm install ``` Start the server: -```console -$ npm start +```bash +npm start ``` View the website at: http://localhost:3000 @@ -115,51 +121,58 @@ $ npm start HTTP APIs. Express does not force you to use any specific ORM or template engine. With support for over - 14 template engines via [Consolidate.js](https://github.com/tj/consolidate.js), + 14 template engines via [@ladjs/consolidate](https://github.com/ladjs/consolidate), you can quickly craft your perfect framework. ## Examples - To view the examples, clone the Express repo and install the dependencies: + To view the examples, clone the Express repository: + +```bash +git clone https://github.com/expressjs/express.git --depth 1 && cd express +``` + + Then install the dependencies: -```console -$ git clone https://github.com/expressjs/express.git --depth 1 -$ cd express -$ npm install +```bash +npm install ``` Then run whichever example you want: -```console -$ node examples/content-negotiation +```bash +node examples/content-negotiation ``` ## Contributing - [![Linux Build][github-actions-ci-image]][github-actions-ci-url] - [![Windows Build][appveyor-image]][appveyor-url] - [![Test Coverage][coveralls-image]][coveralls-url] - The Express.js project welcomes all constructive contributions. Contributions take many forms, from code for bug fixes and enhancements, to additions and fixes to documentation, additional tests, triaging incoming pull requests and issues, and more! -See the [Contributing Guide](Contributing.md) for more technical details on contributing. +See the [Contributing Guide] for more technical details on contributing. ### Security Issues -If you discover a security vulnerability in Express, please see [Security Policies and Procedures](Security.md). +If you discover a security vulnerability in Express, please see [Security Policies and Procedures](https://github.com/expressjs/express/security/policy). ### Running Tests -To run the test suite, first install the dependencies, then run `npm test`: +To run the test suite, first install the dependencies: -```console -$ npm install -$ npm test +```bash +npm install ``` -## People +Then run `npm test`: + +```bash +npm test +``` + +## Current project team members + +For information about the governance of the express.js project, see [GOVERNANCE.md](https://github.com/expressjs/discussions/blob/HEAD/docs/GOVERNANCE.md). The original author of Express is [TJ Holowaychuk](https://github.com/tj) @@ -192,18 +205,16 @@ The original author of Express is [TJ Holowaychuk](https://github.com/tj) ### Triagers * [aravindvnair99](https://github.com/aravindvnair99) - **Aravind Nair** +* [bjohansebas](https://github.com/bjohansebas) - **Sebastian Beltran** * [carpasse](https://github.com/carpasse) - **Carlos Serrano** * [CBID2](https://github.com/CBID2) - **Christine Belzie** -* [enyoghasim](https://github.com/enyoghasim) - **David Enyoghasim** * [UlisesGascon](https://github.com/UlisesGascon) - **Ulises Gascón** (he/him) -* [mertcanaltin](https://github.com/mertcanaltin) - **Mert Can Altin** -* [0ss](https://github.com/0ss) - **Salah** -* [import-brain](https://github.com/import-brain) - **Eric Cheng** (he/him) -* [3imed-jaberi](https://github.com/3imed-jaberi) - **Imed Jaberi** -* [dakshkhetan](https://github.com/dakshkhetan) - **Daksh Khetan** (he/him) -* [lucasraziel](https://github.com/lucasraziel) - **Lucas Soares Do Rego** * [IamLizu](https://github.com/IamLizu) - **S M Mahmudul Hasan** (he/him) -* [Sushmeet](https://github.com/Sushmeet) - **Sushmeet Sunger** +* [Phillip9587](https://github.com/Phillip9587) - **Phillip Barta** +* [efekrskl](https://github.com/efekrskl) - **Efe Karasakal** +* [rxmarbles](https://github.com/rxmarbles) - **Rick Markins** (he/him) +* [krzysdz](https://github.com/krzysdz) +* [GroophyLifefor](https://github.com/GroophyLifefor) - **Murat Kirazkaya** <details> <summary>Triagers emeriti members</summary> @@ -236,6 +247,16 @@ The original author of Express is [TJ Holowaychuk](https://github.com/tj) * [sheplu](https://github.com/sheplu) - **Jean Burellier** * [tarunyadav1](https://github.com/tarunyadav1) - **Tarun yadav** * [tunniclm](https://github.com/tunniclm) - **Mike Tunnicliffe** + * [enyoghasim](https://github.com/enyoghasim) - **David Enyoghasim** + * [0ss](https://github.com/0ss) - **Salah** + * [ejcheng](https://github.com/ejcheng)- **Eric Cheng** (he/him) + * [dakshkhetan](https://github.com/dakshkhetan) - **Daksh Khetan** (he/him) + * [lucasraziel](https://github.com/lucasraziel) - **Lucas Soares Do Rego** + * [mertcanaltin](https://github.com/mertcanaltin) - **Mert Can Altin** + * [dpopp07](https://github.com/dpopp07) - **Dustin Popp** + * [Sushmeet](https://github.com/Sushmeet) - **Sushmeet Sunger** + * [3imed-jaberi](https://github.com/3imed-jaberi) - **Imed Jaberi** + </details> @@ -243,18 +264,15 @@ The original author of Express is [TJ Holowaychuk](https://github.com/tj) [MIT](LICENSE) -[appveyor-image]: https://badgen.net/appveyor/ci/dougwilson/express/master?label=windows -[appveyor-url]: https://ci.appveyor.com/project/dougwilson/express -[coveralls-image]: https://badgen.net/coveralls/c/github/expressjs/express/master +[coveralls-image]: https://img.shields.io/coverallsCoverage/github/expressjs/express?branch=master [coveralls-url]: https://coveralls.io/r/expressjs/express?branch=master -[github-actions-ci-image]: https://badgen.net/github/checks/expressjs/express/master?label=linux +[github-actions-ci-image]: https://img.shields.io/github/actions/workflow/status/expressjs/express/ci.yml?branch=master&label=ci [github-actions-ci-url]: https://github.com/expressjs/express/actions/workflows/ci.yml -[npm-downloads-image]: https://badgen.net/npm/dm/express +[npm-downloads-image]: https://img.shields.io/npm/dm/express [npm-downloads-url]: https://npmcharts.com/compare/express?minimal=true -[npm-install-size-image]: https://badgen.net/packagephobia/install/express -[npm-install-size-url]: https://packagephobia.com/result?p=express [npm-url]: https://npmjs.org/package/express -[npm-version-image]: https://badgen.net/npm/v/express +[npm-version-image]: https://img.shields.io/npm/v/express [ossf-scorecard-badge]: https://api.scorecard.dev/projects/github.com/expressjs/express/badge [ossf-scorecard-visualizer]: https://ossf.github.io/scorecard-visualizer/#/projects/github.com/expressjs/express -[Code of Conduct]: https://github.com/expressjs/express/blob/master/Code-Of-Conduct.md +[Code of Conduct]: https://github.com/expressjs/.github/blob/HEAD/CODE_OF_CONDUCT.md +[Contributing Guide]: https://github.com/expressjs/.github/blob/HEAD/CONTRIBUTING.md diff --git a/Release-Process.md b/Release-Process.md deleted file mode 100644 index 9ca0a15ab46..00000000000 --- a/Release-Process.md +++ /dev/null @@ -1,199 +0,0 @@ -# Express Release Process - -This document contains the technical aspects of the Express release process. The -intended audience is those who have been authorized by the Express Technical -Committee (TC) to create, promote and sign official release builds for Express, -as npm packages hosted on https://npmjs.com/package/express. - -## Who can make releases? - -Release authorization is given by the Express TC. Once authorized, an individual -must have the following access permissions: - -### 1. Github release access - -The individual making the release will need to be a member of the -expressjs/express team with Write permission level so they are able to tag the -release commit and push changes to the expressjs/express repository -(see Steps 4 and 5). - -### 2. npmjs.com release access - -The individual making the release will need to be made an owner on the -`express` package on npmjs.com so they are able to publish the release -(see Step 6). - -## How to publish a release - -Before publishing, the following preconditions should be met: - -- A release proposal issue or tracking pull request (see "Proposal branch" - below) will exist documenting: - - the proposed changes - - the type of release: patch, minor or major - - the version number (according to semantic versioning - http://semver.org) -- The proposed changes should be complete. - -There are two main release flows: patch and non-patch. - -The patch flow is for making **patch releases**. As per semantic versioning, -patch releases are for simple changes, eg: typo fixes, patch dependency updates, -and simple/low-risk bug fixes. Every other type of change is made via the -non-patch flow. - -### Branch terminology - -"Master branch" - -- There is a branch in git used for the current major version of Express, named - `master`. -- This branch contains the completed commits for the next patch release of the - current major version. -- Releases for the current major version are published from this branch. - -"Version branch" - -- For any given major version of Express (current, previous or next) there is - a branch in git for that release named `<major-version>.x` (eg: `4.x`). -- This branch points to the commit of the latest tag for the given major version. - -"Release branch" - -- For any given major version of Express, there is a branch used for publishing - releases. -- For the current major version of Express, the release branch is the - "Master branch" named `master`. -- For all other major versions of Express, the release branch is the - "Version branch" named `<major-version>.x`. - -"Proposal branch" - -- A branch in git representing a proposed new release of Express. This can be a - minor or major release, named `<major-version>.0` for a major release, - `<major-version>.<minor-version>` for a minor release. -- A tracking pull request should exist to document the proposed release, - targeted at the appropriate release branch. Prior to opening the tracking - pull request the content of the release may have be discussed in an issue. -- This branch contains the commits accepted so far that implement the proposal - in the tracking pull request. - -### Pre-release Versions - -Alpha and Beta releases are made from a proposal branch. The version number should be -incremented to the next minor version with a `-beta` or `-alpha` suffix. -For example, if the next beta release is `5.0.1`, the beta release would be `5.0.1-beta.0`. -The pre-releases are unstable and not suitable for production use. - -### Patch flow - -In the patch flow, simple changes are committed to the release branch which -acts as an ever-present branch for the next patch release of the associated -major version of Express. - -The release branch is usually kept in a state where it is ready to release. -Releases are made when sufficient time or change has been made to warrant it. -This is usually proposed and decided using a github issue. - -### Non-patch flow - -In the non-patch flow, changes are committed to a temporary proposal branch -created specifically for that release proposal. The branch is based on the -most recent release of the major version of Express that the release targets. - -Releases are made when all the changes on a proposal branch are complete and -approved. This is done by merging the proposal branch into the release branch -(using a fast-forward merge), tagging it with the new version number and -publishing the release package to npmjs.com. - -### Flow - -Below is a detailed description of the steps to publish a release. - -#### Step 1. Check the release is ready to publish - -Check any relevant information to ensure the release is ready, eg: any -milestone, label, issue or tracking pull request for the release. The release -is ready when all proposed code, tests and documentation updates are complete -(either merged, closed or re-targeted to another release). - -#### Step 2. (Non-patch flow only) Merge the proposal branch into the release branch - -In the patch flow: skip this step. - -In the non-patch flow: -```sh -$ git checkout <release-branch> -$ git merge --ff-only <proposal-branch> -``` - -<release-branch> - see "Release branch" of "Branches" above. -<proposal-branch> - see "Proposal branch" of "Non-patch flow" above. - -**NOTE:** You may need to rebase the proposal branch to allow a fast-forward - merge. Using a fast-forward merge keeps the history clean as it does - not introduce merge commits. - -### Step 3. Update the History.md and package.json to the new version number - -The changes so far for the release should already be documented under the -"unreleased" section at the top of the History.md file, as per the usual -development practice. Change "unreleased" to the new release version / date. -Example diff fragment: - -```diff --unreleased --========== -+4.13.3 / 2015-08-02 -+=================== -``` - -The version property in the package.json should already contain the version of -the previous release. Change it to the new release version. - -Commit these changes together under a single commit with the message set to -the new release version (eg: `4.13.3`): - -```sh -$ git checkout <release-branch> -<..edit files..> -$ git add History.md package.json -$ git commit -m '<version-number>' -``` - -### Step 4. Identify and tag the release commit with the new release version - -Create a lightweight tag (rather than an annotated tag) named after the new -release version (eg: `4.13.3`). - -```sh -$ git tag <version-number> -``` - -### Step 5. Push the release branch changes and tag to github - -The branch and tag should be pushed directly to the main repository -(https://github.com/expressjs/express). - -```sh -$ git push origin <release-branch> -$ git push origin <version-number> -``` - -### Step 6. Publish to npmjs.com - -Ensure your local working copy is completely clean (no extra or changed files). -You can use `git status` for this purpose. - -```sh -$ npm login <npm-username> -$ npm publish -``` - -**NOTE:** The version number to publish will be picked up automatically from - package.json. - -### Step 7. Update documentation website - -The documentation website https://expressjs.com/ documents the current release version in various places. For a new release: -1. Change the value of `current_version` in https://github.com/expressjs/expressjs.com/blob/gh-pages/_data/express.yml to match the latest version number. -2. Add a new section to the change log. For example, for a 4.x release, https://github.com/expressjs/expressjs.com/blob/gh-pages/en/changelog/4x.md, diff --git a/Security.md b/Security.md deleted file mode 100644 index dcfbe88abd4..00000000000 --- a/Security.md +++ /dev/null @@ -1,56 +0,0 @@ -# Security Policies and Procedures - -This document outlines security procedures and general policies for the Express -project. - - * [Reporting a Bug](#reporting-a-bug) - * [Disclosure Policy](#disclosure-policy) - * [Comments on this Policy](#comments-on-this-policy) - -## Reporting a Bug - -The Express team and community take all security bugs in Express seriously. -Thank you for improving the security of Express. We appreciate your efforts and -responsible disclosure and will make every effort to acknowledge your -contributions. - -Report security bugs by emailing the lead maintainer in the Readme.md file. - -To ensure the timely response to your report, please ensure that the entirety -of the report is contained within the email body and not solely behind a web -link or an attachment. - -The lead maintainer will acknowledge your email within 48 hours, and will send a -more detailed response within 48 hours indicating the next steps in handling -your report. After the initial reply to your report, the security team will -endeavor to keep you informed of the progress towards a fix and full -announcement, and may ask for additional information or guidance. - -Report security bugs in third-party modules to the person or team maintaining -the module. - -## Pre-release Versions - -Alpha and Beta releases are unstable and **not suitable for production use**. -Vulnerabilities found in pre-releases should be reported according to the [Reporting a Bug](#reporting-a-bug) section. -Due to the unstable nature of the branch it is not guaranteed that any fixes will be released in the next pre-release. - -## Disclosure Policy - -When the security team receives a security bug report, they will assign it to a -primary handler. This person will coordinate the fix and release process, -involving the following steps: - - * Confirm the problem and determine the affected versions. - * Audit code to find any potential similar problems. - * Prepare fixes for all releases still under maintenance. These fixes will be - released as fast as possible to npm. - -## The Express Threat Model - -We are currently working on a new version of the security model, the most updated version can be found [here](https://github.com/expressjs/security-wg/blob/main/docs/ThreatModel.md) - -## Comments on this Policy - -If you have suggestions on how this process could be improved please submit a -pull request. diff --git a/Triager-Guide.md b/Triager-Guide.md deleted file mode 100644 index c15e6be5313..00000000000 --- a/Triager-Guide.md +++ /dev/null @@ -1,70 +0,0 @@ -# Express Triager Guide - -## Issue Triage Process - -When a new issue or pull request is opened the issue will be labeled with `needs triage`. -If a triage team member is available they can help make sure all the required information -is provided. Depending on the issue or PR there are several next labels they can add for further -classification: - -* `needs triage`: This can be kept if the triager is unsure which next steps to take -* `awaiting more info`: If more info has been requested from the author, apply this label. -* `bug`: Issues that present a reasonable conviction there is a reproducible bug. -* `enhancement`: Issues that are found to be a reasonable candidate feature additions. - -If the issue is a question or discussion, it should be moved to GitHub Discussions. - -### Moving Discussions and Questions to GitHub Discussions - -For issues labeled with `question` or `discuss`, it is recommended to move them to GitHub Discussions instead: - -* **Questions**: User questions that do not appear to be bugs or enhancements should be moved to GitHub Discussions. -* **Discussions**: Topics for discussion should be moved to GitHub Discussions. If the discussion leads to a new feature or bug identification, it can be moved back to Issues. - -In all cases, issues may be closed by maintainers if they don't receive a timely response when -further information is sought, or when additional questions are asked. - -## Approaches and Best Practices for getting into triage contributions - -Review the organization's [StatusBoard](https://expressjs.github.io/statusboard/), -pay special attention to these columns: stars, watchers, open issues, and contributors. -This gives you a general idea about the criticality and health of the repository. -Pick a few projects based on that criteria, your interests, and skills (existing or aspiring). - -Review the project's contribution guideline if present. In a nutshell, -commit to the community's standards and values. Review the -documentation, for most of the projects it is just the README.md, and -make sure you understand the key APIs, semantics, configurations, and use cases. - -It might be helpful to write your own test apps to re-affirm your -understanding of the key functions. This may identify some gaps in -documentation, record those as they might be good PR's to open. -Skim through the issue backlog; identify low hanging issues and mostly new ones. -From those, attempt to recreate issues based on the OP description and -ask questions if required. No question is a bad question! - -## Removal of Triage Role - -There are a few cases where members can be removed as triagers: - -- Breaking the CoC or project contributor guidelines -- Abuse or misuse of the role as deemed by the TC -- Lack of participation for more than 6 months - -If any of these happen we will discuss as a part of the triage portion of the regular TC meetings. -If you have questions feel free to reach out to any of the TC members. - -## Other Helpful Hints: - -- Everyone is welcome to attend the [Express Technical Committee Meetings](https://github.com/expressjs/discussions#expressjs-tc-meetings), and as a triager, it might help to get a better idea of what's happening with the project. -- When exploring the module's functionality there are a few helpful steps: - - Turn on `DEBUG=*` (see https://www.npmjs.com/package/debug) to get detailed log information - - It is also a good idea to do live debugging to follow the control flow, try using `node --inspect` - - It is a good idea to make at least one pass of reading through the entire source -- When reviewing the list of open issues there are some common types and suggested actions: - - New/unattended issues or simple questions: A good place to start - - Hard bugs & ongoing discussions: always feel free to chime in and help - - Issues that imply gaps in the documentation: open PRs with changes or help the user to do so -- For recurring issues, it is helpful to create functional examples to demonstrate (publish as gists or a repo) -- Review and identify the maintainers. If necessary, at-mention one or more of them if you are unsure what to do -- Make sure all your interactions are professional, welcoming, and respectful to the parties involved. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 629499bf37c..00000000000 --- 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/benchmarks/Makefile b/benchmarks/Makefile deleted file mode 100644 index ed1ddfc4f34..00000000000 --- a/benchmarks/Makefile +++ /dev/null @@ -1,17 +0,0 @@ - -all: - @./run 1 middleware 50 - @./run 5 middleware 50 - @./run 10 middleware 50 - @./run 15 middleware 50 - @./run 20 middleware 50 - @./run 30 middleware 50 - @./run 50 middleware 50 - @./run 100 middleware 50 - @./run 10 middleware 100 - @./run 10 middleware 250 - @./run 10 middleware 500 - @./run 10 middleware 1000 - @echo - -.PHONY: all diff --git a/benchmarks/README.md b/benchmarks/README.md deleted file mode 100644 index 1894c527d63..00000000000 --- a/benchmarks/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Express Benchmarks - -## Installation - -You will need to install [wrk](https://github.com/wg/wrk/blob/master/INSTALL) in order to run the benchmarks. - -## Running - -To run the benchmarks, first install the dependencies `npm i`, then run `make` - -The output will look something like this: - -``` - 50 connections - 1 middleware - 7.15ms - 6784.01 - - [...redacted...] - - 1000 connections - 10 middleware - 139.21ms - 6155.19 - -``` - -### Tip: Include Node.js version in output - -You can use `make && node -v` to include the node.js version in the output. - -### Tip: Save the results to a file - -You can use `make > results.log` to save the results to a file `results.log`. diff --git a/benchmarks/middleware.js b/benchmarks/middleware.js deleted file mode 100644 index fed97ba8ce4..00000000000 --- a/benchmarks/middleware.js +++ /dev/null @@ -1,20 +0,0 @@ - -var express = require('..'); -var app = express(); - -// number of middleware - -var n = parseInt(process.env.MW || '1', 10); -console.log(' %s middleware', n); - -while (n--) { - app.use(function(req, res, next){ - next(); - }); -} - -app.use(function(req, res){ - res.send('Hello World') -}); - -app.listen(3333); diff --git a/benchmarks/run b/benchmarks/run deleted file mode 100755 index ec8f55d5643..00000000000 --- a/benchmarks/run +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env bash - -echo -MW=$1 node $2 & -pid=$! - -echo " $3 connections" - -sleep 2 - -wrk 'http://localhost:3333/?foo[bar]=baz' \ - -d 3 \ - -c $3 \ - -t 8 \ - | grep 'Requests/sec\|Latency' \ - | awk '{ print " " $2 }' - -kill $pid diff --git a/examples/auth/index.js b/examples/auth/index.js index 36205d0f994..7c4a572f9d3 100644 --- a/examples/auth/index.js +++ b/examples/auth/index.js @@ -6,7 +6,7 @@ var express = require('../..'); var hash = require('pbkdf2-password')() -var path = require('path'); +var path = require('node:path'); var session = require('express-session'); var app = module.exports = express(); @@ -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 @@ -38,7 +38,7 @@ app.use(function(req, res, next){ next(); }); -// dummy database +// placeholder database var users = { tj: { name: 'tj' } @@ -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 <a href="/https/github.com/logout">logout</a>. ' + ' You may now access <a href="/https/github.com/restricted">/restricted</a>.'; - res.redirect('back'); + res.redirect(req.get('Referrer') || '/'); }); } else { req.session.error = 'Authentication failed, please check your ' diff --git a/examples/auth/views/head.ejs b/examples/auth/views/head.ejs index 65386267d0d..c623b5cc8d1 100644 --- a/examples/auth/views/head.ejs +++ b/examples/auth/views/head.ejs @@ -10,7 +10,7 @@ font: 13px Helvetica, Arial, sans-serif; } .error { - color: red + color: red; } .success { color: green; diff --git a/examples/cookies/index.js b/examples/cookies/index.js index 04093591f79..0620cb40e45 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 6b67e0c8862..ddc549ffec7 100644 --- a/examples/downloads/index.js +++ b/examples/downloads/index.js @@ -5,7 +5,7 @@ */ var express = require('../../'); -var path = require('path'); +var path = require('node:path'); var app = module.exports = express(); @@ -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/ejs/index.js b/examples/ejs/index.js index a39d805a160..05b451b664b 100644 --- a/examples/ejs/index.js +++ b/examples/ejs/index.js @@ -5,7 +5,7 @@ */ var express = require('../../'); -var path = require('path'); +var path = require('node:path'); var app = module.exports = express(); @@ -35,7 +35,7 @@ app.use(express.static(path.join(__dirname, 'public'))); // ex: res.render('users.html'). app.set('view engine', 'html'); -// Dummy users +// Placeholder users var users = [ { name: 'tobi', email: 'tobi@learnboost.com' }, { name: 'loki', email: 'loki@learnboost.com' }, diff --git a/examples/error-pages/index.js b/examples/error-pages/index.js index efa815c4740..0863120bc8f 100644 --- a/examples/error-pages/index.js +++ b/examples/error-pages/index.js @@ -5,7 +5,7 @@ */ var express = require('../../'); -var path = require('path'); +var path = require('node:path'); var app = module.exports = express(); var logger = require('morgan'); var silent = process.env.NODE_ENV === 'test' diff --git a/examples/markdown/index.js b/examples/markdown/index.js index 23d645e66b2..53e40ac38e4 100644 --- a/examples/markdown/index.js +++ b/examples/markdown/index.js @@ -6,9 +6,9 @@ var escapeHtml = require('escape-html'); var express = require('../..'); -var fs = require('fs'); +var fs = require('node:fs'); var marked = require('marked'); -var path = require('path'); +var path = require('node:path'); var app = module.exports = express(); diff --git a/examples/mvc/index.js b/examples/mvc/index.js index da4727b282d..1d8aa0e3c31 100644 --- a/examples/mvc/index.js +++ b/examples/mvc/index.js @@ -6,7 +6,7 @@ var express = require('../..'); var logger = require('morgan'); -var path = require('path'); +var path = require('node:path'); var session = require('express-session'); var methodOverride = require('method-override'); diff --git a/examples/mvc/lib/boot.js b/examples/mvc/lib/boot.js index 0216e5d76d6..fc2ab0fad99 100644 --- a/examples/mvc/lib/boot.js +++ b/examples/mvc/lib/boot.js @@ -5,8 +5,8 @@ */ var express = require('../../..'); -var fs = require('fs'); -var path = require('path'); +var fs = require('node:fs'); +var path = require('node:path'); module.exports = function(parent, options){ var dir = path.join(__dirname, '..', 'controllers'); diff --git a/examples/params/index.js b/examples/params/index.js index f3cd8457eb5..11eef51a592 100644 --- a/examples/params/index.js +++ b/examples/params/index.js @@ -32,7 +32,8 @@ app.param(['to', 'from'], function(req, res, next, num, name){ // Load user by id app.param('user', function(req, res, next, id){ - if (req.user = users[id]) { + req.user = users[id] + if (req.user) { next(); } else { next(createError(404, 'failed to find user')); diff --git a/examples/resource/index.js b/examples/resource/index.js index ff1f6fe11f4..627ab24c5a2 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-middleware/index.js b/examples/route-middleware/index.js index 44ec13a95b8..66df511347b 100644 --- a/examples/route-middleware/index.js +++ b/examples/route-middleware/index.js @@ -15,7 +15,7 @@ var app = express(); // curl http://localhost:3000/user/1/edit (unauthorized since this is not you) // curl -X DELETE http://localhost:3000/user/0 (unauthorized since you are not an admin) -// Dummy users +// Placeholder users var users = [ { id: 0, name: 'tj', email: 'tj@vision-media.ca', role: 'member' } , { id: 1, name: 'ciaran', email: 'ciaranj@gmail.com', role: 'member' } diff --git a/examples/route-separation/index.js b/examples/route-separation/index.js index 5d483811111..0a29c9421a6 100644 --- a/examples/route-separation/index.js +++ b/examples/route-separation/index.js @@ -5,7 +5,7 @@ */ var express = require('../..'); -var path = require('path'); +var path = require('node:path'); var app = express(); var logger = require('morgan'); var cookieParser = require('cookie-parser'); @@ -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 1c2aec7cd23..bc6fbd7baf3 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 0d19444e525..b995b8fab16 100644 --- a/examples/search/index.js +++ b/examples/search/index.js @@ -12,35 +12,51 @@ */ var express = require('../..'); -var path = require('path'); +var path = require('node:path'); var redis = require('redis'); var db = redis.createClient(); +var app = express(); + +app.use(express.static(path.join(__dirname, 'public'))); // npm install redis -var app = express(); +/** + * Redis Initialization + */ -app.use(express.static(path.join(__dirname, 'public'))); +async function initializeRedis() { + try { + // connect to Redis -// populate search + await db.connect(); -db.sadd('ferret', 'tobi'); -db.sadd('ferret', 'loki'); -db.sadd('ferret', 'jane'); -db.sadd('cat', 'manny'); -db.sadd('cat', 'luna'); + // populate search + + await db.sAdd('ferret', 'tobi'); + await db.sAdd('ferret', 'loki'); + await db.sAdd('ferret', 'jane'); + await db.sAdd('cat', 'manny'); + await db.sAdd('cat', 'luna'); + } catch (err) { + console.error('Error initializing Redis:', err); + process.exit(1); + } +} /** * GET search for :query. */ -app.get('/search/:query?', function(req, res){ - var query = req.params.query; - db.smembers(query, function(err, vals){ - if (err) return res.send(500); - res.send(vals); - }); +app.get('/search/{:query}', function (req, res, next) { + var query = req.params.query || ''; + db.sMembers(query) + .then((vals) => res.send(vals)) + .catch((err) => { + console.error(`Redis error for query "${query}":`, err); + next(err); + }); }); /** @@ -54,8 +70,14 @@ app.get('/client.js', function(req, res){ res.sendFile(path.join(__dirname, 'client.js')); }); -/* istanbul ignore next */ -if (!module.parent) { - app.listen(3000); - console.log('Express started on port 3000'); -} +/** + * Start the Server + */ + +(async () => { + await initializeRedis(); + if (!module.parent) { + app.listen(3000); + console.log('Express started on port 3000'); + } +})(); diff --git a/examples/static-files/index.js b/examples/static-files/index.js index 609c546b470..b7c697a2f9f 100644 --- a/examples/static-files/index.js +++ b/examples/static-files/index.js @@ -6,7 +6,7 @@ var express = require('../..'); var logger = require('morgan'); -var path = require('path'); +var path = require('node:path'); var app = express(); // log requests diff --git a/examples/view-constructor/github-view.js b/examples/view-constructor/github-view.js index 43d29336cac..eabfb2d0c18 100644 --- a/examples/view-constructor/github-view.js +++ b/examples/view-constructor/github-view.js @@ -4,8 +4,8 @@ * Module dependencies. */ -var https = require('https'); -var path = require('path'); +var https = require('node:https'); +var path = require('node:path'); var extname = path.extname; /** diff --git a/examples/view-locals/index.js b/examples/view-locals/index.js index a2af24f3553..e6355602d4e 100644 --- a/examples/view-locals/index.js +++ b/examples/view-locals/index.js @@ -5,7 +5,7 @@ */ var express = require('../..'); -var path = require('path'); +var path = require('node:path'); var User = require('./user'); var app = express(); diff --git a/lib/application.js b/lib/application.js index ebb30b51b3d..47be2a20c1a 100644 --- a/lib/application.js +++ b/lib/application.js @@ -14,29 +14,24 @@ */ 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 http = require('node:http'); +var methods = require('./utils').methods; 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 setPrototypeOf = require('setprototypeof') +var resolve = require('node:path').resolve; +var once = require('once') +var Router = require('router'); /** * Module variables. * @private */ -var hasOwnProperty = Object.prototype.hasOwnProperty var slice = Array.prototype.slice; +var flatten = Array.prototype.flat; /** * Application prototype. @@ -62,11 +57,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 +94,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); @@ -102,10 +115,10 @@ app.defaultConfiguration = function defaultConfiguration() { } // inherit protos - setPrototypeOf(this.request, parent.request) - setPrototypeOf(this.response, parent.response) - setPrototypeOf(this.engines, parent.engines) - setPrototypeOf(this.settings, parent.settings) + Object.setPrototypeOf(this.request, parent.request) + Object.setPrototypeOf(this.response, parent.response) + Object.setPrototypeOf(this.engines, parent.engines) + Object.setPrototypeOf(this.settings, parent.settings) }); // setup locals @@ -125,32 +138,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 +150,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 + Object.setPrototypeOf(req, this.request) + Object.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 +207,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 @@ -235,8 +230,8 @@ app.use = function use(fn) { router.use(path, function mounted_app(req, res, next) { var orig = req.app; fn.handle(req, res, function (err) { - setPrototypeOf(req, orig.request) - setPrototypeOf(res, orig.response) + Object.setPrototypeOf(req, orig.request) + Object.setPrototypeOf(res, orig.response) next(err); }); }); @@ -259,8 +254,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 +320,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 +328,7 @@ app.param = function param(name, fn) { return this; } - this._router.param(name, fn); + this.router.param(name, fn); return this; }; @@ -359,17 +351,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); @@ -486,16 +468,14 @@ app.disable = function disable(setting) { * Delegate `.VERB(...)` calls to `router.VERB(...)`. */ -methods.forEach(function(method){ - app[method] = function(path){ +methods.forEach(function (method) { + app[method] = function (path) { if (method === 'get' && arguments.length === 1) { // app.get(setting) 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 +492,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 +502,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 @@ -549,8 +523,7 @@ app.render = function render(name, options, callback) { var cache = this.cache; var done = callback; var engines = this.engines; - var opts = options; - var renderOptions = {}; + var opts = options || {}; var view; // support callback function as second arg @@ -559,16 +532,8 @@ app.render = function render(name, options, callback) { opts = {}; } - // merge app.locals - merge(renderOptions, this.locals); - - // merge options._locals - if (opts._locals) { - merge(renderOptions, opts._locals); - } - // merge options - merge(renderOptions, opts); + var renderOptions = { ...this.locals, ...opts._locals, ...opts }; // set .cache unless explicitly provided if (renderOptions.cache == null) { @@ -618,8 +583,8 @@ app.render = function render(name, options, callback) { * and HTTPS server you may do so with the "http" * and "https" modules as shown here: * - * var http = require('http') - * , https = require('https') + * var http = require('node:http') + * , https = require('node:https') * , express = require('express') * , app = express(); * @@ -631,9 +596,14 @@ app.render = function render(name, options, callback) { */ app.listen = function listen() { - var server = http.createServer(this); - return server.listen.apply(server, arguments); -}; + var server = http.createServer(this) + var args = 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 d188a16db70..2d502eb54e4 100644 --- a/lib/express.js +++ b/lib/express.js @@ -13,11 +13,10 @@ */ var bodyParser = require('body-parser') -var EventEmitter = require('events').EventEmitter; +var EventEmitter = require('node:events').EventEmitter; var mixin = require('merge-descriptors'); var proto = require('./application'); -var Route = require('./router/route'); -var Router = require('./router'); +var Router = require('router'); var req = require('./request'); var res = require('./response'); @@ -68,7 +67,7 @@ exports.response = res; * Expose constructors. */ -exports.Route = Route; +exports.Route = Router.Route; exports.Router = Router; /** @@ -76,41 +75,7 @@ exports.Router = Router; */ exports.json = bodyParser.json -exports.query = require('./middleware/query'); exports.raw = bodyParser.raw exports.static = require('serve-static'); exports.text = bodyParser.text exports.urlencoded = bodyParser.urlencoded - -/** - * Replace removed middleware with an appropriate error message. - */ - -var removedMiddlewares = [ - 'bodyParser', - 'compress', - 'cookieSession', - 'session', - 'logger', - 'cookieParser', - 'favicon', - 'responseTime', - 'errorHandler', - 'timeout', - 'methodOverride', - 'vhost', - 'csrf', - 'directory', - 'limit', - 'multipart', - 'staticCache' -] - -removedMiddlewares.forEach(function (name) { - Object.defineProperty(exports, name, { - get: function () { - throw new Error('Most middleware (like ' + name + ') is no longer bundled with Express and must be installed separately. Please see https://github.com/senchalabs/connect#middleware.'); - }, - configurable: true - }); -}); diff --git a/lib/middleware/init.js b/lib/middleware/init.js deleted file mode 100644 index dfd042747bd..00000000000 --- a/lib/middleware/init.js +++ /dev/null @@ -1,43 +0,0 @@ -/*! - * express - * Copyright(c) 2009-2013 TJ Holowaychuk - * Copyright(c) 2013 Roman Shtylman - * Copyright(c) 2014-2015 Douglas Christopher Wilson - * MIT Licensed - */ - -'use strict'; - -/** - * Module dependencies. - * @private - */ - -var setPrototypeOf = require('setprototypeof') - -/** - * Initialization middleware, exposing the - * request and response to each other, as well - * as defaulting the X-Powered-By header field. - * - * @param {Function} app - * @return {Function} - * @api private - */ - -exports.init = function(app){ - return function expressInit(req, res, next){ - if (app.enabled('x-powered-by')) res.setHeader('X-Powered-By', 'Express'); - req.res = res; - res.req = req; - req.next = next; - - setPrototypeOf(req, app.request) - setPrototypeOf(res, app.response) - - res.locals = res.locals || Object.create(null); - - next(); - }; -}; - diff --git a/lib/middleware/query.js b/lib/middleware/query.js deleted file mode 100644 index 7e9166947af..00000000000 --- a/lib/middleware/query.js +++ /dev/null @@ -1,47 +0,0 @@ -/*! - * express - * Copyright(c) 2009-2013 TJ Holowaychuk - * Copyright(c) 2013 Roman Shtylman - * Copyright(c) 2014-2015 Douglas Christopher Wilson - * MIT Licensed - */ - -'use strict'; - -/** - * Module dependencies. - */ - -var merge = require('utils-merge') -var parseUrl = require('parseurl'); -var qs = require('qs'); - -/** - * @param {Object} options - * @return {Function} - * @api public - */ - -module.exports = function query(options) { - var opts = merge({}, options) - var queryparse = qs.parse; - - if (typeof options === 'function') { - queryparse = options; - opts = undefined; - } - - if (opts !== undefined && opts.allowPrototypes === undefined) { - // back-compat for qs module - opts.allowPrototypes = true; - } - - return function query(req, res, next){ - if (!req.query) { - var val = parseUrl(req).query; - req.query = queryparse(val, opts); - } - - next(); - }; -}; diff --git a/lib/request.js b/lib/request.js index 3f1eeca6c1a..e7a451df6d9 100644 --- a/lib/request.js +++ b/lib/request.js @@ -14,10 +14,9 @@ */ var accepts = require('accepts'); -var deprecate = require('depd')('express'); -var isIP = require('net').isIP; +var isIP = require('node:net').isIP; var typeis = require('type-is'); -var http = require('http'); +var http = require('node:http'); var fresh = require('fresh'); var parseRange = require('range-parser'); var parse = require('parseurl'); @@ -84,16 +83,13 @@ req.header = function header(name) { }; /** - * To do: update docs. - * * Check if the given `type(s)` is acceptable, returning - * the best match when true, otherwise `undefined`, in which + * the best match when true, otherwise `false`, in which * case you should respond with 406 "Not Acceptable". * * The `type` value may be a single MIME type string * such as "application/json", an extension name - * such as "json", a comma-delimited list such as "json, html, text/plain", - * an argument list such as `"json", "html", "text/plain"`, + * such as "json", an argument list such as `"json", "html", "text/plain"`, * or an array `["json", "html", "text/plain"]`. When a list * or array is given, the _best_ match, if any is returned. * @@ -108,7 +104,7 @@ req.header = function header(name) { * // => "html" * req.accepts('text/html'); * // => "text/html" - * req.accepts('json, text'); + * req.accepts('json', 'text'); * // => "json" * req.accepts('application/json'); * // => "application/json" @@ -116,12 +112,11 @@ req.header = function header(name) { * // Accept: text/*, application/json * req.accepts('image/png'); * req.accepts('png'); - * // => undefined + * // => false * * // Accept: text/*;q=.5, application/json * req.accepts(['html', 'json']); * req.accepts('html', 'json'); - * req.accepts('html, json'); * // => "json" * * @param {String|Array} type(s) @@ -147,26 +142,37 @@ 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". + * Checks if the specified `charset`s are acceptable based on the request's `Accept-Charset` header. + * Returns the best matching charset or an array of acceptable charsets. * - * @param {String} ...charset - * @return {String|Array} + * The `charset` argument(s) can be: + * - A single charset string (e.g., "utf-8") + * - Multiple charset strings as arguments (e.g., `"utf-8", "iso-8859-1"`) + * - A comma-delimited list of charsets (e.g., `"utf-8, iso-8859-1"`) + * + * Examples: + * + * // Accept-Charset: utf-8, iso-8859-1 + * req.acceptsCharsets('utf-8'); + * // => "utf-8" + * + * req.acceptsCharsets('utf-8', 'iso-8859-1'); + * // => "utf-8" + * + * req.acceptsCharsets('utf-8, utf-16'); + * // => "utf-8" + * + * @param {...String} charsets - The charset(s) to check against the `Accept-Charset` header. + * @return {String|Array} - The best matching charset, or an array of acceptable charsets. * @public */ -req.acceptsCharsets = function(){ - var accept = accepts(this); - return accept.charsets.apply(accept, arguments); +req.acceptsCharsets = function(...charsets) { + const accept = accepts(this); + return accept.charsets(...charsets); }; -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". @@ -176,14 +182,10 @@ req.acceptsCharset = deprecate.function(req.acceptsCharsets, * @public */ -req.acceptsLanguages = function(){ - var accept = accepts(this); - return accept.languages.apply(accept, arguments); +req.acceptsLanguages = function(...languages) { + return accepts(this).languages(...languages); }; -req.acceptsLanguage = deprecate.function(req.acceptsLanguages, - 'req.acceptsLanguage: Use acceptsLanguages instead'); - /** * Parse Range header field, capping to the given `size`. * @@ -216,38 +218,27 @@ req.range = function range(size, options) { }; /** - * Return the value of param `name` when present or `defaultValue`. + * Parse the query string of `req.url`. * - * - Checks route placeholders, ex: _/user/:id_ - * - Checks body params, ex: id=12, {"id":12} - * - Checks query string params, ex: ?id=12 + * This uses the "query parser" setting to parse the raw + * string into an object. * - * To utilize request bodies, `req.body` - * should be an object. This can be done by using - * the `bodyParser()` middleware. - * - * @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" @@ -304,12 +295,12 @@ req.is = function is(types) { */ defineGetter(req, 'protocol', function protocol(){ - var proto = this.connection.encrypted + var proto = this.socket.encrypted ? 'https' : 'http'; var trust = this.app.get('trust proxy fn'); - if (!trust(this.connection.remoteAddress, 0)) { + if (!trust(this.socket.remoteAddress, 0)) { return proto; } @@ -414,7 +405,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 +415,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.socket.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 +457,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 76b6b54a3b8..f965e539dd2 100644 --- a/lib/response.js +++ b/lib/response.js @@ -12,18 +12,17 @@ * @private */ -var Buffer = require('safe-buffer').Buffer var contentDisposition = require('content-disposition'); var createError = require('http-errors') var deprecate = require('depd')('express'); var encodeUrl = require('encodeurl'); var escapeHtml = require('escape-html'); -var http = require('http'); -var isAbsolute = require('./utils').isAbsolute; +var http = require('node:http'); var onFinished = require('on-finished'); -var path = require('path'); +var mime = require('mime-types') +var path = require('node:path'); +var pathIsAbsolute = require('node:path').isAbsolute; var statuses = require('statuses') -var merge = require('utils-merge'); var sign = require('cookie-signature').sign; var normalizeType = require('./utils').normalizeType; var normalizeTypes = require('./utils').normalizeTypes; @@ -31,9 +30,9 @@ 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'); +const { Buffer } = require('node:buffer'); /** * Response prototype. @@ -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; }; @@ -79,7 +82,11 @@ res.status = function status(code) { * * res.links({ * next: 'http://api.example.com/users?page=2', - * last: 'http://api.example.com/users?page=5' + * last: 'http://api.example.com/users?page=5', + * pages: [ + * 'http://api.example.com/users?page=1', + * 'http://api.example.com/users?page=2' + * ] * }); * * @param {Object} links @@ -87,11 +94,18 @@ res.status = function status(code) { * @public */ -res.links = function(links){ +res.links = function(links) { var link = this.get('Link') || ''; if (link) link += ', '; - return this.set('Link', link + Object.keys(links).map(function(rel){ - return '<' + links[rel] + '>; rel="' + rel + '"'; + return this.set('Link', link + Object.keys(links).map(function(rel) { + // Allow multiple links if links[rel] is an array + if (Array.isArray(links[rel])) { + return links[rel].map(function (singleLink) { + return `<${singleLink}>; rel="${rel}"`; + }).join(', '); + } else { + return `<${links[rel]}>; rel="${rel}"`; + } }).join(', ')); }; @@ -112,40 +126,19 @@ res.send = function send(body) { var chunk = body; var encoding; var req = this.req; - var type; // 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': - if (!this.get('Content-Type')) { + encoding = 'utf8'; + const type = this.get('Content-Type'); + + if (typeof type === 'string') { + this.set('Content-Type', setCharset(type, 'utf-8')); + } else { this.type('html'); } break; @@ -154,7 +147,7 @@ res.send = function send(body) { case 'object': if (chunk === null) { chunk = ''; - } else if (Buffer.isBuffer(chunk)) { + } else if (ArrayBuffer.isView(chunk)) { if (!this.get('Content-Type')) { this.type('bin'); } @@ -164,17 +157,6 @@ res.send = function send(body) { break; } - // write strings in utf-8 - if (typeof chunk === 'string') { - encoding = 'utf8'; - type = this.get('Content-Type'); - - // reflect this in content-type - if (typeof type === 'string') { - this.set('Content-Type', setCharset(type, 'utf-8')); - } - } - // determine if ETag should be generated var etagFn = app.get('etag fn') var generateETag = !this.get('ETag') && typeof etagFn === 'function' @@ -207,7 +189,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 +230,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 +258,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 +321,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,82 +389,16 @@ res.sendFile = function sendFile(path, options, callback) { opts = {}; } - if (!opts.root && !isAbsolute(path)) { + if (!opts.root && !pathIsAbsolute(path)) { throw new TypeError('path must be absolute or specify root to res.sendFile'); } // create file stream var pathname = encodeURI(path); - var file = send(req, pathname, opts); - - // transfer - sendfile(res, file, opts, function (err) { - if (done) return done(err); - if (err && err.code === 'EISDIR') return next(); - - // next() all but write errors - if (err && err.code !== 'ECONNABORTED' && err.syscall !== 'write') { - next(err); - } - }); -}; - -/** - * 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); + // wire application etag option to send + opts.etag = this.app.enabled('etag'); + var file = send(req, pathname, opts); // transfer sendfile(res, file, opts, function (err) { @@ -526,9 +412,6 @@ res.sendfile = function (path, options, callback) { }); }; -res.sendfile = deprecate.function(res.sendfile, - 'res.sendfile: Use res.sendFile instead'); - /** * Transfer the file at the given `path` as an attachment. * @@ -599,8 +482,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 +503,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 +652,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 +673,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 +707,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); }; @@ -860,7 +740,7 @@ res.clearCookie = function clearCookie(name, options) { */ res.cookie = function (name, value, options) { - var opts = merge({}, options); + var opts = { ...options }; var secret = this.req.secret; var signed = opts.signed; @@ -912,26 +792,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 +816,20 @@ 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] + } + + if (!address) { + deprecate('Provide a url argument'); + } + + if (typeof address !== 'string') { + deprecate('Url must be a string'); + } + + if (typeof status !== 'number') { + deprecate('Status must be a number'); } // Set location header @@ -969,7 +843,8 @@ res.redirect = function redirect(url) { html: function(){ var u = escapeHtml(address); - body = '<p>' + statuses.message[status] + '. Redirecting to ' + u + '</p>' + body = '<!DOCTYPE html><head><title>' + statuses.message[status] + '' + + '

' + statuses.message[status] + '. Redirecting to ' + u + '

' }, default: function(){ @@ -978,7 +853,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 +873,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 abb3a6f589e..00000000000 --- 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 4dc8e86d4f7..00000000000 --- a/lib/router/layer.js +++ /dev/null @@ -1,181 +0,0 @@ -/*! - * express - * Copyright(c) 2009-2013 TJ Holowaychuk - * Copyright(c) 2013 Roman Shtylman - * Copyright(c) 2014-2015 Douglas Christopher Wilson - * MIT Licensed - */ - -'use strict'; - -/** - * Module dependencies. - * @private - */ - -var 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 a65756d6de6..00000000000 --- 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 56e12b9b541..4f21e7ef1e3 100644 --- a/lib/utils.js +++ b/lib/utils.js @@ -12,16 +12,21 @@ * @api private */ -var Buffer = require('safe-buffer').Buffer -var contentDisposition = require('content-disposition'); +var { METHODS } = require('node:http'); 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'); +var querystring = require('node:querystring'); +const { Buffer } = require('node:buffer'); + + +/** + * A list of lowercased HTTP methods that are supported by Node.js. + * @api private + */ +exports.methods = METHODS.map((method) => method.toLowerCase()); /** * Return strong ETag for `body`. @@ -45,31 +50,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 +61,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: {} } }; /** @@ -92,27 +72,10 @@ exports.normalizeType = function(type){ * @api private */ -exports.normalizeTypes = function(types){ - var ret = []; - - for (var i = 0; i < types.length; ++i) { - ret.push(exports.normalizeType(types[i])); - } - - return ret; +exports.normalizeTypes = function(types) { + return types.map(exports.normalizeType); }; -/** - * 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 @@ -124,16 +87,33 @@ exports.contentDisposition = deprecate.function(contentDisposition, */ function acceptParams (str) { - var parts = str.split(/ *; */); - var ret = { value: parts[0], quality: 1, params: {} } + var length = str.length; + var colonIndex = str.indexOf(';'); + var index = colonIndex === -1 ? length : colonIndex; + var ret = { value: str.slice(0, index).trim(), quality: 1, params: {} }; + + while (index < length) { + var splitIndex = str.indexOf('=', index); + if (splitIndex === -1) break; + + var colonIndex = str.indexOf(';', index); + var endIndex = colonIndex === -1 ? length : colonIndex; + + if (splitIndex > endIndex) { + index = str.lastIndexOf(';', splitIndex - 1) + 1; + continue; + } - for (var i = 1; i < parts.length; ++i) { - var pms = parts[i].split(/ *= */); - if ('q' === pms[0]) { - ret.quality = parseFloat(pms[1]); + var key = str.slice(index, splitIndex).trim(); + var value = str.slice(splitIndex + 1, endIndex).trim(); + + if (key === 'q') { + ret.quality = parseFloat(value); } else { - ret.params[pms[0]] = pms[1]; + ret.params[key] = value; } + + index = endIndex + 1; } return ret; @@ -192,7 +172,6 @@ exports.compileQueryParser = function compileQueryParser(val) { fn = querystring.parse; break; case false: - fn = newObject; break; case 'extended': fn = parseExtendedQueryString; @@ -290,14 +269,3 @@ function parseExtendedQueryString(str) { allowPrototypes: true }); } - -/** - * Return new empty object. - * - * @return {Object} - * @api private - */ - -function newObject() { - return {}; -} diff --git a/lib/view.js b/lib/view.js index c08ab4d8d52..d66b4a2d89c 100644 --- a/lib/view.js +++ b/lib/view.js @@ -14,8 +14,8 @@ */ var debug = require('debug')('express:view'); -var path = require('path'); -var fs = require('fs'); +var path = require('node:path'); +var fs = require('node:fs'); /** * Module variables. @@ -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 bffa70a6f1c..8c9efb5609d 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.2.1", "author": "TJ Holowaychuk ", "contributors": [ "Aaron Heckmann ", @@ -14,7 +14,11 @@ ], "license": "MIT", "repository": "expressjs/express", - "homepage": "http://expressjs.com/", + "homepage": "https://expressjs.com/", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + }, "keywords": [ "express", "framework", @@ -28,69 +32,66 @@ "api" ], "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", - "content-type": "~1.0.4", - "cookie": "0.6.0", - "cookie-signature": "1.0.6", - "debug": "2.6.9", - "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", - "http-errors": "2.0.0", - "merge-descriptors": "1.0.3", - "methods": "~1.1.2", - "on-finished": "2.4.1", - "parseurl": "~1.3.3", - "path-to-regexp": "0.1.10", - "proxy-addr": "~2.0.7", - "qs": "6.11.0", - "range-parser": "~1.2.1", - "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.0", - "setprototypeof": "1.2.0", - "statuses": "2.0.1", - "type-is": "~1.6.18", - "utils-merge": "1.0.1", - "vary": "~1.1.2" + "accepts": "^2.0.0", + "body-parser": "^2.2.1", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "depd": "^2.0.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.2", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" }, "devDependencies": { "after": "0.8.2", - "connect-redis": "3.4.2", - "cookie-parser": "1.4.6", - "cookie-session": "2.0.0", - "ejs": "3.1.9", + "connect-redis": "^8.0.1", + "cookie-parser": "1.4.7", + "cookie-session": "2.1.1", + "ejs": "^3.1.10", "eslint": "8.47.0", - "express-session": "1.17.2", + "express-session": "^1.18.1", "hbs": "4.2.0", - "marked": "0.7.0", + "marked": "^15.0.3", "method-override": "3.0.0", - "mocha": "10.2.0", - "morgan": "1.10.0", - "nyc": "15.1.0", + "mocha": "^10.7.3", + "morgan": "1.10.1", + "nyc": "^17.1.0", "pbkdf2-password": "1.2.1", - "supertest": "6.3.0", + "supertest": "^6.3.0", "vhost": "~3.0.2" }, "engines": { - "node": ">= 0.10.0" + "node": ">= 18" }, "files": [ "LICENSE", - "History.md", "Readme.md", "index.js", "lib/" ], "scripts": { "lint": "eslint .", - "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/", + "lint:fix": "eslint . --fix", + "test": "mocha --require test/support/env --reporter spec --check-leaks test/ test/acceptance/", "test-ci": "nyc --exclude examples --exclude test --exclude benchmarks --reporter=lcovonly --reporter=text npm test", "test-cov": "nyc --exclude examples --exclude test --exclude benchmarks --reporter=html --reporter=text npm test", "test-tap": "mocha --require test/support/env --reporter tap --check-leaks test/ test/acceptance/" diff --git a/test/Route.js b/test/Route.js index 2a37b9a4839..e4b73c7e6ec 100644 --- a/test/Route.js +++ b/test/Route.js @@ -1,10 +1,10 @@ 'use strict' var after = require('after'); -var assert = require('assert') +var assert = require('node:assert') var express = require('../') , Route = express.Route - , methods = require('methods') + , methods = require('../lib/utils').methods describe('Route', function(){ it('should work without handlers', function(done) { diff --git a/test/Router.js b/test/Router.js index b22001a9ff2..7bac7159b04 100644 --- a/test/Router.js +++ b/test/Router.js @@ -3,11 +3,11 @@ var after = require('after'); var express = require('../') , Router = express.Router - , methods = require('methods') - , assert = require('assert'); + , methods = require('../lib/utils').methods + , assert = require('node:assert'); -describe('Router', function(){ - it('should return a function with router methods', function() { +describe('Router', function () { + it('should return a function with router methods', function () { var router = new Router(); assert(typeof router === 'function') @@ -16,32 +16,32 @@ describe('Router', function(){ assert(typeof router.use === 'function') }); - it('should support .use of other routers', function(done){ + it('should support .use of other routers', function (done) { var router = new Router(); var another = new Router(); - another.get('/bar', function(req, res){ + another.get('/bar', function (req, res) { res.end(); }); 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){ + it('should support dynamic routes', function (done) { var router = new Router(); var another = new Router(); - another.get('/:bar', function(req, res){ + another.get('/:bar', function (req, res) { assert.strictEqual(req.params.bar, 'route') res.end(); }); 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){ + it('should handle blank URL', function (done) { var router = new Router(); router.use(function (req, res) { @@ -88,10 +88,10 @@ describe('Router', function(){ }) }) - it('should not stack overflow with many registered routes', function(done){ + it('should not stack overflow with many registered routes', function (done) { this.timeout(5000) // long-running test - var handler = function(req, res){ res.end(new Error('wrong handler')) }; + var handler = function (req, res) { res.end(new Error('wrong handler')) }; var router = new Router(); for (var i = 0; i < 6000; i++) { @@ -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,72 +154,74 @@ 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(){ - it('should dispatch', function(done){ + describe('.handle', function () { + it('should dispatch', function (done) { var router = new Router(); - router.route('/foo').get(function(req, res){ + router.route('/foo').get(function (req, res) { res.send('foo'); }); var res = { - send: function(val) { + send: function (val) { assert.strictEqual(val, 'foo') done(); } } - router.handle({ url: '/foo', method: 'GET' }, res); + router.handle({ url: '/foo', method: 'GET' }, res, function () { }); }) }) - describe('.multiple callbacks', function(){ - it('should throw if a callback is null', function(){ + describe('.multiple callbacks', function () { + it('should throw if a callback is null', function () { assert.throws(function () { var router = new Router(); router.route('/foo').all(null); }) }) - it('should throw if a callback is undefined', function(){ + it('should throw if a callback is undefined', function () { assert.throws(function () { var router = new Router(); router.route('/foo').all(undefined); }) }) - it('should throw if a callback is not a function', function(){ + it('should throw if a callback is not a function', function () { assert.throws(function () { var router = new Router(); router.route('/foo').all('not a function'); }) }) - it('should not throw if all callbacks are functions', function(){ + it('should not throw if all callbacks are functions', function () { var router = new Router(); - router.route('/foo').all(function(){}).all(function(){}); + router.route('/foo').all(function () { }).all(function () { }); }) }) - describe('error', function(){ - it('should skip non error middleware', function(done){ + describe('error', function () { + it('should skip non error middleware', function (done) { var router = new Router(); - router.get('/foo', function(req, res, next){ + router.get('/foo', function (req, res, next) { next(new Error('foo')); }); - router.get('/bar', function(req, res, next){ + router.get('/bar', function (req, res, next) { next(new Error('bar')); }); - router.use(function(req, res, next){ + router.use(function (req, res, next) { assert(false); }); - router.use(function(err, req, res, next){ + router.use(function (err, req, res, next) { assert.equal(err.message, 'foo'); done(); }); @@ -225,59 +229,59 @@ describe('Router', function(){ router.handle({ url: '/foo', method: 'GET' }, {}, done); }); - it('should handle throwing inside routes with params', function(done) { + it('should handle throwing inside routes with params', function (done) { var router = new Router(); router.get('/foo/:id', function () { throw new Error('foo'); }); - router.use(function(req, res, next){ + router.use(function (req, res, next) { assert(false); }); - router.use(function(err, req, res, next){ + router.use(function (err, req, res, next) { assert.equal(err.message, 'foo'); done(); }); - router.handle({ url: '/foo/2', method: 'GET' }, {}, function() {}); + router.handle({ url: '/foo/2', method: 'GET' }, {}, function () { }); }); - it('should handle throwing in handler after async param', function(done) { + it('should handle throwing in handler after async param', function (done) { var router = new Router(); - router.param('user', function(req, res, next, val){ - process.nextTick(function(){ + router.param('user', function (req, res, next, val) { + process.nextTick(function () { req.user = val; next(); }); }); - router.use('/:user', function(req, res, next){ + router.use('/:user', function (req, res, next) { throw new Error('oh no!'); }); - router.use(function(err, req, res, next){ + router.use(function (err, req, res, next) { assert.equal(err.message, 'oh no!'); done(); }); - router.handle({ url: '/bob', method: 'GET' }, {}, function() {}); + router.handle({ url: '/bob', method: 'GET' }, {}, function () { }); }); - it('should handle throwing inside error handlers', function(done) { + it('should handle throwing inside error handlers', function (done) { var router = new Router(); - router.use(function(req, res, next){ + router.use(function (req, res, next) { throw new Error('boom!'); }); - router.use(function(err, req, res, next){ + router.use(function (err, req, res, next) { throw new Error('oops'); }); - router.use(function(err, req, res, next){ + router.use(function (err, req, res, next) { assert.equal(err.message, 'oops'); done(); }); @@ -408,73 +412,55 @@ describe('Router', function(){ }); }) - describe('.all', function() { - it('should support using .all to capture all http verbs', function(done){ + describe('.all', function () { + it('should support using .all to capture all http verbs', function (done) { var router = new Router(); var count = 0; - router.all('/foo', function(){ count++; }); + router.all('/foo', function () { count++; }); var url = '/foo?bar=baz'; methods.forEach(function testMethod(method) { - router.handle({ url: url, method: method }, {}, function() {}); + router.handle({ url: url, method: method }, {}, 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() { + 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) { var cb = after(4, done) var router = new Router() - function no () { + function no() { throw new Error('should not be called') } @@ -488,39 +474,49 @@ describe('Router', function(){ router.handle({ url: '*', method: 'GET' }, { end: cb }, no) }) - it('should accept array of middleware', function(done){ + it('should accept array of middleware', function (done) { var count = 0; var router = new Router(); - function fn1(req, res, next){ + function fn1(req, res, next) { assert.equal(++count, 1); next(); } - function fn2(req, res, next){ + function fn2(req, res, next) { assert.equal(++count, 2); next(); } - router.use([fn1, fn2], function(req, res){ + router.use([fn1, fn2], function (req, res) { assert.equal(++count, 3); done(); }); - router.handle({ url: '/foo', method: 'GET' }, {}, function(){}); + router.handle({ url: '/foo', method: 'GET' }, {}, function () { }); }) }) - describe('.param', function() { - it('should call param function when routing VERBS', function(done) { + 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(); - router.param('id', function(req, res, next, id) { + router.param('id', function (req, res, next, id) { assert.equal(id, '123'); next(); }); - router.get('/foo/:id/bar', function(req, res, next) { + router.get('/foo/:id/bar', function (req, res, next) { assert.equal(req.params.id, '123'); next(); }); @@ -528,15 +524,15 @@ describe('Router', function(){ router.handle({ url: '/foo/123/bar', method: 'get' }, {}, done); }); - it('should call param function when routing middleware', function(done) { + it('should call param function when routing middleware', function (done) { var router = new Router(); - router.param('id', function(req, res, next, id) { + router.param('id', function (req, res, next, id) { assert.equal(id, '123'); next(); }); - router.use('/foo/:id/bar', function(req, res, next) { + router.use('/foo/:id/bar', function (req, res, next) { assert.equal(req.params.id, '123'); assert.equal(req.url, '/baz'); next(); @@ -545,17 +541,17 @@ describe('Router', function(){ router.handle({ url: '/foo/123/bar/baz', method: 'get' }, {}, done); }); - it('should only call once per request', function(done) { + it('should only call once per request', function (done) { var count = 0; var req = { url: '/foo/bob/bar', method: 'get' }; var router = new Router(); var sub = new Router(); - sub.get('/bar', function(req, res, next) { + sub.get('/bar', function (req, res, next) { next(); }); - router.param('user', function(req, res, next, user) { + router.param('user', function (req, res, next, user) { count++; req.user = user; next(); @@ -564,7 +560,7 @@ describe('Router', function(){ router.use('/foo/:user/', new Router()); router.use('/foo/:user/', sub); - router.handle(req, {}, function(err) { + router.handle(req, {}, function (err) { if (err) return done(err); assert.equal(count, 1); assert.equal(req.user, 'bob'); @@ -572,17 +568,17 @@ describe('Router', function(){ }); }); - it('should call when values differ', function(done) { + it('should call when values differ', function (done) { var count = 0; var req = { url: '/foo/bob/bar', method: 'get' }; var router = new Router(); var sub = new Router(); - sub.get('/bar', function(req, res, next) { + sub.get('/bar', function (req, res, next) { next(); }); - router.param('user', function(req, res, next, user) { + router.param('user', function (req, res, next, user) { count++; req.user = user; next(); @@ -591,7 +587,7 @@ describe('Router', function(){ router.use('/foo/:user/', new Router()); router.use('/:user/bob/', sub); - router.handle(req, {}, function(err) { + router.handle(req, {}, function (err) { if (err) return done(err); assert.equal(count, 2); assert.equal(req.user, 'foo'); @@ -600,8 +596,8 @@ describe('Router', function(){ }); }); - describe('parallel requests', function() { - it('should not mix requests', function(done) { + describe('parallel requests', function () { + it('should not mix requests', function (done) { var req1 = { url: '/foo/50/bar', method: 'get' }; var req2 = { url: '/foo/10/bar', method: 'get' }; var router = new Router(); @@ -609,11 +605,11 @@ describe('Router', function(){ var cb = after(2, done) - sub.get('/bar', function(req, res, next) { + sub.get('/bar', function (req, res, next) { next(); }); - router.param('ms', function(req, res, next, ms) { + router.param('ms', function (req, res, next, ms) { ms = parseInt(ms, 10); req.ms = ms; setTimeout(next, ms); @@ -622,14 +618,14 @@ describe('Router', function(){ router.use('/foo/:ms/', new Router()); router.use('/foo/:ms/', sub); - router.handle(req1, {}, function(err) { + router.handle(req1, {}, function (err) { assert.ifError(err); assert.equal(req1.ms, 50); assert.equal(req1.originalUrl, '/foo/50/bar'); cb() }); - router.handle(req2, {}, function(err) { + router.handle(req2, {}, function (err) { assert.ifError(err); assert.equal(req2.ms, 10); assert.equal(req2.originalUrl, '/foo/10/bar'); diff --git a/test/app.all.js b/test/app.all.js index 185a8332fe7..e4afca7d731 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 e9e5769d658..00000000000 --- 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.engine.js b/test/app.engine.js index 214510a94c0..b0553aa247e 100644 --- a/test/app.engine.js +++ b/test/app.engine.js @@ -1,9 +1,9 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('../') - , fs = require('fs'); -var path = require('path') + , fs = require('node:fs'); +var path = require('node:path') function render(path, options, fn) { fs.readFile(path, 'utf8', function(err, str){ diff --git a/test/app.head.js b/test/app.head.js index fabb98795ab..0207caaedad 100644 --- a/test/app.head.js +++ b/test/app.head.js @@ -2,7 +2,7 @@ var express = require('../'); var request = require('supertest'); -var assert = require('assert'); +var assert = require('node:assert'); describe('HEAD', function(){ it('should default to GET', function(done){ diff --git a/test/app.js b/test/app.js index 6134717c33e..c1e815a052d 100644 --- a/test/app.js +++ b/test/app.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('..') var request = require('supertest') @@ -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 5b150063b9e..3ef94ff184a 100644 --- a/test/app.listen.js +++ b/test/app.listen.js @@ -1,6 +1,7 @@ 'use strict' var express = require('../') +var assert = require('node:assert') describe('app.listen()', function(){ it('should wrap with an HTTP server', function(done){ @@ -10,4 +11,45 @@ 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() + }) + }) + }) + it('accepts port + hostname + backlog + callback', function (done) { + const app = express(); + const server = app.listen(0, '127.0.0.1', 5, function () { + const { address, port } = server.address(); + assert.strictEqual(address, '127.0.0.1'); + assert(Number.isInteger(port) && port > 0); + // backlog isn’t directly inspectable, but if no error was thrown + // we know it was accepted. + server.close(done); + }); + }); + it('accepts just a callback (no args)', function (done) { + const app = express(); + // same as app.listen(0, done) + const server = app.listen(); + server.close(done); + }); + it('server.address() gives a { address, port, family } object', function (done) { + const app = express(); + const server = app.listen(0, () => { + const addr = server.address(); + assert(addr && typeof addr === 'object'); + assert.strictEqual(typeof addr.address, 'string'); + assert(Number.isInteger(addr.port) && addr.port > 0); + assert(typeof addr.family === 'string'); + server.close(done); + }); + }); }) diff --git a/test/app.locals.js b/test/app.locals.js index 657b4b75c79..3963762fe2b 100644 --- a/test/app.locals.js +++ b/test/app.locals.js @@ -1,14 +1,15 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') 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 fdfd38c8a28..ee4c81631cb 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 b4ccc8a2d12..5c9a5630870 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.render.js b/test/app.render.js index 9d202acfdda..bd65ce1035b 100644 --- a/test/app.render.js +++ b/test/app.render.js @@ -1,8 +1,8 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('..'); -var path = require('path') +var path = require('node:path') var tmpl = require('./support/tmpl'); describe('app', function(){ @@ -331,6 +331,24 @@ describe('app', function(){ }) }) + it('should accept null or undefined options', function (done) { + var app = createApp() + + app.set('views', path.join(__dirname, 'fixtures')) + app.locals.user = { name: 'tobi' } + + app.render('user.tmpl', null, function (err, str) { + if (err) return done(err); + assert.strictEqual(str, '

tobi

') + + app.render('user.tmpl', undefined, function (err2, str2) { + if (err2) return done(err2); + assert.strictEqual(str2, '

tobi

') + done() + }) + }) + }) + describe('caching', function(){ it('should cache with cache option', function(done){ var app = express(); diff --git a/test/app.request.js b/test/app.request.js index 4930af84c25..b6c00f5baa3 100644 --- a/test/app.request.js +++ b/test/app.request.js @@ -10,7 +10,7 @@ describe('app', function(){ var app = express(); app.request.querystring = function(){ - return require('url').parse(this.url).query; + return require('node:url').parse(this.url).query; }; app.use(function(req, res){ diff --git a/test/app.route.js b/test/app.route.js index eaf8a120515..03ae1293685 100644 --- a/test/app.route.js +++ b/test/app.route.js @@ -61,4 +61,137 @@ describe('app.route', function(){ .get('/test') .expect(404, done); }); + + describe('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 8e427bd6dc7..6e7be684e55 100644 --- a/test/app.router.js +++ b/test/app.router.js @@ -3,26 +3,26 @@ var after = require('after'); var express = require('../') , request = require('supertest') - , assert = require('assert') - , methods = require('methods'); + , assert = require('node:assert') + , methods = require('../lib/utils').methods; var shouldSkipQuery = require('./support/utils').shouldSkipQuery -describe('app.router', function(){ - it('should restore req.params after leaving router', function(done){ +describe('app.router', function () { + it('should restore req.params after leaving router', function (done) { var app = express(); var router = new express.Router(); - function handler1(req, res, next){ + function handler1(req, res, next) { res.setHeader('x-user-id', String(req.params.id)); next() } - function handler2(req, res){ + function handler2(req, res) { res.send(req.params.id); } - router.use(function(req, res, next){ + router.use(function (req, res, next) { res.setHeader('x-router', String(req.params.id)); next(); }); @@ -30,34 +30,34 @@ describe('app.router', function(){ app.get('/user/:id', handler1, router, handler2); request(app) - .get('/user/1') - .expect('x-router', 'undefined') - .expect('x-user-id', '1') - .expect(200, '1', done); + .get('/user/1') + .expect('x-router', 'undefined') + .expect('x-user-id', '1') + .expect(200, '1', done); }) - describe('methods', function(){ - methods.concat('del').forEach(function(method){ + describe('methods', function () { + methods.forEach(function (method) { if (method === 'connect') return; - it('should include ' + method.toUpperCase(), function(done){ + it('should include ' + method.toUpperCase(), function (done) { if (method === 'query' && shouldSkipQuery(process.versions.node)) { this.skip() } var app = express(); - app[method]('/foo', function(req, res){ + app[method]('/foo', function (req, res) { res.send(method) }); request(app) [method]('/foo') - .expect(200, done) + .expect(200, done) }) - it('should reject numbers for app.' + method, 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/); }) }); @@ -77,22 +77,22 @@ describe('app.router', function(){ }); request(app) - .get('/') - .expect(404, cb) + .get('/') + .expect(404, cb) request(app) - .delete('/') - .expect(200, 'deleted everything', cb); + .delete('/') + .expect(200, 'deleted everything', cb); request(app) - .post('/') - .expect('X-Method-Altered', '1') - .expect(200, 'deleted everything', cb); + .post('/') + .expect('X-Method-Altered', '1') + .expect(200, 'deleted everything', cb); }); }) describe('decode params', function () { - it('should decode correct params', function(done){ + it('should decode correct params', function (done) { var app = express(); app.get('/:name', function (req, res) { @@ -100,11 +100,11 @@ describe('app.router', function(){ }); request(app) - .get('/foo%2Fbar') - .expect('foo/bar', done); + .get('/foo%2Fbar') + .expect('foo/bar', done); }) - it('should not accept params in malformed paths', function(done) { + it('should not accept params in malformed paths', function (done) { var app = express(); app.get('/:name', function (req, res) { @@ -112,11 +112,11 @@ describe('app.router', function(){ }); request(app) - .get('/%foobar') - .expect(400, done); + .get('/%foobar') + .expect(400, done); }) - it('should not decode spaces', function(done) { + it('should not decode spaces', function (done) { var app = express(); app.get('/:name', function (req, res) { @@ -124,11 +124,11 @@ describe('app.router', function(){ }); request(app) - .get('/foo+bar') - .expect('foo+bar', done); + .get('/foo+bar') + .expect('foo+bar', done); }) - it('should work with unicode', function(done) { + it('should work with unicode', function (done) { var app = express(); app.get('/:name', function (req, res) { @@ -136,77 +136,77 @@ describe('app.router', function(){ }); request(app) - .get('/%ce%b1') - .expect('\u03b1', done); + .get('/%ce%b1') + .expect('\u03b1', done); }) }) - it('should be .use()able', function(done){ + it('should be .use()able', function (done) { var app = express(); var calls = []; - app.use(function(req, res, next){ + app.use(function (req, res, next) { calls.push('before'); next(); }); - app.get('/', function(req, res, next){ + app.get('/', function (req, res, next) { calls.push('GET /') next(); }); - app.use(function(req, res, next){ + app.use(function (req, res, next) { calls.push('after'); res.json(calls) }); request(app) - .get('/') - .expect(200, ['before', 'GET /', 'after'], done) + .get('/') + .expect(200, ['before', 'GET /', 'after'], done) }) - describe('when given a regexp', function(){ - it('should match the pathname only', function(done){ + describe('when given a regexp', function () { + it('should match the pathname only', function (done) { var app = express(); - app.get(/^\/user\/[0-9]+$/, function(req, res){ + app.get(/^\/user\/[0-9]+$/, function (req, res) { res.end('user'); }); request(app) - .get('/user/12?foo=bar') - .expect('user', done); + .get('/user/12?foo=bar') + .expect('user', done); }) - it('should populate req.params with the captures', function(done){ + it('should populate req.params with the captures', function (done) { var app = express(); - app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function(req, res){ + app.get(/^\/user\/([0-9]+)\/(view|edit)?$/, function (req, res) { var id = req.params[0] , op = req.params[1]; res.end(op + 'ing user ' + id); }); request(app) - .get('/user/10/edit') - .expect('editing user 10', done); + .get('/user/10/edit') + .expect('editing user 10', done); }) if (supportsRegexp('(?.*)')) { - it('should populate req.params with named captures', function(done){ + it('should populate req.params with named captures', function (done) { var app = express(); var re = new RegExp('^/user/(?[0-9]+)/(view|edit)?$'); - app.get(re, function(req, res){ + app.get(re, function (req, res) { var id = req.params.userId , op = req.params[0]; res.end(op + 'ing user ' + id); }); request(app) - .get('/user/10/edit') - .expect('editing user 10', done); + .get('/user/10/edit') + .expect('editing user 10', done); }) } @@ -240,153 +240,153 @@ describe('app.router', function(){ }) }) - describe('case sensitivity', function(){ - it('should be disabled by default', function(done){ + describe('case sensitivity', function () { + it('should be disabled by default', function (done) { var app = express(); - app.get('/user', function(req, res){ + app.get('/user', function (req, res) { res.end('tj'); }); request(app) - .get('/USER') - .expect('tj', done); + .get('/USER') + .expect('tj', done); }) - describe('when "case sensitive routing" is enabled', function(){ - it('should match identical casing', function(done){ + describe('when "case sensitive routing" is enabled', function () { + it('should match identical casing', function (done) { var app = express(); app.enable('case sensitive routing'); - app.get('/uSer', function(req, res){ + app.get('/uSer', function (req, res) { res.end('tj'); }); request(app) - .get('/uSer') - .expect('tj', done); + .get('/uSer') + .expect('tj', done); }) - it('should not match otherwise', function(done){ + it('should not match otherwise', function (done) { var app = express(); app.enable('case sensitive routing'); - app.get('/uSer', function(req, res){ + app.get('/uSer', function (req, res) { res.end('tj'); }); request(app) - .get('/user') - .expect(404, done); + .get('/user') + .expect(404, done); }) }) }) - describe('params', function(){ - it('should overwrite existing req.params by default', function(done){ + describe('params', function () { + it('should overwrite existing req.params by default', function (done) { var app = express(); var router = new express.Router(); - router.get('/:action', function(req, res){ + router.get('/:action', function (req, res) { res.send(req.params); }); app.use('/user/:user', router); request(app) - .get('/user/1/get') - .expect(200, '{"action":"get"}', done); + .get('/user/1/get') + .expect(200, '{"action":"get"}', done); }) - it('should allow merging existing req.params', function(done){ + it('should allow merging existing req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/:action', function(req, res){ + router.get('/:action', function (req, res) { var keys = Object.keys(req.params).sort(); - res.send(keys.map(function(k){ return [k, req.params[k]] })); + res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use('/user/:user', router); request(app) - .get('/user/tj/get') - .expect(200, '[["action","get"],["user","tj"]]', done); + .get('/user/tj/get') + .expect(200, '[["action","get"],["user","tj"]]', done); }) - it('should use params from router', function(done){ + it('should use params from router', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/:thing', function(req, res){ + router.get('/:thing', function (req, res) { var keys = Object.keys(req.params).sort(); - res.send(keys.map(function(k){ return [k, req.params[k]] })); + res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use('/user/:thing', router); request(app) - .get('/user/tj/get') - .expect(200, '[["thing","get"]]', done); + .get('/user/tj/get') + .expect(200, '[["thing","get"]]', done); }) - it('should merge numeric indices req.params', function(done){ + it('should merge numeric indices req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/*.*', function(req, res){ + router.get(/^\/(.*)\.(.*)/, function (req, res) { var keys = Object.keys(req.params).sort(); - res.send(keys.map(function(k){ return [k, req.params[k]] })); + 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') - .expect(200, '[["0","10"],["1","profile"],["2","json"]]', done); + .get('/user/id:10/profile.json') + .expect(200, '[["0","10"],["1","profile"],["2","json"]]', done); }) - it('should merge numeric indices req.params when more in parent', function(done){ + it('should merge numeric indices req.params when more in parent', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/*', function(req, res){ + router.get(/\/(.*)/, function (req, res) { var keys = Object.keys(req.params).sort(); - res.send(keys.map(function(k){ return [k, req.params[k]] })); + 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') - .expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done); + .get('/user/id:10/name:tj/profile') + .expect(200, '[["0","10"],["1","tj"],["2","profile"]]', done); }) - it('should merge numeric indices req.params when parent has same number', function(done){ + it('should merge numeric indices req.params when parent has same number', function (done) { 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]] })); + 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') - .expect(200, '[["0","10"],["1","tj"]]', done); + .get('/user/id:10/name:tj') + .expect(200, '[["0","10"],["1","tj"]]', done); }) - it('should ignore invalid incoming req.params', function(done){ + it('should ignore invalid incoming req.params', function (done) { var app = express(); var router = new express.Router({ mergeParams: true }); - router.get('/:name', function(req, res){ + router.get('/:name', function (req, res) { var keys = Object.keys(req.params).sort(); - res.send(keys.map(function(k){ return [k, req.params[k]] })); + res.send(keys.map(function (k) { return [k, req.params[k]] })); }); app.use('/user/', function (req, res, next) { @@ -395,60 +395,60 @@ describe('app.router', function(){ }); request(app) - .get('/user/tj') - .expect(200, '[["name","tj"]]', done); + .get('/user/tj') + .expect(200, '[["name","tj"]]', done); }) - it('should restore req.params', function(done){ + it('should restore req.params', function (done) { 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]] })); + res.send(keys.map(function (k) { return [k, req.params[k]] })); }); }); request(app) - .get('/user/id:42/user:tj/profile') - .expect(200, '[["0","42"]]', done); + .get('/user/id:42/user:tj/profile') + .expect(200, '[["0","42"]]', done); }) }) - describe('trailing slashes', function(){ - it('should be optional by default', function(done){ + describe('trailing slashes', function () { + it('should be optional by default', function (done) { var app = express(); - app.get('/user', function(req, res){ + app.get('/user', function (req, res) { res.end('tj'); }); request(app) - .get('/user/') - .expect('tj', done); + .get('/user/') + .expect('tj', done); }) - describe('when "strict routing" is enabled', function(){ - it('should match trailing slashes', function(done){ + describe('when "strict routing" is enabled', function () { + it('should match trailing slashes', function (done) { var app = express(); app.enable('strict routing'); - app.get('/user/', function(req, res){ + app.get('/user/', function (req, res) { res.end('tj'); }); request(app) - .get('/user/') - .expect('tj', done); + .get('/user/') + .expect('tj', done); }) - it('should pass-though middleware', function(done){ + it('should pass-though middleware', function (done) { var app = express(); app.enable('strict routing'); @@ -458,17 +458,17 @@ describe('app.router', function(){ next(); }); - app.get('/user/', function(req, res){ + app.get('/user/', function (req, res) { res.end('tj'); }); request(app) - .get('/user/') - .expect('x-middleware', 'true') - .expect(200, 'tj', done); + .get('/user/') + .expect('x-middleware', 'true') + .expect(200, 'tj', done); }) - it('should pass-though mounted middleware', function(done){ + it('should pass-though mounted middleware', function (done) { var app = express(); app.enable('strict routing'); @@ -478,123 +478,106 @@ describe('app.router', function(){ next(); }); - app.get('/user/test/', function(req, res){ + app.get('/user/test/', function (req, res) { res.end('tj'); }); request(app) - .get('/user/test/') - .expect('x-middleware', 'true') - .expect(200, 'tj', done); + .get('/user/test/') + .expect('x-middleware', 'true') + .expect(200, 'tj', done); }) - it('should match no slashes', function(done){ + it('should match no slashes', function (done) { var app = express(); app.enable('strict routing'); - app.get('/user', function(req, res){ + app.get('/user', function (req, res) { res.end('tj'); }); request(app) - .get('/user') - .expect('tj', done); + .get('/user') + .expect('tj', done); }) - it('should match middleware when omitting the trailing slash', function(done){ + it('should match middleware when omitting the trailing slash', function (done) { var app = express(); app.enable('strict routing'); - app.use('/user/', function(req, res){ + app.use('/user/', function (req, res) { res.end('tj'); }); request(app) - .get('/user') - .expect(200, 'tj', done); + .get('/user') + .expect(200, 'tj', done); }) - it('should match middleware', function(done){ + it('should match middleware', function (done) { var app = express(); app.enable('strict routing'); - app.use('/user', function(req, res){ + app.use('/user', function (req, res) { res.end('tj'); }); request(app) - .get('/user') - .expect(200, 'tj', done); + .get('/user') + .expect(200, 'tj', done); }) - it('should match middleware when adding the trailing slash', function(done){ + it('should match middleware when adding the trailing slash', function (done) { var app = express(); app.enable('strict routing'); - app.use('/user', function(req, res){ + app.use('/user', function (req, res) { res.end('tj'); }); request(app) - .get('/user/') - .expect(200, 'tj', done); + .get('/user/') + .expect(200, 'tj', done); }) - it('should fail when omitting the trailing slash', function(done){ + it('should fail when omitting the trailing slash', function (done) { var app = express(); app.enable('strict routing'); - app.get('/user/', function(req, res){ + app.get('/user/', function (req, res) { res.end('tj'); }); request(app) - .get('/user') - .expect(404, done); + .get('/user') + .expect(404, done); }) - it('should fail when adding the trailing slash', function(done){ + it('should fail when adding the trailing slash', function (done) { var app = express(); app.enable('strict routing'); - app.get('/user', function(req, res){ + app.get('/user', function (req, res) { res.end('tj'); }); request(app) - .get('/user/') - .expect(404, done); + .get('/user/') + .expect(404, done); }) }) }) - it('should allow escaped regexp', function(done){ + it('should allow literal "."', 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(); - - app.get('/api/users/:from..:to', function(req, res){ + app.get('/api/users/:from..:to', function (req, res) { var from = req.params.from , to = req.params.to; @@ -602,294 +585,204 @@ describe('app.router', function(){ }); request(app) - .get('/api/users/1..50') - .expect('users from 1 to 50', done); + .get('/api/users/1..50') + .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){ + describe(':name', function () { + it('should denote a 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); + app.get('/user/:user', function (req, res) { + res.end(req.params.user); }); request(app) - .get('/api/users/0.json') - .expect('users/0.json', done); + .get('/user/tj') + .expect('tj', done); }) - it('should not be greedy immediately after param', function(done){ + it('should match a single segment only', function (done) { var app = express(); - app.get('/user/:user*', function(req, res){ + app.get('/user/:user', function (req, res) { res.end(req.params.user); }); request(app) - .get('/user/122') - .expect('122', done); + .get('/user/tj/edit') + .expect(404, done); }) - it('should eat everything after /', function(done){ + it('should allow several capture groups', function (done) { var app = express(); - app.get('/user/:user*', function(req, res){ - res.end(req.params.user); + app.get('/user/:user/:op', function (req, res) { + res.end(req.params.op + 'ing ' + req.params.user); }); request(app) - .get('/user/122/aaa') - .expect('122', done); + .get('/user/tj/edit') + .expect('editing tj', done); }) - it('should span multiple segments', function(done){ + it('should work following a partial capture group', function (done) { var app = express(); + var cb = after(2, done); - app.get('/file/*', function(req, res){ - res.end(req.params[0]); + 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) - .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]); - }); + .get('/user/tj/edit') + .expect('editing tj', cb); request(app) - .get('/file/') - .expect('', done); + .get('/users/tj/edit') + .expect('editing tj (old)', cb); }) - it('should require a preceding /', function(done){ + it('should work inside literal parenthesis', function (done) { var app = express(); - app.get('/file/*', function(req, res){ - res.end(req.params[0]); + app.get('/:user\\(:op\\)', function (req, res) { + res.end(req.params.op + 'ing ' + req.params.user); }); request(app) - .get('/file') - .expect(404, done); + .get('/tj(edit)') + .expect('editing tj', done); }) - it('should keep correct parameter indexes', function(done){ + it('should work in array of paths', function (done) { var app = express(); + var cb = after(2, done); - app.get('/*/user/:id', function (req, res) { - res.send(req.params); + app.get(['/user/:user/poke', '/user/:user/pokes'], function (req, res) { + res.end('poking ' + req.params.user); }); 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); - }); + .get('/user/tj/poke') + .expect('poking tj', cb); request(app) - .get('/test') - .expect(200, 'test', done); + .get('/user/tj/pokes') + .expect('poking tj', cb); }) }) - describe(':name', function(){ - it('should denote a capture group', function(done){ + describe(':name?', function () { + it('should denote an optional capture group', function (done) { var app = express(); - app.get('/user/:user', function(req, res){ - res.end(req.params.user); + app.get('/user/:user{/:op}', function (req, res) { + var op = req.params.op || 'view'; + res.end(op + 'ing ' + req.params.user); }); request(app) - .get('/user/tj') - .expect('tj', done); + .get('/user/tj') + .expect('viewing tj', done); }) - it('should match a single segment only', function(done){ + it('should populate the capture group', function (done) { var app = express(); - app.get('/user/:user', function(req, res){ - res.end(req.params.user); + app.get('/user/:user{/:op}', function (req, res) { + var op = req.params.op || 'view'; + res.end(op + 'ing ' + req.params.user); }); request(app) - .get('/user/tj/edit') - .expect(404, done); + .get('/user/tj/edit') + .expect('editing tj', done); }) + }) - it('should allow several capture groups', function(done){ - var app = express(); + describe(':name*', function () { + it('should match one segment', function (done) { + var app = express() - app.get('/user/:user/:op', function(req, res){ - res.end(req.params.op + 'ing ' + req.params.user); - }); + app.get('/user/*user', function (req, res) { + res.end(req.params.user[0]) + }) request(app) - .get('/user/tj/edit') - .expect('editing tj', done); + .get('/user/122') + .expect('122', done) }) - it('should work following a partial capture group', function(done){ - 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)' : '')); - }); + it('should match many segments', function (done) { + var app = express() - request(app) - .get('/user/tj/edit') - .expect('editing tj', cb); + app.get('/user/*user', function (req, res) { + res.end(req.params.user.join('/')) + }) request(app) - .get('/users/tj/edit') - .expect('editing tj (old)', cb); + .get('/user/1/2/3/4') + .expect('1/2/3/4', done) }) - it('should work inside literal parenthesis', function(done){ - var app = express(); + it('should match zero segments', function (done) { + var app = express() - app.get('/:user\\(:op\\)', function(req, res){ - res.end(req.params.op + 'ing ' + req.params.user); - }); + app.get('/user{/*user}', function (req, res) { + res.end(req.params.user) + }) request(app) - .get('/tj(edit)') - .expect('editing tj', done); + .get('/user') + .expect('', done) }) + }) - it('should work in array of paths', function(done){ - var app = express(); - var cb = after(2, done); - - app.get(['/user/:user/poke', '/user/:user/pokes'], function(req, res){ - res.end('poking ' + req.params.user); - }); + describe(':name+', function () { + it('should match one segment', function (done) { + var app = express() - request(app) - .get('/user/tj/poke') - .expect('poking tj', cb); + app.get('/user/*user', function (req, res) { + res.end(req.params.user[0]) + }) request(app) - .get('/user/tj/pokes') - .expect('poking tj', cb); + .get('/user/122') + .expect(200, '122', done) }) - }) - describe(':name?', function(){ - it('should denote an optional capture group', function(done){ - var app = express(); + it('should match many segments', function (done) { + var app = express() - app.get('/user/:user/:op?', function(req, res){ - var op = req.params.op || 'view'; - res.end(op + 'ing ' + req.params.user); - }); + app.get('/user/*user', function (req, res) { + res.end(req.params.user.join('/')) + }) request(app) - .get('/user/tj') - .expect('viewing tj', done); + .get('/user/1/2/3/4') + .expect(200, '1/2/3/4', done) }) - it('should populate the capture group', function(done){ - var app = express(); + it('should not match zero segments', function (done) { + var app = express() - app.get('/user/:user/:op?', function(req, res){ - var op = req.params.op || 'view'; - res.end(op + 'ing ' + req.params.user); - }); + app.get('/user/*user', function (req, res) { + res.end(req.params.user) + }) request(app) - .get('/user/tj/edit') - .expect('editing tj', done); + .get('/user') + .expect(404, done) }) }) - describe('.:name', function(){ - it('should denote a format', function(done){ + describe('.:name', function () { + it('should denote a format', function (done) { 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); }); @@ -903,12 +796,12 @@ describe('app.router', function(){ }) }) - describe('.:name?', function(){ - it('should denote an optional format', function(done){ + describe('.:name?', function () { + it('should denote an optional format', function (done) { 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')); }); @@ -922,12 +815,12 @@ describe('app.router', function(){ }) }) - describe('when next() is called', function(){ - it('should continue lookup', function(done){ + describe('when next() is called', function () { + it('should continue lookup', function (done) { 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(); }); @@ -936,7 +829,7 @@ describe('app.router', function(){ assert(0); }); - app.get('/foo', function(req, res, next){ + app.get('/foo', function (req, res, next) { calls.push('/foo'); next(); }); @@ -947,16 +840,16 @@ describe('app.router', function(){ }); request(app) - .get('/foo') - .expect(200, ['/foo/:bar?', '/foo', '/foo 2'], done) + .get('/foo') + .expect(200, ['/foo/:bar?', '/foo', '/foo 2'], done) }) }) - describe('when next("route") is called', function(){ - it('should jump to next route', function(done){ + describe('when next("route") is called', function () { + it('should jump to next route', function (done) { var app = express() - function fn(req, res, next){ + function fn(req, res, next) { res.set('X-Hit', '1') next('route') } @@ -965,14 +858,14 @@ describe('app.router', function(){ res.end('failure') }); - app.get('/foo', function(req, res){ + app.get('/foo', function (req, res) { res.end('success') }) request(app) - .get('/foo') - .expect('X-Hit', '1') - .expect(200, 'success', done) + .get('/foo') + .expect('X-Hit', '1') + .expect(200, 'success', done) }) }) @@ -981,7 +874,7 @@ describe('app.router', function(){ var app = express() var router = express.Router() - function fn (req, res, next) { + function fn(req, res, next) { res.set('X-Hit', '1') next('router') } @@ -1001,18 +894,18 @@ describe('app.router', function(){ }) request(app) - .get('/foo') - .expect('X-Hit', '1') - .expect(200, 'success', done) + .get('/foo') + .expect('X-Hit', '1') + .expect(200, 'success', done) }) }) - describe('when next(err) is called', function(){ - it('should break out of app.router', function(done){ + describe('when next(err) is called', function () { + it('should break out of app.router', function (done) { 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(); }); @@ -1021,7 +914,7 @@ describe('app.router', function(){ assert(0); }); - app.get('/foo', function(req, res, next){ + app.get('/foo', function (req, res, next) { calls.push('/foo'); next(new Error('fail')); }); @@ -1030,7 +923,7 @@ describe('app.router', function(){ assert(0); }); - app.use(function(err, req, res, next){ + app.use(function (err, req, res, next) { res.json({ calls: calls, error: err.message @@ -1038,11 +931,11 @@ describe('app.router', function(){ }) request(app) - .get('/foo') - .expect(200, { calls: ['/foo/:bar?', '/foo'], error: 'fail' }, done) + .get('/foo') + .expect(200, { calls: ['/foo/:bar?', '/foo'], error: 'fail' }, done) }) - it('should call handler in same route, if exists', function(done){ + it('should call handler in same route, if exists', function (done) { var app = express(); function fn1(req, res, next) { @@ -1064,71 +957,253 @@ describe('app.router', function(){ }) request(app) - .get('/foo') - .expect('route go boom!', done) + .get('/foo') + .expect('route go boom!', done) + }) + }) + + describe('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){ + it('should allow rewriting of the url', function (done) { var app = express(); - app.get('/account/edit', function(req, res, next){ + app.get('/account/edit', function (req, res, next) { req.user = { id: 12 }; // faux authenticated user req.url = '/user/' + req.user.id + '/edit'; next(); }); - app.get('/user/:id/edit', function(req, res){ + app.get('/user/:id/edit', function (req, res) { res.send('editing user ' + req.params.id); }); request(app) - .get('/account/edit') - .expect('editing user 12', done); + .get('/account/edit') + .expect('editing user 12', done); }) - it('should run in order added', function(done){ + it('should run in order added', function (done) { var app = express(); var path = []; - app.get('*', function(req, res, next){ + app.get('/*path', function (req, res, next) { path.push(0); next(); }); - app.get('/user/:id', function(req, res, next){ + app.get('/user/:id', function (req, res, next) { path.push(1); next(); }); - app.use(function(req, res, next){ + app.use(function (req, res, next) { path.push(2); next(); }); - app.all('/user/:id', function(req, res, next){ + app.all('/user/:id', function (req, res, next) { path.push(3); next(); }); - app.get('*', function(req, res, next){ + app.get('/*splat', function (req, res, next) { path.push(4); next(); }); - app.use(function(req, res, next){ + app.use(function (req, res, next) { path.push(5); res.end(path.join(',')) }); request(app) - .get('/user/1') - .expect(200, '0,1,2,3,4,5', done); + .get('/user/1') + .expect(200, '0,1,2,3,4,5', done); }) - it('should be chainable', function(){ + it('should be chainable', function () { var app = express(); - assert.strictEqual(app.get('/', function () {}), app) + assert.strictEqual(app.get('/', function () { }), app) + }) + + it('should not use disposed router/middleware', function (done) { + // more context: https://github.com/expressjs/express/issues/5743#issuecomment-2277148412 + + var app = express(); + var router = new express.Router(); + + router.use(function (req, res, next) { + res.setHeader('old', 'foo'); + next(); + }); + + app.use(function (req, res, next) { + return router.handle(req, res, next); + }); + + app.get('/', function (req, res, next) { + res.send('yee'); + next(); + }); + + request(app) + .get('/') + .expect('old', 'foo') + .expect(function (res) { + if (typeof res.headers['new'] !== 'undefined') { + throw new Error('`new` header should not be present'); + } + }) + .expect(200, 'yee', function (err, res) { + if (err) return done(err); + + router = new express.Router(); + + router.use(function (req, res, next) { + res.setHeader('new', 'bar'); + next(); + }); + + request(app) + .get('/') + .expect('new', 'bar') + .expect(function (res) { + if (typeof res.headers['old'] !== 'undefined') { + throw new Error('`old` header should not be present'); + } + }) + .expect(200, 'yee', done); + }); }) }) diff --git a/test/app.routes.error.js b/test/app.routes.error.js index 56081b31127..ed53c7857ba 100644 --- a/test/app.routes.error.js +++ b/test/app.routes.error.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('../') , request = require('supertest'); @@ -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 1de3275c8e2..1d56aa3b029 100644 --- a/test/app.use.js +++ b/test/app.use.js @@ -1,7 +1,7 @@ 'use strict' var after = require('after'); -var assert = require('assert') +var assert = require('node:assert') var express = require('..'); var request = require('supertest'); @@ -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/config.js b/test/config.js index b04367fdbf8..d004de03eaa 100644 --- a/test/config.js +++ b/test/config.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert'); +var assert = require('node:assert'); var express = require('..'); describe('config', function () { diff --git a/test/exports.js b/test/exports.js index 5ab0f885ce4..fc7836c1594 100644 --- a/test/exports.js +++ b/test/exports.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('../'); var request = require('supertest'); @@ -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 f6f536b15e5..28746bfebda 100644 --- a/test/express.json.js +++ b/test/express.json.js @@ -1,15 +1,12 @@ 'use strict' -var assert = require('assert') -var asyncHooks = tryRequire('async_hooks') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage +const { Buffer } = require('node:buffer'); + var express = require('..') var request = require('supertest') -var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' - ? describe - : describe.skip - describe('express.json()', function () { it('should parse JSON', function (done) { request(createApp()) @@ -43,12 +40,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 +70,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 +313,7 @@ describe('express.json()', function () { .post('/') .set('Content-Type', 'application/json') .send('{"user":"tobi"}') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -373,7 +345,7 @@ describe('express.json()', function () { .post('/') .set('Content-Type', 'application/x-json') .send('{"user":"tobi"}') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -528,13 +500,13 @@ describe('express.json()', function () { }) }) - describeAsyncHooks('async local storage', function () { + describe('async local storage', function () { before(function () { var app = express() var store = { foo: 'bar' } app.use(function (req, res, next) { - req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage() + req.asyncLocalStorage = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -568,7 +540,7 @@ describe('express.json()', function () { this.app = app }) - it('should presist store', function (done) { + it('should persist store', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/json') @@ -579,18 +551,18 @@ 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) }) - it('should presist store when inflated', function (done) { + it('should persist store when inflated', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'application/json') @@ -601,7 +573,7 @@ describe('express.json()', function () { test.end(done) }) - it('should presist store when inflate error', function (done) { + it('should persist store when inflate error', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'application/json') @@ -611,7 +583,7 @@ describe('express.json()', function () { test.end(done) }) - it('should presist store when parse error', function (done) { + it('should persist store when parse error', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/json') @@ -621,7 +593,7 @@ describe('express.json()', function () { .end(done) }) - it('should presist store when limit exceeded', function (done) { + it('should persist store when limit exceeded', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/json') @@ -753,6 +725,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']] @@ -780,11 +753,3 @@ function shouldContainInBody (str) { 'expected \'' + res.text + '\' to contain \'' + str + '\'') } } - -function tryRequire (name) { - try { - return require(name) - } catch (e) { - return {} - } -} diff --git a/test/express.raw.js b/test/express.raw.js index 4aa62bb85bc..5576e225283 100644 --- a/test/express.raw.js +++ b/test/express.raw.js @@ -1,14 +1,11 @@ 'use strict' -var assert = require('assert') -var asyncHooks = tryRequire('async_hooks') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage + var express = require('..') var request = require('supertest') - -var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' - ? describe - : describe.skip +const { Buffer } = require('node:buffer'); describe('express.raw()', function () { before(function () { @@ -65,36 +62,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 +203,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 +232,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) }) }) @@ -358,13 +325,13 @@ describe('express.raw()', function () { }) }) - describeAsyncHooks('async local storage', function () { + describe('async local storage', function () { before(function () { var app = express() var store = { foo: 'bar' } app.use(function (req, res, next) { - req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage() + req.asyncLocalStorage = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -402,7 +369,7 @@ describe('express.raw()', function () { this.app = app }) - it('should presist store', function (done) { + it('should persist store', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/octet-stream') @@ -413,18 +380,17 @@ describe('express.raw()', 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('{}') .end(done) }) - it('should presist store when inflated', function (done) { + it('should persist store when inflated', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'application/octet-stream') @@ -435,7 +401,7 @@ describe('express.raw()', function () { test.end(done) }) - it('should presist store when inflate error', function (done) { + it('should persist store when inflate error', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'application/octet-stream') @@ -445,7 +411,7 @@ describe('express.raw()', function () { test.end(done) }) - it('should presist store when limit exceeded', function (done) { + it('should persist store when limit exceeded', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/octet-stream') @@ -545,11 +511,3 @@ function createApp (options) { return app } - -function tryRequire (name) { - try { - return require(name) - } catch (e) { - return {} - } -} diff --git a/test/express.static.js b/test/express.static.js index 23e607ed933..a2035631d66 100644 --- a/test/express.static.js +++ b/test/express.static.js @@ -1,9 +1,10 @@ 'use strict' -var assert = require('assert') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') var express = require('..') -var path = require('path') +var path = require('node:path') +const { Buffer } = require('node:buffer'); + var request = require('supertest') var utils = require('./support/utils') @@ -41,7 +42,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 cb7750a525c..e96cc5efe52 100644 --- a/test/express.text.js +++ b/test/express.text.js @@ -1,15 +1,11 @@ 'use strict' -var assert = require('assert') -var asyncHooks = tryRequire('async_hooks') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage +const { Buffer } = require('node:buffer'); var express = require('..') var request = require('supertest') -var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' - ? describe - : describe.skip - describe('express.text()', function () { before(function () { this.app = createApp() @@ -61,32 +57,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 +217,7 @@ describe('express.text()', function () { .post('/') .set('Content-Type', 'text/plain') .send('user is tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -277,7 +247,7 @@ describe('express.text()', function () { .post('/') .set('Content-Type', 'text/xml') .send('tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -387,13 +357,13 @@ describe('express.text()', function () { }) }) - describeAsyncHooks('async local storage', function () { + describe('async local storage', function () { before(function () { var app = express() var store = { foo: 'bar' } app.use(function (req, res, next) { - req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage() + req.asyncLocalStorage = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -427,7 +397,7 @@ describe('express.text()', function () { this.app = app }) - it('should presist store', function (done) { + it('should persist store', function (done) { request(this.app) .post('/') .set('Content-Type', 'text/plain') @@ -438,18 +408,17 @@ describe('express.text()', 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('{}') .end(done) }) - it('should presist store when inflated', function (done) { + it('should persist store when inflated', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'text/plain') @@ -460,7 +429,7 @@ describe('express.text()', function () { test.end(done) }) - it('should presist store when inflate error', function (done) { + it('should persist store when inflate error', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'text/plain') @@ -470,7 +439,7 @@ describe('express.text()', function () { test.end(done) }) - it('should presist store when limit exceeded', function (done) { + it('should persist store when limit exceeded', function (done) { request(this.app) .post('/') .set('Content-Type', 'text/plain') @@ -595,11 +564,3 @@ function createApp (options) { return app } - -function tryRequire (name) { - try { - return require(name) - } catch (e) { - return {} - } -} diff --git a/test/express.urlencoded.js b/test/express.urlencoded.js index 537fb797e75..f4acf231989 100644 --- a/test/express.urlencoded.js +++ b/test/express.urlencoded.js @@ -1,15 +1,12 @@ 'use strict' -var assert = require('assert') -var asyncHooks = tryRequire('async_hooks') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage +const { Buffer } = require('node:buffer'); + var express = require('..') var request = require('supertest') -var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' - ? describe - : describe.skip - describe('express.urlencoded()', function () { before(function () { this.app = createApp() @@ -62,32 +59,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 +76,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 +444,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 +476,7 @@ describe('express.urlencoded()', function () { .post('/') .set('Content-Type', 'application/x-foo') .send('user=tobi') - .expect(200, '{}', done) + .expect(200, '', done) }) }) @@ -632,13 +603,13 @@ describe('express.urlencoded()', function () { }) }) - describeAsyncHooks('async local storage', function () { + describe('async local storage', function () { before(function () { var app = express() var store = { foo: 'bar' } app.use(function (req, res, next) { - req.asyncLocalStorage = new asyncHooks.AsyncLocalStorage() + req.asyncLocalStorage = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -672,7 +643,7 @@ describe('express.urlencoded()', function () { this.app = app }) - it('should presist store', function (done) { + it('should persist store', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/x-www-form-urlencoded') @@ -683,18 +654,17 @@ describe('express.urlencoded()', 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('{}') .end(done) }) - it('should presist store when inflated', function (done) { + it('should persist store when inflated', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'application/x-www-form-urlencoded') @@ -705,7 +675,7 @@ describe('express.urlencoded()', function () { test.end(done) }) - it('should presist store when inflate error', function (done) { + it('should persist store when inflate error', function (done) { var test = request(this.app).post('/') test.set('Content-Encoding', 'gzip') test.set('Content-Type', 'application/x-www-form-urlencoded') @@ -715,7 +685,7 @@ describe('express.urlencoded()', function () { test.end(done) }) - it('should presist store when limit exceeded', function (done) { + it('should persist store when limit exceeded', function (done) { request(this.app) .post('/') .set('Content-Type', 'application/x-www-form-urlencoded') @@ -856,11 +826,3 @@ function expectKeyCount (count) { assert.strictEqual(Object.keys(JSON.parse(res.text)).length, count) } } - -function tryRequire (name) { - try { - return require(name) - } catch (e) { - return {} - } -} diff --git a/test/middleware.basic.js b/test/middleware.basic.js index 19f00d9a296..1f1ed17571a 100644 --- a/test/middleware.basic.js +++ b/test/middleware.basic.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('../'); var request = require('supertest'); diff --git a/test/req.acceptsCharset.js b/test/req.acceptsCharset.js deleted file mode 100644 index 6dbab439b7e..00000000000 --- 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.acceptsCharsets.js b/test/req.acceptsCharsets.js index 360a9878a78..2df68ae1097 100644 --- a/test/req.acceptsCharsets.js +++ b/test/req.acceptsCharsets.js @@ -45,6 +45,19 @@ describe('req', function(){ .set('Accept-Charset', 'foo, bar') .expect('no', done); }) + + it('should return the best matching charset from multiple inputs', function (done) { + var app = express(); + + app.use(function(req, res, next){ + res.end(req.acceptsCharsets('utf-8', 'iso-8859-1')); + }); + + request(app) + .get('/') + .set('Accept-Charset', 'iso-8859-1, utf-8') + .expect('iso-8859-1', done); + }) }) }) }) diff --git a/test/req.acceptsEncoding.js b/test/req.acceptsEncoding.js deleted file mode 100644 index bcec2280e65..00000000000 --- 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 39bd73c4834..00000000000 --- 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 9160e2caaf6..3bf6a1f65a7 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.get.js b/test/req.get.js index 16589b3f059..e73d109c84a 100644 --- a/test/req.get.js +++ b/test/req.get.js @@ -2,7 +2,7 @@ var express = require('../') , request = require('supertest') - , assert = require('assert'); + , assert = require('node:assert'); describe('req', function(){ describe('.get(field)', function(){ diff --git a/test/req.host.js b/test/req.host.js index 2c051fb9791..cdda82eaae3 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 b3748c02bce..00000000000 --- 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.protocol.js b/test/req.protocol.js index 61f76356b4c..def82eda922 100644 --- a/test/req.protocol.js +++ b/test/req.protocol.js @@ -39,7 +39,7 @@ describe('req', function(){ app.enable('trust proxy'); app.use(function(req, res){ - req.connection.encrypted = true; + req.socket.encrypted = true; res.end(req.protocol); }); diff --git a/test/req.query.js b/test/req.query.js index 6fae592dccc..c0d3c8376e9 100644 --- a/test/req.query.js +++ b/test/req.query.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('../') , request = require('supertest'); @@ -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 6c17fbb1c8f..9bd7ed923b4 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.append.js b/test/res.append.js index 8f72598bf52..2dd17a3a1fb 100644 --- a/test/res.append.js +++ b/test/res.append.js @@ -1,6 +1,6 @@ 'use strict' -var assert = require('assert') +var assert = require('node:assert') var express = require('..') var request = require('supertest') @@ -108,7 +108,7 @@ function shouldHaveHeaderValues (key, values) { return function (res) { var headers = res.headers[key.toLowerCase()] assert.ok(headers, 'should have header "' + key + '"') - assert.strictEqual(headers.length, values.length, 'should have ' + values.length + ' occurances of "' + key + '"') + assert.strictEqual(headers.length, values.length, 'should have ' + values.length + ' occurrences of "' + key + '"') for (var i = 0; i < values.length; i++) { assert.strictEqual(headers[i], values[i]) } diff --git a/test/res.attachment.js b/test/res.attachment.js index 6283ded0d65..8644bab5b2d 100644 --- a/test/res.attachment.js +++ b/test/res.attachment.js @@ -1,6 +1,7 @@ 'use strict' -var Buffer = require('safe-buffer').Buffer +const { Buffer } = require('node:buffer'); + var express = require('../') , request = require('supertest'); diff --git a/test/res.clearCookie.js b/test/res.clearCookie.js index 3d8a6a5a81f..74a746eb7be 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.cookie.js b/test/res.cookie.js index c837820605c..180d1be3452 100644 --- a/test/res.cookie.js +++ b/test/res.cookie.js @@ -3,7 +3,6 @@ var express = require('../') , request = require('supertest') , cookieParser = require('cookie-parser') -var merge = require('utils-merge'); describe('res', function(){ describe('.cookie(name, object)', function(){ @@ -130,7 +129,7 @@ describe('res', function(){ var app = express(); var options = { maxAge: 1000 }; - var optionsCopy = merge({}, options); + var optionsCopy = { ...options }; app.use(function(req, res){ res.cookie('name', 'tobi', options) diff --git a/test/res.download.js b/test/res.download.js index b52e66803c6..e9966007eba 100644 --- a/test/res.download.js +++ b/test/res.download.js @@ -1,20 +1,17 @@ 'use strict' var after = require('after'); -var assert = require('assert') -var asyncHooks = tryRequire('async_hooks') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage +const { Buffer } = require('node:buffer'); + var express = require('..'); -var path = require('path') +var path = require('node:path') var request = require('supertest'); var utils = require('./support/utils') var FIXTURES_PATH = path.join(__dirname, 'fixtures') -var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' - ? describe - : describe.skip - describe('res', function(){ describe('.download(path)', function(){ it('should transfer as an attachment', function(done){ @@ -26,7 +23,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 +66,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,19 +83,19 @@ 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); }) - describeAsyncHooks('async local storage', function () { - it('should presist store', function (done) { + describe('async local storage', function () { + it('should persist 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 = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -115,17 +112,17 @@ 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) }) - it('should presist store on error', function (done) { + it('should persist 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 = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -369,7 +366,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 +385,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) }) @@ -488,11 +485,3 @@ describe('res', function(){ }) }) }) - -function tryRequire (name) { - try { - return require(name) - } catch (e) { - return {} - } -} diff --git a/test/res.format.js b/test/res.format.js index cba6fe136b1..0d770d57651 100644 --- a/test/res.format.js +++ b/test/res.format.js @@ -3,7 +3,7 @@ var after = require('after') var express = require('../') , request = require('supertest') - , assert = require('assert'); + , assert = require('node:assert'); var app1 = express(); @@ -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) @@ -232,7 +237,7 @@ function test(app) { }) describe('when no match is made', function(){ - it('should should respond with 406 not acceptable', function(done){ + it('should respond with 406 not acceptable', function(done){ request(app) .get('/') .set('Accept', 'foo/bar') diff --git a/test/res.json.js b/test/res.json.js index dcaceae5ca4..ffd547e95b2 100644 --- a/test/res.json.js +++ b/test/res.json.js @@ -2,7 +2,7 @@ var express = require('../') , request = require('supertest') - , assert = require('assert'); + , assert = require('node:assert'); describe('res', function(){ describe('.json(object)', function(){ @@ -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 0735d43bd50..e043a3b07f2 100644 --- a/test/res.jsonp.js +++ b/test/res.jsonp.js @@ -2,7 +2,7 @@ var express = require('../') , request = require('supertest') - , assert = require('assert'); + , assert = require('node:assert'); var utils = require('./support/utils'); describe('res', function(){ @@ -327,61 +327,4 @@ 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(); - - app.get('/', function(req, res){ - res.type('application/vnd.example+json'); - res.jsonp({ hello: 'world' }); - }); - - request(app) - .get('/') - .expect('content-type', 'application/vnd.example+json; charset=utf-8') - .expect(200, '{"hello":"world"}', done) - }) }) diff --git a/test/res.links.js b/test/res.links.js index 240b7fcfda3..40665fd558a 100644 --- a/test/res.links.js +++ b/test/res.links.js @@ -43,5 +43,23 @@ describe('res', function(){ .expect('Link', '; rel="next", ; rel="last", ; rel="prev"') .expect(200, done); }) + + it('should set multiple links for single rel', function (done) { + var app = express(); + + app.use(function (req, res) { + res.links({ + next: 'http://api.example.com/users?page=2', + last: ['http://api.example.com/users?page=5', 'http://api.example.com/users?page=1'] + }); + + res.end(); + }); + + request(app) + .get('/') + .expect('Link', '; rel="next", ; rel="last", ; rel="last"') + .expect(200, done); + }) }) }) diff --git a/test/res.location.js b/test/res.location.js index 2e88002625f..a5ceed76279 100644 --- a/test/res.location.js +++ b/test/res.location.js @@ -2,8 +2,8 @@ var express = require('../') , request = require('supertest') - , assert = require('assert') - , url = require('url'); + , assert = require('node:assert') + , url = require('node:url'); describe('res', function(){ describe('.location(url)', function(){ @@ -46,64 +46,6 @@ 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() - }) - - request(app) - .get('/') - .expect('Location', '/') - .expect(200, done) - }) - }) - it('should encode data uri', function (done) { var app = express() app.use(function (req, res) { diff --git a/test/res.redirect.js b/test/res.redirect.js index f7214d93312..8d2b164e4a2 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(); @@ -106,7 +91,7 @@ describe('res', function(){ .set('Accept', 'text/html') .expect('Content-Type', /html/) .expect('Location', 'http://google.com') - .expect(302, '

Found. Redirecting to http://google.com

', done) + .expect(302, 'Found

Found. Redirecting to http://google.com

', done) }) it('should escape the url', function(done){ @@ -122,7 +107,7 @@ describe('res', function(){ .set('Accept', 'text/html') .expect('Content-Type', /html/) .expect('Location', '%3Cla\'me%3E') - .expect(302, '

Found. Redirecting to %3Cla'me%3E

', done) + .expect(302, 'Found

Found. Redirecting to %3Cla'me%3E

', done) }) it('should not render evil javascript links in anchor href (prevent XSS)', function(done){ @@ -140,7 +125,7 @@ describe('res', function(){ .set('Accept', 'text/html') .expect('Content-Type', /html/) .expect('Location', encodedXss) - .expect(302, '

Found. Redirecting to ' + encodedXss +'

', done); + .expect(302, 'Found

Found. Redirecting to ' + encodedXss +'

', done); }); it('should include the redirect type', function(done){ @@ -155,7 +140,7 @@ describe('res', function(){ .set('Accept', 'text/html') .expect('Content-Type', /html/) .expect('Location', 'http://google.com') - .expect(301, '

Moved Permanently. Redirecting to http://google.com

', done); + .expect(301, 'Moved Permanently

Moved Permanently. Redirecting to http://google.com

', done); }) }) diff --git a/test/res.render.js b/test/res.render.js index 50f0b0a7425..114b398e0b4 100644 --- a/test/res.render.js +++ b/test/res.render.js @@ -1,7 +1,7 @@ 'use strict' var express = require('..'); -var path = require('path') +var path = require('node:path') var request = require('supertest'); var tmpl = require('./support/tmpl'); diff --git a/test/res.send.js b/test/res.send.js index b4cf68a7df6..8547b77648b 100644 --- a/test/res.send.js +++ b/test/res.send.js @@ -1,9 +1,9 @@ 'use strict' -var assert = require('assert') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +const { Buffer } = require('node:buffer'); var express = require('..'); -var methods = require('methods'); +var methods = require('../lib/utils').methods; var request = require('supertest'); var utils = require('./support/utils'); @@ -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) }) }) @@ -223,6 +178,19 @@ describe('res', function(){ .expect(200, 'hey', done); }) + it('should accept Uint8Array', function(done){ + var app = express(); + app.use(function(req, res){ + const encodedHey = new TextEncoder().encode("hey"); + res.set("Content-Type", "text/plain").send(encodedHey); + }) + + request(app) + .get("/") + .expect("Content-Type", "text/plain; charset=utf-8") + .expect(200, "hey", done); + }) + it('should not override ETag', function (done) { var app = express() @@ -463,7 +431,7 @@ describe('res', function(){ app.use(function (req, res) { res.set('etag', '"asdf"'); - res.send(200); + res.send('hello!'); }); app.enable('etag'); @@ -514,7 +482,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 4db0a3b6a4e..16eea79761e 100644 --- a/test/res.sendFile.js +++ b/test/res.sendFile.js @@ -1,20 +1,17 @@ 'use strict' var after = require('after'); -var asyncHooks = tryRequire('async_hooks') -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert') +var AsyncLocalStorage = require('node:async_hooks').AsyncLocalStorage +const { Buffer } = require('node:buffer'); + var express = require('../') , request = require('supertest') - , assert = require('assert'); var onFinished = require('on-finished'); -var path = require('path'); +var path = require('node:path'); var fixtures = path.join(__dirname, 'fixtures'); var utils = require('./support/utils'); -var describeAsyncHooks = typeof asyncHooks.AsyncLocalStorage === 'function' - ? describe - : describe.skip - describe('res', function(){ describe('.sendFile(path)', function () { it('should error missing path', function (done) { @@ -82,6 +79,19 @@ describe('res', function(){ }); }); + it('should disable the ETag function if requested', function (done) { + var app = createApp(path.resolve(fixtures, 'name.txt')).disable('etag'); + + request(app) + .get('/') + .expect(handleHeaders) + .expect(200, done); + + function handleHeaders (res) { + assert(res.headers.etag === undefined); + } + }); + it('should 404 for directory', function (done) { var app = createApp(path.resolve(fixtures, 'blog')); @@ -267,14 +277,14 @@ describe('res', function(){ .expect(200, 'got 404 error', done) }) - describeAsyncHooks('async local storage', function () { - it('should presist store', function (done) { + describe('async local storage', function () { + it('should persist 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 = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -291,16 +301,16 @@ 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) }) - it('should presist store on error', function (done) { + it('should persist 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 = new AsyncLocalStorage() req.asyncLocalStorage.run(store, next) }) @@ -890,507 +900,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) { @@ -1402,11 +911,3 @@ function createApp(path, options, fn) { return app; } - -function tryRequire (name) { - try { - return require(name) - } catch (e) { - return {} - } -} diff --git a/test/res.sendStatus.js b/test/res.sendStatus.js index 9b1de8385cd..b244cf9d173 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 1fe08344eaa..59c8a57e702 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 980717a6e30..e438956313a 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) }) @@ -42,5 +42,74 @@ describe('res', function(){ .get('/') .expect('Content-Type', 'application/vnd.amazon.ebook', done); }) + + describe('edge cases', function(){ + it('should handle empty string gracefully', function(done){ + var app = express(); + + app.use(function(req, res){ + res.type('').end('test'); + }); + + request(app) + .get('/') + .expect('Content-Type', 'application/octet-stream') + .end(done); + }) + + it('should handle file extension with dots', function(done){ + var app = express(); + + app.use(function(req, res){ + res.type('.json').end('{"test": true}'); + }); + + request(app) + .get('/') + .expect('Content-Type', 'application/json; charset=utf-8') + .end(done); + }) + + it('should handle multiple file extensions', function(done){ + var app = express(); + + app.use(function(req, res){ + res.type('file.tar.gz').end('compressed'); + }); + + request(app) + .get('/') + .expect('Content-Type', 'application/gzip') + .end(done); + }) + + it('should handle uppercase extensions', function(done){ + var app = express(); + + app.use(function(req, res){ + res.type('FILE.JSON').end('{"test": true}'); + }); + + request(app) + .get('/') + .expect('Content-Type', 'application/json; charset=utf-8') + .end(done); + }) + + it('should handle extension with special characters', function(done){ + var app = express(); + + app.use(function(req, res){ + res.type('file@test.json').end('{"test": true}'); + }); + + request(app) + .get('/') + .expect('Content-Type', 'application/json; charset=utf-8') + .end(done); + }) + }) }) }) + + diff --git a/test/res.vary.js b/test/res.vary.js index 1efc20b4453..ff3c9716529 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/support/tmpl.js b/test/support/tmpl.js index bab65669d33..e24b6fe773b 100644 --- a/test/support/tmpl.js +++ b/test/support/tmpl.js @@ -1,4 +1,4 @@ -var fs = require('fs'); +var fs = require('node:fs'); var variableRegExp = /\$([0-9a-zA-Z\.]+)/g; diff --git a/test/support/utils.js b/test/support/utils.js index 5ad4ca98410..fb816b08000 100644 --- a/test/support/utils.js +++ b/test/support/utils.js @@ -4,8 +4,8 @@ * @private */ -var assert = require('assert'); -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert'); +const { Buffer } = require('node:buffer'); /** * Module exports. @@ -77,10 +77,10 @@ function getMajorVersion(versionString) { } function shouldSkipQuery(versionString) { - // Skipping HTTP QUERY tests on Node 21, it is reported in http.METHODS on 21.7.2 but not supported - // update this implementation to run on supported versions of 21 once they exist + // Skipping HTTP QUERY tests below Node 22, QUERY wasn't fully supported by Node until 22 + // we could update this implementation to run on supported versions of 21 once they exist // upstream tracking https://github.com/nodejs/node/issues/51562 // express tracking issue: https://github.com/expressjs/express/issues/5615 - return Number(getMajorVersion(versionString)) === 21 + return Number(getMajorVersion(versionString)) < 22 } diff --git a/test/utils.js b/test/utils.js index 9a38ede6569..d1174d014cf 100644 --- a/test/utils.js +++ b/test/utils.js @@ -1,7 +1,7 @@ 'use strict' -var assert = require('assert'); -var Buffer = require('safe-buffer').Buffer +var assert = require('node:assert'); +const { Buffer } = require('node:buffer'); var utils = require('../lib/utils'); describe('utils.etag(body, encoding)', function(){ @@ -26,6 +26,25 @@ describe('utils.etag(body, encoding)', function(){ }) }) +describe('utils.normalizeType acceptParams method', () => { + it('should handle a type with a malformed parameter and break the loop in acceptParams', () => { + const result = utils.normalizeType('text/plain;invalid'); + assert.deepEqual(result,{ + value: 'text/plain', + quality: 1, + params: {} // No parameters are added since "invalid" has no "=" + }); + }); + + it('should default to application/octet-stream when mime lookup fails', () => { + const result = utils.normalizeType('unknown-extension-xyz'); + assert.deepEqual(result, { + value: 'application/octet-stream', + params: {} + }); + }); +}); + describe('utils.setCharset(type, charset)', function () { it('should do anything without type', function () { assert.strictEqual(utils.setCharset(), undefined); @@ -70,34 +89,27 @@ describe('utils.wetag(body, encoding)', function(){ }) }) -describe('utils.isAbsolute()', function(){ - it('should support windows', function(){ - assert(utils.isAbsolute('c:\\')); - assert(utils.isAbsolute('c:/')); - assert(!utils.isAbsolute(':\\')); - }) +describe('utils.compileETag()', function () { + it('should return generateETag for true', function () { + const fn = utils.compileETag(true); + assert.strictEqual(fn('express!'), utils.wetag('express!')); + }); - it('should support windows unc', function(){ - assert(utils.isAbsolute('\\\\foo\\bar')) - }) + it('should return undefined for false', function () { + assert.strictEqual(utils.compileETag(false), undefined); + }); - it('should support unices', function(){ - assert(utils.isAbsolute('/foo/bar')); - assert(!utils.isAbsolute('foo/bar')); - }) -}) + it('should return generateETag for string values "strong" and "weak"', function () { + assert.strictEqual(utils.compileETag('strong')("express"), utils.etag("express")); + assert.strictEqual(utils.compileETag('weak')("express"), utils.wetag("express")); + }); -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' })) - }) -}) + it('should throw for unknown string values', function () { + assert.throws(() => utils.compileETag('foo'), TypeError); + }); + + it('should throw for unsupported types like arrays and objects', function () { + assert.throws(() => utils.compileETag([]), TypeError); + assert.throws(() => utils.compileETag({}), TypeError); + }); +});