1
0
Fork 0
mirror of https://github.com/denoland/deno.git synced 2025-01-11 00:21:05 -05:00

fix(std/http): Don't use assert() for user input validation (#6092)

This commit is contained in:
Nayeem Rahman 2020-06-04 03:32:27 +01:00 committed by GitHub
parent 9bd5c08d5a
commit 97d876f6db
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 61 additions and 48 deletions

View file

@ -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"));

View file

@ -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" ].`
);
});