Repository navigation
fix(res.send): add Content-Length header only if Transfer-Encoding is not present - #4893
Conversation
dougwilson
left a comment
There was a problem hiding this comment.
Please add a test that fails without this change and passes with it so the fix does not regress.
|
Hey @dougwilson , |
|
Hey, sorry I was just trying to get the new release out. It looks like you still need to add a test for the change to res.redirect, as that was changed too. I also took a look in the Node.js source and they also have logic for content-length vs transfer-encoding, but node.js only does stuff if transfer-encoding contains chunked while here is it any value (even a single space character). Is that intentional? Should we follow what the node.js logic is? It may help if the underlying issue you ran in to for this change was disclosed. |
|
Hey @dougwilson, About the issue we are running in. |
|
Ah, ok. Can you add tests for other values of transfer-encoding besides chunked as well, then? We want to make sure that res.send will work with other values too. As for mocking exact replicas of other apis, you typically want to use the lower-level apis for that like res.write and res.end instead of res.send. |
|
Hey @dougwilson, Regards, |
|
Hey! It's no problem at all :) Thanks for adding to the tests. But I think there is an issue with them: though you added other values, it does not seem like res.send is actually honoring them. For example, if you have Transfer-Encoding: gzip set, can the test show that res.send will actually send it as gzip? Example I would expect |
|
Hey @dougwilson! Can we actually test that our response transforms to gzip? I think its very low level and I'm not sure that nodejs can transform our response to gzip/compress/deflate. Because I did not find any mentions about Of course tools like I have a suggestion to check our response for having a Please, correct me guys if I’m wrong. Thank you! |
eb10dba to
340be0f
Compare
|
You can check the spec for transfer-encoding at https://datatracker.ietf.org/doc/html/rfc9112#name-transfer-encoding The very first example shows how it can invlue gzip. |
|
@dougwilson Do you expect that response has corresponding |
bjohansebas
left a comment
There was a problem hiding this comment.
LGTM. Some considerations to keep in mind are that Transfer-Encoding might indicate that the response is gzip or some other encoding, while the data being sent is actually not transformed to that encoding. I’d say that responsibility lies with the developer to ensure the data is sent in the correct format—there’s no real way for Express to validate that, and I don’t think we should limit ourselves to only checking whether transfer-encoding has the value chunked. Regardless of the value, those two headers must not be present together.
There was a problem hiding this comment.
Im on the fence here. The response is malformed bc of user action.
Using res.send was the wrong tool for the reporter’s use case. But since res.send already tries to set appropriate headers and respects other user set headers, checking for this edge case makes sense for consistency.
Compliant clients/intermediaries favor Transfer-Encoding over Content-Length, so removing the conflict only reduces risk.
Im trying to think through if taking a change here increases or reduces possible malformed responses folks can send, hence being on the fence.
Edit: I can only see how it reduces the number of malformed responses possible. As any compliant client should already be ignoring content length when transfer-encoding is present. So removing content-length when transfer-encoding is present should be a noop for compliant clients.
…js#4893 master pinned chunked, compress, deflate and gzip plus the empty body in expressjs#4893, this 4.x backport only pinned chunked, so this adds the missing cases to keep the coverage the same as master
This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [express](https://expressjs.com/) ([source](https://github.com/expressjs/express)) | [`5.2.1` → `5.3.0`](https://renovatebot.com/diffs/npm/express/5.2.1/5.3.0) |  |  | --- >⚠️ **Warning** > > Some dependencies could not be looked up. Check the [Dependency Dashboard](issues/99) for more information. --- ### Release Notes <details> <summary>expressjs/express (express)</summary> ### [`v5.3.0`](https://github.com/expressjs/express/blob/HEAD/History.md#530) [Compare Source](expressjs/express@v5.2.1...v5.3.0) \===== #### 🐞 Bug fixes - Fixed HTTP header conflict between Content-Length and Transfer-Encoding in res.send - by [@​YuryShkoda](https://github.com/YuryShkoda) in [#​4893](expressjs/express#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. ETag generation is unaffected by the presence of a `Transfer-Encoding` header - by [@​cuishuang](https://github.com/cuishuang) in [#​7459](expressjs/express#7459) * Upgrade `qs` to `^6.16.0`, which fixes [CVE-2026-2391](https://www.cve.org/CVERecord?id=CVE-2026-2391) ([GHSA-w7fw-mjwx-w883](GHSA-w7fw-mjwx-w883)), [CVE-2026-82417](https://www.cve.org/CVERecord?id=CVE-2026-82417) ([GHSA-4mjr-xmp4-gh2g](GHSA-4mjr-xmp4-gh2g)) and [CVE-2026-82562](https://www.cve.org/CVERecord?id=CVE-2026-82562) ([GHSA-x5fp-wj9c-mxmx](GHSA-x5fp-wj9c-mxmx)): `arrayLimit` bypasses in comma parsing and a denial of service via an attacker-controlled `isBuffer` - by [@​davetashner](https://github.com/davetashner) in [#​7057](expressjs/express#7057) and [#​7478](expressjs/express#7478), and [@​cyphercodes](https://github.com/cyphercodes) in [#​7305](expressjs/express#7305) * Upgrade `proxy-addr` to `^2.0.8`, which fixes [CVE-2026-90711](https://www.cve.org/CVERecord?id=CVE-2026-90711) - by [@​lazerg](https://github.com/lazerg) in [#​7474](expressjs/express#7474) #### 🚀 Improvements - Allow conditional revalidation for QUERY requests. `req.fresh` previously only validated freshness for GET and HEAD requests, so QUERY responses never returned 304 despite a matching validator. Since QUERY is a safe, idempotent, and cacheable method that supports conditional requests, it is now included in the freshness check - by [@​Cherry](https://github.com/Cherry) in [#​7366](expressjs/express#7366) ```js // QUERY /reports with If-None-Match: "12345" app.query('/reports', (req, res) => { res.set('ETag', '"12345"'); res.send(results); // now responds 304 Not Modified }); ``` - Improve HTML structure in `res.redirect()` responses when HTML format is accepted by adding `<!DOCTYPE html>`, `<title>`, and `<body>` tags for better browser compatibility - by [@​Bernice55231](https://github.com/Bernice55231) in [#​5167](expressjs/express#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](expressjs/express#6903) ```js app.render('index', null, callback); // now works as expected ``` - Upgrade `content-type` to `^2.0.0`, bringing a faster parser (\~1.5x quicker `Content-Type` parsing/formatting in `res.send()`) along with a behavior change: `res.send()` now keeps any existing parameters when adding the charset and no longer throws on a `Content-Type` that fails to parse. `type-is` is upgraded to `^2.1.0` as part of the same change - by [@​blakeembrey](https://github.com/blakeembrey) in [#​7234](expressjs/express#7234) ```js res.set('Content-Type', 'text/plain; foo=bar').send('hey'); // -> Content-Type: text/plain; foo=bar; charset=utf-8 ``` - The default error handler now logs the full error object instead of only its stack trace, so nested details such as `Error.cause` and library-specific properties (e.g. Sequelize's `parent`/`original`) are no longer swallowed - by [@​Nitin-Mohapatra](https://github.com/Nitin-Mohapatra) in [#​6464](expressjs/express#6464) - Upgrade `content-disposition` to `^2.0.1`, which changes the `Content-Disposition` header emitted by `res.download()`, `res.attachment()`, and `res.sendFile()`: file names that are valid HTTP tokens are no longer wrapped in quotes. This is equivalent per RFC 6266, but applications asserting on the exact header bytes should update their expectations - by [@​blakeembrey](https://github.com/blakeembrey) in [#​7233](expressjs/express#7233) ```js res.attachment('user.html'); // before -> Content-Disposition: attachment; filename="user.html" // after -> Content-Disposition: attachment; filename=user.html ``` - Upgrade `body-parser` to `^2.3.0`, which fixes [CVE-2026-12590](https://www.cve.org/CVERecord?id=CVE-2026-12590) ([GHSA-v422-hmwv-36x6](GHSA-v422-hmwv-36x6)): an invalid `limit` option value caused request body size enforcement to be silently disabled (fail-open), allowing a denial of service via arbitrarily large payloads. Invalid `limit` values now throw at parser initialization instead of being ignored - by [@​Mayvis](https://github.com/Mayvis) in [#​7390](expressjs/express#7390) #### ⚡ 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](expressjs/express#6991) </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - "before 9am on tuesday" - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate CLI](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0NC4xMzMuMCIsInVwZGF0ZWRJblZlciI6IjQ0LjE2NC41IiwidGFyZ2V0QnJhbmNoIjoiZGV2ZWxvcCIsImxhYmVscyI6WyJkZXBlbmRlbmNpZXMiLCJyZW5vdmF0ZSJdfQ==--> Reviewed-on: https://code.futo.org/harbor/harbor/pulls/1812
Intent
Content-LengthandTransfer-Encodingcan't be present in the response headers together,Content-Lengthshould be added only if there is noTransfer-Encodingheader.