fix(url): expose colon protocol for invalid hyperlinks

This commit is contained in:
ldm0
2026-10-02 03:28:23 +08:00
parent 7ee7a8e3ac
commit 58d08b5b24
2 changed files with 66 additions and 5 deletions
@@ -3,6 +3,7 @@ use super::*;
fn anchor_url_string_function_getter<'s>(
scope: &mut v8::PinScope<'s, '_>,
receiver: v8::Local<'s, v8::Object>,
parse_failure_value: &'static str,
project: impl FnOnce(&url::Url) -> String,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
@@ -14,7 +15,7 @@ fn anchor_url_string_function_getter<'s>(
};
let value = parsed_url_like_attribute(unsafe { &*runtime_ptr }, handle, "href")
.map(|url| project(&url))
.unwrap_or_default();
.unwrap_or_else(|| parse_failure_value.to_owned());
if let Some(value) = v8_string(scope, &value) {
rv.set(value.into());
} else {
@@ -280,6 +281,7 @@ fn anchor_host_getter_function<'s>(
anchor_url_string_function_getter(
scope,
args.this(),
"",
|url| {
url.host_str()
.map(|host| {
@@ -336,6 +338,7 @@ fn anchor_hostname_getter_function<'s>(
anchor_url_string_function_getter(
scope,
args.this(),
"",
|url| url.host_str().unwrap_or_default().to_owned(),
rv,
);
@@ -368,6 +371,7 @@ fn anchor_port_getter_function<'s>(
anchor_url_string_function_getter(
scope,
args.this(),
"",
|url| url.port().map(|port| port.to_string()).unwrap_or_default(),
rv,
);
@@ -397,7 +401,7 @@ fn anchor_pathname_getter_function<'s>(
args: v8::FunctionCallbackArguments<'s>,
rv: v8::ReturnValue<'_, v8::Value>,
) {
anchor_url_string_function_getter(scope, args.this(), |url| url.path().to_owned(), rv);
anchor_url_string_function_getter(scope, args.this(), "", |url| url.path().to_owned(), rv);
}
fn anchor_pathname_setter_function<'s>(
@@ -426,6 +430,7 @@ fn anchor_search_getter_function<'s>(
anchor_url_string_function_getter(
scope,
args.this(),
"",
|url| url::quirks::search(url).to_owned(),
rv,
);
@@ -454,6 +459,7 @@ fn anchor_hash_getter_function<'s>(
anchor_url_string_function_getter(
scope,
args.this(),
"",
|url| url::quirks::hash(url).to_owned(),
rv,
);
@@ -479,7 +485,13 @@ fn anchor_origin_getter_function<'s>(
args: v8::FunctionCallbackArguments<'s>,
rv: v8::ReturnValue<'_, v8::Value>,
) {
anchor_url_string_function_getter(scope, args.this(), moli_url::origin_ascii_serialization, rv);
anchor_url_string_function_getter(
scope,
args.this(),
"",
moli_url::origin_ascii_serialization,
rv,
);
}
fn anchor_protocol_getter_function<'s>(
@@ -487,7 +499,13 @@ fn anchor_protocol_getter_function<'s>(
args: v8::FunctionCallbackArguments<'s>,
rv: v8::ReturnValue<'_, v8::Value>,
) {
anchor_url_string_function_getter(scope, args.this(), |url| format!("{}:", url.scheme()), rv);
anchor_url_string_function_getter(
scope,
args.this(),
":",
|url| format!("{}:", url.scheme()),
rv,
);
}
fn anchor_protocol_setter_function<'s>(
@@ -511,7 +529,7 @@ fn anchor_username_getter_function<'s>(
args: v8::FunctionCallbackArguments<'s>,
rv: v8::ReturnValue<'_, v8::Value>,
) {
anchor_url_string_function_getter(scope, args.this(), |url| url.username().to_owned(), rv);
anchor_url_string_function_getter(scope, args.this(), "", |url| url.username().to_owned(), rv);
}
fn anchor_username_setter_function<'s>(
@@ -540,6 +558,7 @@ fn anchor_password_getter_function<'s>(
anchor_url_string_function_getter(
scope,
args.this(),
"",
|url| url.password().unwrap_or("").to_owned(),
rv,
);
@@ -1648,3 +1648,45 @@ fn detached_document_creation_brand_checks_accept_standard_prototype_methods() {
r#"{"htmlElement":"true,SECTION,true,false","htmlElementNs":"true,x,article,http://www.w3.org/1999/xhtml","xmlElement":"true,Mixed,null","xmlElementNs":"true,p,item,urn:test","characterNodes":"true,txt,true,note,true,2,true,pi","attrs":"true,data-x,true,p,flag,urn:test","importAdopt":"true,p,item,true,true","documentOwn":"false,false,false,false,false"}"#
);
}
#[test]
fn hyperlink_protocol_is_colon_when_href_cannot_be_parsed() {
let mut vm = new_storage_test_vm("https://hyperlink-invalid-url.test/page.html");
let result = vm
.eval(
r#"
(() => {
const base = document.createElement("base");
base.href = "about:blank";
(document.head || document.documentElement || document).appendChild(base);
const inputs = [
"",
"javascript://:443",
"javascript://test:test",
"javascript://[:1]",
"mailto://:443",
"mailto://test:test",
"mailto://[:1]"
];
for (const tag of ["a", "area"]) {
const element = document.createElement(tag);
for (const input of inputs) {
element.setAttribute("href", input);
if (element.href !== input) {
throw new Error(`${tag} should preserve the unparsable href ${input}`);
}
if (element.protocol !== ":") {
throw new Error(`${tag} should expose ':' for the unparsable href ${input}`);
}
}
}
return "ok";
})()
"#,
)
.expect("unparsable hyperlink protocol probe should evaluate");
assert_eq!(result, "ok");
}