Skip to content
This repository was archived by the owner on Mar 13, 2025. It is now read-only.

Commit 8b6b35b

Browse files
committed
Allow forbidden header mutation after Request/Response construction
Ref: cloudflare/workers-sdk#59
1 parent f5d6771 commit 8b6b35b

File tree

2 files changed

+63
-2
lines changed

2 files changed

+63
-2
lines changed

packages/core/src/standards/http.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ export class Body<Inner extends BaseRequest | BaseResponse> {
101101
#inputGatedBody?: ReadableStream;
102102

103103
constructor(inner: Inner) {
104+
// Allow forbidden header mutation after construction
105+
// @ts-expect-error internal kGuard isn't included in type definitions
106+
inner.headers[fetchSymbols.kGuard] = "none";
107+
104108
this[kInner] = inner;
105109

106110
makeEnumerable(Body.prototype, this, enumerableBodyKeys);

packages/core/test/standards/http.spec.ts

Lines changed: 59 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -367,10 +367,10 @@ test("Request: clone retains form data file parsing option", async (t) => {
367367
t.is(resFormData.get("file"), "test");
368368
});
369369
test("Request: Object.keys() returns getters", async (t) => {
370-
const res = new Request("http://localhost", {
370+
const req = new Request("http://localhost", {
371371
headers: { "X-Key": "value " },
372372
});
373-
const keys = Object.keys(res);
373+
const keys = Object.keys(req);
374374
const expectedKeys = [
375375
"body",
376376
"bodyUsed",
@@ -383,6 +383,45 @@ test("Request: Object.keys() returns getters", async (t) => {
383383
];
384384
t.deepEqual(keys.sort(), expectedKeys.sort());
385385
});
386+
test("Request: can mutate forbidden headers after construction", async (t) => {
387+
// From https://github.com/nodejs/undici/blob/cd566ccf65c18b8405793a752247f3e350a50fcf/lib/fetch/constants.js#L3-L24
388+
const forbiddenHeaderNames = [
389+
"accept-charset",
390+
"accept-encoding",
391+
"access-control-request-headers",
392+
"access-control-request-method",
393+
"connection",
394+
"content-length",
395+
"cookie",
396+
"cookie2",
397+
"date",
398+
"dnt",
399+
"expect",
400+
"host",
401+
"keep-alive",
402+
"origin",
403+
"referer",
404+
"te",
405+
"trailer",
406+
"transfer-encoding",
407+
"upgrade",
408+
"via",
409+
];
410+
411+
const req = new Request("http://localhost");
412+
for (const header of forbiddenHeaderNames) {
413+
req.headers.set(header, "value");
414+
t.is(req.headers.get(header), "value");
415+
}
416+
417+
// Check this still works on a clone
418+
const req2 = req.clone();
419+
for (const header of forbiddenHeaderNames) {
420+
t.is(req2.headers.get(header), "value");
421+
req2.headers.set(header, "value2");
422+
t.is(req2.headers.get(header), "value2");
423+
}
424+
});
386425

387426
test("withImmutableHeaders: makes Request's headers immutable", (t) => {
388427
const req = new Request("http://localhost");
@@ -570,6 +609,24 @@ test("Response: Object.keys() returns getters", async (t) => {
570609
];
571610
t.deepEqual(keys.sort(), expectedKeys.sort());
572611
});
612+
test("Response: can mutate forbidden headers after construction", async (t) => {
613+
// From https://github.com/nodejs/undici/blob/cd566ccf65c18b8405793a752247f3e350a50fcf/lib/fetch/constants.js#L61
614+
const forbiddenResponseHeaderNames = ["set-cookie", "set-cookie2"];
615+
616+
const res = new Response("body");
617+
for (const header of forbiddenResponseHeaderNames) {
618+
res.headers.set(header, "value");
619+
t.is(res.headers.get(header), "value");
620+
}
621+
622+
// Check this still works on a clone
623+
const res2 = res.clone();
624+
for (const header of forbiddenResponseHeaderNames) {
625+
t.is(res2.headers.get(header), "value");
626+
res2.headers.set(header, "value2");
627+
t.is(res2.headers.get(header), "value2");
628+
}
629+
});
573630

574631
test("withWaitUntil: adds wait until to (Base)Response", async (t) => {
575632
const waitUntil = [1];

0 commit comments

Comments
 (0)