1use std::cell::RefCell;
5
6use dom_struct::dom_struct;
7use html5ever::{LocalName, Namespace, QualName, local_name, ns};
8use js::jsval::NullValue;
9use js::rust::HandleValue;
10use script_bindings::conversions::SafeToJSValConvertible;
11
12use crate::conversions::Convert;
13use crate::dom::bindings::codegen::Bindings::TrustedTypePolicyFactoryBinding::{
14 TrustedTypePolicyFactoryMethods, TrustedTypePolicyOptions,
15};
16use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
17use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
18use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
19use crate::dom::bindings::conversions::root_from_handlevalue;
20use crate::dom::bindings::error::{Error, Fallible};
21use crate::dom::bindings::inheritance::Castable;
22use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_cx};
23use crate::dom::bindings::root::{DomRoot, MutNullableDom};
24use crate::dom::bindings::str::DOMString;
25use crate::dom::csp::CspReporting;
26use crate::dom::eventtarget::EventTarget;
27use crate::dom::globalscope::GlobalScope;
28use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
29use crate::dom::trustedtypes::trustedscript::TrustedScript;
30use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
31use crate::dom::trustedtypes::trustedtypepolicy::{TrustedType, TrustedTypePolicy};
32use crate::dom::types::WorkerGlobalScope;
33use crate::dom::window::Window;
34use crate::script_runtime::{CanGc, JSContext};
35
36#[dom_struct]
37pub struct TrustedTypePolicyFactory {
38 reflector_: Reflector,
39
40 default_policy: MutNullableDom<TrustedTypePolicy>,
41 policy_names: RefCell<Vec<String>>,
42}
43
44pub(crate) static DEFAULT_SCRIPT_SINK_GROUP: &str = "'script'";
45
46impl Convert<DOMString> for TrustedTypeOrString {
49 fn convert(self) -> DOMString {
50 match self {
51 TrustedTypeOrString::TrustedHTML(trusted_html) => trusted_html.data().clone(),
52 TrustedTypeOrString::TrustedScript(trusted_script) => trusted_script.data().clone(),
53 TrustedTypeOrString::TrustedScriptURL(trusted_script_url) => {
54 trusted_script_url.data().clone()
55 },
56 TrustedTypeOrString::String(str_) => str_,
57 }
58 }
59}
60
61impl TrustedTypePolicyFactory {
62 fn new_inherited() -> Self {
63 Self {
64 reflector_: Reflector::new(),
65 default_policy: Default::default(),
66 policy_names: RefCell::new(vec![]),
67 }
68 }
69
70 pub(crate) fn new(cx: &mut js::context::JSContext, global: &GlobalScope) -> DomRoot<Self> {
71 reflect_dom_object_with_cx(Box::new(Self::new_inherited()), global, cx)
72 }
73
74 fn create_trusted_type_policy(
76 &self,
77 cx: &mut js::context::JSContext,
78 policy_name: String,
79 options: &TrustedTypePolicyOptions,
80 global: &GlobalScope,
81 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
82 {
84 let policy_names = self.policy_names.borrow();
87 let policy_names: Vec<&str> = policy_names.iter().map(String::as_ref).collect();
88 let allowed_by_csp = global
89 .get_csp_list()
90 .is_trusted_type_policy_creation_allowed(global, &policy_name, &policy_names);
91
92 if !allowed_by_csp {
94 return Err(Error::Type(c"Not allowed by CSP".to_owned()));
95 }
96 }
97
98 if policy_name == "default" && self.default_policy.get().is_some() {
101 return Err(Error::Type(
102 c"Already set default policy for factory".to_owned(),
103 ));
104 }
105
106 let policy = TrustedTypePolicy::new(cx, policy_name.clone(), options, global);
112 if policy_name == "default" {
114 self.default_policy.set(Some(&policy))
115 }
116 self.policy_names.borrow_mut().push(policy_name);
118 Ok(policy)
120 }
121
122 #[expect(clippy::if_same_then_else)]
124 fn get_trusted_type_data_for_attribute(
125 element_namespace: &Namespace,
126 element_name: &LocalName,
127 attribute: &str,
128 attribute_namespace: Option<&Namespace>,
129 ) -> Option<(TrustedType, String)> {
130 if attribute_namespace.is_none() &&
136 matches!(*element_namespace, ns!(html) | ns!(svg) | ns!(mathml)) &&
137 EventTarget::is_content_event_handler(attribute)
138 {
139 return Some((
141 TrustedType::TrustedScript,
142 "Element ".to_owned() + attribute,
143 ));
144 }
145 if *element_namespace == ns!(html) &&
150 *element_name == local_name!("iframe") &&
151 attribute_namespace.is_none() &&
152 attribute == "srcdoc"
153 {
154 Some((
155 TrustedType::TrustedHTML,
156 "HTMLIFrameElement srcdoc".to_owned(),
157 ))
158 } else if *element_namespace == ns!(html) &&
159 *element_name == local_name!("script") &&
160 attribute_namespace.is_none() &&
161 attribute == "src"
162 {
163 Some((
164 TrustedType::TrustedScriptURL,
165 "HTMLScriptElement src".to_owned(),
166 ))
167 } else if *element_namespace == ns!(svg) &&
168 *element_name == local_name!("script") &&
169 attribute_namespace.is_none() &&
170 attribute == "href"
171 {
172 Some((
173 TrustedType::TrustedScriptURL,
174 "SVGScriptElement href".to_owned(),
175 ))
176 } else if *element_namespace == ns!(svg) &&
177 *element_name == local_name!("script") &&
178 attribute_namespace == Some(&ns!(xlink)) &&
179 attribute == "href"
180 {
181 Some((
182 TrustedType::TrustedScriptURL,
183 "SVGScriptElement href".to_owned(),
184 ))
185 } else {
186 None
187 }
188 }
189
190 pub(crate) fn get_trusted_types_compliant_attribute_value(
192 cx: &mut js::context::JSContext,
193 element_namespace: &Namespace,
194 element_name: &LocalName,
195 attribute: &str,
196 attribute_namespace: Option<&Namespace>,
197 new_value: TrustedTypeOrString,
198 global: &GlobalScope,
199 ) -> Fallible<DOMString> {
200 let attribute_namespace =
202 attribute_namespace.and_then(|a| if *a == ns!() { None } else { Some(a) });
203 let Some(attribute_data) = Self::get_trusted_type_data_for_attribute(
206 element_namespace,
207 element_name,
208 attribute,
209 attribute_namespace,
210 ) else {
211 return Ok(new_value.convert());
216 };
217 let (expected_type, sink) = attribute_data;
220 let new_value = if let TrustedTypeOrString::String(str_) = new_value {
221 str_
222 } else {
223 if expected_type.matches_idl_trusted_type(&new_value) {
228 return Ok(new_value.convert());
229 }
230 new_value.convert()
231 };
232 Self::get_trusted_type_compliant_string(
235 cx,
236 expected_type,
237 global,
238 new_value,
239 &sink,
240 DEFAULT_SCRIPT_SINK_GROUP,
241 )
242 }
243
244 pub(crate) fn process_value_with_default_policy(
246 cx: &mut js::context::JSContext,
247 expected_type: TrustedType,
248 global: &GlobalScope,
249 input: DOMString,
250 sink: &str,
251 ) -> Fallible<Option<DOMString>> {
252 let global_policy_factory = global.trusted_types(cx);
254 let default_policy = match global_policy_factory.default_policy.get() {
255 None => return Ok(None),
256 Some(default_policy) => default_policy,
257 };
258 rooted!(&in(cx) let mut trusted_type_name_value = NullValue());
261 expected_type.as_ref().safe_to_jsval(
262 cx.into(),
263 trusted_type_name_value.handle_mut(),
264 CanGc::from_cx(cx),
265 );
266
267 rooted!(&in(cx) let mut sink_value = NullValue());
268 sink.safe_to_jsval(cx.into(), sink_value.handle_mut(), CanGc::from_cx(cx));
269
270 let arguments = vec![trusted_type_name_value.handle(), sink_value.handle()];
271 let policy_value = default_policy.get_trusted_type_policy_value(
272 cx,
273 expected_type,
274 input,
275 arguments,
276 false,
277 );
278 let data_string = match policy_value {
279 Err(error) => return Err(error),
281 Ok(policy_value) => match policy_value {
282 None => return Ok(None),
284 Some(policy_value) => policy_value,
286 },
287 };
288 Ok(Some(data_string))
289 }
290 pub(crate) fn get_trusted_type_compliant_string(
293 cx: &mut js::context::JSContext,
294 expected_type: TrustedType,
295 global: &GlobalScope,
296 input: DOMString,
297 sink: &str,
298 sink_group: &str,
299 ) -> Fallible<DOMString> {
300 let require_trusted_types = global
303 .get_csp_list()
304 .does_sink_type_require_trusted_types(sink_group, true);
305 if !require_trusted_types {
307 return Ok(input);
308 }
309 let converted_input = TrustedTypePolicyFactory::process_value_with_default_policy(
312 cx,
313 expected_type,
314 global,
315 input.clone(),
316 sink,
317 );
318 match converted_input? {
320 None => {
322 let is_blocked = global
326 .get_csp_list()
327 .should_sink_type_mismatch_violation_be_blocked_by_csp(
328 global,
329 sink,
330 sink_group,
331 &input.str(),
332 );
333 if !is_blocked {
335 Ok(input)
336 } else {
337 Err(Error::Type(
339 c"Cannot set value, expected trusted type".to_owned(),
340 ))
341 }
342 },
343 Some(converted_input) => Ok(converted_input),
345 }
346 }
349
350 pub(crate) fn is_trusted_script(
352 cx: JSContext,
353 value: HandleValue,
354 ) -> Result<DomRoot<TrustedScript>, ()> {
355 root_from_handlevalue::<TrustedScript>(value, cx)
356 }
357}
358
359impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
360 fn CreatePolicy(
362 &self,
363 cx: &mut js::context::JSContext,
364 policy_name: DOMString,
365 options: &TrustedTypePolicyOptions,
366 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
367 self.create_trusted_type_policy(cx, policy_name.to_string(), options, &self.global())
368 }
369 fn IsHTML(&self, cx: JSContext, value: HandleValue) -> bool {
371 root_from_handlevalue::<TrustedHTML>(value, cx).is_ok()
372 }
373 fn IsScript(&self, cx: JSContext, value: HandleValue) -> bool {
375 TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
376 }
377 fn IsScriptURL(&self, cx: JSContext, value: HandleValue) -> bool {
379 root_from_handlevalue::<TrustedScriptURL>(value, cx).is_ok()
380 }
381 fn EmptyHTML(&self, cx: &mut js::context::JSContext) -> DomRoot<TrustedHTML> {
383 TrustedHTML::new(cx, DOMString::new(), &self.global())
384 }
385 fn EmptyScript(&self, cx: &mut js::context::JSContext) -> DomRoot<TrustedScript> {
387 TrustedScript::new(cx, DOMString::new(), &self.global())
388 }
389 fn GetAttributeType(
391 &self,
392 tag_name: DOMString,
393 attribute: DOMString,
394 element_namespace: Option<DOMString>,
395 attribute_namespace: Option<DOMString>,
396 ) -> Option<DOMString> {
397 let local_name = tag_name.to_ascii_lowercase();
399 let attribute = attribute.to_ascii_lowercase();
401 let element_namespace = match element_namespace {
403 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
404 Some(_) | None => ns!(html),
405 };
406 let attribute_namespace = match attribute_namespace {
408 Some(namespace) if !namespace.is_empty() => Some(Namespace::from(namespace)),
409 Some(_) | None => None,
410 };
411 TrustedTypePolicyFactory::get_trusted_type_data_for_attribute(
419 &element_namespace,
420 &LocalName::from(local_name),
421 &attribute,
422 attribute_namespace.as_ref(),
423 )
424 .map(|tuple| DOMString::from(tuple.0.as_ref()))
425 }
426 #[expect(clippy::if_same_then_else)]
428 fn GetPropertyType(
429 &self,
430 tag_name: DOMString,
431 property: DOMString,
432 element_namespace: Option<DOMString>,
433 ) -> Option<DOMString> {
434 let local_name = tag_name.to_ascii_lowercase();
436 let element_namespace = match element_namespace {
438 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
439 Some(_) | None => ns!(html),
440 };
441 let interface = QualName::new(None, element_namespace, LocalName::from(local_name));
443 let mut expected_type = None;
445 let property = property.str();
449 if interface.ns == ns!(html) &&
450 interface.local == local_name!("iframe") &&
451 property == "srcdoc"
452 {
453 expected_type = Some(DOMString::from("TrustedHTML"))
454 } else if interface.ns == ns!(html) &&
455 interface.local == local_name!("script") &&
456 property == "innerText"
457 {
458 expected_type = Some(DOMString::from("TrustedScript"))
459 } else if interface.ns == ns!(html) &&
460 interface.local == local_name!("script") &&
461 property == "src"
462 {
463 expected_type = Some(DOMString::from("TrustedScriptURL"))
464 } else if interface.ns == ns!(html) &&
465 interface.local == local_name!("script") &&
466 property == "text"
467 {
468 expected_type = Some(DOMString::from("TrustedScript"))
469 } else if interface.ns == ns!(html) &&
470 interface.local == local_name!("script") &&
471 property == "textContent"
472 {
473 expected_type = Some(DOMString::from("TrustedScript"))
474 } else if property == "innerHTML" {
475 expected_type = Some(DOMString::from("TrustedHTML"))
476 } else if property == "outerHTML" {
477 expected_type = Some(DOMString::from("TrustedHTML"))
478 }
479 expected_type
481 }
482 fn GetDefaultPolicy(&self) -> Option<DomRoot<TrustedTypePolicy>> {
484 self.default_policy.get()
485 }
486}
487
488impl GlobalScope {
489 fn trusted_types(&self, cx: &mut js::context::JSContext) -> DomRoot<TrustedTypePolicyFactory> {
490 if let Some(window) = self.downcast::<Window>() {
491 return window.TrustedTypes(cx);
492 }
493 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
494 return worker.TrustedTypes(cx);
495 }
496 unreachable!();
497 }
498}