fix(trusted-types): align legacy URL sink metadata

This commit is contained in:
ldm0
2026-09-09 06:38:21 +08:00
parent 2578710901
commit 73ba82ecdd
7 changed files with 116 additions and 28 deletions
@@ -3702,7 +3702,6 @@ trusted-types/block-string-assignment-to-HTMLIFrameElement-srcdoc.html
trusted-types/block-string-assignment-to-attribute-via-attribute-node.html
trusted-types/eval-function-constructor-untrusted-arguments-and-applying-default-policy.html
trusted-types/inheriting-csp-for-local-schemes.html
trusted-types/legacy-trusted-script-urls.html
trusted-types/modify-attributes-in-callback.html
trusted-types/require-trusted-types-for-TypeError-belongs-to-the-global-object-realm.html
trusted-types/script-enforcement-006.html
@@ -7851,6 +7851,7 @@ trusted-types/eval-no-csp-no-tt.html
trusted-types/eval-with-non-trusted-script-object.html
trusted-types/eval-with-permissive-csp.html
trusted-types/get-trusted-types-compliant-attribute-value.html
trusted-types/legacy-trusted-script-urls.html
trusted-types/legacy-trusted-scripts.html
trusted-types/navigate-to-javascript-url-001.html
trusted-types/navigate-to-javascript-url-002.html
@@ -95,6 +95,13 @@ struct TrustedTypePolicyFactoryInterfaceDeclaration {
enumerable
)]
get_attribute_type: (),
#[webapi(
method = "getPropertyType",
callback = trusted_types_get_property_type_callback,
length = 2,
enumerable
)]
get_property_type: (),
#[webapi(
accessor_property = "defaultPolicy",
getter = trusted_types_default_policy_getter_callback,
@@ -127,6 +134,17 @@ struct TrustedTypesGetAttributeTypeArgs {
attribute_namespace: Option<String>,
}
#[derive(webidl::WebIdlArgs)]
#[webidl(prefix = "TrustedTypePolicyFactory.getPropertyType")]
struct TrustedTypesGetPropertyTypeArgs {
#[webidl(required)]
tag_name: String,
#[webidl(required)]
property: String,
#[webidl(nullable)]
element_namespace: Option<String>,
}
#[derive(WebApiObject)]
#[webapi(interface = "Object")]
struct TrustedTypeObjectDeclaration<'scope> {
@@ -1152,6 +1170,36 @@ fn trusted_types_get_attribute_type_callback<'s>(
rv.set(type_name.into());
}
fn trusted_types_get_property_type_callback<'s>(
scope: &mut v8::PinScope<'s, '_>,
args: v8::FunctionCallbackArguments<'s>,
mut rv: v8::ReturnValue<'_, v8::Value>,
) {
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
return;
}
let Some(parsed) = webidl::parse_args::<TrustedTypesGetPropertyTypeArgs>(scope, &args) else {
return;
};
let element_namespace = parsed
.element_namespace
.filter(|namespace| !namespace.is_empty())
.unwrap_or_else(|| crate::native_bridge::document::XHTML_NS.to_owned());
let tag_name = parsed.tag_name.to_ascii_lowercase();
let Some(type_name) = crate::native_bridge::element::trusted_property_type_name_for_names(
&element_namespace,
&tag_name,
&parsed.property,
) else {
rv.set_null();
return;
};
let Some(type_name) = v8_string(scope, type_name) else {
return;
};
rv.set(type_name.into());
}
#[derive(Clone, Copy)]
enum FunctionConstructorKind {
Function,
@@ -83,6 +83,7 @@ pub(super) fn install_lazy_trusted_types_runtime_state<'s>(
empty_html: (),
empty_script: (),
get_attribute_type: (),
get_property_type: (),
default_policy: (),
}
.bind(scope, global)
@@ -62,6 +62,7 @@ use trusted_types::{
};
pub(crate) use trusted_types::{
set_svg_animated_string_base_value, trusted_attribute_type_name_for_names,
trusted_property_type_name_for_names,
};
pub(crate) use forms::{
@@ -81,15 +81,6 @@ fn trusted_attribute_sink_for_names(
("http://www.w3.org/1999/xhtml", "script", None, "src") => {
Some(TrustedAttributeSink::ScriptUrl("HTMLScriptElement src"))
}
("http://www.w3.org/1999/xhtml", "embed", None, "src") => {
Some(TrustedAttributeSink::ScriptUrl("HTMLEmbedElement src"))
}
("http://www.w3.org/1999/xhtml", "object", None, "data") => {
Some(TrustedAttributeSink::ScriptUrl("HTMLObjectElement data"))
}
("http://www.w3.org/1999/xhtml", "object", None, "codebase") => Some(
TrustedAttributeSink::ScriptUrl("HTMLObjectElement codebase"),
),
(
"http://www.w3.org/2000/svg",
"script",
@@ -115,6 +106,24 @@ pub(crate) fn trusted_attribute_type_name_for_names(
.map(|sink| sink.type_name())
}
pub(crate) fn trusted_property_type_name_for_names(
element_namespace: &str,
element_local_name: &str,
property_name: &str,
) -> Option<&'static str> {
if matches!(property_name, "innerHTML" | "outerHTML") {
return Some("TrustedHTML");
}
match (element_namespace, element_local_name, property_name) {
("http://www.w3.org/1999/xhtml", "iframe", "srcdoc") => Some("TrustedHTML"),
("http://www.w3.org/1999/xhtml", "script", "innerText" | "text" | "textContent") => {
Some("TrustedScript")
}
("http://www.w3.org/1999/xhtml", "script", "src") => Some("TrustedScriptURL"),
_ => None,
}
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub(in crate::native_bridge::element) enum TrustedHtmlSink {
ElementInnerHtml,
@@ -159,7 +159,7 @@ fn trusted_type_factory_interface_exposes_stable_branded_empty_values() {
}
#[test]
fn trusted_type_factory_get_attribute_type_reuses_attribute_sink_classification() {
fn trusted_type_factory_introspection_reuses_current_sink_classification() {
let mut vm = new_storage_test_vm("https://trusted-type-attribute-types.test/");
let result = vm
@@ -172,6 +172,7 @@ fn trusted_type_factory_get_attribute_type_reuses_attribute_sink_classification(
const XLINK = "http://www.w3.org/1999/xlink";
const OTHER = "https://example.test/namespace";
const type = (...args) => trustedTypes.getAttributeType(...args);
const propertyType = (...args) => trustedTypes.getPropertyType(...args);
const errorName = callback => {
try {
callback();
@@ -180,19 +181,28 @@ fn trusted_type_factory_get_attribute_type_reuses_attribute_sink_classification(
return error.constructor.name;
}
};
const descriptor = Object.getOwnPropertyDescriptor(
const attributeDescriptor = Object.getOwnPropertyDescriptor(
TrustedTypePolicyFactory.prototype,
"getAttributeType"
);
const propertyDescriptor = Object.getOwnPropertyDescriptor(
TrustedTypePolicyFactory.prototype,
"getPropertyType"
);
const describe = descriptor => [
typeof descriptor.value,
descriptor.value.name,
descriptor.value.length,
descriptor.enumerable,
descriptor.configurable
];
return JSON.stringify({
interface: [
typeof descriptor.value,
descriptor.value.name,
descriptor.value.length,
descriptor.enumerable,
descriptor.configurable,
Object.hasOwn(trustedTypes, "getAttributeType")
describe(attributeDescriptor),
describe(propertyDescriptor),
Object.hasOwn(trustedTypes, "getAttributeType"),
Object.hasOwn(trustedTypes, "getPropertyType")
],
htmlDefaults: [
type("script", "src"),
@@ -213,6 +223,17 @@ fn trusted_type_factory_get_attribute_type_reuses_attribute_sink_classification(
type("script", "href", SVG, OTHER),
type("script", "href", SVG.toUpperCase())
],
properties: [
propertyType("script", "text"),
propertyType("SCRIPT", "src"),
propertyType("script", "sRc"),
propertyType("div", "innerHTML"),
propertyType("foo", "outerHTML", OTHER),
propertyType("script", "src", SVG),
propertyType("embed", "src"),
propertyType("object", "data"),
propertyType("object", "codeBase")
],
handlers: [
type("div", "onclick"),
type("g", "ondblclick", SVG),
@@ -224,17 +245,20 @@ fn trusted_type_factory_get_attribute_type_reuses_attribute_sink_classification(
errors: [
errorName(() => type()),
errorName(() => type("script")),
errorName(() => descriptor.value.call({}, "script", "src"))
errorName(() => attributeDescriptor.value.call({}, "script", "src")),
errorName(() => propertyType()),
errorName(() => propertyType("script")),
errorName(() => propertyDescriptor.value.call({}, "script", "src"))
]
});
})()
"#,
)
.expect("TrustedTypePolicyFactory getAttributeType probe should evaluate");
.expect("TrustedTypePolicyFactory introspection probe should evaluate");
assert_eq!(
result,
r#"{"interface":["function","getAttributeType",2,true,true,false],"htmlDefaults":["TrustedScriptURL","TrustedScriptURL","TrustedScriptURL","TrustedScriptURL","TrustedScriptURL",null,null],"urls":["TrustedScriptURL","TrustedScriptURL","TrustedScriptURL",null,"TrustedScriptURL","TrustedScriptURL",null,null],"handlers":["TrustedScript","TrustedScript","TrustedScript",null,null,null],"errors":["TypeError","TypeError","TypeError"]}"#
r#"{"interface":[["function","getAttributeType",2,true,true],["function","getPropertyType",2,true,true],false,false],"htmlDefaults":["TrustedScriptURL","TrustedScriptURL","TrustedScriptURL","TrustedScriptURL","TrustedScriptURL",null,null],"urls":[null,null,null,null,"TrustedScriptURL","TrustedScriptURL",null,null],"properties":["TrustedScript","TrustedScriptURL",null,"TrustedHTML","TrustedHTML",null,null,null,null],"handlers":["TrustedScript","TrustedScript","TrustedScript",null,null,null],"errors":["TypeError","TypeError","TypeError","TypeError","TypeError","TypeError"]}"#
);
}
@@ -1298,7 +1322,7 @@ fn event_handler_attribute_writes_enforce_trusted_script_at_the_attribute_bounda
}
#[test]
fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() {
fn url_attribute_writes_enforce_only_the_current_non_iframe_script_url_sinks() {
let mut vm = new_storage_test_vm("https://url-attribute-trusted-types.test/");
vm.set_response_content_security_policies(&["require-trusted-types-for 'script'".to_owned()]);
@@ -1333,10 +1357,7 @@ fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() {
[document.createElement("script"), element => element.setAttribute("src", "plain")],
[document.createElement("script"), element => element.setAttributeNS(null, "src", null)],
[document.createElementNS(svg, "script"), element => element.setAttribute("href", "plain")],
[document.createElementNS(svg, "script"), element => element.setAttributeNS(xlink, "xlink:href", policy.createScript("wrong"))],
[document.createElement("embed"), element => element.setAttribute("src", "plain")],
[document.createElement("object"), element => element.setAttribute("data", "plain")],
[document.createElement("object"), element => element.setAttribute("codebase", "plain")]
[document.createElementNS(svg, "script"), element => element.setAttributeNS(xlink, "xlink:href", policy.createScript("wrong"))]
]) {
try {
setter(element);
@@ -1352,6 +1373,11 @@ fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() {
uppercaseSvg.setAttributeNS(null, "HREF", "uppercase");
const div = document.createElement("div");
div.setAttribute("src", "plain-div");
const legacyEmbed = document.createElement("embed");
legacyEmbed.setAttribute("src", "plain-embed");
const legacyObject = document.createElement("object");
legacyObject.setAttribute("data", "plain-object-data");
legacyObject.setAttribute("codebase", "plain-object-codebase");
const defaultCalls = [];
trustedTypes.createPolicy("default", {
@@ -1381,7 +1407,10 @@ fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() {
ordinary: [
ordinary.getAttributeNS("urn:test", "src"),
uppercaseSvg.getAttribute("HREF"),
div.getAttribute("src")
div.getAttribute("src"),
legacyEmbed.getAttribute("src"),
legacyObject.getAttribute("data"),
legacyObject.getAttribute("codebase")
],
defaultValues: [
defaultHtml.getAttribute("src"),
@@ -1397,7 +1426,7 @@ fn url_attribute_writes_enforce_the_non_iframe_trusted_script_url_sink_table() {
assert_eq!(
result,
r#"{"trustedValues":["script.js","script-ns.js","svg.js","svg-xlink.js","embed.js","object-data.js","object-codebase.js"],"rejected":[true,true,true,true,true,true,true],"ordinary":["namespaced","uppercase","plain-div"],"defaultValues":["safe-default-html","safe-default-svg","safe-default-object"],"defaultCalls":[["default-html","TrustedScriptURL","HTMLScriptElement src"],["default-svg","TrustedScriptURL","SVGScriptElement href"],["default-object","TrustedScriptURL","HTMLObjectElement codebase"]]}"#
r#"{"trustedValues":["script.js","script-ns.js","svg.js","svg-xlink.js","embed.js","object-data.js","object-codebase.js"],"rejected":[true,true,true,true],"ordinary":["namespaced","uppercase","plain-div","plain-embed","plain-object-data","plain-object-codebase"],"defaultValues":["safe-default-html","safe-default-svg","default-object"],"defaultCalls":[["default-html","TrustedScriptURL","HTMLScriptElement src"],["default-svg","TrustedScriptURL","SVGScriptElement href"]]}"#
);
}