From 60f225ad7040a87b937f54e6a3155d9f30744572 Mon Sep 17 00:00:00 2001 From: Yusuke Sakurai Date: Fri, 29 May 2020 21:40:54 +0900 Subject: [PATCH] fix: readTrailer didn't evaluate header names by case-insensitive (#4902) --- std/http/_io.ts | 31 +++++++++++++++---------------- std/http/_io_test.ts | 4 ++-- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/std/http/_io.ts b/std/http/_io.ts index 631adafd01f6b4..6b3796c5bc97b7 100644 --- a/std/http/_io.ts +++ b/std/http/_io.ts @@ -113,11 +113,10 @@ export function chunkedBodyReader(h: Headers, r: BufReader): Deno.Reader { return { read }; } -const kProhibitedTrailerHeaders = [ - "transfer-encoding", - "content-length", - "trailer", -]; +function isProhibidedForTrailer(key: string): boolean { + const s = new Set(["transfer-encoding", "content-length", "trailer"]); + return s.has(key.toLowerCase()); +} /** * Read trailer headers from reader and append values to headers. @@ -127,36 +126,36 @@ export async function readTrailers( headers: Headers, r: BufReader ): Promise { - const keys = parseTrailer(headers.get("trailer")); - if (!keys) return; + const headerKeys = parseTrailer(headers.get("trailer")); + if (!headerKeys) return; const tp = new TextProtoReader(r); const result = await tp.readMIMEHeader(); assert(result !== null, "trailer must be set"); for (const [k, v] of result) { - if (!keys.has(k)) { + if (!headerKeys.has(k)) { throw new Error("Undeclared trailer field"); } - keys.delete(k); + headerKeys.delete(k); headers.append(k, v); } - assert(keys.size === 0, "Missing trailers"); + assert(Array.from(headerKeys).length === 0, "Missing trailers"); headers.delete("trailer"); } -function parseTrailer(field: string | null): Set | undefined { +function parseTrailer(field: string | null): Headers | undefined { if (field == null) { return undefined; } - const keys = field.split(",").map((v) => v.trim()); + const keys = field.split(",").map((v) => v.trim().toLowerCase()); if (keys.length === 0) { throw new Error("Empty trailer"); } - for (const invalid of kProhibitedTrailerHeaders) { - if (keys.includes(invalid)) { + for (const key of keys) { + if (isProhibidedForTrailer(key)) { throw new Error(`Prohibited field for trailer`); } } - return new Set(keys); + return new Headers(keys.map((key) => [key, ""])); } export async function writeChunkedBody( @@ -199,7 +198,7 @@ export async function writeTrailers( .map((s) => s.trim().toLowerCase()); for (const f of trailerHeaderFields) { assert( - !kProhibitedTrailerHeaders.includes(f), + !isProhibidedForTrailer(f), `"${f}" is prohibited for trailer header` ); } diff --git a/std/http/_io_test.ts b/std/http/_io_test.ts index c22ebdf07e4d74..3e74e365d1cda9 100644 --- a/std/http/_io_test.ts +++ b/std/http/_io_test.ts @@ -82,7 +82,7 @@ test("chunkedBodyReader with trailers", async () => { test("readTrailers", async () => { const h = new Headers({ - trailer: "deno,node", + trailer: "Deno, Node", }); const trailer = ["deno: land", "node: js", "", ""].join("\r\n"); await readTrailers(h, new BufReader(new Buffer(encode(trailer)))); @@ -112,7 +112,7 @@ test("readTrailer should throw if undeclared headers found in trailer", async () }); test("readTrailer should throw if trailer contains prohibited fields", async () => { - for (const f of ["content-length", "trailer", "transfer-encoding"]) { + for (const f of ["Content-Length", "Trailer", "Transfer-Encoding"]) { const h = new Headers({ trailer: f, });