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 #[cfg_attr(crown, allow(crown::unrooted_must_root))]
66 pub(crate) fn new(global: &GlobalScope, can_gc: CanGc) -> DomRoot<Self> {
67 reflect_dom_object(Box::new(Self::new_inherited()), global, can_gc)
68 }
69
70 fn create_trusted_type_policy(
72 &self,
73 policy_name: String,
74 options: &TrustedTypePolicyOptions,
75 global: &GlobalScope,
76 can_gc: CanGc,
77 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
78 {
80 let policy_names = self.policy_names.borrow();
83 let policy_names: Vec<&str> = policy_names.iter().map(String::as_ref).collect();
84 let allowed_by_csp = global
85 .get_csp_list()
86 .is_trusted_type_policy_creation_allowed(global, &policy_name, &policy_names);
87
88 if !allowed_by_csp {
90 return Err(Error::Type("Not allowed by CSP".to_string()));
91 }
92 }
93
94 if policy_name == "default" && self.default_policy.get().is_some() {
97 return Err(Error::Type(
98 "Already set default policy for factory".to_string(),
99 ));
100 }
101
102 let policy = TrustedTypePolicy::new(policy_name.clone(), options, global, can_gc);
108 if policy_name == "default" {
110 self.default_policy.set(Some(&policy))
111 }
112 self.policy_names.borrow_mut().push(policy_name);
114 Ok(policy)
116 }
117
118 #[allow(clippy::if_same_then_else)]
120 fn get_trusted_type_data_for_attribute(
121 element_namespace: &Namespace,
122 element_name: &LocalName,
123 attribute: &str,
124 attribute_namespace: Option<&Namespace>,
125 ) -> Option<(TrustedType, String)> {
126 if attribute_namespace.is_none() &&
132 matches!(*element_namespace, ns!(html) | ns!(svg) | ns!(mathml)) &&
133 EventTarget::is_content_event_handler(attribute)
134 {
135 return Some((
137 TrustedType::TrustedScript,
138 "Element ".to_owned() + attribute,
139 ));
140 }
141 if *element_namespace == ns!(html) &&
146 *element_name == local_name!("iframe") &&
147 attribute_namespace.is_none() &&
148 attribute == "srcdoc"
149 {
150 Some((
151 TrustedType::TrustedHTML,
152 "HTMLIFrameElement srcdoc".to_owned(),
153 ))
154 } else if *element_namespace == ns!(html) &&
155 *element_name == local_name!("script") &&
156 attribute_namespace.is_none() &&
157 attribute == "src"
158 {
159 Some((
160 TrustedType::TrustedScriptURL,
161 "HTMLScriptElement src".to_owned(),
162 ))
163 } else if *element_namespace == ns!(svg) &&
164 *element_name == local_name!("script") &&
165 attribute_namespace.is_none() &&
166 attribute == "href"
167 {
168 Some((
169 TrustedType::TrustedScriptURL,
170 "SVGScriptElement href".to_owned(),
171 ))
172 } else if *element_namespace == ns!(svg) &&
173 *element_name == local_name!("script") &&
174 attribute_namespace == Some(&ns!(xlink)) &&
175 attribute == "href"
176 {
177 Some((
178 TrustedType::TrustedScriptURL,
179 "SVGScriptElement href".to_owned(),
180 ))
181 } else {
182 None
183 }
184 }
185
186 pub(crate) fn get_trusted_types_compliant_attribute_value(
188 element_namespace: &Namespace,
189 element_name: &LocalName,
190 attribute: &str,
191 attribute_namespace: Option<&Namespace>,
192 new_value: TrustedTypeOrString,
193 global: &GlobalScope,
194 can_gc: CanGc,
195 ) -> Fallible<DOMString> {
196 let attribute_namespace =
198 attribute_namespace.and_then(|a| if *a == ns!() { None } else { Some(a) });
199 let Some(attribute_data) = Self::get_trusted_type_data_for_attribute(
202 element_namespace,
203 element_name,
204 attribute,
205 attribute_namespace,
206 ) else {
207 return Ok(new_value.convert());
212 };
213 let (expected_type, sink) = attribute_data;
216 let new_value = if let TrustedTypeOrString::String(str_) = new_value {
217 str_
218 } else {
219 if expected_type.matches_idl_trusted_type(&new_value) {
224 return Ok(new_value.convert());
225 }
226 new_value.convert()
227 };
228 Self::get_trusted_type_compliant_string(
231 expected_type,
232 global,
233 new_value,
234 &sink,
235 DEFAULT_SCRIPT_SINK_GROUP,
236 can_gc,
237 )
238 }
239
240 pub(crate) fn process_value_with_default_policy(
242 expected_type: TrustedType,
243 global: &GlobalScope,
244 input: DOMString,
245 sink: &str,
246 can_gc: CanGc,
247 ) -> Fallible<Option<DOMString>> {
248 let global_policy_factory = global.trusted_types(can_gc);
250 let default_policy = match global_policy_factory.default_policy.get() {
251 None => return Ok(None),
252 Some(default_policy) => default_policy,
253 };
254 let cx = GlobalScope::get_cx();
255 rooted!(in(*cx) let mut trusted_type_name_value = NullValue());
258 expected_type.clone().as_ref().safe_to_jsval(
259 cx,
260 trusted_type_name_value.handle_mut(),
261 can_gc,
262 );
263
264 rooted!(in(*cx) let mut sink_value = NullValue());
265 sink.safe_to_jsval(cx, sink_value.handle_mut(), can_gc);
266
267 let arguments = vec![trusted_type_name_value.handle(), sink_value.handle()];
268 let policy_value = default_policy.get_trusted_type_policy_value(
269 expected_type,
270 input,
271 arguments,
272 false,
273 can_gc,
274 );
275 let data_string = match policy_value {
276 Err(error) => return Err(error),
278 Ok(policy_value) => match policy_value {
279 None => return Ok(None),
281 Some(policy_value) => policy_value,
283 },
284 };
285 Ok(Some(data_string))
286 }
287 pub(crate) fn get_trusted_type_compliant_string(
290 expected_type: TrustedType,
291 global: &GlobalScope,
292 input: DOMString,
293 sink: &str,
294 sink_group: &str,
295 can_gc: CanGc,
296 ) -> Fallible<DOMString> {
297 let require_trusted_types = global
300 .get_csp_list()
301 .does_sink_type_require_trusted_types(sink_group, true);
302 if !require_trusted_types {
304 return Ok(input);
305 }
306 let converted_input = TrustedTypePolicyFactory::process_value_with_default_policy(
309 expected_type,
310 global,
311 input.clone(),
312 sink,
313 can_gc,
314 );
315 match converted_input? {
317 None => {
319 let is_blocked = global
323 .get_csp_list()
324 .should_sink_type_mismatch_violation_be_blocked_by_csp(
325 global,
326 sink,
327 sink_group,
328 &input.str(),
329 );
330 if !is_blocked {
332 Ok(input)
333 } else {
334 Err(Error::Type(
336 "Cannot set value, expected trusted type".to_owned(),
337 ))
338 }
339 },
340 Some(converted_input) => Ok(converted_input),
342 }
343 }
346
347 pub(crate) fn is_trusted_script(
349 cx: JSContext,
350 value: HandleValue,
351 ) -> Result<DomRoot<TrustedScript>, ()> {
352 root_from_handlevalue::<TrustedScript>(value, cx)
353 }
354}
355
356impl TrustedTypePolicyFactoryMethods<crate::DomTypeHolder> for TrustedTypePolicyFactory {
357 fn CreatePolicy(
359 &self,
360 policy_name: DOMString,
361 options: &TrustedTypePolicyOptions,
362 can_gc: CanGc,
363 ) -> Fallible<DomRoot<TrustedTypePolicy>> {
364 self.create_trusted_type_policy(policy_name.to_string(), options, &self.global(), can_gc)
365 }
366 fn IsHTML(&self, cx: JSContext, value: HandleValue) -> bool {
368 root_from_handlevalue::<TrustedHTML>(value, cx).is_ok()
369 }
370 fn IsScript(&self, cx: JSContext, value: HandleValue) -> bool {
372 TrustedTypePolicyFactory::is_trusted_script(cx, value).is_ok()
373 }
374 fn IsScriptURL(&self, cx: JSContext, value: HandleValue) -> bool {
376 root_from_handlevalue::<TrustedScriptURL>(value, cx).is_ok()
377 }
378 fn EmptyHTML(&self, can_gc: CanGc) -> DomRoot<TrustedHTML> {
380 TrustedHTML::new(DOMString::new(), &self.global(), can_gc)
381 }
382 fn EmptyScript(&self, can_gc: CanGc) -> DomRoot<TrustedScript> {
384 TrustedScript::new(DOMString::new(), &self.global(), can_gc)
385 }
386 fn GetAttributeType(
388 &self,
389 tag_name: DOMString,
390 attribute: DOMString,
391 element_namespace: Option<DOMString>,
392 attribute_namespace: Option<DOMString>,
393 ) -> Option<DOMString> {
394 let local_name = tag_name.to_ascii_lowercase();
396 let attribute = attribute.to_ascii_lowercase();
398 let element_namespace = match element_namespace {
400 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
401 Some(_) | None => ns!(html),
402 };
403 let attribute_namespace = match attribute_namespace {
405 Some(namespace) if !namespace.is_empty() => Some(Namespace::from(namespace)),
406 Some(_) | None => None,
407 };
408 TrustedTypePolicyFactory::get_trusted_type_data_for_attribute(
416 &element_namespace,
417 &LocalName::from(local_name),
418 &attribute,
419 attribute_namespace.as_ref(),
420 )
421 .map(|tuple| DOMString::from(tuple.0.as_ref()))
422 }
423 #[allow(clippy::if_same_then_else)]
425 fn GetPropertyType(
426 &self,
427 tag_name: DOMString,
428 property: DOMString,
429 element_namespace: Option<DOMString>,
430 ) -> Option<DOMString> {
431 let local_name = tag_name.to_ascii_lowercase();
433 let element_namespace = match element_namespace {
435 Some(namespace) if !namespace.is_empty() => Namespace::from(namespace),
436 Some(_) | None => ns!(html),
437 };
438 let interface = QualName::new(None, element_namespace, LocalName::from(local_name));
440 let mut expected_type = None;
442 let property = property.str();
446 if interface.ns == ns!(html) &&
447 interface.local == local_name!("iframe") &&
448 property == "srcdoc"
449 {
450 expected_type = Some(DOMString::from("TrustedHTML"))
451 } else if interface.ns == ns!(html) &&
452 interface.local == local_name!("script") &&
453 property == "innerText"
454 {
455 expected_type = Some(DOMString::from("TrustedScript"))
456 } else if interface.ns == ns!(html) &&
457 interface.local == local_name!("script") &&
458 property == "src"
459 {
460 expected_type = Some(DOMString::from("TrustedScriptURL"))
461 } else if interface.ns == ns!(html) &&
462 interface.local == local_name!("script") &&
463 property == "text"
464 {
465 expected_type = Some(DOMString::from("TrustedScript"))
466 } else if interface.ns == ns!(html) &&
467 interface.local == local_name!("script") &&
468 property == "textContent"
469 {
470 expected_type = Some(DOMString::from("TrustedScript"))
471 } else if property == "innerHTML" {
472 expected_type = Some(DOMString::from("TrustedHTML"))
473 } else if property == "outerHTML" {
474 expected_type = Some(DOMString::from("TrustedHTML"))
475 }
476 expected_type
478 }
479 fn GetDefaultPolicy(&self) -> Option<DomRoot<TrustedTypePolicy>> {
481 self.default_policy.get()
482 }
483}