mirror of
https://github.com/lexmount/moli.git
synced 2026-10-20 00:01:39 +00:00
fix(trusted-types): expose policy interface and default
This commit is contained in:
@@ -23,12 +23,14 @@ const TRUSTED_TYPE_SCRIPT_URL_PROTOTYPE_SLOT: &str = "__moliTrustedScriptURLProt
|
||||
const TRUSTED_TYPE_HTML_CONSTRUCTOR_SLOT: &str = "__moliTrustedHTMLConstructor";
|
||||
const TRUSTED_TYPE_SCRIPT_CONSTRUCTOR_SLOT: &str = "__moliTrustedScriptConstructor";
|
||||
const TRUSTED_TYPE_SCRIPT_URL_CONSTRUCTOR_SLOT: &str = "__moliTrustedScriptURLConstructor";
|
||||
const TRUSTED_TYPE_POLICY_CONSTRUCTOR_SLOT: &str = "__moliTrustedTypePolicyConstructor";
|
||||
const TRUSTED_TYPE_POLICY_FACTORY_CONSTRUCTOR_SLOT: &str =
|
||||
"__moliTrustedTypePolicyFactoryConstructor";
|
||||
const TRUSTED_TYPES_DEFAULT_POLICY_SLOT: &str = "__moliTrustedTypesDefaultPolicy";
|
||||
const TRUSTED_TYPES_CREATE_HTML_SLOT: &str = "__moliTrustedTypesCreateHTML";
|
||||
const TRUSTED_TYPES_CREATE_SCRIPT_SLOT: &str = "__moliTrustedTypesCreateScript";
|
||||
const TRUSTED_TYPES_CREATE_SCRIPT_URL_SLOT: &str = "__moliTrustedTypesCreateScriptURL";
|
||||
const TRUSTED_TYPES_POLICY_NAME_SLOT: &str = "__moliTrustedTypesPolicyName";
|
||||
const TRUSTED_TYPES_EMPTY_HTML_SLOT: &str = "__moliTrustedTypesEmptyHTML";
|
||||
const TRUSTED_TYPES_EMPTY_SCRIPT_SLOT: &str = "__moliTrustedTypesEmptyScript";
|
||||
const TRUSTED_TYPES_EMPTY_VALUE_SLOTS: [&str; 2] = [
|
||||
@@ -87,6 +89,12 @@ struct TrustedTypePolicyFactoryInterfaceDeclaration {
|
||||
enumerable
|
||||
)]
|
||||
empty_script: (),
|
||||
#[webapi(
|
||||
accessor_property = "defaultPolicy",
|
||||
getter = trusted_types_default_policy_getter_callback,
|
||||
enumerable
|
||||
)]
|
||||
default_policy: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
@@ -116,40 +124,60 @@ struct TrustedTypePrototypeDeclaration {
|
||||
value_of: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(prototype = "Object", interface = web_api_interfaces::TrustedTypePolicy)]
|
||||
struct TrustedTypePolicyDeclaration<'scope> {
|
||||
#[webapi(data_property)]
|
||||
name: v8::Local<'scope, v8::String>,
|
||||
#[webapi(slot = TRUSTED_TYPES_CREATE_HTML_SLOT)]
|
||||
create_html_callback: Option<v8::Local<'scope, v8::Object>>,
|
||||
#[derive(WebApiFunctionTemplate)]
|
||||
#[webapi(
|
||||
interface = web_api_interfaces::TrustedTypePolicy,
|
||||
constructor = "illegal",
|
||||
constructor_length = 0,
|
||||
readonly_prototype,
|
||||
prototype_to_string_tag = "TrustedTypePolicy"
|
||||
)]
|
||||
struct TrustedTypePolicyInterfaceDeclaration {
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = trusted_type_policy_name_getter_callback,
|
||||
enumerable
|
||||
)]
|
||||
name: (),
|
||||
#[webapi(
|
||||
method = "createHTML",
|
||||
callback = trusted_type_policy_create_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 0),
|
||||
length = 1
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
create_html: (),
|
||||
#[webapi(slot = TRUSTED_TYPES_CREATE_SCRIPT_SLOT)]
|
||||
create_script_callback: Option<v8::Local<'scope, v8::Object>>,
|
||||
#[webapi(
|
||||
method,
|
||||
callback = trusted_type_policy_create_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 1),
|
||||
length = 1
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
create_script: (),
|
||||
#[webapi(slot = TRUSTED_TYPES_CREATE_SCRIPT_URL_SLOT)]
|
||||
create_script_url_callback: Option<v8::Local<'scope, v8::Object>>,
|
||||
#[webapi(
|
||||
method = "createScriptURL",
|
||||
callback = trusted_type_policy_create_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 2),
|
||||
length = 1
|
||||
length = 1,
|
||||
enumerable
|
||||
)]
|
||||
create_script_url: (),
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::TrustedTypePolicy, require_prototype)]
|
||||
struct TrustedTypePolicyObjectDeclaration<'scope> {
|
||||
#[webapi(slot = TRUSTED_TYPES_POLICY_NAME_SLOT)]
|
||||
name: v8::Local<'scope, v8::String>,
|
||||
#[webapi(slot = TRUSTED_TYPES_CREATE_HTML_SLOT)]
|
||||
create_html_callback: Option<v8::Local<'scope, v8::Object>>,
|
||||
#[webapi(slot = TRUSTED_TYPES_CREATE_SCRIPT_SLOT)]
|
||||
create_script_callback: Option<v8::Local<'scope, v8::Object>>,
|
||||
#[webapi(slot = TRUSTED_TYPES_CREATE_SCRIPT_URL_SLOT)]
|
||||
create_script_url_callback: Option<v8::Local<'scope, v8::Object>>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
enum TrustedTypeKind {
|
||||
Html,
|
||||
@@ -762,6 +790,9 @@ fn trusted_types_empty_value_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
let Some(slot) = crate::util::callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
@@ -771,17 +802,45 @@ fn trusted_types_empty_value_getter_callback<'s>(
|
||||
return;
|
||||
};
|
||||
let Some(value) = get_private_value(scope, args.this(), slot) else {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
};
|
||||
rv.set(value);
|
||||
}
|
||||
|
||||
fn trusted_types_default_policy_getter_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 global = scope.get_current_context().global(scope);
|
||||
rv.set(
|
||||
get_private_value(scope, global, TRUSTED_TYPES_DEFAULT_POLICY_SLOT)
|
||||
.unwrap_or_else(|| v8::null(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
fn trusted_types_factory_receiver_is_valid<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
if web_api_interfaces::TrustedTypePolicyFactory::is_instance(scope, receiver.into()) {
|
||||
return true;
|
||||
}
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
false
|
||||
}
|
||||
|
||||
fn trusted_types_create_policy_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(name) = webidl::required_argument::<webidl::DomString>(
|
||||
scope,
|
||||
&args,
|
||||
@@ -805,12 +864,12 @@ fn trusted_types_create_policy_callback<'s>(
|
||||
}
|
||||
|
||||
let policy_name = v8_string(scope, &name).unwrap_or_else(|| v8::String::empty(scope));
|
||||
let policy = TrustedTypePolicyDeclaration::new(
|
||||
policy_name,
|
||||
callbacks.create_html,
|
||||
callbacks.create_script,
|
||||
callbacks.create_script_url,
|
||||
)
|
||||
let policy = TrustedTypePolicyObjectDeclaration {
|
||||
name: policy_name,
|
||||
create_html_callback: callbacks.create_html,
|
||||
create_script_callback: callbacks.create_script,
|
||||
create_script_url_callback: callbacks.create_script_url,
|
||||
}
|
||||
.bind(scope)
|
||||
.expect("TrustedTypePolicy declaration should bind");
|
||||
|
||||
@@ -836,6 +895,18 @@ fn trusted_types_policy_name_allowed(scope: &mut v8::PinScope<'_, '_>, name: &st
|
||||
unsafe { &*host_ptr }.allows_trusted_type_policy_name(scope, name)
|
||||
}
|
||||
|
||||
fn trusted_type_policy_name_getter_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let Some(name) = get_private_value(scope, args.this(), TRUSTED_TYPES_POLICY_NAME_SLOT) else {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
};
|
||||
rv.set(name);
|
||||
}
|
||||
|
||||
fn trusted_type_policy_create_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -855,6 +926,10 @@ fn trusted_type_policy_create_callback<'s>(
|
||||
return;
|
||||
};
|
||||
let policy = args.this();
|
||||
if get_private_value(scope, policy, TRUSTED_TYPES_POLICY_NAME_SLOT).is_none() {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(input) = webidl::required_argument::<webidl::DomString>(
|
||||
scope,
|
||||
&args,
|
||||
@@ -897,6 +972,9 @@ fn trusted_types_is_html_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::Html).is_some());
|
||||
}
|
||||
|
||||
@@ -905,6 +983,9 @@ fn trusted_types_is_script_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::Script).is_some());
|
||||
}
|
||||
|
||||
@@ -913,6 +994,9 @@ fn trusted_types_is_script_url_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !trusted_types_factory_receiver_is_valid(scope, args.this()) {
|
||||
return;
|
||||
}
|
||||
rv.set_bool(trusted_type_string(scope, args.get(0), TrustedTypeKind::ScriptUrl).is_some());
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,25 @@ pub(super) fn install_lazy_trusted_types_runtime_state<'s>(
|
||||
return Ok(());
|
||||
}
|
||||
install_trusted_script_code_like_constructor(scope, global)?;
|
||||
let policy_constructor = TrustedTypePolicyInterfaceDeclaration::build(scope)
|
||||
.get_function(scope)
|
||||
.ok_or_else(|| anyhow!("failed to build TrustedTypePolicy interface"))?;
|
||||
global
|
||||
.define_own_property(
|
||||
scope,
|
||||
v8str(scope, "TrustedTypePolicy").into(),
|
||||
policy_constructor.into(),
|
||||
v8::PropertyAttribute::DONT_ENUM,
|
||||
)
|
||||
.unwrap_or(false)
|
||||
.then_some(())
|
||||
.ok_or_else(|| anyhow!("failed to publish TrustedTypePolicy interface"))?;
|
||||
set_private_value(
|
||||
scope,
|
||||
global,
|
||||
TRUSTED_TYPE_POLICY_CONSTRUCTOR_SLOT,
|
||||
policy_constructor.into(),
|
||||
);
|
||||
let policy_factory_constructor = TrustedTypePolicyFactoryInterfaceDeclaration::build(scope)
|
||||
.get_function(scope)
|
||||
.ok_or_else(|| anyhow!("failed to build TrustedTypePolicyFactory interface"))?;
|
||||
|
||||
@@ -572,7 +572,7 @@ fn wpt_webdriver_delete_all_cookies_is_declared_on_prototype() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_type_policy_declared_methods_preserve_surface() {
|
||||
fn trusted_type_policy_interface_shares_methods_and_rejects_missing_callbacks() {
|
||||
let mut vm = new_storage_test_vm("https://trusted-type-policy-declared-methods.test/");
|
||||
|
||||
let result = vm
|
||||
@@ -587,11 +587,9 @@ fn trusted_type_policy_declared_methods_preserve_surface() {
|
||||
createScript: value => value,
|
||||
createScriptURL: value => value
|
||||
});
|
||||
const describe = (object, name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, name);
|
||||
if (!descriptor) {
|
||||
return `${name}:missing`;
|
||||
}
|
||||
const prototype = TrustedTypePolicy.prototype;
|
||||
const describe = name => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(prototype, name);
|
||||
return [
|
||||
name,
|
||||
descriptor.enumerable,
|
||||
@@ -605,36 +603,55 @@ fn trusted_type_policy_declared_methods_preserve_surface() {
|
||||
const html = htmlOnly.createHTML("ok");
|
||||
const script = full.createScript("1 + 1");
|
||||
const scriptURL = full.createScriptURL("data:text/javascript,");
|
||||
const missingCallbackErrors = ["createScript", "createScriptURL"].map(name => {
|
||||
const errorName = callback => {
|
||||
try {
|
||||
htmlOnly[name]("missing");
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error && error.name;
|
||||
return error.constructor.name;
|
||||
}
|
||||
});
|
||||
};
|
||||
const nameDescriptor = Object.getOwnPropertyDescriptor(prototype, "name");
|
||||
|
||||
return JSON.stringify({
|
||||
htmlOnlyKeys: Object.keys(htmlOnly).join(","),
|
||||
fullKeys: Object.keys(full).join(","),
|
||||
htmlOnlyMethods: ["createHTML", "createScript", "createScriptURL"].map(name => describe(htmlOnly, name)),
|
||||
fullMethods: ["createHTML", "createScript", "createScriptURL"].map(name => describe(full, name)),
|
||||
ownMethods: ["createHTML", "createScript", "createScriptURL"].map(name => Object.hasOwn(htmlOnly, name)),
|
||||
prototypeMethods: ["createHTML", "createScript", "createScriptURL"].map(describe),
|
||||
name: [
|
||||
htmlOnly.name,
|
||||
full.name,
|
||||
typeof nameDescriptor.get,
|
||||
nameDescriptor.set === undefined,
|
||||
nameDescriptor.enumerable,
|
||||
nameDescriptor.configurable,
|
||||
Object.hasOwn(htmlOnly, "name")
|
||||
],
|
||||
brands: [
|
||||
htmlOnly instanceof TrustedTypePolicy,
|
||||
full instanceof TrustedTypePolicy,
|
||||
Object.getPrototypeOf(htmlOnly) === prototype,
|
||||
htmlOnly.createHTML === full.createHTML
|
||||
],
|
||||
missingCallbackErrors: [
|
||||
errorName(() => htmlOnly.createScript("1 + 1")),
|
||||
errorName(() => htmlOnly.createScriptURL("data:text/javascript,"))
|
||||
],
|
||||
trusted: [
|
||||
trustedTypes.isHTML(html),
|
||||
trustedTypes.isScript(script),
|
||||
trustedTypes.isScriptURL(scriptURL)
|
||||
],
|
||||
values: [String(html), String(script), String(scriptURL)],
|
||||
missingCallbackErrors
|
||||
values: [String(html), String(script), String(scriptURL)]
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("TrustedTypePolicy optional method descriptors should evaluate");
|
||||
.expect("TrustedTypePolicy interface descriptors should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"htmlOnlyKeys":"","fullKeys":"","htmlOnlyMethods":["createHTML:false:true:true:function:createHTML:1","createScript:false:true:true:function:createScript:1","createScriptURL:false:true:true:function:createScriptURL:1"],"fullMethods":["createHTML:false:true:true:function:createHTML:1","createScript:false:true:true:function:createScript:1","createScriptURL:false:true:true:function:createScriptURL:1"],"trusted":[true,true,true],"values":["<b>ok</b>","1 + 1","data:text/javascript,"],"missingCallbackErrors":["TypeError","TypeError"]}"#
|
||||
r#"{"htmlOnlyKeys":"","fullKeys":"","ownMethods":[false,false,false],"prototypeMethods":["createHTML:true:true:true:function:createHTML:1","createScript:true:true:true:function:createScript:1","createScriptURL:true:true:true:function:createScriptURL:1"],"name":["html-only","full","function",true,true,true,false],"brands":[true,true,true,true],"missingCallbackErrors":["TypeError","TypeError"],"trusted":[true,true,true],"values":["<b>ok</b>","1 + 1","data:text/javascript,"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -98,6 +98,7 @@ fn trusted_type_policy_callbacks_follow_webidl_dictionary_and_callback_rules() {
|
||||
|
||||
return JSON.stringify({
|
||||
emptyMethods: [methodTypes(emptyFromNull), methodTypes(emptyFromOmission)],
|
||||
policyBrand: nullPolicy instanceof TrustedTypePolicy,
|
||||
nullResults,
|
||||
observed,
|
||||
variadicResults,
|
||||
@@ -107,6 +108,7 @@ fn trusted_type_policy_callbacks_follow_webidl_dictionary_and_callback_rules() {
|
||||
errorName(() => trustedTypes.createPolicy("throwing-getter", {
|
||||
get createHTML() { throw new RangeError("getter"); }
|
||||
})),
|
||||
errorName(() => emptyFromNull.createHTML("missing callback")),
|
||||
errorName(() => nullPolicy.createHTML()),
|
||||
errorName(() => emptyFromOmission.createHTML("html")),
|
||||
errorName(() => emptyFromOmission.createScript("script")),
|
||||
@@ -120,7 +122,7 @@ fn trusted_type_policy_callbacks_follow_webidl_dictionary_and_callback_rules() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"emptyMethods":[["function","function","function"],["function","function","function"]],"nullResults":["","",""],"observed":[true,"converted",2,true,42],"variadicResults":["abc","abundefined","a123null"],"errors":["TypeError","TypeError","RangeError","TypeError","TypeError","TypeError","TypeError"]}"#
|
||||
r#"{"emptyMethods":[["function","function","function"],["function","function","function"]],"policyBrand":true,"nullResults":["","",""],"observed":[true,"converted",2,true,42],"variadicResults":["abc","abundefined","a123null"],"errors":["TypeError","TypeError","RangeError","TypeError","TypeError","TypeError","TypeError","TypeError"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -196,6 +198,64 @@ fn trusted_type_factory_interface_exposes_stable_branded_empty_values() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn trusted_type_factory_default_policy_getter_tracks_the_branded_policy() {
|
||||
let mut vm = new_storage_test_vm("https://trusted-type-factory-default-policy.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const errorName = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error.constructor.name;
|
||||
}
|
||||
};
|
||||
const initial = trustedTypes.defaultPolicy;
|
||||
const policy = trustedTypes.createPolicy("default", { createHTML: value => value });
|
||||
const other = trustedTypes.createPolicy("other", { createHTML: value => value });
|
||||
const descriptor = Object.getOwnPropertyDescriptor(
|
||||
TrustedTypePolicyFactory.prototype,
|
||||
"defaultPolicy"
|
||||
);
|
||||
const errors = [
|
||||
errorName(() => {
|
||||
"use strict";
|
||||
trustedTypes.defaultPolicy = other;
|
||||
}),
|
||||
errorName(() => descriptor.get.call({})),
|
||||
errorName(() => new TrustedTypePolicy())
|
||||
];
|
||||
return JSON.stringify({
|
||||
initialIsNull: initial === null,
|
||||
policy: [
|
||||
policy instanceof TrustedTypePolicy,
|
||||
policy.name,
|
||||
trustedTypes.defaultPolicy === policy,
|
||||
trustedTypes.defaultPolicy !== other
|
||||
],
|
||||
descriptor: [
|
||||
typeof descriptor.get,
|
||||
descriptor.set === undefined,
|
||||
descriptor.enumerable,
|
||||
descriptor.configurable
|
||||
],
|
||||
errors
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("TrustedTypePolicyFactory default policy probe should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"initialIsNull":true,"policy":[true,"default",true,true],"descriptor":["function",true,true,true],"errors":["TypeError","TypeError","TypeError"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_markup_sinks_enforce_trusted_html_and_standard_sink_names() {
|
||||
let mut vm = new_storage_test_vm("https://element-markup-trusted-types.test/");
|
||||
|
||||
Reference in New Issue
Block a user