diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 89d95f85d7f..150b67c3db0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,176 +1,99 @@ name: ci on: -- pull_request -- push + push: + branches: + - master + - develop + - '4.x' + - '5.x' + - '5.0' + paths-ignore: + - '*.md' + pull_request: + paths-ignore: + - '*.md' + +# Cancel in progress workflows +# in the scenario where we already had a run going for that PR/branch/tag but then triggered a new run +concurrency: + group: "${{ github.workflow }} ✨ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}" + cancel-in-progress: true jobs: - test: + lint: + name: Lint runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Setup Node.js {{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: 'lts/*' + persist-credentials: false + + - name: Install dependencies + run: npm install --ignore-scripts --only=dev + + - name: Run lint + run: npm run lint + + test: strategy: fail-fast: false matrix: - name: - - Node.js 4.0 - - Node.js 4.x - - Node.js 5.x - - Node.js 6.x - - Node.js 7.x - - Node.js 8.x - - Node.js 9.x - - Node.js 10.x - - Node.js 11.x - - Node.js 12.x - - Node.js 13.x - - Node.js 14.x - - Node.js 15.x - - Node.js 16.x - - Node.js 17.x - - Node.js 18.x - - Node.js 19.x - - Node.js 20.x - - Node.js 21.x - - include: - - name: Node.js 4.0 - node-version: "4.0" - npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - - - name: Node.js 4.x - node-version: "4.9" - npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - - - name: Node.js 5.x - node-version: "5.12" - npm-i: mocha@5.2.0 nyc@11.9.0 supertest@3.4.2 - - - name: Node.js 6.x - node-version: "6.17" - npm-i: mocha@6.2.2 nyc@14.1.1 supertest@3.4.2 - - - name: Node.js 7.x - node-version: "7.10" - npm-i: mocha@6.2.2 nyc@14.1.1 supertest@6.1.6 - - - name: Node.js 8.x - node-version: "8.17" - npm-i: mocha@7.2.0 nyc@14.1.1 - - - name: Node.js 9.x - node-version: "9.11" - npm-i: mocha@7.2.0 nyc@14.1.1 - - - name: Node.js 10.x - node-version: "10.24" - npm-i: mocha@8.4.0 - - - name: Node.js 11.x - node-version: "11.15" - npm-i: mocha@8.4.0 - - - name: Node.js 12.x - node-version: "12.22" - npm-i: mocha@9.2.2 - - - name: Node.js 13.x - node-version: "13.14" - npm-i: mocha@9.2.2 - - - name: Node.js 14.x - node-version: "14.20" - - - name: Node.js 15.x - node-version: "15.14" - - - name: Node.js 16.x - node-version: "16.20" - - - name: Node.js 17.x - node-version: "17.9" - - - name: Node.js 18.x - node-version: "18.19" - - - name: Node.js 19.x - node-version: "19.9" - - - name: Node.js 20.x - node-version: "20.11" - - - name: Node.js 21.x - node-version: "21.6" - - steps: - - uses: actions/checkout@v4 + os: [ubuntu-latest, windows-latest] + node-version: [18, 19, 20, 21, 22] + # Node.js release schedule: https://nodejs.org/en/about/releases/ - - name: Install Node.js ${{ 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 - if [[ "$(npm config get package-lock)" == "true" ]]; then - npm config set package-lock false - else - npm config set shrinkwrap false - fi - - - 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: Setup Node.js version-specific dependencies - shell: bash - run: | - # eslint for linting - # - remove on Node.js < 12 - if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 12 ]]; then - node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ - grep -E '^eslint(-|$)' | \ - sort -r | \ - xargs -n1 npm rm --silent --save-dev - fi - - - 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: Node.js ${{ matrix.node-version }} - - name: Run tests - shell: bash - run: | - npm run test-ci - cp coverage/lcov.info "coverage/${{ matrix.name }}.lcov" - - - name: Lint code - if: steps.list_env.outputs.eslint != '' - run: npm run lint - - - name: Collect code coverage - run: | - mv ./coverage "./${{ matrix.name }}" - mkdir ./coverage - mv "./${{ matrix.name }}" "./coverage/${{ matrix.name }}" - - - name: Upload code coverage - uses: actions/upload-artifact@v3 - with: - name: coverage - path: ./coverage - retention-days: 1 + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v4 + with: + persist-credentials: false + + - name: Setup Node.js ${{ matrix.node-version }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node-version }} + + - name: Npm version fixes + if: ${{matrix.npm-version != ''}} + run: npm install -g ${{ matrix.npm-version }} + + - name: Configure npm loglevel + run: | + npm config set loglevel error + shell: bash + + - name: Install dependencies + run: npm install + + - name: Output Node and NPM versions + run: | + echo "Node.js version: $(node -v)" + echo "NPM version: $(npm -v)" + + - name: Run tests + shell: bash + run: | + npm run test-ci + cp coverage/lcov.info "coverage/${{ matrix.node-version }}.lcov" + + - name: Collect code coverage + run: | + mv ./coverage "./${{ matrix.node-version }}" + mkdir ./coverage + mv "./${{ matrix.node-version }}" "./coverage/${{ matrix.node-version }}" + + - name: Upload code coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: ./coverage + retention-days: 1 coverage: needs: test diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 00000000000..db4e01aff56 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,66 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: ["master"] + pull_request: + # The branches below must be a subset of the branches above + branches: ["master"] + schedule: + - cron: "0 0 * * 1" + +permissions: + contents: read + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + permissions: + actions: read + contents: read + security-events: write + + steps: + - name: Checkout repository + uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + with: + languages: javascript + # 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. + + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + # - name: Autobuild + # uses: github/codeql-action/autobuild@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@3ab4101902695724f9365a384f86c1074d94e18c # v3.24.7 + with: + category: "/language:javascript" diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml new file mode 100644 index 00000000000..2d9d50440de --- /dev/null +++ b/.github/workflows/legacy.yml @@ -0,0 +1,142 @@ +name: legacy + +on: +- pull_request +- push + +jobs: + test: + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + name: + - Node.js 11.x + - Node.js 12.x + - Node.js 13.x + - Node.js 14.x + - Node.js 15.x + - Node.js 16.x + - Node.js 17.x + + include: + - name: Node.js 11.x + node-version: "11.15" + npm-i: mocha@8.4.0 + + - name: Node.js 12.x + node-version: "12.22" + npm-i: mocha@9.2.2 + + - name: Node.js 13.x + node-version: "13.14" + npm-i: mocha@9.2.2 + + - name: Node.js 14.x + node-version: "14.20" + + - name: Node.js 15.x + node-version: "15.14" + + - name: Node.js 16.x + node-version: "16.20" + + - name: Node.js 17.x + node-version: "17.9" + + steps: + - uses: actions/checkout@v4 + + - name: Install Node.js ${{ 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 + if [[ "$(npm config get package-lock)" == "true" ]]; then + npm config set package-lock false + else + npm config set shrinkwrap false + fi + + - 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: Setup Node.js version-specific dependencies + shell: bash + run: | + # eslint for linting + # - remove on Node.js < 12 + if [[ "$(cut -d. -f1 <<< "${{ matrix.node-version }}")" -lt 12 ]]; then + node -pe 'Object.keys(require("./package").devDependencies).join("\n")' | \ + grep -E '^eslint(-|$)' | \ + sort -r | \ + xargs -n1 npm rm --silent --save-dev + fi + + - 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-ci + cp coverage/lcov.info "coverage/${{ matrix.name }}.lcov" + + - name: Lint code + if: steps.list_env.outputs.eslint != '' + run: npm run lint + + - name: Collect code coverage + run: | + mv ./coverage "./${{ matrix.name }}" + mkdir ./coverage + mv "./${{ matrix.name }}" "./coverage/${{ matrix.name }}" + + - name: Upload code coverage + uses: actions/upload-artifact@v3 + with: + name: coverage + path: ./coverage + retention-days: 1 + + coverage: + needs: test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Install lcov + shell: bash + run: sudo apt-get -y install lcov + + - name: Collect coverage reports + uses: actions/download-artifact@v3 + with: + name: coverage + path: ./coverage + + - name: Merge coverage reports + shell: bash + run: find ./coverage -name lcov.info -exec printf '-a %q\n' {} \; | xargs lcov -o ./coverage/lcov.info + + - name: Upload coverage report + uses: coverallsapp/github-action@master + with: + github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.gitignore b/.gitignore index 3a673d9cc09..1bd5c02b28b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ # npm node_modules package-lock.json +npm-shrinkwrap.json *.log *.gz diff --git a/.npmrc b/.npmrc new file mode 100644 index 00000000000..43c97e719a5 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +package-lock=false diff --git a/Code-Of-Conduct.md b/Code-Of-Conduct.md index bbb8996a659..ca4c6b31468 100644 --- a/Code-Of-Conduct.md +++ b/Code-Of-Conduct.md @@ -127,7 +127,7 @@ project community. ## Attribution -This Code of Conduct is adapted from the [Contributor Covenant, version 2.0](cc-20-doc). +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). diff --git a/Contributing.md b/Contributing.md index a9ba84690cb..1654cee02f2 100644 --- a/Contributing.md +++ b/Contributing.md @@ -63,29 +63,14 @@ compromise among committers be the default resolution mechanism. Anyone can become a triager! Read more about the process of being a triager in [the triage process document](Triager-Guide.md). -[Open an issue in `expressjs/express` repo](https://github.com/expressjs/express/issues/new) -to request the triage role. State that you have read and agree to the -[Code of Conduct](Code-Of-Conduct.md) and details of the role. +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. -Here is an example issue content you can copy and paste: - -``` -Title: Request triager role for - -I have read and understood the project's Code of Conduct. -I also have read and understood the process and best practices around Express triaging. - -I request for a triager role for the following GitHub organizations: - -jshttp -pillarjs -express -``` - -Once you have opened your issue, a member of the TC will add you to the `triage` team in -the organizations requested. They will then close the issue. - -Happy triaging! +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 @@ -154,23 +139,71 @@ the project, their GitHub handle and npm username (if different). The PR will re 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. -### Current Project Captains +### Active Projects and Captains -- `expressjs/express`: @wesleytodd +- `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/expressjs.com`: @crandmck -- `expressjs/body-parser`: @wesleytodd -- `expressjs/multer`: @LinusU -- `expressjs/cookie-parser`: @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 -- `pillarjs/path-to-regexp`: @blakeembrey -- `pillarjs/router`: @dougwilson, @wesleytodd -- `pillarjs/finalhandler`: @wesleytodd -- `pillarjs/request`: @wesleytodd -- `jshttp/http-errors`: @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/forwarded`: @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 0f4515b55c9..63efe1234f3 100644 --- a/History.md +++ b/History.md @@ -191,6 +191,24 @@ This is the first Express 5.0 alpha release, based off 4.10.1. * add: - `app.router` is a reference to the base router +4.20.0 / 2024-09-10 +========== + * deps: serve-static@0.16.0 + * Remove link renderization in html while redirecting + * deps: send@0.19.0 + * Remove link renderization in html while redirecting + * deps: body-parser@0.6.0 + * add `depth` option to customize the depth level in the parser + * IMPORTANT: The default `depth` level for parsing URL-encoded data is now `32` (previously was `Infinity`) + * Remove link renderization in html while using `res.redirect` + * deps: path-to-regexp@0.1.10 + - Adds support for named matching groups in the routes using a regex + - Adds backtracking protection to parameters without regexes defined + * deps: encodeurl@~2.0.0 + - Removes encoding of `\`, `|`, and `^` to align better with URL spec + * Deprecate passing `options.maxAge` and `options.expires` to `res.clearCookie` + - Will be ignored in v5, clearCookie will set a cookie with an expires in the past to instruct clients to delete the cookie + 4.19.2 / 2024-03-25 ========== diff --git a/Readme.md b/Readme.md index d0f3cf56e6d..bc108d55fc0 100644 --- a/Readme.md +++ b/Readme.md @@ -1,10 +1,29 @@ [![Express Logo](https://i.cloudup.com/zfY6lL7eFa-3000x3000.png)](http://expressjs.com/) - Fast, unopinionated, minimalist web framework for [Node.js](http://nodejs.org). +**Fast, unopinionated, minimalist web framework for [Node.js](http://nodejs.org).** + +**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) + + +[![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] +[![OpenSSF Scorecard Badge][ossf-scorecard-badge]][ossf-scorecard-visualizer] - [![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] ```js const express = require('express') @@ -144,10 +163,82 @@ $ npm test The original author of Express is [TJ Holowaychuk](https://github.com/tj) -The current lead maintainer is [Douglas Christopher Wilson](https://github.com/dougwilson) - [List of all contributors](https://github.com/expressjs/express/graphs/contributors) +### TC (Technical Committee) + +* [UlisesGascon](https://github.com/UlisesGascon) - **Ulises Gascón** (he/him) +* [jonchurch](https://github.com/jonchurch) - **Jon Church** +* [wesleytodd](https://github.com/wesleytodd) - **Wes Todd** +* [LinusU](https://github.com/LinusU) - **Linus Unnebäck** +* [blakeembrey](https://github.com/blakeembrey) - **Blake Embrey** +* [sheplu](https://github.com/sheplu) - **Jean Burellier** +* [crandmck](https://github.com/crandmck) - **Rand McKinney** +* [ctcpip](https://github.com/ctcpip) - **Chris de Almeida** + +
+TC emeriti members + +#### TC emeriti members + + * [dougwilson](https://github.com/dougwilson) - **Douglas Wilson** + * [hacksparrow](https://github.com/hacksparrow) - **Hage Yaapa** + * [jonathanong](https://github.com/jonathanong) - **jongleberry** + * [niftylettuce](https://github.com/niftylettuce) - **niftylettuce** + * [troygoode](https://github.com/troygoode) - **Troy Goode** +
+ + +### Triagers + +* [aravindvnair99](https://github.com/aravindvnair99) - **Aravind Nair** +* [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** + +
+Triagers emeriti members + +#### Emeritus Triagers + + * [AuggieH](https://github.com/AuggieH) - **Auggie Hudak** + * [G-Rath](https://github.com/G-Rath) - **Gareth Jones** + * [MohammadXroid](https://github.com/MohammadXroid) - **Mohammad Ayashi** + * [NawafSwe](https://github.com/NawafSwe) - **Nawaf Alsharqi** + * [NotMoni](https://github.com/NotMoni) - **Moni** + * [VigneshMurugan](https://github.com/VigneshMurugan) - **Vignesh Murugan** + * [davidmashe](https://github.com/davidmashe) - **David Ashe** + * [digitaIfabric](https://github.com/digitaIfabric) - **David** + * [e-l-i-s-e](https://github.com/e-l-i-s-e) - **Elise Bonner** + * [fed135](https://github.com/fed135) - **Frederic Charette** + * [firmanJS](https://github.com/firmanJS) - **Firman Abdul Hakim** + * [getspooky](https://github.com/getspooky) - **Yasser Ameur** + * [ghinks](https://github.com/ghinks) - **Glenn** + * [ghousemohamed](https://github.com/ghousemohamed) - **Ghouse Mohamed** + * [gireeshpunathil](https://github.com/gireeshpunathil) - **Gireesh Punathil** + * [jake32321](https://github.com/jake32321) - **Jake Reed** + * [jonchurch](https://github.com/jonchurch) - **Jon Church** + * [lekanikotun](https://github.com/lekanikotun) - **Troy Goode** + * [marsonya](https://github.com/marsonya) - **Lekan Ikotun** + * [mastermatt](https://github.com/mastermatt) - **Matt R. Wilson** + * [maxakuru](https://github.com/maxakuru) - **Max Edell** + * [mlrawlings](https://github.com/mlrawlings) - **Michael Rawlings** + * [rodion-arr](https://github.com/rodion-arr) - **Rodion Abdurakhimov** + * [sheplu](https://github.com/sheplu) - **Jean Burellier** + * [tarunyadav1](https://github.com/tarunyadav1) - **Tarun yadav** + * [tunniclm](https://github.com/tunniclm) - **Mike Tunnicliffe** +
+ + ## License [MIT](LICENSE) @@ -164,3 +255,6 @@ The current lead maintainer is [Douglas Christopher Wilson](https://github.com/d [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 +[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 diff --git a/Release-Process.md b/Release-Process.md index 55e62189250..9ca0a15ab46 100644 --- a/Release-Process.md +++ b/Release-Process.md @@ -77,6 +77,13 @@ non-patch flow. - 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 diff --git a/Security.md b/Security.md index cdcd7a6e0aa..dcfbe88abd4 100644 --- a/Security.md +++ b/Security.md @@ -29,6 +29,12 @@ 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 @@ -40,6 +46,10 @@ involving the following steps: * 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 diff --git a/Triager-Guide.md b/Triager-Guide.md index a2909ef30db..c15e6be5313 100644 --- a/Triager-Guide.md +++ b/Triager-Guide.md @@ -9,11 +9,18 @@ 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. -* `question`: User questions that do not appear to be bugs or enhancements. -* `discuss`: Topics for discussion. Might end in an `enhancement` or `question` 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. diff --git a/appveyor.yml b/appveyor.yml deleted file mode 100644 index 185eccf8cab..00000000000 --- a/appveyor.yml +++ /dev/null @@ -1,106 +0,0 @@ -environment: - matrix: - - 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" -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 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/lib/response.js b/lib/response.js index 1f1b7e924a8..4035d4fb06f 100644 --- a/lib/response.js +++ b/lib/response.js @@ -48,13 +48,6 @@ var res = Object.create(http.ServerResponse.prototype) module.exports = res -/** - * Module variables. - * @private - */ - -var schemaAndHostRegExp = /^(?:[a-zA-Z][a-zA-Z0-9+.-]*:)?\/\/[^\\\/\?]+/; - /** * Set the HTTP status code for the response. * @@ -801,14 +794,7 @@ res.location = function location(url) { loc = String(url); } - var m = schemaAndHostRegExp.exec(loc); - var pos = m ? m[0].length + 1 : 0; - - // Only encode after host to avoid invalid encoding which can introduce - // vulnerabilities (e.g. `\\` to `%5C`). - loc = loc.slice(0, pos) + encodeUrl(loc.slice(pos)); - - return this.set('Location', loc); + return this.set('Location', encodeUrl(loc)); }; /** @@ -851,7 +837,7 @@ res.redirect = function redirect(url) { html: function(){ var u = escapeHtml(address); - body = '

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

