Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions core/runtime/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ fetch = [
"dep:either",
"dep:http",
"dep:serde_json",
"dep:url",
"boa_engine/either",
]
reqwest-blocking = ["dep:reqwest", "reqwest/blocking"]
Expand Down
8 changes: 4 additions & 4 deletions core/runtime/src/fetch/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,17 +303,17 @@ impl JsResponse {
if !matches!(status, 301 | 302 | 303 | 307 | 308) {
return Err(js_error!(RangeError: "Invalid redirect status: {}", status));
}
let url_str = url.to_string(context)?.to_std_string_escaped();
http::Uri::try_from(url_str.as_str())
.map_err(|_| js_error!(TypeError: "Invalid URL: {}", url_str))?;
let parsed_url = url::Url::parse(&url.to_string(context)?.to_std_string_escaped())
.map_err(|e| js_error!(TypeError: "Invalid URL: {}", e))?;
let serialized_url = parsed_url.to_string();

let status_code = StatusCode::from_u16(status)
.map_err(|_| js_error!(RangeError: "Invalid status code: {}", status))?;

let mut headers = http::header::HeaderMap::new();
headers.insert(
HeaderName::from_static("location"),
HeaderValue::try_from(url_str)
HeaderValue::try_from(serialized_url)
.map_err(|_| js_error!(TypeError: "Invalid URL for header value"))?,
);

Expand Down
70 changes: 67 additions & 3 deletions core/runtime/src/fetch/tests/response.rs
Original file line number Diff line number Diff line change
Expand Up @@ -180,10 +180,74 @@ fn response_redirect_custom_status_and_coercion() {
r#"
const response = Response.redirect("http://example.com/", 301);
assertEq(response.status, 301);
"#,
),
]);
}

#[test]
fn response_redirect_rejects_non_url_after_string_coercion() {
run_test_actions([
TestAction::harness(),
TestAction::inspect_context(|ctx| register(&[], ctx)),
TestAction::run(
r#"
let threw = false;
try {
Response.redirect(12345);
} catch (e) {
threw = true;
if (!(e instanceof TypeError)) {
throw new Error("Expected TypeError, got " + e.name);
}
}
if (!threw) {
throw new Error("Expected TypeError, but no error was thrown");
}
"#,
),
]);
}

#[test]
fn response_redirect_serializes_parsed_url() {
run_test_actions([
TestAction::harness(),
TestAction::inspect_context(|ctx| register(&[], ctx)),
TestAction::run(
r#"
const response1 = Response.redirect("https://example.com");
assertEq(response1.headers.get("location"), "https://example.com/");
"#,
),
TestAction::run(
r#"
const response2 = Response.redirect("https://example.com#frag");
assertEq(response2.headers.get("location"), "https://example.com/#frag");
"#,
),
]);
}

// Tests Web IDL coercion of the URL parameter
const response2 = Response.redirect(12345);
assertEq(response2.headers.get("location"), "12345");
#[test]
fn response_redirect_rejects_relative_url() {
run_test_actions([
TestAction::harness(),
TestAction::inspect_context(|ctx| register(&[], ctx)),
TestAction::run(
r#"
let threw = false;
try {
Response.redirect("/foo");

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need adjusting (see comments in #5301. )
this should not throw.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I'll push the commit by night was working on something else

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I rechecked this more carefully. The spec parses against the current settings objects API base URL, so I think the /foo case depends on the host environment. that should explain why it works in your browser console ss while node and boa CLI both throw here

The part this PR is fixing is narrower: Location should use the serialized parsed url

@jedel1043 what do you think about keeping this PR scoped to that part ??

@jedel1043 jedel1043 Sep 1, 2026

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just needs to call Fetcher::resolve_uri, which is the platform-dependant API for URI resolution. (unless the spec says otherwise, but since browsers and CLIs differ, I expect it to have something related to a host-dependant operation for URIs)

} catch (e) {
threw = true;
if (!(e instanceof TypeError)) {
throw new Error("Expected TypeError, got " + e.name);
}
}
if (!threw) {
throw new Error("Expected TypeError, but no error was thrown");
}
"#,
),
]);
Expand Down
Loading