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::UnionTypes::TrustedHTMLOrTrustedScriptOrTrustedScriptURLOrString as TrustedTypeOrString;
17use crate::dom::bindings::conversions::root_from_handlevalue;
18use crate::dom::bindings::error::{Error, Fallible};
19use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object};
20use crate::dom::bindings::root::{DomRoot, MutNullableDom};
21use crate::dom::bindings::str::DOMString;
22use crate::dom::csp::CspReporting;
23use crate::dom::eventtarget::EventTarget;
24use crate::dom::globalscope::GlobalScope;
25use crate::dom::trustedhtml::TrustedHTML;
26use crate::dom::trustedscript::TrustedScript;
27use crate::dom::trustedscripturl::TrustedScriptURL;
28use crate::dom::trustedtypepolicy::{TrustedType, TrustedTypePolicy};
29use crate::script_runtime::{CanGc, JSContext};
30
31#[dom_struct]
32pub struct TrustedTypePolicyFactory {
33 reflector_: Reflector,
34
35 default_policy: MutNullableDom<TrustedTypePolicy>,
36 policy_names: RefCell<Vec<String>>,
37}
38
39pub(crate) static DEFAULT_SCRIPT_SINK_GROUP: &str = "'script'";
40
41impl Convert<DOMString> for TrustedTypeOrString {
44 fn convert(self) -> DOMString {
45 match self {
46 TrustedTypeOrString::TrustedHTML(trusted_html) => trusted_html.data().clone(),
47 TrustedTypeOrString::TrustedScript(trusted_script) => trusted_script.data().clone(),
48 TrustedTypeOrString::TrustedScriptURL(trusted_script_url) => {
49 trusted_script_url.data().clone()
50 },
51 TrustedTypeOrString::String(str_) => str_,
52 }
53 }
54}
55
56impl TrustedTypePolicyFactory {
57 fn new_inherited() -> Self {
58 Self {
59 reflector_: Reflector::new(),
60 default_policy: Default::default(),
61 policy_names: RefCell::new(vec![]),
62 }
63 }
64
65 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
66 reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
67 }
68
69 fn create_trusted_type_policy(
71 &self,
72 policy_name: String,
73 options: &TrustedTypePolicyOptions,
74 global: &GlobalScope,
75 can_gc: CanGc,
76 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
77 {
79 let policy_names = self.policy_names.borrow();
82 let policy_names: Vec<&str> = policy_names.iter().map(String::as_ref).collect();
83 let allowed_by_csp = global
84 .get_csp_list()
85 .is_trusted_type_policy_creation_allowed(global, &policy_name, &policy_names);
86
87 if !allowed_by_csp {
89 return Err(Error::Type("Not allowed by CSP".to_string()));
90 }
91 }
92
93 if policy_name == "default" && self.default_policy.get().is_some() {
96 return Err(Error::Type(
97 "Already set default policy for factory".to_string(),
98 ));
99 }
100
101 let policy = TrustedTypePolicy::new(policy_name.clone(), options, global, can_gc);
107 if policy_name == "default" {
109 self.default_policy.set(Some(&policy))
110 }
111 self.policy_names.borrow_mut().push(policy_name);
113 Ok(policy)
115 }
116
117 #[expect(clippy::if_same_then_else)]
119 fn get_trusted_type_data_for_attribute(
120 element_namespace: &Namespace,
121 element_name: &LocalName,
122 attribute: &str,
123 attribute_namespace: Option<&Namespace>,
124 ) -> Option<(TrustedType, String)> {
125 if attribute_namespace.is_none() &&
131 matches!(*element_namespace, ns!(html) | ns!(svg) | ns!(mathml)) &&
132 EventTarget::is_content_event_handler(attribute)
133 {
134 return Some((
136 TrustedType::TrustedScript,
137 "Element ".to_owned() + attribute,
138 ));
139 }
140 if *element_namespace == ns!(html) &&
145 *element_name == local_name!("iframe") &&
146 attribute_namespace.is_none() &&
147 attribute == "srcdoc"
148 {
149 Some((
150 TrustedType::TrustedHTML,
151 "HTMLIFrameElement srcdoc".to_owned(),
152 ))
153 } else if *element_namespace == ns!(html) &&
154 *element_name == local_name!("script") &&
155 attribute_namespace.is_none() &&
156 attribute == "src"
157 {
158 Some((
159 TrustedType::TrustedScriptURL,
160 "HTMLScriptElement src".to_owned(),
161 ))
162 } else if *element_namespace == ns!(svg) &&
163 *element_name == local_name!("script") &&
164 attribute_namespace.is_none() &&
165 attribute == "href"
166 {
167 Some((
168 TrustedType::TrustedScriptURL,
169 "SVGScriptElement href".to_owned(),
170 ))
171 } else if *element_namespace == ns!(svg) &&
172 *element_name == local_name!("script") &&
173 attribute_namespace == Some(&ns!(xlink)) &&
174 attribute == "href"
175 {
176 Some((
177 TrustedType::TrustedScriptURL,
178 "SVGScriptElement href".to_owned(),
179 ))
180 } else {
181 None
182 }
183 }
184
185 pub(crate) fn get_trusted_types_compliant_attribute_value(
187 element_namespace: &Namespace,
188 element_name: &LocalName,
189 attribute: &str,
190 attribute_namespace: Option<&Namespace>,
191 new_value: TrustedTypeOrString,
192 global: &GlobalScope,
193 can_gc: CanGc,
194 ) -> Fallible<DOMString> {
195 let attribute_namespace =
197 attribute_namespace.and_then(|a| if *a == ns!() { None } else { Some(a) });
198 let Some(attribute_data) = Self::get_trusted_type_data_for_attribute(
201 element_namespace,
202 element_name,
203 attribute,
204 attribute_namespace,
205 ) else {
206 return Ok(new_value.convert());
211 };
212 let (expected_type, sink) = attribute_data;
215 let new_value = if let TrustedTypeOrString::String(str_) = new_value {
216 str_
217 } else {
218 if expected_type.matches_idl_trusted_type(&new_value) {
223 return Ok(new_value.convert());
224 }
225 new_value.convert()
226 };
227 Self::get_trusted_type_compliant_string(
230 expected_type,
231 global,
232 new_value,
233 &sink,
234 DEFAULT_SCRIPT_SINK_GROUP,
235 can_gc,
236 )
237 }
238
239 pub(crate) fn process_value_with_default_policy(
241 expected_type: TrustedType,
242 global: &GlobalScope,
243 input: DOMString,
244 sink: &str,
245 can_gc: CanGc,
246 ) -> Fallible<Option<DOMString>> {
247 let global_policy_factory = global.trusted_types(can_gc);
249 let default_policy = match global_policy_factory.default_policy.get() {
250 None => return Ok(None),
251 Some(default_policy) => default_policy,
252 };
253 let cx = GlobalScope::get_cx();
254 rooted!(in(*cx) let mut trusted_type_name_value = NullValue());
257 expected_type.clone().as_ref().safe_to_jsval(
258 cx,
259 trusted_type_name_value.handle_mut(),
260 can_gc,
261 );
262
263 rooted!(in(*cx) let mut sink_value = NullValue());
264 sink.safe_to_jsval(cx, sink_value.handle_mut(), can_gc);
265
266 let arguments = vec![trusted_type_name_value.handle(), sink_value.handle()];
267 let policy_value = default_policy.get_trusted_type_policy_value(
268 expected_type,
269 input,
270 arguments,
271 false,
272 can_gc,
273 );
274 let data_string = match policy_value {
275 Err(error) => return Err(error),
277 Ok(policy_value) => match policy_value {
278 None => return Ok(None),
280 Some(policy_value) => policy_value,
282 },
283 };
284 Ok(Some(data_string))
285 }
286 pub(crate) fn get_trusted_type_compliant_string(
289 expected_type: TrustedType,
290 global: &GlobalScope,
291 input: DOMString,
292 sink: &str,
293 sink_group: &str,
294 can_gc: CanGc,
295 ) -> Fallible<DOMString> {
296 let require_trusted_types = global
299 .get_csp_list()
300 .does_sink_type_require_trusted_types(sink_group, true);
301 if !require_trusted_types {
303 return Ok(input);
304 }
305 let converted_input = TrustedTypePolicyFactory::process_value_with_default_policy(
308 expected_type,
309 global,
310 input.clone(),
311 sink,
312 can_gc,
313 );
314 match converted_input? {
316 None => {
318 let is_blocked = global
322 .get_csp_list()
323 .should_sink_type_mismatch_violation_be_blocked_by_csp(
324 global,
325 sink,
326 sink_group,
327 &input.str(),
328 );
329 if !is_blocked {
331 Ok(input)
332 } else {
333 Err(Error::Type(
335 "Cannot set value, expected trusted type".to_owned(),
336 ))
337 }
338 },
339 Some(converted_input) => Ok(converted_input),
341 }
342 }
345
346 pub(crate) fn is_trusted_script(
348 cx: JSContext,
349 value: HandleValue,
350 ) -> Result<DomRoot<TrustedScript>, ()> {
351 root_from_handlevalue::<TrustedScript>(value, cx)
352 }
353}
354
355impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
356 fn CreatePolicy(
358 &self,
359 policy_name: DOMString,
360 options: &TrustedTypePolicyOptions,
361 can_gc: CanGc,
362 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
363 self.create_trusted_type_policy(policy_name.to_string(), options, &self.global(), can_gc)
364 }
365 fn IsHTML(&self, cx: JSContext, value: HandleValue) -> bool {
367 root_from_handlevalue::<TrustedHTML>(value, cx).is_ok()
368 }
369 fn IsScript(&self, cx: JSContext, value: HandleValue) -> bool {
371 TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
372 }
373 fn IsScriptURL(&self, cx: JSContext, value: HandleValue) -> bool {
375 root_from_handlevalue::<TrustedScriptURL>(value, cx).is_ok()
376 }
377 fn EmptyHTML(&self, can_gc: CanGc) -> DomRoot<TrustedHTML> {
379 TrustedHTML::new(DOMString::new(), &self.global(), can_gc)
380 }
381 fn EmptyScript(&self, can_gc: CanGc) -> DomRoot<TrustedScript> {
383 TrustedScript::new(DOMString::new(), &self.global(), can_gc)
384 }
385 fn GetAttributeType(
387 &self,
388 tag_name: DOMString,
389 attribute: DOMString,
390 element_namespace: Option<DOMString>,
391 attribute_namespace: Option<DOMString>,
392 ) -> Option<DOMString> {
393 let local_name = tag_name.to_ascii_lowercase();
395 let attribute = attribute.to_ascii_lowercase();
397 let element_namespace = match element_namespace {
399 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
400 Some(_) | None => ns!(html),
401 };
402 let attribute_namespace = match attribute_namespace {
404 Some(namespace) if !namespace.is_empty() => Some(Namespace::from(namespace)),
405 Some(_) | None => None,
406 };
407 TrustedTypePolicyFactory::get_trusted_type_data_for_attribute(
415 &element_namespace,
416 &LocalName::from(local_name),
417 &attribute,
418 attribute_namespace.as_ref(),
419 )
420 .map(|tuple| DOMString::from(tuple.0.as_ref()))
421 }
422 #[expect(clippy::if_same_then_else)]
424 fn GetPropertyType(
425 &self,
426 tag_name: DOMString,
427 property: DOMString,
428 element_namespace: Option<DOMString>,
429 ) -> Option<DOMString> {
430 let local_name = tag_name.to_ascii_lowercase();
432 let element_namespace = match element_namespace {
434 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
435 Some(_) | None => ns!(html),
436 };
437 let interface = QualName::new(None, element_namespace, LocalName::from(local_name));
439 let mut expected_type = None;
441 let property = property.str();
445 if interface.ns == ns!(html) &&
446 interface.local == local_name!("iframe") &&
447 property == "srcdoc"
448 {
449 expected_type = Some(DOMString::from("TrustedHTML"))
450 } else if interface.ns == ns!(html) &&
451 interface.local == local_name!("script") &&
452 property == "innerText"
453 {
454 expected_type = Some(DOMString::from("TrustedScript"))
455 } else if interface.ns == ns!(html) &&
456 interface.local == local_name!("script") &&
457 property == "src"
458 {
459 expected_type = Some(DOMString::from("TrustedScriptURL"))
460 } else if interface.ns == ns!(html) &&
461 interface.local == local_name!("script") &&
462 property == "text"
463 {
464 expected_type = Some(DOMString::from("TrustedScript"))
465 } else if interface.ns == ns!(html) &&
466 interface.local == local_name!("script") &&
467 property == "textContent"
468 {
469 expected_type = Some(DOMString::from("TrustedScript"))
470 } else if property == "innerHTML" {
471 expected_type = Some(DOMString::from("TrustedHTML"))
472 } else if property == "outerHTML" {
473 expected_type = Some(DOMString::from("TrustedHTML"))
474 }
475 expected_type
477 }
478 fn GetDefaultPolicy(&self) -> Option<DomRoot<TrustedTypePolicy>> {
480 self.default_policy.get()
481 }
482}