Compare commits

...

1 Commits

Author SHA1 Message Date
Claude Bot
836e50b840 fix: send well-known HTTP headers in lowercase instead of title-case
Use lowercase header names (e.g., `content-type` instead of `Content-Type`)
when writing well-known headers to HTTP responses. This matches Node.js
behavior and is more compatible with HTTP/2 which requires lowercase headers.

The fix changes `writeFetchHeadersToUWSResponse` to use
`httpHeaderNameStringImpl` (lowercase) instead of `httpHeaderNameString`
(title-case) for common header names.

Closes #15578

Co-Authored-By: Claude <noreply@anthropic.com>
2026-02-20 02:35:25 +00:00
2 changed files with 102 additions and 1 deletions

View File

@@ -538,7 +538,7 @@ static void writeFetchHeadersToUWSResponse(WebCore::FetchHeaders& headers, uWS::
for (const auto& header : internalHeaders.commonHeaders()) {
const auto& name = WebCore::httpHeaderNameString(header.key);
const auto& name = WTF::httpHeaderNameStringImpl(header.key);
const auto& value = header.value;
// We have to tell uWS not to automatically insert a TransferEncoding or Date header.

View File

@@ -0,0 +1,101 @@
import { expect, test } from "bun:test";
// Regression test for https://github.com/oven-sh/bun/issues/15578
// Well-known HTTP headers should be sent lowercase (matching Node.js behavior),
// not title-cased.
test("node:http server sends well-known headers in lowercase", async () => {
const { createServer } = await import("node:http");
const server = createServer((req, res) => {
res.setHeader("location", "http://test.com");
res.setHeader("content-type", "text/plain");
res.setHeader("cache-control", "no-cache");
res.setHeader("x-custom-header", "custom-value");
res.end("ok");
});
server.listen(0);
const port = (server.address() as any).port;
try {
const response = await fetch(`http://localhost:${port}/`);
const text = await response.text();
expect(text).toBe("ok");
// Verify well-known headers are lowercase by inspecting raw headers
// response.headers normalizes casing, so we need to check the raw response
// Using a raw TCP connection to inspect actual header casing
const { connect } = await import("node:net");
const rawResponse = await new Promise<string>((resolve, reject) => {
const client = connect(port, "127.0.0.1", () => {
client.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n");
});
let data = "";
client.on("data", chunk => {
data += chunk.toString();
});
client.on("end", () => {
resolve(data);
});
client.on("error", reject);
});
// The raw HTTP response should contain lowercase header names
expect(rawResponse).toContain("location: http://test.com");
expect(rawResponse).toContain("content-type: text/plain");
expect(rawResponse).toContain("cache-control: no-cache");
expect(rawResponse).toContain("x-custom-header: custom-value");
// Should NOT contain title-cased versions
expect(rawResponse).not.toContain("Location:");
expect(rawResponse).not.toContain("Content-Type:");
expect(rawResponse).not.toContain("Cache-Control:");
} finally {
server.close();
}
});
test("Bun.serve sends well-known headers in lowercase", async () => {
using server = Bun.serve({
port: 0,
fetch(req) {
return new Response("ok", {
headers: {
"location": "http://test.com",
"content-type": "text/plain",
"cache-control": "no-cache",
"x-custom-header": "custom-value",
},
});
},
});
const { connect } = await import("node:net");
const rawResponse = await new Promise<string>((resolve, reject) => {
const client = connect(server.port, "127.0.0.1", () => {
client.write("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n");
});
let data = "";
client.on("data", chunk => {
data += chunk.toString();
});
client.on("end", () => {
resolve(data);
});
client.on("error", reject);
});
// The raw HTTP response should contain lowercase header names
expect(rawResponse).toContain("location: http://test.com");
expect(rawResponse).toContain("content-type: text/plain");
expect(rawResponse).toContain("cache-control: no-cache");
expect(rawResponse).toContain("x-custom-header: custom-value");
// Should NOT contain title-cased versions
expect(rawResponse).not.toContain("Location:");
expect(rawResponse).not.toContain("Content-Type:");
expect(rawResponse).not.toContain("Cache-Control:");
});