diff options
-rw-r--r-- | std/http/_io.ts | 84 | ||||
-rw-r--r-- | std/http/_io_test.ts | 25 |
2 files changed, 61 insertions, 48 deletions
diff --git a/std/http/_io.ts b/std/http/_io.ts index 6b3796c5b..82954ccee 100644 --- a/std/http/_io.ts +++ b/std/http/_io.ts @@ -118,27 +118,37 @@ function isProhibidedForTrailer(key: string): boolean { return s.has(key.toLowerCase()); } -/** - * Read trailer headers from reader and append values to headers. - * "trailer" field will be deleted. - * */ +/** Read trailer headers from reader and append values to headers. "trailer" + * field will be deleted. */ export async function readTrailers( headers: Headers, r: BufReader ): Promise<void> { - const headerKeys = parseTrailer(headers.get("trailer")); - if (!headerKeys) return; + const trailers = parseTrailer(headers.get("trailer")); + if (trailers == null) return; + const trailerNames = [...trailers.keys()]; const tp = new TextProtoReader(r); const result = await tp.readMIMEHeader(); - assert(result !== null, "trailer must be set"); + if (result == null) { + throw new Deno.errors.InvalidData("Missing trailer header."); + } + const undeclared = [...result.keys()].filter( + (k) => !trailerNames.includes(k) + ); + if (undeclared.length > 0) { + throw new Deno.errors.InvalidData( + `Undeclared trailers: ${Deno.inspect(undeclared)}.` + ); + } for (const [k, v] of result) { - if (!headerKeys.has(k)) { - throw new Error("Undeclared trailer field"); - } - headerKeys.delete(k); headers.append(k, v); } - assert(Array.from(headerKeys).length === 0, "Missing trailers"); + const missingTrailers = trailerNames.filter((k) => !result.has(k)); + if (missingTrailers.length > 0) { + throw new Deno.errors.InvalidData( + `Missing trailers: ${Deno.inspect(missingTrailers)}.` + ); + } headers.delete("trailer"); } @@ -146,16 +156,17 @@ function parseTrailer(field: string | null): Headers | undefined { if (field == null) { return undefined; } - const keys = field.split(",").map((v) => v.trim().toLowerCase()); - if (keys.length === 0) { - throw new Error("Empty trailer"); + const trailerNames = field.split(",").map((v) => v.trim().toLowerCase()); + if (trailerNames.length === 0) { + throw new Deno.errors.InvalidData("Empty trailer header."); } - for (const key of keys) { - if (isProhibidedForTrailer(key)) { - throw new Error(`Prohibited field for trailer`); - } + const prohibited = trailerNames.filter((k) => isProhibidedForTrailer(k)); + if (prohibited.length > 0) { + throw new Deno.errors.InvalidData( + `Prohibited trailer names: ${Deno.inspect(prohibited)}.` + ); } - return new Headers(keys.map((key) => [key, ""])); + return new Headers(trailerNames.map((key) => [key, ""])); } export async function writeChunkedBody( @@ -176,7 +187,8 @@ export async function writeChunkedBody( await writer.write(endChunk); } -/** write trailer headers to writer. it mostly should be called after writeResponse */ +/** Write trailer headers to writer. It should mostly should be called after + * `writeResponse()`. */ export async function writeTrailers( w: Deno.Writer, headers: Headers, @@ -184,29 +196,31 @@ export async function writeTrailers( ): Promise<void> { const trailer = headers.get("trailer"); if (trailer === null) { - throw new Error('response headers must have "trailer" header field'); + throw new TypeError("Missing trailer header."); } const transferEncoding = headers.get("transfer-encoding"); if (transferEncoding === null || !transferEncoding.match(/^chunked/)) { - throw new Error( - `trailer headers is only allowed for "transfer-encoding: chunked": got "${transferEncoding}"` + throw new TypeError( + `Trailers are only allowed for "transfer-encoding: chunked", got "transfer-encoding: ${transferEncoding}".` ); } const writer = BufWriter.create(w); - const trailerHeaderFields = trailer - .split(",") - .map((s) => s.trim().toLowerCase()); - for (const f of trailerHeaderFields) { - assert( - !isProhibidedForTrailer(f), - `"${f}" is prohibited for trailer header` + const trailerNames = trailer.split(",").map((s) => s.trim().toLowerCase()); + const prohibitedTrailers = trailerNames.filter((k) => + isProhibidedForTrailer(k) + ); + if (prohibitedTrailers.length > 0) { + throw new TypeError( + `Prohibited trailer names: ${Deno.inspect(prohibitedTrailers)}.` ); } + const undeclared = [...trailers.keys()].filter( + (k) => !trailerNames.includes(k) + ); + if (undeclared.length > 0) { + throw new TypeError(`Undeclared trailers: ${Deno.inspect(undeclared)}.`); + } for (const [key, value] of trailers) { - assert( - trailerHeaderFields.includes(key), - `Not trailer header field: ${key}` - ); await writer.write(encoder.encode(`${key}: ${value}\r\n`)); } await writer.write(encoder.encode("\r\n")); diff --git a/std/http/_io_test.ts b/std/http/_io_test.ts index 3e74e365d..3b385d013 100644 --- a/std/http/_io_test.ts +++ b/std/http/_io_test.ts @@ -1,5 +1,4 @@ import { - AssertionError, assertThrowsAsync, assertEquals, assert, @@ -105,8 +104,8 @@ test("readTrailer should throw if undeclared headers found in trailer", async () async () => { await readTrailers(h, new BufReader(new Buffer(encode(trailer)))); }, - Error, - "Undeclared trailer field" + Deno.errors.InvalidData, + `Undeclared trailers: [ "` ); } }); @@ -120,8 +119,8 @@ test("readTrailer should throw if trailer contains prohibited fields", async () async () => { await readTrailers(h, new BufReader(new Buffer())); }, - Error, - "Prohibited field for trailer" + Deno.errors.InvalidData, + `Prohibited trailer names: [ "` ); } }); @@ -145,15 +144,15 @@ test("writeTrailer should throw", async () => { () => { return writeTrailers(w, new Headers(), new Headers()); }, - Error, - 'must have "trailer"' + TypeError, + "Missing trailer header." ); await assertThrowsAsync( () => { return writeTrailers(w, new Headers({ trailer: "deno" }), new Headers()); }, - Error, - "only allowed" + TypeError, + `Trailers are only allowed for "transfer-encoding: chunked", got "transfer-encoding: null".` ); for (const f of ["content-length", "trailer", "transfer-encoding"]) { await assertThrowsAsync( @@ -164,8 +163,8 @@ test("writeTrailer should throw", async () => { new Headers({ [f]: "1" }) ); }, - AssertionError, - "prohibited" + TypeError, + `Prohibited trailer names: [ "` ); } await assertThrowsAsync( @@ -176,8 +175,8 @@ test("writeTrailer should throw", async () => { new Headers({ node: "js" }) ); }, - AssertionError, - "Not trailer" + TypeError, + `Undeclared trailers: [ "node" ].` ); }); |