From 55cbd98e1b825adeb9678374fe2444ad207224fb Mon Sep 17 00:00:00 2001 From: Ahab Date: Thu, 30 Sep 2021 00:42:06 +0800 Subject: [PATCH] fix(ext/fetch): avoid panic when header is invalid (#12244) --- cli/tests/unit/fetch_test.ts | 48 ++++++++++ ext/fetch/lib.rs | 6 +- tools/wpt/expectation.json | 164 +++++++++++++++++++++++++++++++++-- 3 files changed, 210 insertions(+), 8 deletions(-) diff --git a/cli/tests/unit/fetch_test.ts b/cli/tests/unit/fetch_test.ts index eca62f8ebb..5bce2af43e 100644 --- a/cli/tests/unit/fetch_test.ts +++ b/cli/tests/unit/fetch_test.ts @@ -1299,3 +1299,51 @@ unitTest( } }, ); + +unitTest( + { permissions: { net: true } }, + async function fetchHeaderValueShouldNotPanic() { + for (let i = 0; i < 0x21; i++) { + if (i === 0x09 || i === 0x0A || i === 0x0D || i === 0x20) { + continue; // these header value will be normalized, will not cause an error. + } + // ensure there will be an error instead of panic. + await assertRejects(() => + fetch("http://localhost:4545/echo_server", { + method: "HEAD", + headers: { "val": String.fromCharCode(i) }, + }), TypeError); + } + await assertRejects(() => + fetch("http://localhost:4545/echo_server", { + method: "HEAD", + headers: { "val": String.fromCharCode(127) }, + }), TypeError); + }, +); + +unitTest( + { permissions: { net: true } }, + async function fetchHeaderNameShouldNotPanic() { + const validTokens = + "!#$%&'*+-.0123456789ABCDEFGHIJKLMNOPQRSTUWVXYZ^_`abcdefghijklmnopqrstuvwxyz|~" + .split(""); + for (let i = 0; i <= 255; i++) { + const token = String.fromCharCode(i); + if (validTokens.includes(token)) { + continue; + } + // ensure there will be an error instead of panic. + await assertRejects(() => + fetch("http://localhost:4545/echo_server", { + method: "HEAD", + headers: { [token]: "value" }, + }), TypeError); + } + await assertRejects(() => + fetch("http://localhost:4545/echo_server", { + method: "HEAD", + headers: { "": "value" }, + }), TypeError); + }, +); diff --git a/ext/fetch/lib.rs b/ext/fetch/lib.rs index 70ed403586..3085e7826e 100644 --- a/ext/fetch/lib.rs +++ b/ext/fetch/lib.rs @@ -220,8 +220,10 @@ where }; for (key, value) in args.headers { - let name = HeaderName::from_bytes(&key).unwrap(); - let v = HeaderValue::from_bytes(&value).unwrap(); + let name = HeaderName::from_bytes(&key) + .map_err(|err| type_error(err.to_string()))?; + let v = HeaderValue::from_bytes(&value) + .map_err(|err| type_error(err.to_string()))?; if name != HOST { request = request.header(name, v); } diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index ceaa0cc7bb..9065e484ce 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -14034,17 +14034,169 @@ ] }, "headers": { - "header-values-normalize.any.html": false, - "header-values-normalize.any.worker.html": false, - "header-values.any.html": false, - "header-values.any.worker.html": false, + "header-values-normalize.any.html": [ + "XMLHttpRequest with value %00", + "XMLHttpRequest with value %01", + "XMLHttpRequest with value %02", + "XMLHttpRequest with value %03", + "XMLHttpRequest with value %04", + "XMLHttpRequest with value %05", + "XMLHttpRequest with value %06", + "XMLHttpRequest with value %07", + "XMLHttpRequest with value %08", + "XMLHttpRequest with value %09", + "XMLHttpRequest with value %0A", + "XMLHttpRequest with value %0D", + "XMLHttpRequest with value %0E", + "XMLHttpRequest with value %0F", + "XMLHttpRequest with value %10", + "XMLHttpRequest with value %11", + "XMLHttpRequest with value %12", + "XMLHttpRequest with value %13", + "XMLHttpRequest with value %14", + "XMLHttpRequest with value %15", + "XMLHttpRequest with value %16", + "XMLHttpRequest with value %17", + "XMLHttpRequest with value %18", + "XMLHttpRequest with value %19", + "XMLHttpRequest with value %1A", + "XMLHttpRequest with value %1B", + "XMLHttpRequest with value %1C", + "XMLHttpRequest with value %1D", + "XMLHttpRequest with value %1E", + "XMLHttpRequest with value %1F", + "XMLHttpRequest with value %20", + "fetch() with value %01", + "fetch() with value %02", + "fetch() with value %03", + "fetch() with value %04", + "fetch() with value %05", + "fetch() with value %06", + "fetch() with value %07", + "fetch() with value %08", + "fetch() with value %0E", + "fetch() with value %0F", + "fetch() with value %10", + "fetch() with value %11", + "fetch() with value %12", + "fetch() with value %13", + "fetch() with value %14", + "fetch() with value %15", + "fetch() with value %16", + "fetch() with value %17", + "fetch() with value %18", + "fetch() with value %19", + "fetch() with value %1A", + "fetch() with value %1B", + "fetch() with value %1C", + "fetch() with value %1D", + "fetch() with value %1E", + "fetch() with value %1F" + ], + "header-values-normalize.any.worker.html": [ + "fetch() with value %01", + "fetch() with value %02", + "fetch() with value %03", + "fetch() with value %04", + "fetch() with value %05", + "fetch() with value %06", + "fetch() with value %07", + "fetch() with value %08", + "fetch() with value %0E", + "fetch() with value %0F", + "fetch() with value %10", + "fetch() with value %11", + "fetch() with value %12", + "fetch() with value %13", + "fetch() with value %14", + "fetch() with value %15", + "fetch() with value %16", + "fetch() with value %17", + "fetch() with value %18", + "fetch() with value %19", + "fetch() with value %1A", + "fetch() with value %1B", + "fetch() with value %1C", + "fetch() with value %1D", + "fetch() with value %1E", + "fetch() with value %1F" + ], + "header-values.any.html": [ + "XMLHttpRequest with value x%00x needs to throw", + "XMLHttpRequest with value x%0Ax needs to throw", + "XMLHttpRequest with value x%0Dx needs to throw", + "XMLHttpRequest with all valid values", + "fetch() with all valid values" + ], + "header-values.any.worker.html": [ + "fetch() with all valid values" + ], "headers-basic.any.html": true, "headers-casing.any.html": true, "headers-combine.any.html": true, "headers-errors.any.html": true, "headers-normalize.any.html": true, "headers-record.any.html": true, - "headers-structure.any.html": true + "headers-structure.any.html": true, + "headers-basic.any.worker.html": false, + "headers-casing.any.worker.html": true, + "headers-combine.any.worker.html": true, + "headers-errors.any.worker.html": true, + "headers-no-cors.any.html": [ + "\"no-cors\" Headers object cannot have accept set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have accept-language set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have content-language set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have accept set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have accept-language set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have content-language set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have content-type set to text/plain;ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, text/plain", + "\"no-cors\" Headers object cannot have accept/\" as header", + "\"no-cors\" Headers object cannot have accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 as header", + "\"no-cors\" Headers object cannot have accept-language/\u0001 as header", + "\"no-cors\" Headers object cannot have accept-language/@ as header", + "\"no-cors\" Headers object cannot have authorization/basics as header", + "\"no-cors\" Headers object cannot have content-language/\u0001 as header", + "\"no-cors\" Headers object cannot have content-language/@ as header", + "\"no-cors\" Headers object cannot have content-type/text/html as header", + "\"no-cors\" Headers object cannot have content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 as header", + "\"no-cors\" Headers object cannot have range/bytes 0- as header", + "\"no-cors\" Headers object cannot have test/hi as header", + "\"no-cors\" Headers object cannot have dpr/2 as header", + "\"no-cors\" Headers object cannot have downlink/1 as header", + "\"no-cors\" Headers object cannot have save-data/on as header", + "\"no-cors\" Headers object cannot have viewport-width/100 as header", + "\"no-cors\" Headers object cannot have width/100 as header", + "\"no-cors\" Headers object cannot have unknown/doesitmatter as header" + ], + "headers-no-cors.any.worker.html": [ + "\"no-cors\" Headers object cannot have accept set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have accept-language set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have content-language set to sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have accept set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have accept-language set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have content-language set to , sssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss", + "\"no-cors\" Headers object cannot have content-type set to text/plain;ssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssssss, text/plain", + "\"no-cors\" Headers object cannot have accept/\" as header", + "\"no-cors\" Headers object cannot have accept/012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678 as header", + "\"no-cors\" Headers object cannot have accept-language/\u0001 as header", + "\"no-cors\" Headers object cannot have accept-language/@ as header", + "\"no-cors\" Headers object cannot have authorization/basics as header", + "\"no-cors\" Headers object cannot have content-language/\u0001 as header", + "\"no-cors\" Headers object cannot have content-language/@ as header", + "\"no-cors\" Headers object cannot have content-type/text/html as header", + "\"no-cors\" Headers object cannot have content-type/text/plain; long=0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901 as header", + "\"no-cors\" Headers object cannot have range/bytes 0- as header", + "\"no-cors\" Headers object cannot have test/hi as header", + "\"no-cors\" Headers object cannot have dpr/2 as header", + "\"no-cors\" Headers object cannot have downlink/1 as header", + "\"no-cors\" Headers object cannot have save-data/on as header", + "\"no-cors\" Headers object cannot have viewport-width/100 as header", + "\"no-cors\" Headers object cannot have width/100 as header", + "\"no-cors\" Headers object cannot have unknown/doesitmatter as header" + ], + "headers-normalize.any.worker.html": true, + "headers-record.any.worker.html": true, + "headers-structure.any.worker.html": true }, "basic": { "request-head.any.html": true, @@ -15190,4 +15342,4 @@ "Pattern: [] Inputs: []" ] } -} +} \ No newline at end of file