fix: collapse identical repeated content-length in legacy dispatcher bridge#5502
fix: collapse identical repeated content-length in legacy dispatcher bridge#5502mcollina wants to merge 3 commits into
Conversation
…bridge Node's bundled fetch (undici v6/v7) appends its computed content-length even when the request already carries one, producing an identical comma-repeated value (e.g. "58, 58"). The v1 core tolerated this via parseInt, but the current core rejects it since #5060, so a bare require('undici') on Node 22/24 silently broke bundled-fetch requests that set their own Content-Length. Collapse identical repeated values (permitted by RFC 9110 section 8.6) in Dispatcher1Wrapper before dispatching; genuinely conflicting values are still rejected. Fixes #5500
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #5502 +/- ##
==========================================
+ Coverage 93.44% 93.46% +0.02%
==========================================
Files 110 110
Lines 37328 37383 +55
==========================================
+ Hits 34881 34940 +59
+ Misses 2447 2443 -4 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
jeswr
left a comment
There was a problem hiding this comment.
Verified independently against the original report's environment (Node 22.23.1, bundled undici
6.27.0, undici 8.6.0 on Linux x64):
- the #5500 repro (both require orders, request-set
Content-Lengthvia the bundled global fetch)
fails onmainand passes on this branch; node --test test/dispatcher1-wrapper-content-length.js: 4/4 pass;- confirmed off the live dispatch that the bundled v6 fetch produces the identical comma-repeated
value this collapses (captured"14, 14"), and that conflicting values still reject with
UND_ERR_INVALID_ARG, preserving the #5060 hardening.
The RFC 9110 §8.6 identical-repeat scoping and the clone-not-mutate handling both look right, and
restricting the collapse to the legacy bridge keeps v8-native paths untouched. LGTM — this fixes
the in-the-wild breakage (fetch-sparql-endpoint ≤7.1.0 / Comunica / CSS SPARQL backends) exactly
at the divergence point.
One optional note, not blocking: object-form headers with an array value
({'content-length': ['13','13']}) aren't collapsed, but no known legacy consumer emits that
shape and the core rejects it either way, so I think leaving it is correct.
Summary
A bare
require('undici')(v8) on Node 22/24 silently breaks Node's bundledfetchfor any request that sets its ownContent-Lengthheader, failing withTypeError: fetch failed→InvalidArgumentError: invalid content-length header.Fixes #5500
Root cause
lib/global.jseagerly claims the legacySymbol.for('undici.globalDispatcher.1')at import time, so the bundled fetch (undici v6 on Node 22, v7 on Node 24) starts dispatching throughDispatcher1Wrapperinto the v8 core."58, 58").This is the same class of bridge-semantics divergence as #4990 (
allowH2) and #5345 (rawHeaders), this time on request-header validation. Note the issue reports Node 22, but Node 24 (bundled undici 7.x) is affected as well; Node 26 (bundled v8) is not.Fix
Dispatcher1Wrapper.dispatch()now collapses an identical comma-repeatedcontent-lengthvalue to the single value before forwarding, for both plain-object and flat-array header shapes. RFC 9110 §8.6 explicitly permits treating identical repeated field values as one, so this does not reopen the request-smuggling hardening from #5060 — genuinely conflicting values (e.g."10, 13") are left untouched and still rejected by the core.opts.headersis cloned rather than mutated, and the fast path (no collapse needed) allocates nothing.Tests
test/node-test/global-dispatcher-version.js: end-to-end regression —require('./index.js'), then a POST through the bundled global fetch with a request-setContent-Length. Fails without the fix on Node 22/24, passes with it.test/dispatcher1-wrapper-content-length.js(new): direct wrapper tests — collapse for object and flat-array headers, conflicting values still rejected withUND_ERR_INVALID_ARG, caller headers not mutated.Verified the issue's own repro script passes against this branch on Node 22.22.1, 24.14.1, and 26.3.0. Full
test:unitsuite: 1417 pass, 0 fail.