script/dom/security/sanitizer.rs
1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::cell::LazyCell;
6use std::cmp::Ordering;
7use std::collections::HashSet;
8
9use dom_struct::dom_struct;
10use html5ever::{LocalName, Namespace, local_name, ns};
11use js::context::JSContext;
12use js::rust::HandleObject;
13use script_bindings::cell::DomRefCell;
14use script_bindings::reflector::{Reflector, reflect_dom_object_with_proto};
15use style::attr::AttrValue;
16use url::Url;
17
18use crate::dom::Node;
19use crate::dom::bindings::codegen::Bindings::HTMLTemplateElementBinding::HTMLTemplateElementMethods;
20use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
21use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
22 SanitizerAttribute, SanitizerAttributeNamespace, SanitizerConfig, SanitizerElement,
23 SanitizerElementNamespace, SanitizerElementNamespaceWithAttributes,
24 SanitizerElementWithAttributes, SanitizerMethods, SanitizerPI, SanitizerPresets,
25 SanitizerProcessingInstruction, SetHTMLOptions, SetHTMLUnsafeOptions,
26};
27use crate::dom::bindings::codegen::UnionTypes::{
28 SanitizerConfigOrSanitizerPresets, SanitizerOrSanitizerConfigOrSanitizerPresets,
29};
30use crate::dom::bindings::domname::is_custom_data_attribute;
31use crate::dom::bindings::error::{Error, ErrorResult, Fallible};
32use crate::dom::bindings::inheritance::{Castable, CharacterDataTypeId, NodeTypeId};
33use crate::dom::bindings::reflector::DomGlobal;
34use crate::dom::bindings::root::DomRoot;
35use crate::dom::bindings::str::DOMString;
36use crate::dom::console::Console;
37use crate::dom::documentfragment::DocumentFragment;
38use crate::dom::element::Element;
39use crate::dom::eventtarget::CONTENT_EVENT_HANDLER_NAMES;
40use crate::dom::html::htmltemplateelement::HTMLTemplateElement;
41use crate::dom::node::node::NodeTraits;
42use crate::dom::processinginstruction::ProcessingInstruction;
43use crate::dom::servoparser::ServoParser;
44use crate::dom::window::Window;
45
46#[dom_struct]
47pub(crate) struct Sanitizer {
48 reflector_: Reflector,
49 /// <https://wicg.github.io/sanitizer-api/#sanitizer-configuration>
50 configuration: DomRefCell<SanitizerConfig>,
51}
52
53impl Sanitizer {
54 fn new_inherited(configuration: SanitizerConfig) -> Sanitizer {
55 Sanitizer {
56 reflector_: Reflector::new(),
57 configuration: DomRefCell::new(configuration),
58 }
59 }
60
61 pub(crate) fn new_with_proto(
62 cx: &mut JSContext,
63 window: &Window,
64 proto: Option<HandleObject>,
65 configuration: SanitizerConfig,
66 ) -> DomRoot<Sanitizer> {
67 reflect_dom_object_with_proto(
68 cx,
69 Box::new(Sanitizer::new_inherited(configuration)),
70 window,
71 proto,
72 )
73 }
74
75 /// <https://html.spec.whatwg.org/multipage/#get-a-sanitizer-instance-from-options>
76 pub(crate) fn get_sanitizer_instance_from_options(
77 cx: &mut JSContext,
78 window: &Window,
79 options: &impl SanitizerMember,
80 safe: bool,
81 ) -> Fallible<DomRoot<Sanitizer>> {
82 // Step 1. Let sanitizerSpec be "default".
83 // Step 2. If options["sanitizer"] exists, then set sanitizerSpec to options["sanitizer"].
84 //
85 // NOTE: options["sanitizer"] always exists.
86 let mut sanitizer_spec = options.sanitizer().clone();
87
88 // Step 3. Assert: sanitizerSpec is either a Sanitizer instance, a SanitizerPresets member,
89 // or a SanitizerConfig dictionary.
90 assert!(matches!(
91 sanitizer_spec,
92 SanitizerOrSanitizerConfigOrSanitizerPresets::Sanitizer(_) |
93 SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerPresets(_) |
94 SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerConfig(_)
95 ));
96
97 // Step 4. If sanitizerSpec is a string:
98 if let SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerPresets(
99 sanitizer_spec_string,
100 ) = sanitizer_spec
101 {
102 // Step 4.1. Assert: sanitizerSpec is "default"
103 assert_eq!(sanitizer_spec_string, SanitizerPresets::Default);
104
105 // Step 4.2. Set sanitizerSpec to the built-in safe default configuration.
106 sanitizer_spec = SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerConfig(
107 built_in_safe_default_configuration(),
108 );
109 }
110
111 // Step 5. If sanitizerSpec is a dictionary:
112 if let SanitizerOrSanitizerConfigOrSanitizerPresets::SanitizerConfig(
113 sanitizer_spec_dictionary,
114 ) = sanitizer_spec
115 {
116 // Step 5.1. Let sanitizer be a new Sanitizer object.
117 let sanitizer = Sanitizer::new_with_proto(cx, window, None, SanitizerConfig::default());
118
119 // Step 5.2. Let permissiveDefaults be true if safe is false; false otherwise.
120 let permissive_defaults = !safe;
121
122 // Step 5.3. Configure sanitizer given sanitizerSpec and permissiveDefaults.
123 sanitizer.configure(sanitizer_spec_dictionary, permissive_defaults)?;
124
125 // Step 5.4. Set sanitizerSpec to sanitizer.
126 sanitizer_spec = SanitizerOrSanitizerConfigOrSanitizerPresets::Sanitizer(sanitizer);
127 }
128
129 // Step 6. Return sanitizerSpec.
130 if let SanitizerOrSanitizerConfigOrSanitizerPresets::Sanitizer(sanitizer) = sanitizer_spec {
131 Ok(sanitizer)
132 } else {
133 unreachable!("Guaranteed by Step 4 and 5")
134 }
135 }
136
137 /// <https://html.spec.whatwg.org/multipage/#configure-a-sanitizer>
138 fn configure(
139 &self,
140 mut configuration: SanitizerConfig,
141 permissive_defaults: bool,
142 ) -> ErrorResult {
143 // Step 1. Canonicalize the configuration configuration with permissiveDefaults.
144 configuration.canonicalize(permissive_defaults);
145
146 // Step 2. If configuration is not valid, then throw a TypeError.
147 if !configuration.is_valid() {
148 return Err(Error::Type(c"The configuration is invalid".into()));
149 }
150
151 // Step 3. Set sanitizer's configuration to configuration.
152 let mut sanitizer_configuration = self.configuration.borrow_mut();
153 *sanitizer_configuration = configuration;
154 Ok(())
155 }
156
157 /// <https://wicg.github.io/sanitizer-api/#set-and-filter-html>
158 pub(crate) fn set_and_filter_html(
159 cx: &mut JSContext,
160 target: &Node,
161 context_element: &Element,
162 html: DOMString,
163 options: &impl SanitizerMember,
164 safe: bool,
165 ) -> ErrorResult {
166 // Step 1. If safe and contextElement’s local name is "script" and contextElement’s
167 // namespace is the HTML namespace or the SVG namespace, then return.
168 if safe &&
169 context_element.local_name() == &local_name!("script") &&
170 (context_element.namespace() == &ns!(html) ||
171 context_element.namespace() == &ns!(svg))
172 {
173 return Ok(());
174 }
175
176 // Step 2. Let sanitizer be the result of calling get a sanitizer instance from options with
177 // options and safe.
178 let sanitizer = Sanitizer::get_sanitizer_instance_from_options(
179 cx,
180 &target.owner_window(),
181 options,
182 safe,
183 )?;
184
185 // Step 3. Let newChildren be the result of the HTML fragment parsing algorithm given
186 // contextElement, html, and true.
187 let new_children = ServoParser::parse_html_fragment(cx, context_element, html, true);
188
189 // Step 4. Let fragment be a new DocumentFragment whose node document is contextElement’s
190 // node document.
191 let context_document = context_element.owner_document();
192 let fragment = DomRoot::upcast::<Node>(DocumentFragment::new(cx, &context_document));
193
194 // Step 5. For each node in newChildren, append node to fragment.
195 for child in new_children {
196 fragment
197 .AppendChild(cx, &child)
198 .expect("Must be able to append child to node");
199 }
200
201 // Step 6. Run sanitize on fragment using sanitizer and safe.
202 sanitizer.sanitize(cx, &fragment, safe)?;
203
204 // Step 7. Replace all with fragment within target.
205 Node::replace_all(cx, Some(&fragment), target);
206
207 Ok(())
208 }
209
210 /// <https://html.spec.whatwg.org/multipage/#sanitize>
211 pub(crate) fn sanitize(&self, cx: &mut JSContext, node: &Node, safe: bool) -> ErrorResult {
212 // Step 1. Let configuration be sanitizer's configuration.
213 {
214 let mut configuration = self.configuration.borrow_mut();
215
216 // Step 2. Assert: configuration is valid.
217 debug_assert!(configuration.is_valid());
218
219 // Step 3. If safe is true, then remove unsafe from configuration.
220 if safe {
221 configuration.remove_unsafe();
222 }
223 }
224
225 let configuration = self.configuration.borrow();
226 // Step 4. Run the inner sanitize steps given node and configuration.
227 inner_sanitize_steps(cx, node, &configuration)
228 }
229}
230
231/// <https://html.spec.whatwg.org/multipage/#inner-sanitize-steps>
232fn inner_sanitize_steps(
233 cx: &mut JSContext,
234 node: &Node,
235 configuration: &SanitizerConfig,
236) -> ErrorResult {
237 // Step 1. For each child of node’s children:
238 for child in node.children() {
239 // Step 1.1. Assert: child is a Text, Comment, Element, ProcessingInstruction, or
240 // DocumentType node.
241 assert!(matches!(
242 child.type_id(),
243 NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) |
244 NodeTypeId::CharacterData(CharacterDataTypeId::Comment) |
245 NodeTypeId::Element(_) |
246 NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) |
247 NodeTypeId::DocumentType
248 ));
249
250 match child.type_id() {
251 // Step 1.2. If child is a DocumentType or Text node, then continue.
252 NodeTypeId::DocumentType | NodeTypeId::CharacterData(CharacterDataTypeId::Text(_)) => {
253 continue;
254 },
255
256 // Step 1.3. If child is a Comment node:
257 NodeTypeId::CharacterData(CharacterDataTypeId::Comment) => {
258 // Step 1.3.1. If configuration["comments"] is not true, then remove child.
259 if configuration.comments != Some(true) {
260 child.remove_self(cx);
261 }
262 // Step 1.3.2. Continue.
263 continue;
264 },
265
266 // Step 1.4. If child is a ProcessingInstruction node:
267 //
268 // FIXME: <https://github.com/whatwg/html/pull/12118>
269 // Currently, processing instructions are parsed as comments, since HTML parsing has not
270 // yet supported processing instructions. This will be resolved once the PR
271 // <https://github.com/whatwg/html/pull/12118> at HTML specification is merged and the
272 // relavent changes are implemented in html5ever.
273 NodeTypeId::CharacterData(CharacterDataTypeId::ProcessingInstruction) => {
274 // Step 1.4.1. Let piTarget be child's target.
275 let pi_target = SanitizerPI::String(
276 child
277 .downcast::<ProcessingInstruction>()
278 .expect("Guaranteed by pattern matching of child.type_id()")
279 .target()
280 .clone(),
281 );
282
283 // Step 1.4.2. If configuration["processingInstructions"] exists:
284 // Step 1.4.2.1. If configuration["processingInstructions"] does not contain
285 // piTarget, then remove child.
286 // Step 1.4.3. Otherwise:
287 // Step 1.4.3.1. If configuration["removeProcessingInstructions"] contains
288 // piTarget, then remove child.
289 if configuration.processingInstructions.as_ref().is_some_and(
290 |configuration_processing_instructions| {
291 !configuration_processing_instructions.contains_target(&pi_target)
292 },
293 ) || configuration
294 .removeProcessingInstructions
295 .as_ref()
296 .is_some_and(|configuration_remove_processing_instructions| {
297 configuration_remove_processing_instructions.contains_target(&pi_target)
298 })
299 {
300 child.remove_self(cx);
301 }
302 },
303
304 // Step 1.5. Otherwise:
305 _ => {
306 // Step 1.5.1. Let elementName be a SanitizerElementNamespace with child’s local
307 // name and namespace.
308 let child = DomRoot::downcast::<Element>(child).expect("Guaranteed by Step 1.1");
309 let element_name =
310 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
311 name: DOMString::from(&**child.local_name()),
312 namespace: Some(DOMString::from(&**child.namespace())),
313 });
314
315 // Step 1.5.2. If configuration["replaceWithChildrenElements"] exists and if
316 // configuration["replaceWithChildrenElements"] contains elementName:
317 if configuration
318 .replaceWithChildrenElements
319 .as_ref()
320 .is_some_and(|configuration_replace_with_children_elements| {
321 configuration_replace_with_children_elements.contains_item(&element_name)
322 })
323 {
324 // Step 1.5.2.1. Assert: node does not implement Document.
325 assert!(!matches!(node.type_id(), NodeTypeId::Document(_)));
326
327 // Step 1.5.2.2. Call sanitize core on child with configuration and
328 // handleJavascriptNavigationUrls.
329 inner_sanitize_steps(cx, child.upcast(), configuration)?;
330
331 // Step 1.5.2.3. Let fragment be the result of creating a document fragment
332 // given node's node document.
333 let fragment = DocumentFragment::new(cx, &node.owner_document());
334
335 // Step 1.5.2.4. For each innerChild of child’s children, append innerChild to
336 // fragment.
337 let child = DomRoot::upcast::<Node>(child);
338 let fragment = DomRoot::upcast::<Node>(fragment);
339 for inner_child in child.children() {
340 fragment.AppendChild(cx, &inner_child)?;
341 }
342
343 // Step 1.5.2.5. Replace child with fragment within node.
344 // Assert that this did not throw.
345 node.ReplaceChild(cx, &fragment, &child)
346 .expect("Replacing child with fragment within node should not fail.");
347
348 // Step 1.5.2.6. Continue.
349 continue;
350 }
351
352 // Step 1.5.3. If configuration["elements"] exists:
353 if let Some(configuration_elements) = configuration.elements.as_ref() {
354 // Step 1.5.3.1. If configuration["elements"] does not contain elementName,
355 // then remove child and continue.
356 if !configuration_elements.contains_item(&element_name) {
357 child.upcast::<Node>().remove_self(cx);
358 continue;
359 }
360 }
361 // Step 1.5.4. Otherwise:
362 else {
363 // Step 1.5.4.1. If configuration["removeElements"] contains elementName,
364 // then remove child and continue.
365 if configuration.removeElements.as_ref().is_some_and(
366 |configuration_remove_elements| {
367 configuration_remove_elements.contains_item(&element_name)
368 },
369 ) {
370 child.upcast::<Node>().remove_self(cx);
371 continue;
372 }
373 }
374
375 // Step 1.5.5. If elementName["name"] is "template" and elementName["namespace"]
376 // is the HTML namespace, then run the inner sanitize steps given child's template
377 // contents and configuration.
378 if element_name.name().str() == "template" &&
379 element_name
380 .namespace()
381 .is_some_and(|namespace| *namespace.str() == ns!(html))
382 {
383 let template_contents = child
384 .downcast::<HTMLTemplateElement>()
385 .expect("Guaranteed by elementName's name being \"template\"")
386 .Content(cx);
387 inner_sanitize_steps(cx, template_contents.upcast(), configuration)?;
388 }
389
390 // Step 1.5.6. If child is a shadow host, then run the inner sanitize steps given
391 // child's shadow root and configuration.
392 if let Some(shadow_root) = child.shadow_root() {
393 inner_sanitize_steps(cx, shadow_root.upcast(), configuration)?;
394 }
395
396 // Step 1.5.7. Let elementWithLocalAttributes be « [] ».
397 let mut element_with_local_attributes =
398 SanitizerElementWithAttributes::String(DOMString::new());
399
400 // Step 1.5.8. If configuration["elements"] exists and configuration["elements"]
401 // contains elementName, then set elementWithLocalAttributes to
402 // configuration["elements"][elementName].
403 if let Some(configuration_elements) = &configuration.elements &&
404 let Some(found) = configuration_elements.iter().find(|entry| {
405 entry.name() == element_name.name() &&
406 entry.namespace() == element_name.namespace()
407 })
408 {
409 element_with_local_attributes = found.clone();
410 }
411
412 // Step 1.5.9. For each attribute in child's attribute list:
413 //
414 // NOTE: We will modify the attribute list in the "for" block. So, we clone the
415 // attribute list first to avoid holding an immutable reference to the attribute
416 // list.
417 let attribute_list = child
418 .attrs()
419 .borrow()
420 .iter()
421 .map(|attribute| {
422 (
423 attribute.local_name().clone(),
424 attribute.namespace().clone(),
425 attribute.value().clone(),
426 )
427 })
428 .collect::<Vec<_>>();
429 for (attribute_local_name, attribute_namespace, attribute_value) in
430 attribute_list.iter()
431 {
432 // Step 1.5.9.1. Let attrName be a SanitizerAttributeNamespace with attribute’s
433 // local name and namespace.
434 let attribute_name = SanitizerAttribute::SanitizerAttributeNamespace(
435 SanitizerAttributeNamespace {
436 name: DOMString::from(attribute_local_name.as_ref()),
437 namespace: if attribute_namespace.is_empty() {
438 None
439 } else {
440 Some(DOMString::from(attribute_namespace.as_ref()))
441 },
442 },
443 );
444
445 // Step 1.5.9.2. If elementWithLocalAttributes["removeAttributes"] exists and
446 // elementWithLocalAttributes["removeAttributes"] contains attrName,
447 // then remove attribute.
448 if element_with_local_attributes
449 .remove_attributes()
450 .is_some_and(|remove_attributes| {
451 remove_attributes.contains_item(&attribute_name)
452 })
453 {
454 child.remove_attribute(cx, attribute_namespace, attribute_local_name);
455 }
456 // Step 1.5.9.3. Otherwise, if configuration["attributes"] exists:
457 else if let Some(configuration_attributes) = configuration.attributes.as_ref()
458 {
459 // Step 1.5.9.3.1. If configuration["attributes"] does not contain attrName
460 // and elementWithLocalAttributes["attributes"] with default « » does not
461 // contain attrName, and if "data-" is not a code unit prefix of attribute's
462 // local name or attribute's namespace is not null or
463 // configuration["dataAttributes"] is not true, then remove attribute.
464 if (!configuration_attributes.contains_item(&attribute_name) &&
465 !element_with_local_attributes
466 .attributes()
467 .unwrap_or_default()
468 .contains_item(&attribute_name)) &&
469 (!attribute_local_name.starts_with("data-") ||
470 !attribute_namespace.is_empty() ||
471 configuration.dataAttributes != Some(true))
472 {
473 child.remove_attribute(cx, attribute_namespace, attribute_local_name);
474 }
475 }
476 // Step 1.5.9.4. Otherwise:
477 else {
478 // Step 1.5.9.4.1. If elementWithLocalAttributes["attributes"] exists and
479 // elementWithLocalAttributes["attributes"] does not contain attrName, then
480 // remove attribute.
481 // Step 1.5.9.4.2. Otherwise, if configuration["removeAttributes"] contains
482 // attrName, then remove attribute.
483 if element_with_local_attributes.attributes().is_some_and(
484 |local_attributes| !local_attributes.contains_item(&attribute_name),
485 ) || configuration.removeAttributes.as_ref().is_some_and(
486 |configuration_remove_attributes| {
487 configuration_remove_attributes.contains_item(&attribute_name)
488 },
489 ) {
490 child.remove_attribute(cx, attribute_namespace, attribute_local_name);
491 }
492 }
493
494 // Step 1.5.9.5. If configuration["javascriptURLs"] is not true:
495 if configuration.javascriptURLs != Some(true) {
496 // Step 1.5.9.5.1. If the pair (elementName, attrName) matches an entry in
497 // the built-in navigating URL attributes list, and if attribute contains a
498 // javascript: URL, then remove attribute.
499 if BUILT_IN_NAVIGATING_URL_ATTRIBUTES_LIST.iter().any(
500 |(
501 entry_element_name,
502 entry_element_namespace,
503 entry_attribute_name,
504 entry_attribute_namespace,
505 )| {
506 (
507 entry_element_name.as_ref(),
508 entry_element_namespace.as_deref(),
509 entry_attribute_name.as_ref(),
510 entry_attribute_namespace.as_deref(),
511 ) == (
512 element_name.name().str().as_ref(),
513 element_name.namespace().map(DOMString::str).as_deref(),
514 attribute_name.name().str().as_ref(),
515 attribute_name.namespace().map(DOMString::str).as_deref(),
516 )
517 },
518 ) && contains_javascript_url(attribute_value)
519 {
520 child.remove_attribute(cx, attribute_namespace, attribute_local_name);
521 }
522
523 // Step 1.5.9.5.2. If child’s namespace is the MathML Namespace and attr’s
524 // local name is "href" and attr’s namespace is null or the XLink namespace
525 // and attr contains a javascript: URL, then remove attribute.
526 if child.namespace() == &ns!(mathml) &&
527 attribute_local_name == &local_name!("href") &&
528 (attribute_namespace.is_empty() ||
529 attribute_namespace == &ns!(xlink)) &&
530 contains_javascript_url(attribute_value)
531 {
532 child.remove_attribute(cx, attribute_namespace, attribute_local_name);
533 }
534
535 // Step 1.5.9.5.3. If the built-in animating URL attributes list contains
536 // the pair (elementName, attrName) and attr’s value is "href" or
537 // "xlink:href", then remove attribute.
538 if BUILT_IN_ANIMATING_URL_ATTRIBUTES_LIST.iter().any(
539 |(
540 entry_element_name,
541 entry_element_namespace,
542 entry_attribute_name,
543 entry_attribute_namespace,
544 )| {
545 (
546 entry_element_name.as_ref(),
547 entry_element_namespace.as_deref(),
548 entry_attribute_name.as_ref(),
549 entry_attribute_namespace.as_deref(),
550 ) == (
551 element_name.name().str().as_ref(),
552 element_name.namespace().map(DOMString::str).as_deref(),
553 attribute_name.name().str().as_ref(),
554 attribute_name.namespace().map(DOMString::str).as_deref(),
555 )
556 },
557 ) && matches!(attribute_value.as_ref(), "href" | "xlink:href")
558 {
559 child.remove_attribute(cx, attribute_namespace, attribute_local_name);
560 }
561 }
562 }
563
564 // Step 1.5.10. Run the inner sanitize steps given child and configuration.
565 inner_sanitize_steps(cx, child.upcast(), configuration)?;
566 },
567 }
568 }
569
570 Ok(())
571}
572
573/// <https://wicg.github.io/sanitizer-api/#contains-a-javascript-url>
574fn contains_javascript_url(attribute_value: &AttrValue) -> bool {
575 // Step 1. Let url be the result of running the basic URL parser on attribute’s value.
576 // Step 2. If url is failure, then return false.
577 let Ok(url) = Url::parse(attribute_value) else {
578 return false;
579 };
580
581 // Step 3. Return whether url’s scheme is "javascript".
582 url.scheme() == "javascript"
583}
584
585impl SanitizerMethods<crate::DomTypeHolder> for Sanitizer {
586 /// <https://html.spec.whatwg.org/multipage/#dom-sanitizer-constructor>
587 fn Constructor(
588 cx: &mut JSContext,
589 window: &Window,
590 proto: Option<HandleObject>,
591 configuration: SanitizerConfigOrSanitizerPresets,
592 ) -> Fallible<DomRoot<Sanitizer>> {
593 let configuration = match configuration {
594 // Step 1. If configuration is a SanitizerPresets string:
595 SanitizerConfigOrSanitizerPresets::SanitizerPresets(configuration) => {
596 // Step 1.1. Assert: configuration is default.
597 assert_eq!(configuration, SanitizerPresets::Default);
598
599 // Step 1.2. Set configuration to the built-in safe default configuration.
600 built_in_safe_default_configuration()
601 },
602 SanitizerConfigOrSanitizerPresets::SanitizerConfig(configuration) => configuration,
603 };
604
605 // Step 2. Configure this given configuration and true.
606 let sanitizer = Sanitizer::new_with_proto(cx, window, proto, SanitizerConfig::default());
607 sanitizer.configure(configuration, true)?;
608
609 Ok(sanitizer)
610 }
611
612 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-get>
613 fn Get(&self) -> SanitizerConfig {
614 // Step 1. Let config be this’s configuration.
615 let mut config = self.configuration.borrow_mut();
616
617 // Step 2. Assert: config is valid.
618 debug_assert!(config.is_valid());
619
620 match &mut config.elements {
621 // Step 3. If config["elements"] exists:
622 Some(config_elements) => {
623 // Step 3.1. For any element of config["elements"]:
624 for element in config_elements.iter_mut() {
625 // Step 3.1.1. If element["attributes"] exists:
626 if let Some(element_attributes) = &mut element.attributes_mut() {
627 // Step 3.1.1.1. Set element["attributes"] to the result of sort in
628 // ascending order element["attributes"], with attrA being less than item
629 // attrB.
630 element_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
631 }
632
633 // Step 3.1.2. If element["removeAttributes"] exists:
634 if let Some(element_remove_attributes) = &mut element.remove_attributes_mut() {
635 // Step 3.1.2.1. Set element["removeAttributes"] to the result of sort in
636 // ascending order element["removeAttributes"], with attrA being less than
637 // item attrB.
638 element_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
639 }
640 }
641
642 // Step 3.2. Set config["elements"] to the result of sort in ascending order
643 // config["elements"], with elementA being less than item elementB.
644 config_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
645 },
646 // Step 4. Otherwise:
647 None => {
648 // Step 4.1. Set config["removeElements"] to the result of sort in ascending order
649 // config["removeElements"], with elementA being less than item elementB.
650 if let Some(config_remove_elements) = &mut config.removeElements {
651 config_remove_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
652 }
653 },
654 }
655
656 // Step 5. If config["replaceWithChildrenElements"] exists:
657 if let Some(config_replace_with_children_elements) = &mut config.replaceWithChildrenElements
658 {
659 // Step 5.1.Set config["replaceWithChildrenElements"] to the result of sort in ascending
660 // order config["replaceWithChildrenElements"], with elementA being less than item
661 // elementB.
662 config_replace_with_children_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
663 }
664
665 match &mut config.processingInstructions {
666 // Step 6. If config["processingInstructions"] exists:
667 Some(config_processing_instructions) => {
668 // Step 6.1. Set config["processingInstructions"] to the result of sort in ascending
669 // order config["processingInstructions"], with piA["target"] being code unit less
670 // than piB["target"].
671 config_processing_instructions.sort_by(
672 |processing_instruction_a, processing_instruction_b| {
673 if processing_instruction_a.target() < processing_instruction_b.target() {
674 Ordering::Less
675 } else {
676 Ordering::Greater
677 }
678 },
679 )
680 },
681 // Step 7. Otherwise:
682 None => {
683 // Step 7.1. Set config["removeProcessingInstructions"] to the result of sort in
684 // ascending order config["removeProcessingInstructions"], with piA["target"] being
685 // code unit less than piB["target"].
686 if let Some(config_remove_processing_instructions) =
687 &mut config.removeProcessingInstructions
688 {
689 config_remove_processing_instructions.sort_by(
690 |processing_instruction_a, processing_instruction_b| {
691 if processing_instruction_a.target() < processing_instruction_b.target()
692 {
693 Ordering::Less
694 } else {
695 Ordering::Greater
696 }
697 },
698 )
699 }
700 },
701 }
702
703 match &mut config.attributes {
704 // Step 8. If config["attributes"] exists:
705 Some(config_attributes) => {
706 // Step 8.1. Set config["attributes"] to the result of sort in ascending order
707 // config["attributes"], with attrA being less than item attrB.
708 config_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
709 },
710 // Step 9. Otherwise:
711 None => {
712 // Step 9.1. Set config["removeAttributes"] to the result of sort in ascending order
713 // config["removeAttributes"], with attrA being less than item attrB.
714 if let Some(config_remove_attributes) = &mut config.removeAttributes {
715 config_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
716 }
717 },
718 }
719
720 // Step 10. Return config.
721 (*config).clone()
722 }
723
724 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowelement>
725 fn AllowElement(&self, cx: &mut JSContext, element: SanitizerElementWithAttributes) -> bool {
726 // Step 1. Let configuration be this’s configuration.
727 let mut configuration = self.configuration.safe_borrow_mut(cx);
728
729 // Step 2. Assert: configuration is valid.
730 debug_assert!(configuration.is_valid());
731
732 // Step 3. Set element to the result of canonicalize a sanitizer element with attributes
733 // with element.
734 let mut element = element.canonicalize();
735
736 // Step 4. If configuration["elements"] exists:
737 if configuration.elements.is_some() {
738 // Step 4.1. Set modified to the result of remove element from
739 // configuration["replaceWithChildrenElements"].
740 let modified = if let Some(replace_with_children_elements) =
741 &mut configuration.replaceWithChildrenElements
742 {
743 replace_with_children_elements.remove_item(&element)
744 } else {
745 false
746 };
747
748 // Step 4.2. Comment: We need to make sure the per-element attributes do not overlap
749 // with global attributes.
750
751 match &configuration.attributes {
752 // Step 4.3. If configuration["attributes"] exists:
753 Some(configuration_attributes) => {
754 // Step 4.3.1. If element["attributes"] exists:
755 if let Some(element_attributes) = element.attributes_mut() {
756 // Step 4.3.1.1. Set element["attributes"] to remove duplicates from
757 // element["attributes"].
758 element_attributes.remove_duplicates();
759
760 // Step 4.3.1.2. Set element["attributes"] to the difference of
761 // element["attributes"] and configuration["attributes"].
762 element_attributes.difference(configuration_attributes);
763
764 // Step 4.3.1.3. If configuration["dataAttributes"] is true:
765 if configuration.dataAttributes == Some(true) {
766 // Step 4.3.1.3.1. Remove all items item from element["attributes"]
767 // where item is a custom data attribute.
768 element_attributes
769 .retain(|attribute| !attribute.is_custom_data_attribute());
770 }
771 }
772
773 // Step 4.3.2. If element["removeAttributes"] exists:
774 if let Some(element_remove_attributes) = element.remove_attributes_mut() {
775 // Step 4.3.2.1. set element["removeattributes"] to remove duplicates from
776 // element["removeattributes"].
777 element_remove_attributes.remove_duplicates();
778
779 // Step 4.3.2.2. set element["removeattributes"] to the intersection of
780 // element["removeattributes"] and configuration["attributes"].
781 element_remove_attributes.intersection(configuration_attributes);
782 }
783 },
784 // Step 4.4. Otherwise:
785 None => {
786 // NOTE: To avoid borrowing `element` again at Step 4.4.1.2 and 4.4.1.3 after
787 // borrowing `element` mutably at the beginning of Step 4.4.1, we clone
788 // element["attributes"] first, and call `set_attributes` at the end of Step
789 // 4.4.1 to put it back into `element`.
790
791 // Step 4.4.1. If element["attributes"] exists:
792 if let Some(mut element_attributes) = element.attributes_mut().cloned() {
793 // Step 4.4.1.1. Set element["attributes"] to remove duplicates from
794 // element["attributes"].
795 element_attributes.remove_duplicates();
796
797 // Step 4.4.1.2. Set element["attributes"] to the difference of
798 // element["attributes"] and element["removeAttributes"] with default « ».
799 element_attributes
800 .difference(element.remove_attributes().unwrap_or_default());
801
802 // Step 4.4.1.3. Remove element["removeAttributes"].
803 element.set_remove_attributes(None);
804
805 // Step 4.4.1.4. Set element["attributes"] to the difference of
806 // element["attributes"] and configuration["removeAttributes"].
807 element_attributes.difference(
808 configuration
809 .removeAttributes
810 .as_deref()
811 .unwrap_or_default(),
812 );
813
814 element.set_attributes(Some(element_attributes));
815 }
816
817 // Step 4.4.2. If element["removeAttributes"] exists:
818 if let Some(mut element_remove_attributes) = element.remove_attributes_mut() {
819 // Step 4.4.2.1. Set element["removeAttributes"] to remove duplicates from
820 // element["removeAttributes"].
821 element_remove_attributes = element_remove_attributes.remove_duplicates();
822
823 // Step 4.4.2.2. Set element["removeAttributes"] to the difference of
824 // element["removeAttributes"] and configuration["removeAttributes"].
825 element_remove_attributes.difference(
826 configuration
827 .removeAttributes
828 .as_deref()
829 .unwrap_or_default(),
830 );
831 }
832 },
833 }
834
835 // Step 4.5. If configuration["elements"] does not contain element:
836 let configuration_elements = configuration
837 .elements
838 .as_mut()
839 .expect("Guaranteed by Step 4");
840 if !configuration_elements.contains_item(&element) {
841 // Step 4.5.1. Comment: This is the case with a global allow-list that does not yet
842 // contain element.
843
844 // Step 4.5.2. Append element to configuration["elements"].
845 configuration_elements.push(element.clone());
846
847 // Step 4.5.3. Return true.
848 return true;
849 }
850
851 // Step 4.6. Comment: This is the case with a global allow-list that already contains
852 // element.
853
854 // Step 4.7. Let current element be the item in configuration["elements"] where
855 // item["name"] equals element["name"] and item["namespace"] equals
856 // element["namespace"].
857 let current_element = configuration_elements
858 .iter()
859 .find(|item| {
860 item.name() == element.name() && item.namespace() == element.namespace()
861 })
862 .expect("Guaranteed by Step 4.5 and Step 4.5.2");
863
864 // Step 4.8. If element equals current element then return modified.
865 if element == *current_element {
866 return modified;
867 }
868
869 // Step 4.9. Remove element from configuration["elements"].
870 configuration_elements.remove_item(&element);
871
872 // Step 4.10. Append element to configuration["elements"]
873 configuration_elements.push(element);
874
875 // Step 4.11. Return true.
876 true
877 }
878 // Step 5. Otherwise:
879 else {
880 // Step 5.1. If element["attributes"] exists or element["removeAttributes"] with default
881 // « » is not empty:
882 if element.attributes().is_some() ||
883 !element.remove_attributes().unwrap_or_default().is_empty()
884 {
885 std::mem::drop(configuration);
886
887 // Step 5.1.1. The user agent may report a warning to the console that this
888 // operation is not supported.
889 Console::internal_warn(
890 cx,
891 &self.global(),
892 "Do not support adding an element with attributes to a sanitizer \
893 whose configuration[\"elements\"] does not exist."
894 .into(),
895 );
896
897 // Step 5.1.2. Return false.
898 return false;
899 }
900
901 // Step 5.2. Set modified to the result of remove element from
902 // configuration["replaceWithChildrenElements"].
903 let modified = if let Some(replace_with_children_elements) =
904 &mut configuration.replaceWithChildrenElements
905 {
906 replace_with_children_elements.remove_item(&element)
907 } else {
908 false
909 };
910
911 // Step 5.3. If configuration["removeElements"] does not contain element:
912 if !configuration
913 .removeElements
914 .as_ref()
915 .is_some_and(|configuration_remove_elements| {
916 configuration_remove_elements.contains_item(&element)
917 })
918 {
919 // Step 5.3.1. Comment: This is the case with a global remove-list that does not
920 // contain element.
921
922 // Step 5.3.2. Return modified.
923 return modified;
924 }
925
926 // Step 5.4. Comment: This is the case with a global remove-list that contains element.
927
928 // Step 5.5. Remove element from configuration["removeElements"].
929 if let Some(configuration_remove_elements) = &mut configuration.removeElements {
930 configuration_remove_elements.remove_item(&element);
931 }
932
933 // Step 5.6. Return true.
934 true
935 }
936 }
937
938 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
939 fn RemoveElement(&self, element: SanitizerElement) -> bool {
940 // Remove an element with element and this’s configuration.
941 self.configuration.borrow_mut().remove_element(element)
942 }
943
944 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
945 fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
946 // Step 1. Let configuration be this’s configuration.
947 let mut configuration = self.configuration.borrow_mut();
948
949 // Step 2. Assert: configuration is valid.
950 debug_assert!(configuration.is_valid());
951
952 // Step 3. Set element to the result of canonicalize a sanitizer element with element.
953 let element = element.canonicalize();
954
955 // Step 4. If the built-in non-replaceable elements list contains element:
956 if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(&element)) {
957 // Step 4.1. Return false.
958 return false;
959 }
960
961 // Step 5. If configuration["replaceWithChildrenElements"] contains element:
962 if configuration
963 .replaceWithChildrenElements
964 .as_ref()
965 .is_some_and(|configuration_replace_with_children_elements| {
966 configuration_replace_with_children_elements.contains_item(&element)
967 })
968 {
969 // Step 5.1. Return false.
970 return false;
971 }
972
973 // Step 6. Remove element from configuration["removeElements"].
974 if let Some(configuration_remove_elements) = &mut configuration.removeElements {
975 configuration_remove_elements.remove_item(&element);
976 }
977
978 // Step 7. Remove element from configuration["elements"] list.
979 if let Some(configuration_elements) = &mut configuration.elements {
980 configuration_elements.remove_item(&element);
981 }
982
983 // Step 8. Add element to configuration["replaceWithChildrenElements"].
984 if let Some(configuration_replace_with_children_elements) =
985 &mut configuration.replaceWithChildrenElements
986 {
987 configuration_replace_with_children_elements.add_item(element);
988 } else {
989 configuration.replaceWithChildrenElements = Some(vec![element]);
990 }
991
992 // Step 9. Return true.
993 true
994 }
995
996 /// <https://html.spec.whatwg.org/multipage/#dom-sanitizer-setjavascripturls>
997 fn SetJavascriptURLs(&self, allow: bool) -> bool {
998 // Step 1. Let configuration be this's configuration.
999 let mut configuration = self.configuration.borrow_mut();
1000
1001 // Step 2. Assert: configuration is valid.
1002 debug_assert!(configuration.is_valid());
1003
1004 // Step 3. If configuration["javascriptURLs"] is allow, then return false.
1005 if configuration.javascriptURLs == Some(allow) {
1006 return false;
1007 }
1008
1009 // Step 4. Set configuration["javascriptURLs"] to allow.
1010 configuration.javascriptURLs = Some(allow);
1011
1012 // Step 5. Return true.
1013 true
1014 }
1015
1016 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowprocessinginstruction>
1017 fn AllowProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1018 // Step 1. Let configuration be this’s configuration.
1019 let mut configuration = self.configuration.borrow_mut();
1020
1021 // Step 2. Assert: configuration is valid.
1022 debug_assert!(configuration.is_valid());
1023
1024 // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1025 let processing_instruction = processing_instruction.canonicalize();
1026
1027 match &mut configuration.processingInstructions {
1028 // Step 4. If configuration["processingInstructions"] exists:
1029 Some(configuration_processing_instructions) => {
1030 // Step 4.1. If configuration["processingInstructions"] contains pi:
1031 if configuration_processing_instructions.contains_target(&processing_instruction) {
1032 // Step 4.1.1. Return false.
1033 return false;
1034 }
1035
1036 // Step 4.2. Append pi to configuration["processingInstructions"].
1037 configuration_processing_instructions.push(processing_instruction);
1038
1039 // Step 4.3. Return true.
1040 true
1041 },
1042 // Step 5. Otherwise:
1043 None => {
1044 // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1045 if configuration
1046 .removeProcessingInstructions
1047 .as_ref()
1048 .is_some_and(|configuration_remove_processing_instructions| {
1049 configuration_remove_processing_instructions
1050 .contains_target(&processing_instruction)
1051 })
1052 {
1053 // Step 5.1.1. Remove the item from
1054 // configuration["removeProcessingInstructions"] whose "target" is pi["target"].
1055 if let Some(configuration_remove_processing_instructions) =
1056 &mut configuration.removeProcessingInstructions
1057 {
1058 configuration_remove_processing_instructions
1059 .retain(|item| item.target() != processing_instruction.target())
1060 }
1061
1062 // Step 5.1.2. Return true.
1063 return true;
1064 }
1065
1066 // Step 5.2. Return false.
1067 false
1068 },
1069 }
1070 }
1071
1072 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeprocessinginstruction>
1073 fn RemoveProcessingInstruction(&self, processing_instruction: SanitizerPI) -> bool {
1074 // Step 1. Let configuration be this’s configuration.
1075 let mut configuration = self.configuration.borrow_mut();
1076
1077 // Step 2. Assert: configuration is valid.
1078 debug_assert!(configuration.is_valid());
1079
1080 // Step 3. Set pi to the result of canonicalize a sanitizer processing instruction with pi.
1081 let processing_instruction = processing_instruction.canonicalize();
1082
1083 match &mut configuration.processingInstructions {
1084 // Step 4. If configuration["processingInstructions"] exists:
1085 Some(configuration_processing_instructions) => {
1086 // Step 4.1. If configuration["processingInstructions"] contains pi:
1087 if configuration_processing_instructions.contains_target(&processing_instruction) {
1088 // Step 4.1.1. Remove the item from configuration["processingInstructions"]
1089 // whose "target" is pi["target"].
1090 configuration_processing_instructions
1091 .retain(|item| item.target() != processing_instruction.target());
1092
1093 // Step 4.1.2. Return true.
1094 return true;
1095 }
1096
1097 // Step 4.2. Return false.
1098 false
1099 },
1100 // Step 5. Otherwise:
1101 None => {
1102 // Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
1103 if configuration
1104 .removeProcessingInstructions
1105 .as_ref()
1106 .is_some_and(|configuration_remove_processing_instructions| {
1107 configuration_remove_processing_instructions
1108 .contains_target(&processing_instruction)
1109 })
1110 {
1111 // Step 5.1.1. Return false.
1112 return false;
1113 }
1114
1115 // Step 5.2. Append pi to configuration["removeProcessingInstructions"].
1116 if let Some(configuration_remove_processing_instructions) =
1117 &mut configuration.removeProcessingInstructions
1118 {
1119 configuration_remove_processing_instructions.push(processing_instruction);
1120 } else {
1121 configuration.removeProcessingInstructions = Some(vec![processing_instruction]);
1122 }
1123
1124 // Step 5.3. Return true.
1125 true
1126 },
1127 }
1128 }
1129
1130 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowattribute>
1131 fn AllowAttribute(&self, attribute: SanitizerAttribute) -> bool {
1132 // Step 1. Let configuration be this’s configuration.
1133 let mut configuration = self.configuration.borrow_mut();
1134
1135 // Step 2. Assert: configuration is valid.
1136 debug_assert!(configuration.is_valid());
1137
1138 // Step 3. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1139 let attribute = attribute.canonicalize();
1140
1141 // Step 4. If configuration["attributes"] exists:
1142 if configuration.attributes.is_some() {
1143 // Step 4.1. Comment: If we have a global allow-list, we need to add attribute.
1144
1145 // Step 4.2. If configuration["dataAttributes"] is true and attribute is a custom data
1146 // attribute, then return false.
1147 if configuration.dataAttributes == Some(true) && attribute.is_custom_data_attribute() {
1148 return false;
1149 }
1150
1151 // Step 4.3. If configuration["attributes"] contains attribute return false.
1152 if configuration
1153 .attributes
1154 .as_ref()
1155 .is_some_and(|configuration_attributes| {
1156 configuration_attributes.contains(&attribute)
1157 })
1158 {
1159 return false;
1160 }
1161
1162 // Step 4.4. Comment: Fix-up per-element allow and remove lists.
1163
1164 // Step 4.5. If configuration["elements"] exists:
1165 if let Some(configuration_elements) = &mut configuration.elements {
1166 // Step 4.5.1. For each element in configuration["elements"]:
1167 for element in configuration_elements.iter_mut() {
1168 // Step 4.5.1.1. If element["attributes"] with default « » contains attribute:
1169 // Step 4.5.1.1.1. Remove attribute from element["attributes"].
1170 if let Some(element_attributes) = element.attributes_mut() {
1171 element_attributes
1172 .retain(|element_attribute| *element_attribute != attribute);
1173 }
1174
1175 // Step 4.5.1.2. Assert: element["removeAttributes"] with default « » does not
1176 // contain attribute.
1177 debug_assert!(!element.remove_attributes().is_some_and(
1178 |element_remove_attributes| element_remove_attributes.contains(&attribute)
1179 ));
1180 }
1181 }
1182
1183 // Step 4.6. Append attribute to configuration["attributes"]
1184 if let Some(configuration_attributes) = &mut configuration.attributes {
1185 configuration_attributes.push(attribute);
1186 } else {
1187 configuration.attributes = Some(vec![attribute]);
1188 }
1189
1190 // Step 4.7. Return true.
1191 true
1192 }
1193 // Step 5. Otherwise:
1194 else {
1195 // Step 5.1. Comment: If we have a global remove-list, we need to remove attribute.
1196
1197 // Step 5.2. If configuration["removeAttributes"] does not contain attribute:
1198 if !configuration.removeAttributes.as_ref().is_some_and(
1199 |configuration_remove_attributes| {
1200 configuration_remove_attributes.contains(&attribute)
1201 },
1202 ) {
1203 // Step 5.2.1. Return false.
1204 return false;
1205 }
1206
1207 // Step 5.3. Remove attribute from configuration["removeAttributes"].
1208 if let Some(configuration_remove_attributes) = &mut configuration.removeAttributes {
1209 configuration_remove_attributes.retain(|configuration_remove_attribute| {
1210 *configuration_remove_attribute != attribute
1211 });
1212 }
1213
1214 // Step 5.4. Return true.
1215 true
1216 }
1217 }
1218
1219 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeattribute>
1220 fn RemoveAttribute(&self, attribute: SanitizerAttribute) -> bool {
1221 // Remove an attribute with attribute and this’s configuration.
1222 self.configuration.borrow_mut().remove_attribute(attribute)
1223 }
1224
1225 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setcomments>
1226 fn SetComments(&self, allow: bool) -> bool {
1227 // Step 1. Let configuration be this’s configuration.
1228 let mut configuration = self.configuration.borrow_mut();
1229
1230 // Step 2. Assert: configuration is valid.
1231 debug_assert!(configuration.is_valid());
1232
1233 // Step 3. If configuration["comments"] exists and configuration["comments"] equals allow,
1234 // then return false;
1235 if configuration
1236 .comments
1237 .is_some_and(|configuration_comments| configuration_comments == allow)
1238 {
1239 return false;
1240 }
1241
1242 // Step 4. Set configuration["comments"] to allow.
1243 configuration.comments = Some(allow);
1244
1245 // Step 5. Return true.
1246 true
1247 }
1248
1249 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-setdataattributes>
1250 fn SetDataAttributes(&self, allow: bool) -> bool {
1251 // Step 1. Let configuration be this’s configuration.
1252 let mut configuration = self.configuration.borrow_mut();
1253
1254 // Step 2. Assert: configuration is valid.
1255 debug_assert!(configuration.is_valid());
1256
1257 // Step 3. If configuration["attributes"] does not exist, then return false.
1258 if configuration.attributes.is_none() {
1259 return false;
1260 }
1261
1262 // Step 4. If configuration["dataAttributes"] equals allow, then return false.
1263 if configuration.dataAttributes == Some(allow) {
1264 return false;
1265 }
1266
1267 // Step 5. If allow is true:
1268 if allow {
1269 // Step 5.1. Remove any items attr from configuration["attributes"] where attr is a
1270 // custom data attribute.
1271 if let Some(configuration_attributes) = &mut configuration.attributes {
1272 configuration_attributes.retain(|attribute| !attribute.is_custom_data_attribute());
1273 }
1274
1275 // Step 5.2. If configuration["elements"] exists:
1276 if let Some(configuration_elements) = &mut configuration.elements {
1277 // Step 5.2.1. For each element in configuration["elements"]:
1278 for element in configuration_elements {
1279 // Step 5.2.1.1. If element["attributes"] exists:
1280 if let Some(element_attributes) = element.attributes_mut() {
1281 // Step 5.2.1.1.1. Remove any items attr from element["attributes"] where
1282 // attr is a custom data attribute.
1283 element_attributes
1284 .retain(|attribute| !attribute.is_custom_data_attribute());
1285 }
1286 }
1287 }
1288 }
1289
1290 // Step 6. Set configuration["dataAttributes"] to allow.
1291 configuration.dataAttributes = Some(allow);
1292
1293 // Step 7. Return true.
1294 true
1295 }
1296
1297 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeunsafe>
1298 fn RemoveUnsafe(&self) -> bool {
1299 // Update this’s configuration with the result of calling remove unsafe on this’s
1300 // configuration.
1301 self.configuration.borrow_mut().remove_unsafe()
1302 }
1303}
1304
1305trait SanitizerConfigAlgorithm {
1306 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1307 fn is_valid(&self) -> bool;
1308
1309 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1310 fn remove_element(&mut self, element: SanitizerElement) -> bool;
1311
1312 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1313 fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool;
1314
1315 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe>
1316 fn remove_unsafe(&mut self) -> bool;
1317
1318 /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
1319 fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
1320}
1321
1322impl SanitizerConfigAlgorithm for SanitizerConfig {
1323 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
1324 fn is_valid(&self) -> bool {
1325 // NOTE: It’s expected that the configuration being passing in has previously been run
1326 // through the canonicalize the configuration steps. We will simply assert conditions that
1327 // that algorithm should have guaranteed to hold.
1328
1329 // Step 1. Assert: config["elements"] exists or config["removeElements"] exists.
1330 assert!(self.elements.is_some() || self.removeElements.is_some());
1331
1332 // Step 2. If config["elements"] exists and config["removeElements"] exists, then return
1333 // false.
1334 if self.elements.is_some() && self.removeElements.is_some() {
1335 return false;
1336 }
1337
1338 // Step 3. Assert: Either config["processingInstructions"] exists or
1339 // config["removeProcessingInstructions"] exists.
1340 assert!(
1341 self.processingInstructions.is_some() || self.removeProcessingInstructions.is_some()
1342 );
1343
1344 // Step 4. If config["processingInstructions"] exists and
1345 // config["removeProcessingInstructions"] exists, then return false.
1346 if self.processingInstructions.is_some() && self.removeProcessingInstructions.is_some() {
1347 return false;
1348 }
1349
1350 // Step 5. Assert: Either config["attributes"] exists or config["removeAttributes"] exists.
1351 assert!(self.attributes.is_some() || self.removeAttributes.is_some());
1352
1353 // Step 6. If config["attributes"] exists and config["removeAttributes"] exists, then return
1354 // false.
1355 if self.attributes.is_some() && self.removeAttributes.is_some() {
1356 return false;
1357 }
1358
1359 // Step 7. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace,
1360 // SanitizerProcessingInstruction, and SanitizerAttributeNamespace items in config are
1361 // canonical, meaning they have been run through canonicalize a sanitizer element,
1362 // canonicalize a sanitizer processing instruction, or canonicalize a sanitizer attribute,
1363 // as appropriate.
1364 //
1365 // NOTE: This assertion could be done by running the canonicalization again to see if there
1366 // is any changes. Since it is expected to canonicalize the configuration before running
1367 // this `is_valid` function, we simply skip this assert for the sake of performace.
1368
1369 match &self.elements {
1370 // Step 8. If config["elements"] exists:
1371 Some(config_elements) => {
1372 // Step 8.1. If config["elements"] has duplicates, then return false.
1373 if config_elements.has_duplicates() {
1374 return false;
1375 }
1376 },
1377 // Step 9. Otherwise:
1378 None => {
1379 // Step 9.1. If config["removeElements"] has duplicates, then return false.
1380 if self
1381 .removeElements
1382 .as_ref()
1383 .is_some_and(|config_remove_elements| config_remove_elements.has_duplicates())
1384 {
1385 return false;
1386 }
1387 },
1388 }
1389
1390 // Step 10. If config["replaceWithChildrenElements"] exists and has duplicates, then return
1391 // false.
1392 if self
1393 .replaceWithChildrenElements
1394 .as_ref()
1395 .is_some_and(|replace_with_children_elements| {
1396 replace_with_children_elements.has_duplicates()
1397 })
1398 {
1399 return false;
1400 }
1401
1402 match &self.processingInstructions {
1403 // Step 11. If config["processingInstructions"] exists:
1404 Some(config_processing_instructions) => {
1405 // Step 11.1. If config["processingInstructions"] has duplicate targets, then return
1406 // false.
1407 if config_processing_instructions.has_duplicate_targets() {
1408 return false;
1409 }
1410 },
1411 // Step 12. Otherwise:
1412 None => {
1413 // Step 12.1. If config["removeProcessingInstructions"] has duplicate targets, then
1414 // return false.
1415 if self.removeProcessingInstructions.as_ref().is_some_and(
1416 |config_remove_processing_instructions| {
1417 config_remove_processing_instructions.has_duplicate_targets()
1418 },
1419 ) {
1420 return false;
1421 }
1422 },
1423 }
1424
1425 match &self.attributes {
1426 // Step 13. If config["attributes"] exists:
1427 Some(config_attributes) => {
1428 // Step 13.1. If config["attributes"] has duplicates, then return false.
1429 if config_attributes.has_duplicates() {
1430 return false;
1431 }
1432 },
1433 // Step 14. Otherwise:
1434 None => {
1435 // Step 14.1. If config["removeAttributes"] has duplicates, then return false.
1436 if self
1437 .removeAttributes
1438 .as_ref()
1439 .is_some_and(|config_remove_attributes| {
1440 config_remove_attributes.has_duplicates()
1441 })
1442 {
1443 return false;
1444 }
1445 },
1446 }
1447
1448 // Step 15. If config["replaceWithChildrenElements"] exists:
1449 if let Some(config_replace_with_children_elements) = &self.replaceWithChildrenElements {
1450 // Step 15.1. For each element of config["replaceWithChildrenElements"]:
1451 for element in config_replace_with_children_elements {
1452 // Step 15.1.1. If the built-in non-replaceable elements list contains element, then
1453 // return false.
1454 if BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST.with(|list| list.contains_item(element)) {
1455 return false;
1456 }
1457 }
1458
1459 match &self.elements {
1460 // Step 15.2. If config["elements"] exists:
1461 Some(config_elements) => {
1462 // Step 15.2.1. If the intersection of config["elements"] and
1463 // config["replaceWithChildrenElements"] is not empty, then return false.
1464 if config_elements
1465 .is_intersection_non_empty(config_replace_with_children_elements)
1466 {
1467 return false;
1468 }
1469 },
1470 // Step 15.3. Otherwise:
1471 None => {
1472 // Step 15.3.1. If the intersection of config["removeElements"] and
1473 // config["replaceWithChildrenElements"] is not empty, then return false.
1474 if self
1475 .removeElements
1476 .as_ref()
1477 .is_some_and(|config_remove_elements| {
1478 config_remove_elements
1479 .is_intersection_non_empty(config_replace_with_children_elements)
1480 })
1481 {
1482 return false;
1483 }
1484 },
1485 }
1486 }
1487
1488 match &self.attributes {
1489 // Step 16. If config["attributes"] exists:
1490 Some(config_attributes) => {
1491 // Step 16.1. Assert: config["dataAttributes"] exists.
1492 assert!(self.dataAttributes.is_some());
1493
1494 // Step 16.2. If config["elements"] exists:
1495 if let Some(config_elements) = &self.elements {
1496 // Step 16.2.1. For each element of config["elements"]:
1497 for element in config_elements {
1498 // Step 16.2.1.1. If element["attributes"] exists and element["attributes"]
1499 // has duplicates, then return false.
1500 if element
1501 .attributes()
1502 .is_some_and(|element_attributes| element_attributes.has_duplicates())
1503 {
1504 return false;
1505 }
1506
1507 // Step 16.2.1.2. If element["removeAttributes"] exists and
1508 // element["removeAttributes"] has duplicates, then return false.
1509 if element
1510 .remove_attributes()
1511 .is_some_and(|element_remove_attributes| {
1512 element_remove_attributes.has_duplicates()
1513 })
1514 {
1515 return false;
1516 }
1517
1518 // Step 16.2.1.3. If the intersection of config["attributes"] and
1519 // element["attributes"] with default « » is not empty, then return false.
1520 if config_attributes
1521 .is_intersection_non_empty(element.attributes().unwrap_or_default())
1522 {
1523 return false;
1524 }
1525
1526 // Step 16.2.1.4. If element["removeAttributes"] with default « » is not a
1527 // subset of config["attributes"], then return false.
1528 if !element
1529 .remove_attributes()
1530 .unwrap_or_default()
1531 .iter()
1532 .all(|entry| config_attributes.contains_item(entry))
1533 {
1534 return false;
1535 }
1536
1537 // Step 16.2.1.5. If config["dataAttributes"] is true and
1538 // element["attributes"] contains a custom data attribute, then return
1539 // false.
1540 if self.dataAttributes == Some(true) &&
1541 element.attributes().is_some_and(|attributes| {
1542 attributes
1543 .iter()
1544 .any(|attribute| attribute.is_custom_data_attribute())
1545 })
1546 {
1547 return false;
1548 }
1549 }
1550 }
1551
1552 // Step 16.3. If config["dataAttributes"] is true and config["attributes"] contains
1553 // a custom data attribute, then return false.
1554 if self.dataAttributes == Some(true) &&
1555 config_attributes
1556 .iter()
1557 .any(|attribute| attribute.is_custom_data_attribute())
1558 {
1559 return false;
1560 }
1561 },
1562 // Step 17. Otherwise:
1563 None => {
1564 // Step 17.1. If config["elements"] exists:
1565 if let Some(config_elements) = &self.elements {
1566 // Step 17.1.1. For each element of config["elements"]:
1567 for element in config_elements {
1568 // Step 17.1.1.1. If element["attributes"] exists and
1569 // element["removeAttributes"] exists, then return false.
1570 if element.attributes().is_some() && element.remove_attributes().is_some() {
1571 return false;
1572 }
1573
1574 // Step 17.1.1.2. If element["attributes"] exist and element["attributes"]
1575 // has duplicates, then return false.
1576 if element
1577 .attributes()
1578 .as_ref()
1579 .is_some_and(|element_attributes| element_attributes.has_duplicates())
1580 {
1581 return false;
1582 }
1583
1584 // Step 17.1.1.3. If element["removeAttributes"] exist and
1585 // element["removeAttributes"] has duplicates, then return false.
1586 if element.remove_attributes().as_ref().is_some_and(
1587 |element_remove_attributes| element_remove_attributes.has_duplicates(),
1588 ) {
1589 return false;
1590 }
1591
1592 // Step 17.1.1.4. If the intersection of config["removeAttributes"] and
1593 // element["attributes"] with default « » is not empty, then return false.
1594 if self
1595 .removeAttributes
1596 .as_ref()
1597 .is_some_and(|config_remove_attributes| {
1598 config_remove_attributes.is_intersection_non_empty(
1599 element.attributes().unwrap_or_default(),
1600 )
1601 })
1602 {
1603 return false;
1604 }
1605
1606 // Step 17.1.1.5. If the intersection of config["removeAttributes"] and
1607 // element["removeAttributes"] with default « » is not empty, then return
1608 // false.
1609 if self
1610 .removeAttributes
1611 .as_ref()
1612 .is_some_and(|config_remove_attributes| {
1613 config_remove_attributes.is_intersection_non_empty(
1614 element.remove_attributes().unwrap_or_default(),
1615 )
1616 })
1617 {
1618 return false;
1619 }
1620 }
1621 }
1622
1623 // Step 17.2. If config["dataAttributes"] exists, then return false.
1624 if self.dataAttributes.is_some() {
1625 return false;
1626 }
1627 },
1628 }
1629
1630 // Step 18. Return true.
1631 true
1632 }
1633
1634 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
1635 fn remove_element(&mut self, element: SanitizerElement) -> bool {
1636 // Step 1. Assert: configuration is valid.
1637 debug_assert!(self.is_valid());
1638
1639 // Step 2. Set element to the result of canonicalize a sanitizer element with element.
1640 let element = element.canonicalize();
1641
1642 // Step 3. Set modified to the result of remove element from
1643 // configuration["replaceWithChildrenElements"].
1644 let modified = if let Some(configuration_replace_with_children_elements) =
1645 &mut self.replaceWithChildrenElements
1646 {
1647 configuration_replace_with_children_elements.remove_item(&element)
1648 } else {
1649 false
1650 };
1651
1652 // Step 4. If configuration["elements"] exists:
1653 if let Some(configuration_elements) = &mut self.elements {
1654 // Step 4.1. If configuration["elements"] contains element:
1655 if configuration_elements.contains_item(&element) {
1656 // Step 4.1.1. Comment: We have a global allow list and it contains element.
1657
1658 // Step 4.1.2. Remove element from configuration["elements"].
1659 configuration_elements.remove_item(&element);
1660
1661 // Step 4.1.3. Return true.
1662 return true;
1663 }
1664
1665 // Step 4.2. Comment: We have a global allow list and it does not contain element.
1666
1667 // Step 4.3. Return modified.
1668 modified
1669 }
1670 // Step 5. Otherwise:
1671 else {
1672 // Step 5.1. If configuration["removeElements"] contains element:
1673 if self
1674 .removeElements
1675 .as_mut()
1676 .is_some_and(|configuration_remove_elements| {
1677 configuration_remove_elements.contains_item(&element)
1678 })
1679 {
1680 // Step 5.1.1. Comment: We have a global remove list and it already contains element.
1681
1682 // Step 5.1.2. Return modified.
1683 return modified;
1684 }
1685
1686 // Step 5.2. Comment: We have a global remove list and it does not contain element.
1687
1688 // Step 5.3. Add element to configuration["removeElements"].
1689 if let Some(configuration_remove_elements) = &mut self.removeElements {
1690 configuration_remove_elements.add_item(element);
1691 } else {
1692 self.removeElements = Some(vec![element]);
1693 }
1694
1695 // Step 5.4. Return true.
1696 true
1697 }
1698 }
1699
1700 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute>
1701 fn remove_attribute(&mut self, attribute: SanitizerAttribute) -> bool {
1702 // Step 1. Assert: configuration is valid.
1703 debug_assert!(self.is_valid());
1704
1705 // Step 2. Set attribute to the result of canonicalize a sanitizer attribute with attribute.
1706 let attribute = attribute.canonicalize();
1707
1708 // Step 3. If configuration["attributes"] exists:
1709 if self.attributes.is_some() {
1710 // Step 3.1. Comment: If we have a global allow-list, we need to remove attribute.
1711
1712 // Step 3.2. Set modified to the result of remove attribute from
1713 // configuration["attributes"].
1714 let mut modified = self
1715 .attributes
1716 .as_mut()
1717 .is_some_and(|configuration_attributes| {
1718 configuration_attributes.remove_item(&attribute)
1719 });
1720
1721 // Step 3.3. Comment: Fix-up per-element allow and remove lists.
1722
1723 // Step 3.4. If configuration["elements"] exists:
1724 if let Some(configuration_elements) = &mut self.elements {
1725 // Step 3.4.1. For each element of configuration["elements"]:
1726 for element in configuration_elements {
1727 // Step 3.4.1.1. If element["attributes"] with default « » contains attribute:
1728 if element
1729 .attributes()
1730 .unwrap_or_default()
1731 .contains(&attribute)
1732 {
1733 // Step 3.4.1.1.1. Set modified to true.
1734 modified = true;
1735
1736 // Step 3.4.1.1.2. Remove attribute from element["attributes"].
1737 if let Some(element_attributes) = element.attributes_mut() {
1738 element_attributes
1739 .retain(|element_attribute| *element_attribute != attribute);
1740 }
1741 }
1742
1743 // Step 3.4.1.2. If element["removeAttributes"] with default « » contains
1744 // attribute:
1745 if element
1746 .remove_attributes()
1747 .unwrap_or_default()
1748 .contains(&attribute)
1749 {
1750 // Step 3.4.1.2.1. Assert: modified is true.
1751 assert!(modified);
1752
1753 // Step 3.4.1.2.2. Remove attribute from element["removeAttributes"].
1754 if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1755 element_remove_attributes.retain(|element_remove_attribute| {
1756 *element_remove_attribute != attribute
1757 });
1758 }
1759 }
1760 }
1761 }
1762
1763 // Step 3.5. Return modified.
1764 modified
1765 }
1766 // Step 4. Otherwise:
1767 else {
1768 // Step 4.1. Comment: If we have a global remove-list, we need to add attribute.
1769
1770 // Step 4.2. If configuration["removeAttributes"] contains attribute return false.
1771 if self
1772 .removeAttributes
1773 .as_ref()
1774 .is_some_and(|configuration_remove_attributes| {
1775 configuration_remove_attributes.contains(&attribute)
1776 })
1777 {
1778 return false;
1779 }
1780
1781 // Step 4.3. Comment: Fix-up per-element allow and remove lists.
1782
1783 // Step 4.4. If configuration["elements"] exists:
1784 if let Some(configuration_elements) = &mut self.elements {
1785 // Step 4.4.1. For each element in configuration["elements"]:
1786 for element in configuration_elements {
1787 // Step 4.4.1.1. If element["attributes"] with default « » contains attribute:
1788 // Step 4.4.1.1.1. Remove attribute from element["attributes"].
1789 if let Some(element_attributes) = element.attributes_mut() {
1790 element_attributes
1791 .retain(|element_attribute| *element_attribute != attribute);
1792 }
1793
1794 // Step 4.4.1.2. If element["removeAttributes"] with default « » contains
1795 // attribute:
1796 // Step 4.4.1.2.1. Remove attribute from element["removeAttributes"].
1797 if let Some(element_remove_attributes) = element.remove_attributes_mut() {
1798 element_remove_attributes.retain(|element_remove_attribute| {
1799 *element_remove_attribute != attribute
1800 });
1801 }
1802 }
1803 }
1804
1805 // Step 4.5. Append attribute to configuration["removeAttributes"]
1806 if let Some(configuration_remove_attributes) = &mut self.removeAttributes {
1807 configuration_remove_attributes.push(attribute);
1808 } else {
1809 self.removeAttributes = Some(vec![attribute]);
1810 }
1811
1812 // Step 4.6. Return true.
1813 true
1814 }
1815 }
1816
1817 /// <https://html.spec.whatwg.org/multipage/#remove-unsafe>
1818 fn remove_unsafe(&mut self) -> bool {
1819 let baseline = built_in_safe_baseline_configuration();
1820
1821 // Step 1. Assert: configuration is valid.
1822 debug_assert!(self.is_valid());
1823
1824 // Step 2. Let result be false.
1825 let mut result = false;
1826
1827 // Step 3. For each element in built-in safe baseline configuration["removeElements"]:
1828 for element in baseline.removeElements.unwrap_or_default() {
1829 // Step 3.1. If removing element from configuration is true, then set result to true.
1830 if self.remove_element(element) {
1831 result = true;
1832 }
1833 }
1834
1835 // Step 4. For each attribute in built-in safe baseline configuration["removeAttributes"]:
1836 for attribute in baseline.removeAttributes.unwrap_or_default() {
1837 // Step 4.1. If removing attribute from configuration is true, then set result to true.
1838 if self.remove_attribute(attribute) {
1839 result = true;
1840 }
1841 }
1842
1843 // Step 5. For each attribute listed in event handler content attributes:
1844 for attribute in CONTENT_EVENT_HANDLER_NAMES.iter() {
1845 // Step 5.1. If removing attribute from configuration is true, then set result to true.
1846 let attribute = SanitizerAttribute::String(DOMString::from(*attribute));
1847 if self.remove_attribute(attribute) {
1848 result = true;
1849 }
1850 }
1851
1852 // Step 6. If configuration["javascriptURLs"] is true:
1853 if self.javascriptURLs == Some(true) {
1854 // Step 6.1. Set result to true.
1855 result = true;
1856 // Step 6.2. Set configuration["javascriptURLs"] to false.
1857 self.javascriptURLs = Some(false);
1858 }
1859
1860 // Step 7. Return result.
1861 result
1862 }
1863
1864 /// <https://html.spec.whatwg.org/multipage/#canonicalize-the-configuration>
1865 fn canonicalize(&mut self, permissive_defaults: bool) {
1866 // Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
1867 // then set configuration["removeElements"] to an empty list.
1868 if self.elements.is_none() && self.removeElements.is_none() {
1869 self.removeElements = Some(Vec::new());
1870 }
1871
1872 // Step 2. If neither configuration["attributes"] nor configuration["removeAttributes"]
1873 // exist, then set configuration["removeAttributes"] to to an empty list.
1874 if self.attributes.is_none() && self.removeAttributes.is_none() {
1875 self.removeAttributes = Some(Vec::new());
1876 }
1877
1878 // Step 3. If neither configuration["processingInstructions"] nor
1879 // configuration["removeProcessingInstructions"] exist:
1880 if self.processingInstructions.is_none() && self.removeProcessingInstructions.is_none() {
1881 // Step 2.1. If permissiveDefaults is true, then set
1882 // configuration["removeProcessingInstructions"] to an empty list.
1883 if permissive_defaults {
1884 self.removeProcessingInstructions = Some(Vec::new());
1885 }
1886 // Step 2.2. Otherwise, set configuration["processingInstructions"] to an empty list.
1887 else {
1888 self.processingInstructions = Some(Vec::new());
1889 }
1890 }
1891
1892 // Step 4. If configuration["elements"] exists:
1893 if let Some(elements) = &mut self.elements {
1894 // Step 4.1. Let newElements be « ».
1895 // Step 4.2. For each element of configuration["elements"], append the result of
1896 // canonicalizing element to newElements.
1897 // Step 4.3. Set configuration["elements"] to newElements.
1898 *elements = elements
1899 .iter()
1900 .cloned()
1901 .map(SanitizerElementWithAttributes::canonicalize)
1902 .collect();
1903 }
1904
1905 // Step 5. If configuration["removeElements"] exists, then set
1906 // configuration["removeElements"] to the result of canonicalizing
1907 // configuration["removeElements"].
1908 if let Some(remove_elements) = &mut self.removeElements {
1909 *remove_elements = std::mem::take(remove_elements).canonicalize();
1910 }
1911
1912 // Step 6. If configuration["attributes"] exists, then set configuration["attributes"] to
1913 // the result of canonicalizing configuration["attributes"].
1914 if let Some(attributes) = &mut self.attributes {
1915 *attributes = std::mem::take(attributes).canonicalize()
1916 }
1917
1918 // Step 7. If configuration["removeAttributes"] exists, then set
1919 // configuration["removeAttributes"] to the result of canonicalizing
1920 // configuration["removeAttributes"].
1921 if let Some(remove_attributes) = &mut self.removeAttributes {
1922 *remove_attributes = std::mem::take(remove_attributes).canonicalize();
1923 }
1924
1925 // Step 8. If configuration["replaceWithChildrenElements"] exists, then set
1926 // configuration["replaceWithChildrenElements"] to the result of canonicalizing
1927 // configuration["replaceWithChildrenElements"].
1928 if let Some(replace_with_children_elements) = &mut self.replaceWithChildrenElements {
1929 *replace_with_children_elements =
1930 std::mem::take(replace_with_children_elements).canonicalize();
1931 }
1932
1933 // Step 9. If configuration["processingInstructions"] exists, then set
1934 // configuration["processingInstructions"] to the result of canonicalizing
1935 // configuration["processingInstructions"].
1936 if let Some(processing_instructions) = &mut self.processingInstructions {
1937 *processing_instructions = std::mem::take(processing_instructions).canonicalize();
1938 }
1939
1940 // Step 10. If configuration["removeProcessingInstructions"] exists, then set
1941 // configuration["removeProcessingInstructions"] to the result of canonicalizing
1942 // configuration["removeProcessingInstructions"].
1943 if let Some(remove_processing_instructions) = &mut self.removeProcessingInstructions {
1944 *remove_processing_instructions =
1945 std::mem::take(remove_processing_instructions).canonicalize();
1946 }
1947
1948 // Step 11. If configuration["comments"] does not exist, then set it to permissiveDefaults.
1949 if self.comments.is_none() {
1950 self.comments = Some(permissive_defaults);
1951 }
1952
1953 // Step 12. If configuration["attributes"] exists and configuration["dataAttributes"] does
1954 // not exist, then set it to permissiveDefaults.
1955 if self.attributes.is_some() && self.dataAttributes.is_none() {
1956 self.dataAttributes = Some(permissive_defaults);
1957 }
1958
1959 // Step 13. If configuration["javascriptURLs"] does not exist, then set it to permissiveDefaults.
1960 if self.javascriptURLs.is_none() {
1961 self.javascriptURLs = Some(permissive_defaults)
1962 }
1963 }
1964}
1965
1966trait Canonicalization {
1967 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
1968 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
1969 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
1970 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
1971 /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-sanitizer-list>
1972 /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-processing-instruction-list>
1973 fn canonicalize(self) -> Self;
1974}
1975
1976impl Canonicalization for SanitizerElementWithAttributes {
1977 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
1978 fn canonicalize(mut self) -> Self {
1979 // Step 1. Let result be the result of canonicalize a sanitizer element with element.
1980 let parent = match &mut self {
1981 SanitizerElementWithAttributes::String(name) => {
1982 SanitizerElement::String(std::mem::take(name))
1983 },
1984 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1985 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1986 name: std::mem::take(&mut dictionary.parent.name),
1987 namespace: dictionary.parent.namespace.as_mut().map(std::mem::take),
1988 })
1989 },
1990 };
1991 let mut canonicalized_parent = parent.canonicalize();
1992 let mut result = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1993 SanitizerElementNamespaceWithAttributes {
1994 parent: SanitizerElementNamespace {
1995 name: std::mem::take(canonicalized_parent.name_mut()),
1996 namespace: canonicalized_parent.namespace_mut().map(std::mem::take),
1997 },
1998 attributes: None,
1999 removeAttributes: None,
2000 },
2001 );
2002
2003 // Step 2. If element is a dictionary:
2004 if matches!(
2005 self,
2006 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(_)
2007 ) {
2008 // Step 2.1. If element["attributes"] exists:
2009 if let Some(attributes) = self.attributes() {
2010 // Step 2.1.1. Let attributes be « ».
2011 // Step 2.1.2. For each attribute of element["attributes"]:
2012 // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute with
2013 // attribute to attributes.
2014 let attributes = attributes
2015 .iter()
2016 .cloned()
2017 .map(|attribute| attribute.canonicalize())
2018 .collect();
2019
2020 // Step 2.1.3. Set result["attributes"] to attributes.
2021 result.set_attributes(Some(attributes));
2022 }
2023
2024 // Step 2.2. If element["removeAttributes"] exists:
2025 if let Some(remove_attributes) = self.remove_attributes() {
2026 // Step 2.2.1. Let attributes be « ».
2027 // Step 2.2.2. For each attribute of element["removeAttributes"]:
2028 // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute with
2029 // attribute to attributes.
2030 let attributes = remove_attributes
2031 .iter()
2032 .cloned()
2033 .map(|attribute| attribute.canonicalize())
2034 .collect();
2035
2036 // Step 2.2.3. Set result["removeAttributes"] to attributes.
2037 result.set_remove_attributes(Some(attributes));
2038 }
2039 }
2040
2041 // Step 3. If neither result["attributes"] nor result["removeAttributes"] exist:
2042 if result.attributes().is_none() && result.remove_attributes().is_none() {
2043 // Step 3.1. Set result["removeAttributes"] to « ».
2044 result.set_remove_attributes(Some(Vec::new()));
2045 }
2046
2047 // Step 4. Return result.
2048 result
2049 }
2050}
2051
2052impl Canonicalization for SanitizerElement {
2053 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
2054 fn canonicalize(self) -> Self {
2055 // Return the result of canonicalize a sanitizer name with element and the HTML namespace as
2056 // the default namespace.
2057 self.canonicalize_name(Some(ns!(html).to_string()))
2058 }
2059}
2060impl Canonicalization for SanitizerPI {
2061 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-processing-instruction>
2062 fn canonicalize(self) -> Self {
2063 // Step 1. Assert: pi is either a DOMString or a dictionary.
2064 assert!(matches!(
2065 self,
2066 SanitizerPI::String(_) | SanitizerPI::SanitizerProcessingInstruction(_)
2067 ));
2068
2069 // Step 2. If pi is a DOMString, then return «[ "target" → pi ]».
2070 if let SanitizerPI::String(target) = self {
2071 return SanitizerPI::SanitizerProcessingInstruction(SanitizerProcessingInstruction {
2072 target,
2073 });
2074 }
2075
2076 // Step 3. Assert: pi is a dictionary and pi["target"] exists.
2077 // NOTE: The latter is guaranteed by Rust type system.
2078 assert!(matches!(
2079 self,
2080 SanitizerPI::SanitizerProcessingInstruction(_)
2081 ));
2082
2083 // Step 4. Return «[ "target" → pi["target"] ]».
2084 self
2085 }
2086}
2087
2088impl Canonicalization for SanitizerAttribute {
2089 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
2090 fn canonicalize(self) -> Self {
2091 // Return the result of canonicalize a sanitizer name with attribute and null as the default
2092 // namespace.
2093 self.canonicalize_name(None)
2094 }
2095}
2096
2097impl<T: Canonicalization> Canonicalization for Vec<T> {
2098 /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-sanitizer-list>
2099 /// <https://html.spec.whatwg.org/multipage/#canonicalize-a-processing-instruction-list>
2100 fn canonicalize(self) -> Self {
2101 // Step 1. Let newList be « ».
2102 // Step 2. For each item in list, append the result of canonicalizing item to newList.
2103 // Step 3. Return newList.
2104 self.into_iter()
2105 .map(Canonicalization::canonicalize)
2106 .collect()
2107 }
2108}
2109
2110trait NameCanonicalization: NameMember {
2111 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self;
2112 fn is_string(&self) -> bool;
2113 fn is_dictionary(&self) -> bool;
2114
2115 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name>
2116 fn canonicalize_name(mut self, default_namespace: Option<String>) -> Self {
2117 // Step 1. Assert: name is either a DOMString or a dictionary.
2118 assert!(self.is_string() || self.is_dictionary());
2119
2120 // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" →
2121 // defaultNamespace]».
2122 if self.is_string() {
2123 return Self::new_dictionary(
2124 std::mem::take(self.name_mut()),
2125 default_namespace.map(DOMString::from),
2126 );
2127 }
2128
2129 // Step 3. Assert: name is a dictionary and both name["name"] and name["namespace"] exist.
2130 // NOTE: The latter is guaranteed by Rust type system.
2131 assert!(self.is_dictionary());
2132
2133 // Step 4. If name["namespace"] is the empty string, then set it to null.
2134 if self
2135 .namespace()
2136 .is_some_and(|namespace| namespace.str() == "")
2137 {
2138 self.set_namespace(None);
2139 }
2140
2141 // Step 5. Return «[
2142 // "name" → name["name"],
2143 // "namespace" → name["namespace"]
2144 // ]».
2145 Self::new_dictionary(
2146 std::mem::take(self.name_mut()),
2147 self.namespace_mut().map(std::mem::take),
2148 )
2149 }
2150}
2151
2152impl NameCanonicalization for SanitizerElement {
2153 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2154 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace { name, namespace })
2155 }
2156
2157 fn is_string(&self) -> bool {
2158 matches!(self, SanitizerElement::String(_))
2159 }
2160
2161 fn is_dictionary(&self) -> bool {
2162 matches!(self, SanitizerElement::SanitizerElementNamespace(_))
2163 }
2164}
2165
2166impl NameCanonicalization for SanitizerAttribute {
2167 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
2168 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2169 name,
2170 namespace,
2171 })
2172 }
2173
2174 fn is_string(&self) -> bool {
2175 matches!(self, SanitizerAttribute::String(_))
2176 }
2177
2178 fn is_dictionary(&self) -> bool {
2179 matches!(self, SanitizerAttribute::SanitizerAttributeNamespace(_))
2180 }
2181}
2182
2183/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2184trait NameSlice<T>
2185where
2186 T: NameMember + Canonicalization + Clone,
2187{
2188 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2189 fn contains_item<S: NameMember>(&self, other: &S) -> bool;
2190
2191 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2192 fn has_duplicates(&self) -> bool;
2193
2194 /// Custom version of the supporting algorithm
2195 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2196 /// intersection is non-empty, returning early if it is non-empty for efficiency.
2197 fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2198 where
2199 S: NameMember + Canonicalization + Clone;
2200}
2201
2202impl<T> NameSlice<T> for [T]
2203where
2204 T: NameMember + Canonicalization + Clone,
2205{
2206 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
2207 fn contains_item<S: NameMember>(&self, other: &S) -> bool {
2208 // A Sanitizer name list contains an item if there exists an entry of list that is an
2209 // ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
2210 // entry["namespace"].
2211 self.iter()
2212 .any(|entry| entry.name() == other.name() && entry.namespace() == other.namespace())
2213 }
2214
2215 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
2216 fn has_duplicates(&self) -> bool {
2217 // A list list has duplicates, if for any item of list, there is more than one entry in list
2218 // where item["name"] is entry["name"] and item["namespace"] is entry["namespace"].
2219 let mut used = HashSet::new();
2220 self.iter().any(move |entry| {
2221 !used.insert((
2222 entry.name().to_string(),
2223 entry.namespace().map(DOMString::to_string),
2224 ))
2225 })
2226 }
2227
2228 /// Custom version of the supporting algorithm
2229 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
2230 /// intersection is non-empty, returning early if it is non-empty for efficiency.
2231 fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
2232 where
2233 S: NameMember + Canonicalization + Clone,
2234 {
2235 // Step 1. Let set A be « [] ».
2236 // Step 2. Let set B be « [] ».
2237 // Step 3. For each entry of A, append the result of canonicalize a sanitizer name entry to
2238 // set A.
2239 // Step 4. For each entry of B, append the result of canonicalize a sanitizer name entry to
2240 // set B.
2241 let a = self.iter().map(|entry| entry.clone().canonicalize());
2242 let b = others
2243 .iter()
2244 .map(|entry| entry.clone().canonicalize())
2245 .collect::<Vec<S>>();
2246
2247 // Step 5. Return the intersection of set A and set B.
2248 // NOTE: Instead of returning the intersection itself, return true if the intersection is
2249 // non-empty, and false otherwise.
2250 a.filter(|entry| {
2251 b.iter()
2252 .any(|other| entry.name() == other.name() && entry.namespace() == other.namespace())
2253 })
2254 .any(|_| true)
2255 }
2256}
2257
2258/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
2259trait NameVec<T>
2260where
2261 T: NameMember + Canonicalization + Clone,
2262{
2263 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2264 fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
2265
2266 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2267 fn add_item(&mut self, name: T);
2268
2269 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2270 fn remove_duplicates(&mut self) -> &mut Self;
2271
2272 /// Set itself to the set intersection of itself and another list.
2273 ///
2274 /// <https://infra.spec.whatwg.org/#set-intersection>
2275 fn intersection<S>(&mut self, others: &[S])
2276 where
2277 S: NameMember + Canonicalization + Clone;
2278
2279 /// <https://infra.spec.whatwg.org/#set-difference>
2280 fn difference(&mut self, others: &[T]);
2281}
2282
2283impl<T> NameVec<T> for Vec<T>
2284where
2285 T: NameMember + Canonicalization + Clone,
2286{
2287 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
2288 fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
2289 // Step 1. Set removed to false.
2290 let mut removed = false;
2291
2292 // Step 2. For each entry of list:
2293 // Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
2294 // Step 2.1.1. Remove item entry from list.
2295 // Step 2.1.2. Set removed to true.
2296 self.retain(|entry| {
2297 let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
2298 if matched {
2299 removed = true;
2300 }
2301 !matched
2302 });
2303
2304 // Step 3. Return removed.
2305 removed
2306 }
2307
2308 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
2309 fn add_item(&mut self, name: T) {
2310 // Step 1. If list contains name, then return.
2311 if self.contains_item(&name) {
2312 return;
2313 };
2314
2315 // Step 2. Append name to list.
2316 self.push(name);
2317 }
2318
2319 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
2320 fn remove_duplicates(&mut self) -> &mut Self {
2321 // Step 1. Let result be « ».
2322 // Step 2. For each entry of list, add entry to result.
2323 // Step 3. Return result.
2324 self.sort_by(|item_a, item_b| item_a.compare(item_b));
2325 self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
2326 self
2327 }
2328
2329 /// Set itself to the set intersection of itself and another list.
2330 ///
2331 /// <https://infra.spec.whatwg.org/#set-intersection>
2332 fn intersection<S>(&mut self, others: &[S])
2333 where
2334 S: NameMember + Canonicalization + Clone,
2335 {
2336 // The intersection of ordered sets A and B, is the result of creating a new ordered set set
2337 // and, for each item of A, if B contains item, appending item to set.
2338 self.retain(|item| {
2339 others
2340 .iter()
2341 .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2342 })
2343 }
2344
2345 /// Set itself to the set difference of itself and another list.
2346 ///
2347 /// <https://infra.spec.whatwg.org/#set-difference>
2348 fn difference(&mut self, others: &[T]) {
2349 // The difference of ordered sets A and B, is the result of creating a new ordered set set
2350 // and, for each item of A, if B does not contain item, appending item to set.
2351 self.retain(|item| {
2352 !others
2353 .iter()
2354 .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
2355 })
2356 }
2357}
2358
2359/// Helper functions for accessing the "name" and "namespace" members of
2360/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
2361trait NameMember: Sized {
2362 fn name(&self) -> &DOMString;
2363 fn name_mut(&mut self) -> &mut DOMString;
2364 fn namespace(&self) -> Option<&DOMString>;
2365 fn namespace_mut(&mut self) -> Option<&mut DOMString>;
2366
2367 fn set_namespace(&mut self, namespace: Option<&str>);
2368
2369 // <https://wicg.github.io/sanitizer-api/#sanitizerconfig-less-than-item>
2370 fn is_less_than_item(&self, item_b: &Self) -> bool {
2371 let item_a = self;
2372 match item_a.namespace() {
2373 // Step 1. If itemA["namespace"] is null:
2374 None => {
2375 // Step 1.1. If itemB["namespace"] is not null, then return true.
2376 if item_b.namespace().is_some() {
2377 return true;
2378 }
2379 },
2380 // Step 2. Otherwise:
2381 Some(item_a_namespace) => {
2382 // Step 2.1. If itemB["namespace"] is null, then return false.
2383 if item_b.namespace().is_none() {
2384 return false;
2385 }
2386
2387 // Step 2.2. If itemA["namespace"] is code unit less than itemB["namespace"], then
2388 // return true.
2389 if item_b
2390 .namespace()
2391 .is_some_and(|item_b_namespace| item_a_namespace < item_b_namespace)
2392 {
2393 return true;
2394 }
2395
2396 // Step 2.3. If itemA["namespace"] is not itemB["namespace"], then return false.
2397 if item_b
2398 .namespace()
2399 .is_some_and(|item_b_namespace| item_a_namespace != item_b_namespace)
2400 {
2401 return false;
2402 }
2403 },
2404 }
2405
2406 // Step 3. Return itemA["name"] is code unit less than itemB["name"].
2407 item_a.name() < item_b.name()
2408 }
2409
2410 /// Wrapper of [`NameMember::is_less_than_item`] that returns [`std::cmp::Ordering`].
2411 fn compare(&self, other: &Self) -> Ordering {
2412 if self.is_less_than_item(other) {
2413 Ordering::Less
2414 } else {
2415 Ordering::Greater
2416 }
2417 }
2418
2419 /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2420 /// ['SanitizerAttribute']. For other types such as ['SanitizerElementWithAttributes'] and
2421 /// [`SanitizerElement`], return false by default.
2422 fn is_custom_data_attribute(&self) -> bool {
2423 false
2424 }
2425}
2426
2427impl NameMember for SanitizerElementWithAttributes {
2428 fn name(&self) -> &DOMString {
2429 match self {
2430 SanitizerElementWithAttributes::String(name) => name,
2431 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2432 &dictionary.parent.name
2433 },
2434 }
2435 }
2436
2437 fn name_mut(&mut self) -> &mut DOMString {
2438 match self {
2439 SanitizerElementWithAttributes::String(name) => name,
2440 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2441 &mut dictionary.parent.name
2442 },
2443 }
2444 }
2445
2446 fn namespace(&self) -> Option<&DOMString> {
2447 match self {
2448 SanitizerElementWithAttributes::String(_) => None,
2449 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2450 dictionary.parent.namespace.as_ref()
2451 },
2452 }
2453 }
2454
2455 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2456 match self {
2457 SanitizerElementWithAttributes::String(_) => None,
2458 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2459 dictionary.parent.namespace.as_mut()
2460 },
2461 }
2462 }
2463
2464 fn set_namespace(&mut self, namespace: Option<&str>) {
2465 match self {
2466 SanitizerElementWithAttributes::String(name) => {
2467 let new_instance =
2468 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2469 SanitizerElementNamespaceWithAttributes {
2470 parent: SanitizerElementNamespace {
2471 name: std::mem::take(name),
2472 namespace: namespace.map(DOMString::from),
2473 },
2474 attributes: None,
2475 removeAttributes: None,
2476 },
2477 );
2478 *self = new_instance;
2479 },
2480 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2481 dictionary.parent.namespace = namespace.map(DOMString::from);
2482 },
2483 }
2484 }
2485}
2486
2487impl NameMember for SanitizerElement {
2488 fn name(&self) -> &DOMString {
2489 match self {
2490 SanitizerElement::String(name) => name,
2491 SanitizerElement::SanitizerElementNamespace(dictionary) => &dictionary.name,
2492 }
2493 }
2494
2495 fn name_mut(&mut self) -> &mut DOMString {
2496 match self {
2497 SanitizerElement::String(name) => name,
2498 SanitizerElement::SanitizerElementNamespace(dictionary) => &mut dictionary.name,
2499 }
2500 }
2501
2502 fn namespace(&self) -> Option<&DOMString> {
2503 match self {
2504 SanitizerElement::String(_) => None,
2505 SanitizerElement::SanitizerElementNamespace(dictionary) => {
2506 dictionary.namespace.as_ref()
2507 },
2508 }
2509 }
2510
2511 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2512 match self {
2513 SanitizerElement::String(_) => None,
2514 SanitizerElement::SanitizerElementNamespace(dictionary) => {
2515 dictionary.namespace.as_mut()
2516 },
2517 }
2518 }
2519
2520 fn set_namespace(&mut self, namespace: Option<&str>) {
2521 match self {
2522 SanitizerElement::String(name) => {
2523 let new_instance =
2524 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
2525 name: std::mem::take(name),
2526 namespace: namespace.map(DOMString::from),
2527 });
2528 *self = new_instance;
2529 },
2530 SanitizerElement::SanitizerElementNamespace(dictionary) => {
2531 dictionary.namespace = namespace.map(DOMString::from);
2532 },
2533 }
2534 }
2535}
2536
2537impl NameMember for SanitizerAttribute {
2538 fn name(&self) -> &DOMString {
2539 match self {
2540 SanitizerAttribute::String(name) => name,
2541 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &dictionary.name,
2542 }
2543 }
2544
2545 fn name_mut(&mut self) -> &mut DOMString {
2546 match self {
2547 SanitizerAttribute::String(name) => name,
2548 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &mut dictionary.name,
2549 }
2550 }
2551
2552 fn namespace(&self) -> Option<&DOMString> {
2553 match self {
2554 SanitizerAttribute::String(_) => None,
2555 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2556 dictionary.namespace.as_ref()
2557 },
2558 }
2559 }
2560
2561 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
2562 match self {
2563 SanitizerAttribute::String(_) => None,
2564 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2565 dictionary.namespace.as_mut()
2566 },
2567 }
2568 }
2569
2570 fn set_namespace(&mut self, namespace: Option<&str>) {
2571 match self {
2572 SanitizerAttribute::String(name) => {
2573 let new_instance =
2574 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
2575 name: std::mem::take(name),
2576 namespace: namespace.map(DOMString::from),
2577 });
2578 *self = new_instance;
2579 },
2580 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
2581 dictionary.namespace = namespace.map(DOMString::from);
2582 },
2583 }
2584 }
2585
2586 /// Wrapper of [`script::dom::bindings::domname::is_custom_data_attribute`] for
2587 /// ['SanitizerAttribute'].
2588 fn is_custom_data_attribute(&self) -> bool {
2589 is_custom_data_attribute(
2590 &self.name().str(),
2591 self.namespace().map(|namespace| namespace.str()).as_deref(),
2592 )
2593 }
2594}
2595
2596/// Helper functions for accessing the "attributes" and "removeAttributes" members of
2597/// [`SanitizerElementWithAttributes`].
2598trait AttributeMember {
2599 fn attributes(&self) -> Option<&[SanitizerAttribute]>;
2600 fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2601 fn remove_attributes(&self) -> Option<&[SanitizerAttribute]>;
2602 fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
2603
2604 fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>);
2605 fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>);
2606}
2607
2608impl AttributeMember for SanitizerElementWithAttributes {
2609 fn attributes(&self) -> Option<&[SanitizerAttribute]> {
2610 match self {
2611 SanitizerElementWithAttributes::String(_) => None,
2612 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2613 dictionary.attributes.as_deref()
2614 },
2615 }
2616 }
2617
2618 fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2619 match self {
2620 SanitizerElementWithAttributes::String(_) => None,
2621 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2622 dictionary.attributes.as_mut()
2623 },
2624 }
2625 }
2626
2627 fn remove_attributes(&self) -> Option<&[SanitizerAttribute]> {
2628 match self {
2629 SanitizerElementWithAttributes::String(_) => None,
2630 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2631 dictionary.removeAttributes.as_deref()
2632 },
2633 }
2634 }
2635
2636 fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
2637 match self {
2638 SanitizerElementWithAttributes::String(_) => None,
2639 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2640 dictionary.removeAttributes.as_mut()
2641 },
2642 }
2643 }
2644
2645 fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>) {
2646 match self {
2647 SanitizerElementWithAttributes::String(name) => {
2648 *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2649 SanitizerElementNamespaceWithAttributes {
2650 parent: SanitizerElementNamespace {
2651 name: std::mem::take(name),
2652 namespace: None,
2653 },
2654 attributes,
2655 removeAttributes: None,
2656 },
2657 );
2658 },
2659 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2660 dictionary.attributes = attributes;
2661 },
2662 }
2663 }
2664
2665 fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>) {
2666 match self {
2667 SanitizerElementWithAttributes::String(name) => {
2668 *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
2669 SanitizerElementNamespaceWithAttributes {
2670 parent: SanitizerElementNamespace {
2671 name: std::mem::take(name),
2672 namespace: None,
2673 },
2674 attributes: None,
2675 removeAttributes: remove_attributes,
2676 },
2677 );
2678 },
2679 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
2680 dictionary.removeAttributes = remove_attributes;
2681 },
2682 }
2683 }
2684}
2685
2686/// Helper functions for accessing the "target" members of [`SanitizerPI`].
2687trait TargetMember {
2688 fn target(&self) -> &DOMString;
2689}
2690
2691impl TargetMember for SanitizerPI {
2692 fn target(&self) -> &DOMString {
2693 match self {
2694 SanitizerPI::String(string) => string,
2695 SanitizerPI::SanitizerProcessingInstruction(dictionary) => &dictionary.target,
2696 }
2697 }
2698}
2699
2700/// Supporting algorithms on lists of processing instructions, from the specification.
2701trait TargetSlice<T>
2702where
2703 T: TargetMember,
2704{
2705 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2706 fn contains_target(&self, other: &T) -> bool;
2707
2708 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2709 fn has_duplicate_targets(&self) -> bool;
2710}
2711
2712impl<T> TargetSlice<T> for [T]
2713where
2714 T: TargetMember,
2715{
2716 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains-a-target>
2717 fn contains_target(&self, other: &T) -> bool {
2718 // A Sanitizer target list contains a target target if there exists an entry of list that is
2719 // an ordered map, and where target equals entry["target"].
2720 self.iter().any(|entry| entry.target() == other.target())
2721 }
2722
2723 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicate-targets>
2724 fn has_duplicate_targets(&self) -> bool {
2725 // A list list has duplicate targets, if for any item of list, there is more than one entry
2726 // in list where item["target"] is entry["target"].
2727 let mut used = HashSet::new();
2728 self.iter()
2729 .any(move |entry| !used.insert(entry.target().to_string()))
2730 }
2731}
2732
2733/// Helper functions for accessing the "sanitizer" members of [`SetHTMLOptions`] and
2734/// [`SetHTMLUnsafeOptions`].
2735pub(crate) trait SanitizerMember {
2736 fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets;
2737}
2738
2739impl SanitizerMember for SetHTMLOptions {
2740 fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2741 &self.sanitizer
2742 }
2743}
2744
2745impl SanitizerMember for SetHTMLUnsafeOptions {
2746 fn sanitizer(&self) -> &SanitizerOrSanitizerConfigOrSanitizerPresets {
2747 &self.sanitizer
2748 }
2749}
2750
2751/// <https://html.spec.whatwg.org/multipage/#built-in-safe-default-configuration>
2752fn built_in_safe_default_configuration() -> SanitizerConfig {
2753 const ELEMENTS: &[(&str, &Namespace, &[&str])] = &[
2754 ("math", &ns!(mathml), &[]),
2755 ("merror", &ns!(mathml), &[]),
2756 ("mfrac", &ns!(mathml), &[]),
2757 ("mi", &ns!(mathml), &[]),
2758 ("mmultiscripts", &ns!(mathml), &[]),
2759 ("mn", &ns!(mathml), &[]),
2760 (
2761 "mo",
2762 &ns!(mathml),
2763 &[
2764 "fence",
2765 "form",
2766 "largeop",
2767 "lspace",
2768 "maxsize",
2769 "minsize",
2770 "movablelimits",
2771 "rspace",
2772 "separator",
2773 "stretchy",
2774 "symmetric",
2775 ],
2776 ),
2777 ("mover", &ns!(mathml), &["accent"]),
2778 (
2779 "mpadded",
2780 &ns!(mathml),
2781 &["depth", "height", "lspace", "voffset", "width"],
2782 ),
2783 ("mphantom", &ns!(mathml), &[]),
2784 ("mprescripts", &ns!(mathml), &[]),
2785 ("mroot", &ns!(mathml), &[]),
2786 ("mrow", &ns!(mathml), &[]),
2787 ("ms", &ns!(mathml), &[]),
2788 ("mspace", &ns!(mathml), &["depth", "height", "width"]),
2789 ("msqrt", &ns!(mathml), &[]),
2790 ("mstyle", &ns!(mathml), &[]),
2791 ("msub", &ns!(mathml), &[]),
2792 ("msubsup", &ns!(mathml), &[]),
2793 ("msup", &ns!(mathml), &[]),
2794 ("mtable", &ns!(mathml), &[]),
2795 ("mtd", &ns!(mathml), &["columnspan", "rowspan"]),
2796 ("mtext", &ns!(mathml), &[]),
2797 ("mtr", &ns!(mathml), &[]),
2798 ("munder", &ns!(mathml), &["accentunder"]),
2799 ("munderover", &ns!(mathml), &["accent", "accentunder"]),
2800 ("semantics", &ns!(mathml), &[]),
2801 ("a", &ns!(html), &["href", "hreflang", "type"]),
2802 ("abbr", &ns!(html), &[]),
2803 ("address", &ns!(html), &[]),
2804 ("article", &ns!(html), &[]),
2805 ("aside", &ns!(html), &[]),
2806 ("b", &ns!(html), &[]),
2807 ("bdi", &ns!(html), &[]),
2808 ("bdo", &ns!(html), &[]),
2809 ("blockquote", &ns!(html), &["cite"]),
2810 ("body", &ns!(html), &[]),
2811 ("br", &ns!(html), &[]),
2812 ("caption", &ns!(html), &[]),
2813 ("cite", &ns!(html), &[]),
2814 ("code", &ns!(html), &[]),
2815 ("col", &ns!(html), &["span"]),
2816 ("colgroup", &ns!(html), &["span"]),
2817 ("data", &ns!(html), &["value"]),
2818 ("dd", &ns!(html), &[]),
2819 ("del", &ns!(html), &["cite", "datetime"]),
2820 ("dfn", &ns!(html), &[]),
2821 ("div", &ns!(html), &[]),
2822 ("dl", &ns!(html), &[]),
2823 ("dt", &ns!(html), &[]),
2824 ("em", &ns!(html), &[]),
2825 ("figcaption", &ns!(html), &[]),
2826 ("figure", &ns!(html), &[]),
2827 ("footer", &ns!(html), &[]),
2828 ("h1", &ns!(html), &[]),
2829 ("h2", &ns!(html), &[]),
2830 ("h3", &ns!(html), &[]),
2831 ("h4", &ns!(html), &[]),
2832 ("h5", &ns!(html), &[]),
2833 ("h6", &ns!(html), &[]),
2834 ("head", &ns!(html), &[]),
2835 ("header", &ns!(html), &[]),
2836 ("hgroup", &ns!(html), &[]),
2837 ("hr", &ns!(html), &[]),
2838 ("html", &ns!(html), &[]),
2839 ("i", &ns!(html), &[]),
2840 ("ins", &ns!(html), &["cite", "datetime"]),
2841 ("kbd", &ns!(html), &[]),
2842 ("li", &ns!(html), &["value"]),
2843 ("main", &ns!(html), &[]),
2844 ("mark", &ns!(html), &[]),
2845 ("menu", &ns!(html), &[]),
2846 ("nav", &ns!(html), &[]),
2847 ("ol", &ns!(html), &["reversed", "start", "type"]),
2848 ("p", &ns!(html), &[]),
2849 ("pre", &ns!(html), &[]),
2850 ("q", &ns!(html), &[]),
2851 ("rp", &ns!(html), &[]),
2852 ("rt", &ns!(html), &[]),
2853 ("ruby", &ns!(html), &[]),
2854 ("s", &ns!(html), &[]),
2855 ("samp", &ns!(html), &[]),
2856 ("search", &ns!(html), &[]),
2857 ("section", &ns!(html), &[]),
2858 ("small", &ns!(html), &[]),
2859 ("span", &ns!(html), &[]),
2860 ("strong", &ns!(html), &[]),
2861 ("sub", &ns!(html), &[]),
2862 ("sup", &ns!(html), &[]),
2863 ("table", &ns!(html), &[]),
2864 ("tbody", &ns!(html), &[]),
2865 ("td", &ns!(html), &["colspan", "headers", "rowspan"]),
2866 ("tfoot", &ns!(html), &[]),
2867 (
2868 "th",
2869 &ns!(html),
2870 &["abbr", "colspan", "headers", "rowspan", "scope"],
2871 ),
2872 ("thead", &ns!(html), &[]),
2873 ("time", &ns!(html), &["datetime"]),
2874 ("title", &ns!(html), &[]),
2875 ("tr", &ns!(html), &[]),
2876 ("u", &ns!(html), &[]),
2877 ("ul", &ns!(html), &[]),
2878 ("var", &ns!(html), &[]),
2879 ("wbr", &ns!(html), &[]),
2880 ("a", &ns!(svg), &["href", "hreflang", "type"]),
2881 ("circle", &ns!(svg), &["cx", "cy", "pathLength", "r"]),
2882 ("defs", &ns!(svg), &[]),
2883 ("desc", &ns!(svg), &[]),
2884 (
2885 "ellipse",
2886 &ns!(svg),
2887 &["cx", "cy", "pathLength", "rx", "ry"],
2888 ),
2889 ("foreignObject", &ns!(svg), &["height", "width", "x", "y"]),
2890 ("g", &ns!(svg), &[]),
2891 ("line", &ns!(svg), &["pathLength", "x1", "x2", "y1", "y2"]),
2892 (
2893 "marker",
2894 &ns!(svg),
2895 &[
2896 "markerHeight",
2897 "markerUnits",
2898 "markerWidth",
2899 "orient",
2900 "preserveAspectRatio",
2901 "refX",
2902 "refY",
2903 "viewBox",
2904 ],
2905 ),
2906 ("metadata", &ns!(svg), &[]),
2907 ("path", &ns!(svg), &["d", "pathLength"]),
2908 ("polygon", &ns!(svg), &["pathLength", "points"]),
2909 ("polyline", &ns!(svg), &["pathLength", "points"]),
2910 (
2911 "rect",
2912 &ns!(svg),
2913 &["height", "pathLength", "rx", "ry", "width", "x", "y"],
2914 ),
2915 (
2916 "svg",
2917 &ns!(svg),
2918 &[
2919 "height",
2920 "preserveAspectRatio",
2921 "viewBox",
2922 "width",
2923 "x",
2924 "y",
2925 ],
2926 ),
2927 (
2928 "text",
2929 &ns!(svg),
2930 &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
2931 ),
2932 (
2933 "textPath",
2934 &ns!(svg),
2935 &[
2936 "lengthAdjust",
2937 "method",
2938 "path",
2939 "side",
2940 "spacing",
2941 "startOffset",
2942 "textLength",
2943 ],
2944 ),
2945 ("title", &ns!(svg), &[]),
2946 (
2947 "tspan",
2948 &ns!(svg),
2949 &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
2950 ),
2951 ];
2952 const ATTRIBUTES: &[&str] = &[
2953 "alignment-baseline",
2954 "baseline-shift",
2955 "clip-path",
2956 "clip-rule",
2957 "color",
2958 "color-interpolation",
2959 "cursor",
2960 "dir",
2961 "direction",
2962 "display",
2963 "displaystyle",
2964 "dominant-baseline",
2965 "fill",
2966 "fill-opacity",
2967 "fill-rule",
2968 "font-family",
2969 "font-size",
2970 "font-size-adjust",
2971 "font-stretch",
2972 "font-style",
2973 "font-variant",
2974 "font-weight",
2975 "lang",
2976 "letter-spacing",
2977 "marker-end",
2978 "marker-mid",
2979 "marker-start",
2980 "mathbackground",
2981 "mathcolor",
2982 "mathsize",
2983 "opacity",
2984 "paint-order",
2985 "pointer-events",
2986 "scriptlevel",
2987 "shape-rendering",
2988 "stop-color",
2989 "stop-opacity",
2990 "stroke",
2991 "stroke-dasharray",
2992 "stroke-dashoffset",
2993 "stroke-linecap",
2994 "stroke-linejoin",
2995 "stroke-miterlimit",
2996 "stroke-opacity",
2997 "stroke-width",
2998 "text-anchor",
2999 "text-decoration",
3000 "text-overflow",
3001 "text-rendering",
3002 "title",
3003 "transform",
3004 "transform-origin",
3005 "unicode-bidi",
3006 "vector-effect",
3007 "visibility",
3008 "white-space",
3009 "word-spacing",
3010 "writing-mode",
3011 ];
3012
3013 let create_attribute_vec = |attributes: &[&str]| -> Vec<SanitizerAttribute> {
3014 attributes
3015 .iter()
3016 .map(|&attribute| {
3017 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
3018 name: attribute.into(),
3019 namespace: None,
3020 })
3021 })
3022 .collect()
3023 };
3024
3025 let elements = ELEMENTS
3026 .iter()
3027 .map(|&(name, namespace, attributes)| {
3028 let attributes = create_attribute_vec(attributes);
3029 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
3030 SanitizerElementNamespaceWithAttributes {
3031 parent: SanitizerElementNamespace {
3032 name: name.into(),
3033 namespace: Some(namespace.to_string().into()),
3034 },
3035 attributes: Some(attributes),
3036 removeAttributes: None,
3037 },
3038 )
3039 })
3040 .collect();
3041
3042 let attributes = create_attribute_vec(ATTRIBUTES);
3043
3044 SanitizerConfig {
3045 elements: Some(elements),
3046 removeElements: None,
3047 replaceWithChildrenElements: None,
3048 processingInstructions: Some(Vec::new()),
3049 removeProcessingInstructions: None,
3050 attributes: Some(attributes),
3051 removeAttributes: None,
3052 comments: Some(false),
3053 dataAttributes: Some(false),
3054 javascriptURLs: Some(false),
3055 }
3056}
3057
3058/// <https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration>
3059fn built_in_safe_baseline_configuration() -> SanitizerConfig {
3060 const REMOVE_ELEMENTS: &[(&str, &Namespace)] = &[
3061 ("embed", &ns!(html)),
3062 ("frame", &ns!(html)),
3063 ("iframe", &ns!(html)),
3064 ("object", &ns!(html)),
3065 ("script", &ns!(html)),
3066 ("script", &ns!(svg)),
3067 ("use", &ns!(svg)),
3068 ];
3069
3070 let remove_elements = REMOVE_ELEMENTS
3071 .iter()
3072 .map(|&(name, namespace)| {
3073 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3074 name: name.into(),
3075 namespace: Some(namespace.to_string().into()),
3076 })
3077 })
3078 .collect();
3079
3080 SanitizerConfig {
3081 elements: None,
3082 removeElements: Some(remove_elements),
3083 replaceWithChildrenElements: None,
3084 processingInstructions: None,
3085 removeProcessingInstructions: None,
3086 attributes: None,
3087 removeAttributes: Some(Vec::new()),
3088 comments: None,
3089 dataAttributes: None,
3090 javascriptURLs: None,
3091 }
3092}
3093
3094/// <https://wicg.github.io/sanitizer-api/#built-in-navigating-url-attributes-list>
3095const BUILT_IN_NAVIGATING_URL_ATTRIBUTES_LIST: &[(
3096 LocalName,
3097 Option<Namespace>,
3098 LocalName,
3099 Option<Namespace>,
3100)] = &[
3101 (local_name!("a"), Some(ns!(html)), local_name!("href"), None),
3102 (
3103 local_name!("area"),
3104 Some(ns!(html)),
3105 local_name!("href"),
3106 None,
3107 ),
3108 (
3109 local_name!("base"),
3110 Some(ns!(html)),
3111 local_name!("href"),
3112 None,
3113 ),
3114 (
3115 local_name!("button"),
3116 Some(ns!(html)),
3117 local_name!("formaction"),
3118 None,
3119 ),
3120 (
3121 local_name!("form"),
3122 Some(ns!(html)),
3123 local_name!("action"),
3124 None,
3125 ),
3126 (
3127 local_name!("input"),
3128 Some(ns!(html)),
3129 local_name!("formaction"),
3130 None,
3131 ),
3132 (local_name!("a"), Some(ns!(svg)), local_name!("href"), None),
3133 (
3134 local_name!("a"),
3135 Some(ns!(svg)),
3136 local_name!("href"),
3137 Some(ns!(xlink)),
3138 ),
3139];
3140
3141/// <https://wicg.github.io/sanitizer-api/#built-in-animating-url-attributes-list>
3142const BUILT_IN_ANIMATING_URL_ATTRIBUTES_LIST: &[(
3143 LocalName,
3144 Option<Namespace>,
3145 LocalName,
3146 Option<Namespace>,
3147)] = &[
3148 (
3149 local_name!("animate"),
3150 Some(ns!(svg)),
3151 local_name!("attributeName"),
3152 None,
3153 ),
3154 (
3155 local_name!("animateTransform"),
3156 Some(ns!(svg)),
3157 local_name!("attributeName"),
3158 None,
3159 ),
3160 (
3161 local_name!("set"),
3162 Some(ns!(svg)),
3163 local_name!("attributeName"),
3164 None,
3165 ),
3166];
3167
3168thread_local! {
3169 /// <https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list>
3170 static BUILT_IN_NON_REPLACEABLE_ELEMENTS_LIST: LazyCell<Vec<SanitizerElement>> =
3171 LazyCell::new(|| {
3172 vec![
3173 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3174 name: DOMString::from_static("html"),
3175 namespace: Some(ns!(html).as_str().into()),
3176 }),
3177 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3178 name: DOMString::from_static("svg"),
3179 namespace: Some(ns!(svg).as_str().into()),
3180 }),
3181 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
3182 name: DOMString::from_static("math"),
3183 namespace: Some(ns!(mathml).as_str().into()),
3184 }),
3185 ]
3186 });
3187}