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