From 24f439e82ea3706d671918d56998e479348cfcbd Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Sat, 21 Mar 2026 08:30:45 +0530 Subject: [PATCH 1/2] fix(fetch): reject Request body for GET and HEAD methods --- core/runtime/src/fetch/request.rs | 15 +++++++-- core/runtime/src/fetch/tests/request.rs | 41 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index 17fd3f4528e..14164734c11 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) @@ -62,9 +66,16 @@ impl RequestInit { } if let Some(Convert(ref method)) = self.method.take() { - builder = builder.method(method.to_std_string().map_err( + let method = method.to_std_string().map_err( |_| js_error!(TypeError: "Request constructor: {} is an invalid method", method.to_std_string_escaped()), - )?.as_str()); + )?; + 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 { diff --git a/core/runtime/src/fetch/tests/request.rs b/core/runtime/src/fetch/tests/request.rs index 7d516258a0e..4a8d3f037d2 100644 --- a/core/runtime/src/fetch/tests/request.rs +++ b/core/runtime/src/fetch/tests/request.rs @@ -5,6 +5,7 @@ use crate::test::{TestAction, run_test_actions}; use boa_engine::{js_str, js_string}; use either::Either; use http::{Response, Uri}; +use indoc::indoc; #[test] fn request_constructor() { @@ -48,6 +49,46 @@ fn request_constructor() { ]); } +#[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 a31e50ee9f74fc5a7a9a70e849e5eab6359c8fd5 Mon Sep 17 00:00:00 2001 From: HiteshShonak Date: Sat, 21 Mar 2026 09:22:52 +0530 Subject: [PATCH 2/2] 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 14164734c11..e80e4fc8ed1 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) @@ -92,9 +95,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 4a8d3f037d2..549bdb16243 100644 --- a/core/runtime/src/fetch/tests/request.rs +++ b/core/runtime/src/fetch/tests/request.rs @@ -89,6 +89,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([