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