Bug Description
In both lib/dispatcher/client-h1.js and lib/dispatcher/client-h2.js, the writeBlob function contains an assert and a RequestContentLengthMismatchError guard that are both dead code — neither can ever trigger under any reachable code path.
|
assert(contentLength === body.size, 'blob body must have content length') |
|
|
|
try { |
|
if (contentLength != null && contentLength !== body.size) { |
|
throw new RequestContentLengthMismatchError() |
|
} |
The contentLength parameter passed to writeBlob is computed in writeH1/writeH2 as follows:
|
const bodyLength = util.bodyLength(body) |
|
|
|
contentLength = bodyLength ?? contentLength |
Since util.bodyLength returns body.size for blob-like bodies (never null), the local contentLength variable is always overwritten to body.size before writeBlob is called. This means:
- The
assert(contentLength === body.size) is always true — it can never fire.
- The
if (contentLength != null && contentLength !== body.size) is always false — RequestContentLengthMismatchError can never be thrown from this path.
Meanwhile, if a user provides a mismatched content-length header, it is caught earlier in writeH1/writeH2 by the shouldSendContentLength check (which compares request.contentLength against the computed contentLength), so the mismatch never propagates to writeBlob.
The result is that blob bodies have no graceful RequestContentLengthMismatchError path inside writeBlob — the error handling code exists but is unreachable.
Reproducible By
This script demonstrates that the mismatch is caught before writeBlob, not inside it:
import { Client } from "undici";
import { once } from "node:events";
import { createServer } from "node:http";
const server = createServer((req, res) => {
req.resume();
req.on("end", () => res.end("ok"));
});
server.listen(0);
await once(server, "listening");
const client = new Client(`http://localhost:${server.address().port}`);
const blob = new Blob(["hello"], { type: "text/plain" }); // size = 5
try {
await client.request({
path: "/",
method: "POST",
headers: {
"content-length": 999, // intentionally wrong
},
body: blob,
});
} catch (err) {
// Error is thrown from writeH1's early check, NOT from writeBlob
console.log("Caught error:", err.constructor.name, err.message);
} finally {
await client.close();
server.close();
}
Expected Behavior
The dead code in writeBlob should be cleaned up. Either:
- Remove both the assert and the
if guard — they are redundant since the mismatch is already handled upstream in writeH1/writeH2.
- Remove the assert and keep the
if guard as a defensive safety net, in case future refactors change how contentLength is computed before reaching writeBlob.
Logs & Screenshots
Caught error: RequestContentLengthMismatchError Request body length does not match content-length header
Environment
macOS 26.4.1
Node v24.15.0
undici v8.1.0
Bug Description
In both
lib/dispatcher/client-h1.jsandlib/dispatcher/client-h2.js, thewriteBlobfunction contains anassertand aRequestContentLengthMismatchErrorguard that are both dead code — neither can ever trigger under any reachable code path.undici/lib/dispatcher/client-h1.js
Lines 1385 to 1390 in bf684f7
The
contentLengthparameter passed towriteBlobis computed inwriteH1/writeH2as follows:undici/lib/dispatcher/client-h1.js
Lines 1053 to 1055 in bf684f7
Since
util.bodyLengthreturnsbody.sizefor blob-like bodies (nevernull), the localcontentLengthvariable is always overwritten tobody.sizebeforewriteBlobis called. This means:assert(contentLength === body.size)is always true — it can never fire.if (contentLength != null && contentLength !== body.size)is always false —RequestContentLengthMismatchErrorcan never be thrown from this path.Meanwhile, if a user provides a mismatched
content-lengthheader, it is caught earlier inwriteH1/writeH2by theshouldSendContentLengthcheck (which comparesrequest.contentLengthagainst the computedcontentLength), so the mismatch never propagates towriteBlob.The result is that blob bodies have no graceful
RequestContentLengthMismatchErrorpath insidewriteBlob— the error handling code exists but is unreachable.Reproducible By
This script demonstrates that the mismatch is caught before
writeBlob, not inside it:Expected Behavior
The dead code in
writeBlobshould be cleaned up. Either:ifguard — they are redundant since the mismatch is already handled upstream inwriteH1/writeH2.ifguard as a defensive safety net, in case future refactors change howcontentLengthis computed before reachingwriteBlob.Logs & Screenshots
Caught error: RequestContentLengthMismatchError Request body length does not match content-length headerEnvironment
macOS 26.4.1
Node v24.15.0
undici v8.1.0