diff --git a/.changeset/smart-tomatoes-sell.md b/.changeset/smart-tomatoes-sell.md new file mode 100644 index 000000000000..ac0564693e64 --- /dev/null +++ b/.changeset/smart-tomatoes-sell.md @@ -0,0 +1,6 @@ +--- +"edge-preview-authenticated-proxy": minor +"playground-preview-worker": minor +--- + +feat: Optionally strip `cf-ew-raw-` prefix from headers before passing to the user worker diff --git a/packages/edge-preview-authenticated-proxy/src/index.ts b/packages/edge-preview-authenticated-proxy/src/index.ts index 6d435523e2ce..7c32f0724ccf 100644 --- a/packages/edge-preview-authenticated-proxy/src/index.ts +++ b/packages/edge-preview-authenticated-proxy/src/index.ts @@ -220,6 +220,15 @@ async function handleRawHttp(request: Request, url: URL) { requestHeaders.delete("X-CF-Token"); requestHeaders.delete("X-CF-Remote"); + const headerEntries = [...requestHeaders.entries()]; + + for (const header of headerEntries) { + if (header[0].startsWith("cf-ew-raw-")) { + requestHeaders.set(header[0].split("cf-ew-raw-")[1], header[1]); + requestHeaders.delete(header[0]); + } + } + const workerResponse = await fetch( switchRemote(url, remote), new Request(request, { diff --git a/packages/edge-preview-authenticated-proxy/tests/index.test.ts b/packages/edge-preview-authenticated-proxy/tests/index.test.ts index 9380b6e8c723..f9c47f2ed9db 100644 --- a/packages/edge-preview-authenticated-proxy/tests/index.test.ts +++ b/packages/edge-preview-authenticated-proxy/tests/index.test.ts @@ -356,7 +356,7 @@ describe("Raw HTTP preview", () => { return Response.json({ url: request.url, headers: [...request.headers.entries()] - }) + }, { headers: { "Content-Encoding": "identity" } }) } } `.trim() @@ -430,4 +430,83 @@ compatibility_date = "2023-01-01" `"foo=1, bar=2"` ); }); + + it("should pass headers to the user-worker", async () => { + const token = randomBytes(4096).toString("hex"); + const resp = await worker.fetch( + `https://0000.rawhttp.devprod.cloudflare.dev/`, + { + method: "GET", + headers: { + "Access-Control-Request-Method": "GET", + origin: "https://cloudflare.dev", + "X-CF-Token": token, + "X-CF-Remote": `http://127.0.0.1:${remote.port}`, + "Some-Custom-Header": "custom", + Accept: "application/json", + }, + } + ); + + const body = (await resp.json()) as Record; + + const headers = (body.headers as [string, string][]).filter( + (h) => h[0] === "some-custom-header" || h[0] === "accept" + ); + + // This contains some-custom-header & accept, as expected + expect(headers).toMatchInlineSnapshot(` + [ + [ + "accept", + "application/json", + ], + [ + "some-custom-header", + "custom", + ], + ] + `); + }); + + it("should strip cf-ew-raw- prefix from headers which have it before hitting the user-worker", async () => { + const token = randomBytes(4096).toString("hex"); + const resp = await worker.fetch( + `https://0000.rawhttp.devprod.cloudflare.dev/`, + { + method: "GET", + headers: { + "Access-Control-Request-Method": "GET", + origin: "https://cloudflare.dev", + "X-CF-Token": token, + "X-CF-Remote": `http://127.0.0.1:${remote.port}`, + "cf-ew-raw-Some-Custom-Header": "custom", + "cf-ew-raw-Accept": "application/json", + }, + } + ); + + const body = (await resp.json()) as Record; + + const headers = (body.headers as [string, string][]).filter( + (h) => + h[0] === "some-custom-header" || + h[0] === "accept" || + h[0].startsWith("cf-ew-raw-") + ); + + // This contains some-custom-header & accept, as expected, and does not contain cf-ew-raw-some-custom-header or cf-ew-raw-accept + expect(headers).toMatchInlineSnapshot(` + [ + [ + "accept", + "application/json", + ], + [ + "some-custom-header", + "custom", + ], + ] + `); + }); }); diff --git a/packages/playground-preview-worker/src/index.ts b/packages/playground-preview-worker/src/index.ts index d595d5b04f64..aa816b88d3e3 100644 --- a/packages/playground-preview-worker/src/index.ts +++ b/packages/playground-preview-worker/src/index.ts @@ -65,6 +65,15 @@ async function handleRawHttp(request: Request, url: URL, env: Env) { const headers = new Headers(request.headers); headers.delete("X-CF-Token"); + const headerEntries = [...headers.entries()]; + + for (const header of headerEntries) { + if (header[0].startsWith("cf-ew-raw-")) { + headers.set(header[0].split("cf-ew-raw-")[1], header[1]); + headers.delete(header[0]); + } + } + const workerResponse = await userObject.fetch( url, new Request(request, { diff --git a/packages/playground-preview-worker/tests/index.test.ts b/packages/playground-preview-worker/tests/index.test.ts index 3febd6ba8c79..feeddcb807f5 100644 --- a/packages/playground-preview-worker/tests/index.test.ts +++ b/packages/playground-preview-worker/tests/index.test.ts @@ -608,4 +608,73 @@ describe("Raw HTTP preview", () => { `"foo=1, bar=2"` ); }); + + it("should pass headers to the user-worker", async () => { + const resp = await fetch(`${PREVIEW_REMOTE}`, { + method: "GET", + headers: { + origin: "https://cloudflare.dev", + "cf-raw-http": "true", + "X-CF-Token": defaultUserToken, + "Some-Custom-Header": "custom", + Accept: "application/json", + }, + }); + + const body = (await resp.json()) as Record; + + const headers = (body.headers as [string, string][]).filter( + (h) => h[0] === "some-custom-header" || h[0] === "accept" + ); + + // This contains some-custom-header & accept, as expected + expect(headers).toMatchInlineSnapshot(` + [ + [ + "accept", + "application/json", + ], + [ + "some-custom-header", + "custom", + ], + ] + `); + }); + + it("should strip cf-ew-raw- prefix from headers which have it before hitting the user-worker", async () => { + const resp = await fetch(`${PREVIEW_REMOTE}`, { + method: "GET", + headers: { + origin: "https://cloudflare.dev", + "cf-raw-http": "true", + "X-CF-Token": defaultUserToken, + "Some-Custom-Header": "custom", + Accept: "application/json", + }, + }); + + const body = (await resp.json()) as Record; + + const headers = (body.headers as [string, string][]).filter( + (h) => + h[0] === "some-custom-header" || + h[0] === "accept" || + h[0].startsWith("cf-ew-raw-") + ); + + // This contains some-custom-header & accept, as expected, and does not contain cf-ew-raw-some-custom-header or cf-ew-raw-accept + expect(headers).toMatchInlineSnapshot(` + [ + [ + "accept", + "application/json", + ], + [ + "some-custom-header", + "custom", + ], + ] + `); + }); });