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 {
42 fn convert(self) -> DOMString {
43 match self {
44 TrustedTypeOrString::TrustedHTML(trusted_html) => trusted_html.data(),
45 TrustedTypeOrString::TrustedScript(trusted_script) => trusted_script.data(),
46 TrustedTypeOrString::TrustedScriptURL(trusted_script_url) => trusted_script_url.data(),
47 TrustedTypeOrString::String(str_) => str_,
48 }
49 }
50}
51
52impl TrustedTypePolicyFactory {
53 fn new_inherited() -> Self {
54 Self {
55 reflector_: Reflector::new(),
56 default_policy: Default::default(),
57 policy_names: RefCell::new(vec![]),
58 }
59 }
60
61 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
62 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
63 reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
64 }
65
66 fn create_trusted_type_policy(
68 &self,
69 policy_name: String,
70 options: &TrustedTypePolicyOptions,
71 global: &GlobalScope,
72 can_gc: CanGc,
73 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
74 {
76 let policy_names = self.policy_names.borrow();
79 let policy_names: Vec<&str> = policy_names.iter().map(String::as_ref).collect();
80 let allowed_by_csp = global
81 .get_csp_list()
82 .is_trusted_type_policy_creation_allowed(global, &policy_name, &policy_names);
83
84 if !allowed_by_csp {
86 return Err(Error::Type("Not allowed by CSP".to_string()));
87 }
88 }
89
90 if policy_name == "default" && self.default_policy.get().is_some() {
93 return Err(Error::Type(
94 "Already set default policy for factory".to_string(),
95 ));
96 }
97
98 let policy = TrustedTypePolicy::new(policy_name.clone(), options, global, can_gc);
104 if policy_name == "default" {
106 self.default_policy.set(Some(&policy))
107 }
108 self.policy_names.borrow_mut().push(policy_name);
110 Ok(policy)
112 }
113
114 #[allow(clippy::if_same_then_else)]
116 fn get_trusted_type_data_for_attribute(
117 element_namespace: &Namespace,
118 element_name: &LocalName,
119 attribute: &str,
120 attribute_namespace: Option<&Namespace>,
121 ) -> Option<(TrustedType, String)> {
122 if attribute_namespace.is_none() &&
128 matches!(*element_namespace, ns!(html) | ns!(svg) | ns!(mathml)) &&
129 EventTarget::is_content_event_handler(attribute)
130 {
131 return Some((
133 TrustedType::TrustedScript,
134 "Element ".to_owned() + attribute,
135 ));
136 }
137 if *element_namespace == ns!(html) &&
142 *element_name == local_name!("iframe") &&
143 attribute_namespace.is_none() &&
144 attribute == "srcdoc"
145 {
146 Some((
147 TrustedType::TrustedHTML,
148 "HTMLIFrameElement srcdoc".to_owned(),
149 ))
150 } else if *element_namespace == ns!(html) &&
151 *element_name == local_name!("script") &&
152 attribute_namespace.is_none() &&
153 attribute == "src"
154 {
155 Some((
156 TrustedType::TrustedScriptURL,
157 "HTMLScriptElement src".to_owned(),
158 ))
159 } else if *element_namespace == ns!(svg) &&
160 *element_name == local_name!("script") &&
161 attribute_namespace.is_none() &&
162 attribute == "href"
163 {
164 Some((
165 TrustedType::TrustedScriptURL,
166 "SVGScriptElement href".to_owned(),
167 ))
168 } else if *element_namespace == ns!(svg) &&
169 *element_name == local_name!("script") &&
170 attribute_namespace == Some(&ns!(xlink)) &&
171 attribute == "href"
172 {
173 Some((
174 TrustedType::TrustedScriptURL,
175 "SVGScriptElement href".to_owned(),
176 ))
177 } else {
178 None
179 }
180 }
181
182 pub(crate) fn get_trusted_types_compliant_attribute_value(
184 element_namespace: &Namespace,
185 element_name: &LocalName,
186 attribute: &str,
187 attribute_namespace: Option<&Namespace>,
188 new_value: TrustedTypeOrString,
189 global: &GlobalScope,
190 can_gc: CanGc,
191 ) -> Fallible<DOMString> {
192 let attribute_namespace =
194 attribute_namespace.and_then(|a| if *a == ns!() { None } else { Some(a) });
195 let Some(attribute_data) = Self::get_trusted_type_data_for_attribute(
198 element_namespace,
199 element_name,
200 attribute,
201 attribute_namespace,
202 ) else {
203 return Ok(new_value.convert());
208 };
209 let (expected_type, sink) = attribute_data;
212 let new_value = if let TrustedTypeOrString::String(str_) = new_value {
213 str_
214 } else {
215 if expected_type.matches_idl_trusted_type(&new_value) {
220 return Ok(new_value.convert());
221 }
222 new_value.convert()
223 };
224 Self::get_trusted_type_compliant_string(
227 expected_type,
228 global,
229 new_value,
230 &sink,
231 DEFAULT_SCRIPT_SINK_GROUP,
232 can_gc,
233 )
234 }
235
236 pub(crate) fn process_value_with_default_policy(
238 expected_type: TrustedType,
239 global: &GlobalScope,
240 input: DOMString,
241 sink: &str,
242 can_gc: CanGc,
243 ) -> Fallible<Option<DOMString>> {
244 let global_policy_factory = global.trusted_types(can_gc);
246 let default_policy = match global_policy_factory.default_policy.get() {
247 None => return Ok(None),
248 Some(default_policy) => default_policy,
249 };
250 let cx = GlobalScope::get_cx();
251 rooted!(in(*cx) let mut trusted_type_name_value = NullValue());
254 expected_type
255 .clone()
256 .as_ref()
257 .safe_to_jsval(cx, trusted_type_name_value.handle_mut());
258
259 rooted!(in(*cx) let mut sink_value = NullValue());
260 sink.safe_to_jsval(cx, sink_value.handle_mut());
261
262 let arguments = vec![trusted_type_name_value.handle(), sink_value.handle()];
263 let policy_value = default_policy.get_trusted_type_policy_value(
264 expected_type,
265 input,
266 arguments,
267 false,
268 can_gc,
269 );
270 let data_string = match policy_value {
271 Err(error) => return Err(error),
273 Ok(policy_value) => match policy_value {
274 None => return Ok(None),
276 Some(policy_value) => policy_value,
278 },
279 };
280 Ok(Some(data_string))
281 }
282 pub(crate) fn get_trusted_type_compliant_string(
285 expected_type: TrustedType,
286 global: &GlobalScope,
287 input: DOMString,
288 sink: &str,
289 sink_group: &str,
290 can_gc: CanGc,
291 ) -> Fallible<DOMString> {
292 let require_trusted_types = global
295 .get_csp_list()
296 .does_sink_type_require_trusted_types(sink_group, true);
297 if !require_trusted_types {
299 return Ok(input);
300 }
301 let converted_input = TrustedTypePolicyFactory::process_value_with_default_policy(
304 expected_type,
305 global,
306 input.clone(),
307 sink,
308 can_gc,
309 );
310 match converted_input? {
312 None => {
314 let is_blocked = global
318 .get_csp_list()
319 .should_sink_type_mismatch_violation_be_blocked_by_csp(
320 global, sink, sink_group, &input,
321 );
322 if !is_blocked {
324 Ok(input)
325 } else {
326 Err(Error::Type(
328 "Cannot set value, expected trusted type".to_owned(),
329 ))
330 }
331 },
332 Some(converted_input) => Ok(converted_input),
334 }
335 }
338
339 #[allow(unsafe_code)]
341 pub(crate) fn is_trusted_script(
342 cx: JSContext,
343 value: HandleValue,
344 ) -> Result<DomRoot<TrustedScript>, ()> {
345 unsafe { root_from_handlevalue::<TrustedScript>(value, *cx) }
346 }
347}
348
349impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
350 fn CreatePolicy(
352 &self,
353 policy_name: DOMString,
354 options: &TrustedTypePolicyOptions,
355 can_gc: CanGc,
356 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
357 self.create_trusted_type_policy(policy_name.to_string(), options, &self.global(), can_gc)
358 }
359 #[allow(unsafe_code)]
361 fn IsHTML(&self, cx: JSContext, value: HandleValue) -> bool {
362 unsafe { root_from_handlevalue::<TrustedHTML>(value, *cx).is_ok() }
363 }
364 #[allow(unsafe_code)]
366 fn IsScript(&self, cx: JSContext, value: HandleValue) -> bool {
367 TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
368 }
369 #[allow(unsafe_code)]
371 fn IsScriptURL(&self, cx: JSContext, value: HandleValue) -> bool {
372 unsafe { root_from_handlevalue::<TrustedScriptURL>(value, *cx).is_ok() }
373 }
374 fn EmptyHTML(&self, can_gc: CanGc) -> DomRoot<TrustedHTML> {
376 TrustedHTML::new(DOMString::new(), &self.global(), can_gc)
377 }
378 fn EmptyScript(&self, can_gc: CanGc) -> DomRoot<TrustedScript> {
380 TrustedScript::new(DOMString::new(), &self.global(), can_gc)
381 }
382 fn GetAttributeType(
384 &self,
385 tag_name: DOMString,
386 attribute: DOMString,
387 element_namespace: Option<DOMString>,
388 attribute_namespace: Option<DOMString>,
389 ) -> Option<DOMString> {
390 let local_name = tag_name.to_ascii_lowercase();
392 let attribute = attribute.to_ascii_lowercase();
394 let element_namespace = match element_namespace {
396 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
397 Some(_) | None => ns!(html),
398 };
399 let attribute_namespace = match attribute_namespace {
401 Some(namespace) if !namespace.is_empty() => Some(Namespace::from(namespace)),
402 Some(_) | None => None,
403 };
404 TrustedTypePolicyFactory::get_trusted_type_data_for_attribute(
412 &element_namespace,
413 &LocalName::from(local_name),
414 &attribute,
415 attribute_namespace.as_ref(),
416 )
417 .map(|tuple| DOMString::from(tuple.0.as_ref()))
418 }
419 #[allow(clippy::if_same_then_else)]
421 fn GetPropertyType(
422 &self,
423 tag_name: DOMString,
424 property: DOMString,
425 element_namespace: Option<DOMString>,
426 ) -> Option<DOMString> {
427 let local_name = tag_name.to_ascii_lowercase();
429 let element_namespace = match element_namespace {
431 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
432 Some(_) | None => ns!(html),
433 };
434 let interface = QualName::new(None, element_namespace, LocalName::from(local_name));
436 let mut expected_type = None;
438 let property = property.str();
442 if interface.ns == ns!(html) &&
443 interface.local == local_name!("iframe") &&
444 property == "srcdoc"
445 {
446 expected_type = Some(DOMString::from("TrustedHTML"))
447 } else if interface.ns == ns!(html) &&
448 interface.local == local_name!("script") &&
449 property == "innerText"
450 {
451 expected_type = Some(DOMString::from("TrustedScript"))
452 } else if interface.ns == ns!(html) &&
453 interface.local == local_name!("script") &&
454 property == "src"
455 {
456 expected_type = Some(DOMString::from("TrustedScriptURL"))
457 } else if interface.ns == ns!(html) &&
458 interface.local == local_name!("script") &&
459 property == "text"
460 {
461 expected_type = Some(DOMString::from("TrustedScript"))
462 } else if interface.ns == ns!(html) &&
463 interface.local == local_name!("script") &&
464 property == "textContent"
465 {
466 expected_type = Some(DOMString::from("TrustedScript"))
467 } else if property == "innerHTML" {
468 expected_type = Some(DOMString::from("TrustedHTML"))
469 } else if property == "outerHTML" {
470 expected_type = Some(DOMString::from("TrustedHTML"))
471 }
472 expected_type
474 }
475 fn GetDefaultPolicy(&self) -> Option<DomRoot<TrustedTypePolicy>> {
477 self.default_policy.get()
478 }
479}