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::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(cx, 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
263 .as_ref()
264 .safe_to_jsval(cx, trusted_type_name_value.handle_mut());
265
266 rooted!(&in(cx) let mut sink_value = NullValue());
267 sink.safe_to_jsval(cx, sink_value.handle_mut());
268
269 let arguments = vec![trusted_type_name_value.handle(), sink_value.handle()];
270 let policy_value = default_policy.get_trusted_type_policy_value(
271 cx,
272 expected_type,
273 input,
274 arguments,
275 false,
276 );
277 let data_string = match policy_value {
278 Err(error) => return Err(error),
280 Ok(policy_value) => match policy_value {
281 None => return Ok(None),
283 Some(policy_value) => policy_value,
285 },
286 };
287 Ok(Some(data_string))
288 }
289 pub(crate) fn get_trusted_type_compliant_string(
292 cx: &mut js::context::JSContext,
293 expected_type: TrustedType,
294 global: &GlobalScope,
295 input: DOMString,
296 sink: &str,
297 sink_group: &str,
298 ) -> Fallible<DOMString> {
299 let require_trusted_types = global
302 .get_csp_list()
303 .does_sink_type_require_trusted_types(sink_group, true);
304 if !require_trusted_types {
306 return Ok(input);
307 }
308 let converted_input = TrustedTypePolicyFactory::process_value_with_default_policy(
311 cx,
312 expected_type,
313 global,
314 input.clone(),
315 sink,
316 );
317 match converted_input? {
319 None => {
321 let is_blocked = global
325 .get_csp_list()
326 .should_sink_type_mismatch_violation_be_blocked_by_csp(
327 cx,
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, String::from(policy_name), 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}