Skip to content

Commit 458d627

Browse files
authored
fix(node/http): normalize header names in ServerResponse (denoland#26339)
Fixes denoland#26115. We weren't normalizing the headers to lower case, so code that attempted to delete the `Content-Length` header (but used a different case) wasn't actually removing the header.
1 parent 3385d12 commit 458d627

File tree

2 files changed

+38
-9
lines changed

2 files changed

+38
-9
lines changed

ext/node/polyfills/http.ts

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ import { STATUS_CODES } from "node:_http_server";
7272
import { methods as METHODS } from "node:_http_common";
7373

7474
const { internalRidSymbol } = core;
75-
const { ArrayIsArray } = primordials;
75+
const { ArrayIsArray, StringPrototypeToLowerCase } = primordials;
7676

7777
type Chunk = string | Buffer | Uint8Array;
7878

@@ -1270,20 +1270,21 @@ export class ServerResponse extends NodeWritable {
12701270
if (Array.isArray(value)) {
12711271
this.#hasNonStringHeaders = true;
12721272
}
1273-
this.#headers[name] = value;
1273+
this.#headers[StringPrototypeToLowerCase(name)] = value;
12741274
return this;
12751275
}
12761276

12771277
appendHeader(name: string, value: string | string[]) {
1278-
if (this.#headers[name] === undefined) {
1278+
const key = StringPrototypeToLowerCase(name);
1279+
if (this.#headers[key] === undefined) {
12791280
if (Array.isArray(value)) this.#hasNonStringHeaders = true;
1280-
this.#headers[name] = value;
1281+
this.#headers[key] = value;
12811282
} else {
12821283
this.#hasNonStringHeaders = true;
1283-
if (!Array.isArray(this.#headers[name])) {
1284-
this.#headers[name] = [this.#headers[name]];
1284+
if (!Array.isArray(this.#headers[key])) {
1285+
this.#headers[key] = [this.#headers[key]];
12851286
}
1286-
const header = this.#headers[name];
1287+
const header = this.#headers[key];
12871288
if (Array.isArray(value)) {
12881289
header.push(...value);
12891290
} else {
@@ -1294,10 +1295,10 @@ export class ServerResponse extends NodeWritable {
12941295
}
12951296

12961297
getHeader(name: string) {
1297-
return this.#headers[name];
1298+
return this.#headers[StringPrototypeToLowerCase(name)];
12981299
}
12991300
removeHeader(name: string) {
1300-
delete this.#headers[name];
1301+
delete this.#headers[StringPrototypeToLowerCase(name)];
13011302
}
13021303
getHeaderNames() {
13031304
return Object.keys(this.#headers);

tests/unit_node/http_test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1147,6 +1147,34 @@ Deno.test("[node/http] ServerResponse appendHeader set-cookie", async () => {
11471147
await promise;
11481148
});
11491149

1150+
Deno.test("[node/http] ServerResponse header names case insensitive", async () => {
1151+
const { promise, resolve } = Promise.withResolvers<void>();
1152+
const server = http.createServer((_req, res) => {
1153+
res.setHeader("Content-Length", "12345");
1154+
res.removeHeader("content-length");
1155+
assertEquals(res.getHeader("Content-Length"), undefined);
1156+
assert(!res.hasHeader("Content-Length"));
1157+
res.appendHeader("content-length", "12345");
1158+
res.removeHeader("Content-Length");
1159+
assertEquals(res.getHeader("content-length"), undefined);
1160+
assert(!res.hasHeader("content-length"));
1161+
res.end("Hello World");
1162+
});
1163+
1164+
server.listen(async () => {
1165+
const { port } = server.address() as { port: number };
1166+
const res = await fetch(`http://localhost:${port}`);
1167+
assertEquals(res.headers.get("Content-Length"), null);
1168+
assertEquals(res.headers.get("content-length"), null);
1169+
assertEquals(await res.text(), "Hello World");
1170+
server.close(() => {
1171+
resolve();
1172+
});
1173+
});
1174+
1175+
await promise;
1176+
});
1177+
11501178
Deno.test("[node/http] IncomingMessage override", () => {
11511179
const req = new http.IncomingMessage(new net.Socket());
11521180
// https://github.com/dougmoscrop/serverless-http/blob/3aaa6d0fe241109a8752efb011c242d249f32368/lib/request.js#L20-L30

0 commit comments

Comments
 (0)