From 475dcb0263115ccc25211a3455d90635ccae5092 Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Sat, 21 Mar 2026 08:30:45 +0530 Subject: [PATCH 1/5] fix(fetch): reject Request body for GET and HEAD methods --- core/runtime/src/fetch/request.rs | 12 +++++++- core/runtime/src/fetch/tests/request.rs | 40 +++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-) diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index 2991dfdd9a2..e170f0829a7 100644 --- a/core/runtime/src/fetch/request.rs +++ b/core/runtime/src/fetch/request.rs @@ -41,9 +41,13 @@ impl RequestInit { request: Option>>, ) -> JsResult>> { let mut builder = HttpRequest::builder(); + let mut is_get_or_head_method = true; + let mut has_inherited_body = false; let mut request_body = Vec::new(); if let Some(r) = request { let (parts, body) = r.into_parts(); + is_get_or_head_method = matches!(parts.method, http::Method::GET | http::Method::HEAD); + has_inherited_body = !body.is_empty(); builder = builder .method(parts.method) .uri(parts.uri) @@ -65,7 +69,6 @@ impl RequestInit { let method = method.to_std_string().map_err( |_| js_error!(TypeError: "Request constructor: {} is an invalid method", method.to_std_string_escaped()), )?; - // 25. If init["method"] exists, then: // 1. Let method be init["method"]. // 2. If method is not a method or method is a forbidden method, throw a TypeError. @@ -82,9 +85,16 @@ impl RequestInit { )); } + is_get_or_head_method = + method.eq_ignore_ascii_case("GET") || method.eq_ignore_ascii_case("HEAD"); + builder = builder.method(method.as_str()); } + if is_get_or_head_method && (self.body.is_some() || has_inherited_body) { + return Err(js_error!(TypeError: "Request with GET/HEAD method cannot have body.")); + } + if let Some(body) = &self.body { // TODO: add more support types. if let Some(body) = body.as_string() { diff --git a/core/runtime/src/fetch/tests/request.rs b/core/runtime/src/fetch/tests/request.rs index b14a7c84859..4e8b8313ddf 100644 --- a/core/runtime/src/fetch/tests/request.rs +++ b/core/runtime/src/fetch/tests/request.rs @@ -71,6 +71,46 @@ fn request_constructor_forbidden_method_throws() { ]); } +#[test] +fn request_constructor_get_with_body_throws() { + run_test_actions([ + TestAction::inspect_context(|ctx| { + let fetcher = TestFetcher::default(); + crate::fetch::register(fetcher, None, ctx).expect("failed to register fetch"); + }), + TestAction::run(indoc! {r#" + try { + new Request("http://unit.test", { method: "GET", body: "x" }); + throw Error("expected the call above to throw"); + } catch (e) { + if (!(e instanceof TypeError)) { + throw e; + } + } + "#}), + ]); +} + +#[test] +fn request_constructor_head_with_body_throws() { + run_test_actions([ + TestAction::inspect_context(|ctx| { + let fetcher = TestFetcher::default(); + crate::fetch::register(fetcher, None, ctx).expect("failed to register fetch"); + }), + TestAction::run(indoc! {r#" + try { + new Request("http://unit.test", { method: "HEAD", body: "x" }); + throw Error("expected the call above to throw"); + } catch (e) { + if (!(e instanceof TypeError)) { + throw e; + } + } + "#}), + ]); +} + #[test] fn request_clone_preserves_body_without_override() { run_test_actions([ From 9226e0b7495ffe52ac09203945c14ec72f6fd6da Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Sat, 21 Mar 2026 09:22:52 +0530 Subject: [PATCH 2/5] fix(fetch): handle inherited empty body in GET/HEAD check --- core/runtime/src/fetch/request.rs | 13 ++++++-- core/runtime/src/fetch/tests/request.rs | 42 +++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index e170f0829a7..e95b31ea357 100644 --- a/core/runtime/src/fetch/request.rs +++ b/core/runtime/src/fetch/request.rs @@ -25,6 +25,9 @@ pub struct RequestInit { signal: Option, } +#[derive(Clone, Copy)] +struct HasBody; + impl RequestInit { /// Takes the abort signal from the options, if present. pub fn take_signal(&mut self) -> Option { @@ -47,7 +50,7 @@ impl RequestInit { if let Some(r) = request { let (parts, body) = r.into_parts(); is_get_or_head_method = matches!(parts.method, http::Method::GET | http::Method::HEAD); - has_inherited_body = !body.is_empty(); + has_inherited_body = parts.extensions.get::().is_some() || !body.is_empty(); builder = builder .method(parts.method) .uri(parts.uri) @@ -109,9 +112,13 @@ impl RequestInit { } } - builder + let mut request = builder .body(request_body) - .map_err(|_| js_error!(Error: "Cannot construct request")) + .map_err(|_| js_error!(Error: "Cannot construct request"))?; + if self.body.is_some() || has_inherited_body { + request.extensions_mut().insert(HasBody); + } + Ok(request) } } diff --git a/core/runtime/src/fetch/tests/request.rs b/core/runtime/src/fetch/tests/request.rs index 4e8b8313ddf..d1b71376c50 100644 --- a/core/runtime/src/fetch/tests/request.rs +++ b/core/runtime/src/fetch/tests/request.rs @@ -111,6 +111,48 @@ fn request_constructor_head_with_body_throws() { ]); } +#[test] +fn request_constructor_get_with_inherited_empty_body_throws() { + run_test_actions([ + TestAction::inspect_context(|ctx| { + let fetcher = TestFetcher::default(); + crate::fetch::register(fetcher, None, ctx).expect("failed to register fetch"); + }), + TestAction::run(indoc! {r#" + const request = new Request("http://unit.test", { method: "POST", body: "" }); + try { + new Request(request, { method: "GET" }); + throw Error("expected the call above to throw"); + } catch (e) { + if (!(e instanceof TypeError)) { + throw e; + } + } + "#}), + ]); +} + +#[test] +fn request_constructor_head_with_inherited_empty_body_throws() { + run_test_actions([ + TestAction::inspect_context(|ctx| { + let fetcher = TestFetcher::default(); + crate::fetch::register(fetcher, None, ctx).expect("failed to register fetch"); + }), + TestAction::run(indoc! {r#" + const request = new Request("http://unit.test", { method: "POST", body: "" }); + try { + new Request(request, { method: "HEAD" }); + throw Error("expected the call above to throw"); + } catch (e) { + if (!(e instanceof TypeError)) { + throw e; + } + } + "#}), + ]); +} + #[test] fn request_clone_preserves_body_without_override() { run_test_actions([ From 4c5623717b5f0c7db49ad4a5cc432261cc317031 Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Tue, 7 Apr 2026 07:34:10 +0530 Subject: [PATCH 3/5] refactor(fetch): add spec comments to GET/HEAD body validation --- core/runtime/src/fetch/request.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index e95b31ea357..3139f47f7b9 100644 --- a/core/runtime/src/fetch/request.rs +++ b/core/runtime/src/fetch/request.rs @@ -50,6 +50,7 @@ impl RequestInit { if let Some(r) = request { let (parts, body) = r.into_parts(); is_get_or_head_method = matches!(parts.method, http::Method::GET | http::Method::HEAD); + // https://fetch.spec.whatwg.org/#dom-request - "Let inputBody be input's request's body if input is a Request object; otherwise null." has_inherited_body = parts.extensions.get::().is_some() || !body.is_empty(); builder = builder .method(parts.method) @@ -94,6 +95,7 @@ impl RequestInit { builder = builder.method(method.as_str()); } + // https://fetch.spec.whatwg.org/#dom-request - "If either init["body"] exists and is non-null or inputBody is non-null, and request's method is GET or HEAD, then throw a TypeError." if is_get_or_head_method && (self.body.is_some() || has_inherited_body) { return Err(js_error!(TypeError: "Request with GET/HEAD method cannot have body.")); } From 651acdf37f8553a90e07a69752c6eb51ac1e49c9 Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Wed, 8 Apr 2026 00:31:41 +0530 Subject: [PATCH 4/5] refactor(fetch): use Option> for request body handling and remove HasBody extension --- core/runtime/src/fetch/request.rs | 35 ++++++++++++++++++------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index 3139f47f7b9..cbbaf9b9969 100644 --- a/core/runtime/src/fetch/request.rs +++ b/core/runtime/src/fetch/request.rs @@ -25,9 +25,6 @@ pub struct RequestInit { signal: Option, } -#[derive(Clone, Copy)] -struct HasBody; - impl RequestInit { /// Takes the abort signal from the options, if present. pub fn take_signal(&mut self) -> Option { @@ -45,13 +42,15 @@ impl RequestInit { ) -> JsResult>> { let mut builder = HttpRequest::builder(); let mut is_get_or_head_method = true; - let mut has_inherited_body = false; - let mut request_body = Vec::new(); + let mut inherited_is_get_or_head_method = true; + let mut inherited_body = None; + let mut request_body: Option> = None; if let Some(r) = request { let (parts, body) = r.into_parts(); is_get_or_head_method = matches!(parts.method, http::Method::GET | http::Method::HEAD); + inherited_is_get_or_head_method = is_get_or_head_method; // https://fetch.spec.whatwg.org/#dom-request - "Let inputBody be input's request's body if input is a Request object; otherwise null." - has_inherited_body = parts.extensions.get::().is_some() || !body.is_empty(); + inherited_body = Some(body); builder = builder .method(parts.method) .uri(parts.uri) @@ -60,7 +59,6 @@ impl RequestInit { for (key, value) in &parts.headers { builder = builder.header(key, value); } - request_body = body; } if let Some(headers) = self.headers.take() { @@ -95,8 +93,16 @@ impl RequestInit { builder = builder.method(method.as_str()); } - // https://fetch.spec.whatwg.org/#dom-request - "If either init["body"] exists and is non-null or inputBody is non-null, and request's method is GET or HEAD, then throw a TypeError." - if is_get_or_head_method && (self.body.is_some() || has_inherited_body) { + // Fetch Standard §5.4 Request constructor: + // If either init["body"] exists and is non-null or inputBody is non-null, + // and request's method is GET or HEAD, then throw a TypeError. + // https://fetch.spec.whatwg.org/#dom-request + if is_get_or_head_method + && (self.body.is_some() + || inherited_body + .as_ref() + .is_some_and(|body| !body.is_empty() || !inherited_is_get_or_head_method)) + { return Err(js_error!(TypeError: "Request with GET/HEAD method cannot have body.")); } @@ -106,20 +112,19 @@ impl RequestInit { let body = body.to_std_string().map_err( |_| js_error!(TypeError: "Request constructor: body is not a valid string"), )?; - request_body = body.into_bytes(); + request_body = Some(body.into_bytes()); } else { return Err( js_error!(TypeError: "Request constructor: body is not a supported type"), ); } + } else if let Some(body) = inherited_body { + request_body = Some(body); } - let mut request = builder - .body(request_body) + let request = builder + .body(request_body.unwrap_or_default()) .map_err(|_| js_error!(Error: "Cannot construct request"))?; - if self.body.is_some() || has_inherited_body { - request.extensions_mut().insert(HasBody); - } Ok(request) } } From 3273766fb52bf69ec529583d4150bba2e295f92b Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Fri, 10 Apr 2026 22:23:59 +0530 Subject: [PATCH 5/5] fix(fetch): reject Request body for GET and HEAD methods --- core/runtime/src/fetch/request.rs | 39 +++++++++++-------------------- 1 file changed, 14 insertions(+), 25 deletions(-) diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index cbbaf9b9969..006b1e00ae1 100644 --- a/core/runtime/src/fetch/request.rs +++ b/core/runtime/src/fetch/request.rs @@ -41,14 +41,9 @@ impl RequestInit { request: Option>>, ) -> JsResult>> { let mut builder = HttpRequest::builder(); - let mut is_get_or_head_method = true; - let mut inherited_is_get_or_head_method = true; let mut inherited_body = None; - let mut request_body: Option> = None; if let Some(r) = request { let (parts, body) = r.into_parts(); - is_get_or_head_method = matches!(parts.method, http::Method::GET | http::Method::HEAD); - inherited_is_get_or_head_method = is_get_or_head_method; // https://fetch.spec.whatwg.org/#dom-request - "Let inputBody be input's request's body if input is a Request object; otherwise null." inherited_body = Some(body); builder = builder @@ -87,43 +82,37 @@ impl RequestInit { )); } - is_get_or_head_method = + let is_get_or_head_method = method.eq_ignore_ascii_case("GET") || method.eq_ignore_ascii_case("HEAD"); + // Fetch Standard §5.4 Request constructor: + // If either init["body"] exists and is non-null or inputBody is non-null, + // and request's method is GET or HEAD, then throw a TypeError. + // https://fetch.spec.whatwg.org/#dom-request + if is_get_or_head_method && (self.body.is_some() || inherited_body.is_some()) { + return Err(js_error!(TypeError: "Request with GET/HEAD method cannot have body.")); + } builder = builder.method(method.as_str()); } - // Fetch Standard §5.4 Request constructor: - // If either init["body"] exists and is non-null or inputBody is non-null, - // and request's method is GET or HEAD, then throw a TypeError. - // https://fetch.spec.whatwg.org/#dom-request - if is_get_or_head_method - && (self.body.is_some() - || inherited_body - .as_ref() - .is_some_and(|body| !body.is_empty() || !inherited_is_get_or_head_method)) - { - return Err(js_error!(TypeError: "Request with GET/HEAD method cannot have body.")); - } - - if let Some(body) = &self.body { + let request_body = if let Some(body) = &self.body { // TODO: add more support types. if let Some(body) = body.as_string() { let body = body.to_std_string().map_err( |_| js_error!(TypeError: "Request constructor: body is not a valid string"), )?; - request_body = Some(body.into_bytes()); + body.into_bytes() } else { return Err( js_error!(TypeError: "Request constructor: body is not a supported type"), ); } - } else if let Some(body) = inherited_body { - request_body = Some(body); - } + } else { + inherited_body.unwrap_or_default() + }; let request = builder - .body(request_body.unwrap_or_default()) + .body(request_body) .map_err(|_| js_error!(Error: "Cannot construct request"))?; Ok(request) }