diff --git a/.github/workflows/bench-ci.yml b/.github/workflows/bench-ci.yml new file mode 100644 index 00000000..c36ab2af --- /dev/null +++ b/.github/workflows/bench-ci.yml @@ -0,0 +1,26 @@ +# merget-harness live benchmark CI: the recipe the benchmark VM discovered for +# this corpus, run on every pull request, on main and on sema's batch branches. +# The corpus's own workflows are removed on the mirror (docs/SCENARIOS.md "bench"). +name: bench-ci +on: + push: + branches: + - "main" + - "sema/queue/**" + - "replay/**" + pull_request: {} +jobs: + test: + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - name: "bench:checkout" + uses: actions/checkout@v4 + - name: "bench:toolchain" + uses: actions/setup-node@v4 + with: + node-version: "22" + - name: "bench:setup" + run: if [ -f package-lock.json ] || [ -f npm-shrinkwrap.json ]; then npm ci; else npm install --no-audit --no-fund; fi + - name: "bench:test" + run: npm test diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index 7a9797e0..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,117 +0,0 @@ -name: ci - -on: - push: - branches: - - master - - develop - - '4.x' - - '5.x' - - '5.0' - paths-ignore: - - '*.md' - pull_request: - 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: - lint: - name: Lint - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - name: Setup Node.js - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.0 - with: - node-version: 'lts/*' - - - name: Install dependencies - run: npm install --ignore-scripts --include=dev - - - name: Run lint - run: npm run lint - - test: - strategy: - fail-fast: false - matrix: - os: [ubuntu-latest, windows-latest] - 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - - name: Install lcov - shell: bash - run: sudo apt-get -y install lcov - - - name: Collect coverage reports - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - 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 deleted file mode 100644 index b080ea25..00000000 --- a/.github/workflows/codeql.yml +++ /dev/null @@ -1,74 +0,0 @@ -# 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" - workflow_dispatch: - -permissions: - contents: read - -jobs: - analyze: - name: Analyze - runs-on: ubuntu-latest - permissions: - actions: read - contents: read - security-events: write - strategy: - fail-fast: false - matrix: - language: [javascript, actions] - - steps: - - name: Checkout repository - uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 - with: - 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. - - # 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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 diff --git a/.github/workflows/legacy.yml b/.github/workflows/legacy.yml deleted file mode 100644 index e31f55e3..00000000 --- a/.github/workflows/legacy.yml +++ /dev/null @@ -1,101 +0,0 @@ -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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - - name: Setup Node.js ${{ matrix.node-version }} - uses: actions/setup-node@395ad3262231945c25e8478fd5baf05154b1d79f # v6.1.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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - with: - persist-credentials: false - - - name: Install lcov - shell: bash - run: sudo apt-get -y install lcov - - - name: Collect coverage reports - uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0 - 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 deleted file mode 100644 index 28b34e01..00000000 --- a/.github/workflows/scorecard.yml +++ /dev/null @@ -1,72 +0,0 @@ -# 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@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1 - 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@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.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@5d4e8d1aca955e8d8589aabd499c5cae939e33c7 # v3.29.5 - with: - sarif_file: results.sarif diff --git a/History.md b/History.md index 689c08a9..d2b49551 100644 --- a/History.md +++ b/History.md @@ -1,5 +1,12 @@ # Unreleased Changes +## 🐞 Bug fixes + +- Fixed HTTP header conflict between Content-Length and Transfer-Encoding in res.send - by [@YuryShkoda](https://github.com/YuryShkoda) in [#4893](https://github.com/expressjs/express/pull/4893) + + + Fixed the behavior of `res.send()` to prevent conflicts between `Content-Length` and `Transfer-Encoding` HTTP headers in responses. The `Content-Length` header in `res.send()` is now only added when a `Transfer-Encoding` header is not present, complying with the HTTP specification that states both headers should not coexist in the same response + ## 🚀 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) diff --git a/lib/response.js b/lib/response.js index 731afb78..02c3dfb2 100644 --- a/lib/response.js +++ b/lib/response.js @@ -168,9 +168,10 @@ res.send = function send(body) { var etagFn = app.get('etag fn') var generateETag = !this.get('ETag') && typeof etagFn === 'function' - // populate Content-Length - var len - if (chunk !== undefined) { + // Because Content-Length and Transfer-Encoding can't be present in the response headers together, + // Content-Length should be added only if there is no Transfer-Encoding header + var len; + if (chunk !== undefined && !this.get('Transfer-Encoding')) { if (Buffer.isBuffer(chunk)) { // get length of Buffer len = chunk.length diff --git a/test/res.send.js b/test/res.send.js index 8547b776..213ab381 100644 --- a/test/res.send.js +++ b/test/res.send.js @@ -566,4 +566,29 @@ describe('res', function(){ }) }) }) + + describe('when Transfer-Encoding header is present', function(){ + var transferEncodings = [ + 'chunked', + 'compress', + 'deflate', + 'gzip' + ]; + + transferEncodings.forEach(function(encoding){ + it('should not add Content-Length header if Transfer-Encoding header is equal to ' + encoding, function(done){ + var app = express(); + + app.use(function(_, res){ + res.status(200).set('Transfer-Encoding', encoding).send(''); + }); + + request(app) + .get('/') + .expect(utils.shouldNotHaveHeader('Content-Length')) + .expect(utils.shouldHaveHeader('Transfer-Encoding')) + .expect(200, '', done); + }) + }); + }) })