diff --git a/core/runtime/src/fetch/request.rs b/core/runtime/src/fetch/request.rs index 17fd3f4528e..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 { @@ -41,9 +44,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 = parts.extensions.get::().is_some() || !body.is_empty(); builder = builder .method(parts.method) .uri(parts.uri) @@ -62,9 +69,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 { @@ -81,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 7d516258a0e..549bdb16243 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,88 @@ 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_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([