1use std::cell::RefCell;
5
6use dom_struct::dom_struct;
7use html5ever::{LocalName, Namespace, QualName, local_name, ns};
8use js::context::JSContext;
9use js::conversions::ToJSValConvertible;
10use js::jsval::NullValue;
11use js::rust::HandleValue;
12use script_bindings::reflector::{Reflector, reflect_dom_object};
13
14use crate::conversions::Convert;
15use crate::dom::bindings::codegen::Bindings::TrustedTypePolicyFactoryBinding::{
16 TrustedTypePolicyFactoryMethods, TrustedTypePolicyOptions,
17};
18use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
19use crate::dom::bindings::codegen::Bindings::WorkerGlobalScopeBinding::WorkerGlobalScopeMethods;
20use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
21use crate::dom::bindings::conversions::root_from_handlevalue;
22use crate::dom::bindings::error::{Error, Fallible};
23use crate::dom::bindings::inheritance::Castable;
24use crate::dom::bindings::reflector::DomGlobal;
25use crate::dom::bindings::root::{DomRoot, MutNullableDom};
26use crate::dom::bindings::str::DOMString;
27use crate::dom::csp::CspReporting;
28use crate::dom::eventtarget::EventTarget;
29use crate::dom::globalscope::GlobalScope;
30use crate::dom::trustedtypes::trustedhtml::TrustedHTML;
31use crate::dom::trustedtypes::trustedscript::TrustedScript;
32use crate::dom::trustedtypes::trustedscripturl::TrustedScriptURL;
33use crate::dom::trustedtypes::trustedtypepolicy::{TrustedType, TrustedTypePolicy};
34use crate::dom::types::WorkerGlobalScope;
35use crate::dom::window::Window;
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().root(),
53 TrustedTypeOrString::TrustedScript(trusted_script) => {
54 trusted_script.data().clone().root()
55 },
56 TrustedTypeOrString::TrustedScriptURL(trusted_script_url) => {
57 trusted_script_url.data().clone().root()
58 },
59 TrustedTypeOrString::String(str_) => str_,
60 }
61 }
62}
63
64impl TrustedTypePolicyFactory {
65 fn new_inherited() -> Self {
66 Self {
67 reflector_: Reflector::new(),
68 default_policy: Default::default(),
69 policy_names: RefCell::new(vec![]),
70 }
71 }
72
73 pub(crate) fn new(cx: &mut JSContext, global: &GlobalScope) -> DomRoot<Self> {
74 reflect_dom_object(cx, Box::new(Self::new_inherited()), global)
75 }
76
77 fn create_trusted_type_policy(
79 &self,
80 cx: &mut JSContext,
81 policy_name: String,
82 options: &TrustedTypePolicyOptions,
83 global: &GlobalScope,
84 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
85 {
87 let policy_names = self.policy_names.borrow();
90 let policy_names: Vec<&str> = policy_names.iter().map(String::as_ref).collect();
91 let allowed_by_csp = global
92 .get_csp_list()
93 .is_trusted_type_policy_creation_allowed(cx, global, &policy_name, &policy_names);
94
95 if !allowed_by_csp {
97 return Err(Error::Type(c"Not allowed by CSP".to_owned()));
98 }
99 }
100
101 if policy_name == "default" && self.default_policy.get().is_some() {
104 return Err(Error::Type(
105 c"Already set default policy for factory".to_owned(),
106 ));
107 }
108
109 let policy = TrustedTypePolicy::new(cx, policy_name.clone(), options, global);
115 if policy_name == "default" {
117 self.default_policy.set(Some(&policy))
118 }
119 self.policy_names.borrow_mut().push(policy_name);
121 Ok(policy)
123 }
124
125 #[expect(clippy::if_same_then_else)]
127 fn get_trusted_type_data_for_attribute(
128 element_namespace: &Namespace,
129 element_name: &LocalName,
130 attribute: &str,
131 attribute_namespace: Option<&Namespace>,
132 ) -> Option<(TrustedType, String)> {
133 if attribute_namespace.is_none() &&
139 matches!(*element_namespace, ns!(html) | ns!(svg) | ns!(mathml)) &&
140 EventTarget::is_content_event_handler(attribute)
141 {
142 return Some((
144 TrustedType::TrustedScript,
145 "Element ".to_owned() + attribute,
146 ));
147 }
148 if *element_namespace == ns!(html) &&
153 *element_name == local_name!("iframe") &&
154 attribute_namespace.is_none() &&
155 attribute == "srcdoc"
156 {
157 Some((
158 TrustedType::TrustedHTML,
159 "HTMLIFrameElement srcdoc".to_owned(),
160 ))
161 } else if *element_namespace == ns!(html) &&
162 *element_name == local_name!("script") &&
163 attribute_namespace.is_none() &&
164 attribute == "src"
165 {
166 Some((
167 TrustedType::TrustedScriptURL,
168 "HTMLScriptElement src".to_owned(),
169 ))
170 } else if *element_namespace == ns!(svg) &&
171 *element_name == local_name!("script") &&
172 attribute_namespace.is_none() &&
173 attribute == "href"
174 {
175 Some((
176 TrustedType::TrustedScriptURL,
177 "SVGScriptElement href".to_owned(),
178 ))
179 } else if *element_namespace == ns!(svg) &&
180 *element_name == local_name!("script") &&
181 attribute_namespace == Some(&ns!(xlink)) &&
182 attribute == "href"
183 {
184 Some((
185 TrustedType::TrustedScriptURL,
186 "SVGScriptElement href".to_owned(),
187 ))
188 } else {
189 None
190 }
191 }
192
193 pub(crate) fn get_trusted_types_compliant_attribute_value(
195 cx: &mut JSContext,
196 element_namespace: &Namespace,
197 element_name: &LocalName,
198 attribute: &str,
199 attribute_namespace: Option<&Namespace>,
200 new_value: TrustedTypeOrString,
201 global: &GlobalScope,
202 ) -> Fallible<DOMString> {
203 let attribute_namespace = attribute_namespace.filter(|a| **a != ns!());
205 let Some(attribute_data) = Self::get_trusted_type_data_for_attribute(
208 element_namespace,
209 element_name,
210 attribute,
211 attribute_namespace,
212 ) else {
213 return Ok(new_value.convert());
218 };
219 let (expected_type, sink) = attribute_data;
222 let new_value = if let TrustedTypeOrString::String(str_) = new_value {
223 str_
224 } else {
225 if expected_type.matches_idl_trusted_type(&new_value) {
230 return Ok(new_value.convert());
231 }
232 new_value.convert()
233 };
234 Self::get_trusted_type_compliant_string(
237 cx,
238 expected_type,
239 global,
240 new_value,
241 &sink,
242 DEFAULT_SCRIPT_SINK_GROUP,
243 )
244 }
245
246 pub(crate) fn process_value_with_default_policy(
248 cx: &mut JSContext,
249 expected_type: TrustedType,
250 global: &GlobalScope,
251 input: DOMString,
252 sink: &str,
253 ) -> Fallible<Option<DOMString>> {
254 let global_policy_factory = global.trusted_types(cx);
256 let default_policy = match global_policy_factory.default_policy.get() {
257 None => return Ok(None),
258 Some(default_policy) => default_policy,
259 };
260 rooted!(&in(cx) let mut trusted_type_name_value = NullValue());
263 expected_type
264 .as_ref()
265 .to_jsval(cx, trusted_type_name_value.handle_mut());
266
267 rooted!(&in(cx) let mut sink_value = NullValue());
268 sink.to_jsval(cx, sink_value.handle_mut());
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 #[expect(clippy::question_mark, reason = "better match the spec")]
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 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 cx,
330 global,
331 sink,
332 sink_group,
333 &input.str(),
334 );
335 if !is_blocked {
337 Ok(input)
338 } else {
339 Err(Error::Type(
341 c"Cannot set value, expected trusted type".to_owned(),
342 ))
343 }
344 },
345 Some(converted_input) => Ok(converted_input),
347 }
348 }
351
352 pub(crate) fn is_trusted_script(
354 cx: &mut JSContext,
355 value: HandleValue,
356 ) -> Result<DomRoot<TrustedScript>, ()> {
357 root_from_handlevalue::<TrustedScript>(cx, value)
358 }
359}
360
361impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
362 fn CreatePolicy(
364 &self,
365 cx: &mut JSContext,
366 policy_name: DOMString,
367 options: &TrustedTypePolicyOptions,
368 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
369 self.create_trusted_type_policy(cx, String::from(policy_name), options, &self.global())
370 }
371 fn IsHTML(&self, cx: &mut JSContext, value: HandleValue) -> bool {
373 root_from_handlevalue::<TrustedHTML>(cx, value).is_ok()
374 }
375 fn IsScript(&self, cx: &mut JSContext, value: HandleValue) -> bool {
377 TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
378 }
379 fn IsScriptURL(&self, cx: &mut JSContext, value: HandleValue) -> bool {
381 root_from_handlevalue::<TrustedScriptURL>(cx, value).is_ok()
382 }
383 fn EmptyHTML(&self, cx: &mut JSContext) -> DomRoot<TrustedHTML> {
385 TrustedHTML::new(cx, DOMString::new(), &self.global())
386 }
387 fn EmptyScript(&self, cx: &mut JSContext) -> DomRoot<TrustedScript> {
389 TrustedScript::new(cx, DOMString::new(), &self.global())
390 }
391 fn GetAttributeType(
393 &self,
394 tag_name: DOMString,
395 attribute: DOMString,
396 element_namespace: Option<DOMString>,
397 attribute_namespace: Option<DOMString>,
398 ) -> Option<DOMString> {
399 let local_name = tag_name.to_ascii_lowercase();
401 let attribute = attribute.to_ascii_lowercase();
403 let element_namespace = match element_namespace {
405 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
406 Some(_) | None => ns!(html),
407 };
408 let attribute_namespace = match attribute_namespace {
410 Some(namespace) if !namespace.is_empty() => Some(Namespace::from(namespace)),
411 Some(_) | None => None,
412 };
413 TrustedTypePolicyFactory::get_trusted_type_data_for_attribute(
421 &element_namespace,
422 &LocalName::from(local_name),
423 &attribute,
424 attribute_namespace.as_ref(),
425 )
426 .map(|tuple| DOMString::from(tuple.0.as_ref()))
427 }
428 #[expect(clippy::if_same_then_else)]
430 fn GetPropertyType(
431 &self,
432 tag_name: DOMString,
433 property: DOMString,
434 element_namespace: Option<DOMString>,
435 ) -> Option<DOMString> {
436 let local_name = tag_name.to_ascii_lowercase();
438 let element_namespace = match element_namespace {
440 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
441 Some(_) | None => ns!(html),
442 };
443 let interface = QualName::new(None, element_namespace, LocalName::from(local_name));
445 let mut expected_type = None;
447 let property = property.str();
451 if interface.ns == ns!(html) &&
452 interface.local == local_name!("iframe") &&
453 property == "srcdoc"
454 {
455 expected_type = Some(DOMString::from_static("TrustedHTML"))
456 } else if interface.ns == ns!(html) &&
457 interface.local == local_name!("script") &&
458 property == "innerText"
459 {
460 expected_type = Some(DOMString::from_static("TrustedScript"))
461 } else if interface.ns == ns!(html) &&
462 interface.local == local_name!("script") &&
463 property == "src"
464 {
465 expected_type = Some(DOMString::from_static("TrustedScriptURL"))
466 } else if interface.ns == ns!(html) &&
467 interface.local == local_name!("script") &&
468 property == "text"
469 {
470 expected_type = Some(DOMString::from_static("TrustedScript"))
471 } else if interface.ns == ns!(html) &&
472 interface.local == local_name!("script") &&
473 property == "textContent"
474 {
475 expected_type = Some(DOMString::from_static("TrustedScript"))
476 } else if property == "innerHTML" {
477 expected_type = Some(DOMString::from_static("TrustedHTML"))
478 } else if property == "outerHTML" {
479 expected_type = Some(DOMString::from_static("TrustedHTML"))
480 }
481 expected_type
483 }
484 fn GetDefaultPolicy(&self) -> Option<DomRoot<TrustedTypePolicy>> {
486 self.default_policy.get()
487 }
488}
489
490impl GlobalScope {
491 fn trusted_types(&self, cx: &mut JSContext) -> DomRoot<TrustedTypePolicyFactory> {
492 if let Some(window) = self.downcast::<Window>() {
493 return window.TrustedTypes(cx);
494 }
495 if let Some(worker) = self.downcast::<WorkerGlobalScope>() {
496 return worker.TrustedTypes(cx);
497 }
498 unreachable!();
499 }
500}