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