' + body = '

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

' }, default: function(){ diff --git a/package.json b/package.json index d7600267ca4..0367f403373 100644 --- a/package.json +++ b/package.json @@ -36,7 +36,7 @@ "cookie-signature": "^1.2.1", "debug": "4.3.6", "depd": "2.0.0", - "encodeurl": "~1.0.2", + "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "finalhandler": "^2.0.0", @@ -92,8 +92,8 @@ "scripts": { "lint": "eslint .", "test": "mocha --require test/support/env --reporter spec --bail --check-leaks test/ test/acceptance/", - "test-ci": "nyc --reporter=lcovonly --reporter=text npm test", - "test-cov": "nyc --reporter=html --reporter=text npm test", + "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/app.router.js b/test/app.router.js index 7961aa348dc..11742d98a74 100644 --- a/test/app.router.js +++ b/test/app.router.js @@ -7,6 +7,7 @@ var express = require('../') , methods = require('methods'); var describePromises = global.Promise ? describe : describe.skip +var shouldSkipQuery = require('./support/utils').shouldSkipQuery describe('app.router', function(){ it('should restore req.params after leaving router', function(done){ @@ -41,6 +42,9 @@ describe('app.router', function(){ if (method === 'connect') return; 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){ @@ -190,6 +194,23 @@ describe('app.router', function(){ .expect('editing user 10', done); }) + if (supportsRegexp('(?.*)')) { + 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){ + 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); + }) + } + it('should ensure regexp matches path prefix', function (done) { var app = express() var p = [] @@ -1136,3 +1157,12 @@ describe('app.router', function(){ assert.strictEqual(app.get('/', function () {}), app) }) }) + +function supportsRegexp(source) { + try { + new RegExp(source) + return true + } catch (e) { + return false + } +} diff --git a/test/express.static.js b/test/express.static.js index 57a23accc13..e5100e8c8d2 100644 --- a/test/express.static.js +++ b/test/express.static.js @@ -486,7 +486,7 @@ describe('express.static()', function () { request(this.app) .get('/users') .expect('Location', '/users/') - .expect(301, //, done) + .expect(301, /\/users\//, done) }) it('should redirect directories with query string', function (done) { @@ -508,7 +508,7 @@ describe('express.static()', function () { .get('/snow') .expect('Location', '/snow%20%E2%98%83/') .expect('Content-Type', /html/) - .expect(301, />Redirecting to \/snow%20%E2%98%83\/<\/a>Redirecting to \/snow%20%E2%98%83\/ { }').end(); @@ -116,7 +116,7 @@ describe('res', function(){ .expect(200, done) }) - it('should encode data uri', function (done) { + it('should encode data uri2', function (done) { var app = express() app.use(function (req, res) { res.location('data:text/javascript,export default () => { }').end(); @@ -305,23 +305,12 @@ describe('res', function(){ ); }); - it('should percent encode backslashes in the path', function (done) { + it('should keep backslashes in the path', function (done) { var app = createRedirectServerForDomain('google.com'); testRequestedRedirect( app, 'https://google.com/foo\\bar\\baz', - 'https://google.com/foo%5Cbar%5Cbaz', - 'google.com', - done - ); - }); - - it('should encode backslashes in the path after the first backslash that triggered path parsing', function (done) { - var app = createRedirectServerForDomain('google.com'); - testRequestedRedirect( - app, - 'https://google.com\\@app\\l\\e.com', - 'https://google.com\\@app%5Cl%5Ce.com', + 'https://google.com/foo\\bar\\baz', 'google.com', done ); @@ -376,7 +365,7 @@ describe('res', function(){ testRequestedRedirect( app, 'file:///etc\\passwd', - 'file:///etc%5Cpasswd', + 'file:///etc\\passwd', '', done ); diff --git a/test/res.redirect.js b/test/res.redirect.js index d57f57f2f22..264e0f2b8f3 100644 --- a/test/res.redirect.js +++ b/test/res.redirect.js @@ -91,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. Redirecting to http://google.com

', done) }) it('should escape the url', function(done){ @@ -107,9 +107,27 @@ 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. Redirecting to %3Cla'me%3E

', done) }) + it('should not render evil javascript links in anchor href (prevent XSS)', function(done){ + var app = express(); + var xss = 'javascript:eval(document.body.innerHTML=`

XSS

`);'; + var encodedXss = 'javascript:eval(document.body.innerHTML=%60%3Cp%3EXSS%3C/p%3E%60);'; + + app.use(function(req, res){ + res.redirect(xss); + }); + + request(app) + .get('/') + .set('Host', 'http://example.com') + .set('Accept', 'text/html') + .expect('Content-Type', /html/) + .expect('Location', encodedXss) + .expect(302, '

Found. Redirecting to ' + encodedXss +'

', done); + }); + it('should include the redirect type', function(done){ var app = express(); @@ -122,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. Redirecting to http://google.com

', done); }) }) diff --git a/test/res.send.js b/test/res.send.js index 2cdbe41dbf0..bce62c8d406 100644 --- a/test/res.send.js +++ b/test/res.send.js @@ -7,6 +7,8 @@ var methods = require('methods'); var request = require('supertest'); var utils = require('./support/utils'); +var shouldSkipQuery = require('./support/utils').shouldSkipQuery + describe('res', function(){ describe('.send()', function(){ it('should set body to ""', function(done){ @@ -364,6 +366,9 @@ describe('res', function(){ if (method === 'connect') return; it('should send ETag in response to ' + method.toUpperCase() + ' request', function (done) { + if (method === 'query' && shouldSkipQuery(process.versions.node)) { + this.skip() + } var app = express(); app[method]('/', function (req, res) { diff --git a/test/support/utils.js b/test/support/utils.js index 5b4062e0b2a..5ad4ca98410 100644 --- a/test/support/utils.js +++ b/test/support/utils.js @@ -16,6 +16,7 @@ exports.shouldHaveBody = shouldHaveBody exports.shouldHaveHeader = shouldHaveHeader exports.shouldNotHaveBody = shouldNotHaveBody exports.shouldNotHaveHeader = shouldNotHaveHeader; +exports.shouldSkipQuery = shouldSkipQuery /** * Assert that a supertest response has a specific body. @@ -70,3 +71,16 @@ function shouldNotHaveHeader(header) { assert.ok(!(header.toLowerCase() in res.headers), 'should not have header ' + header); }; } + +function getMajorVersion(versionString) { + return versionString.split('.')[0]; +} + +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 + // upstream tracking https://github.com/nodejs/node/issues/51562 + // express tracking issue: https://github.com/expressjs/express/issues/5615 + return Number(getMajorVersion(versionString)) === 21 +} +