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::cmp::Ordering;
6use std::collections::HashSet;
7
8use dom_struct::dom_struct;
9use html5ever::{Namespace, ns};
10use js::context::JSContext;
11use js::rust::HandleObject;
12
13use crate::dom::bindings::cell::DomRefCell;
14use crate::dom::bindings::codegen::Bindings::SanitizerBinding::{
15 SanitizerAttribute, SanitizerAttributeNamespace, SanitizerConfig, SanitizerElement,
16 SanitizerElementNamespace, SanitizerElementNamespaceWithAttributes,
17 SanitizerElementWithAttributes, SanitizerMethods, SanitizerPresets,
18};
19use crate::dom::bindings::codegen::UnionTypes::SanitizerConfigOrSanitizerPresets;
20use crate::dom::bindings::domname::is_valid_attribute_local_name;
21use crate::dom::bindings::error::{Error, Fallible};
22use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto_and_cx};
23use crate::dom::bindings::root::DomRoot;
24use crate::dom::bindings::str::DOMString;
25use crate::dom::types::Console;
26use crate::dom::window::Window;
27
28#[dom_struct]
29pub(crate) struct Sanitizer {
30 reflector_: Reflector,
31 /// <https://wicg.github.io/sanitizer-api/#sanitizer-configuration>
32 configuration: DomRefCell<SanitizerConfig>,
33}
34
35impl Sanitizer {
36 fn new_inherited(configuration: SanitizerConfig) -> Sanitizer {
37 Sanitizer {
38 reflector_: Reflector::new(),
39 configuration: DomRefCell::new(configuration),
40 }
41 }
42
43 pub(crate) fn new_with_proto(
44 cx: &mut JSContext,
45 window: &Window,
46 proto: Option<HandleObject>,
47 configuration: SanitizerConfig,
48 ) -> DomRoot<Sanitizer> {
49 reflect_dom_object_with_proto_and_cx(
50 Box::new(Sanitizer::new_inherited(configuration)),
51 window,
52 proto,
53 cx,
54 )
55 }
56
57 /// <https://wicg.github.io/sanitizer-api/#sanitizer-set-a-configuration>
58 fn set_configuration(
59 &self,
60 mut configuration: SanitizerConfig,
61 allow_comments_pis_and_data_attributes: bool,
62 ) -> bool {
63 // Step 1. Canonicalize configuration with allowCommentsPIsAndDataAttributes.
64 configuration.canonicalize(allow_comments_pis_and_data_attributes);
65
66 // Step 2. If configuration is not valid, then return false.
67 if !configuration.is_valid() {
68 return false;
69 }
70
71 // Step 3. Set sanitizer’s configuration to configuration.
72 let mut sanitizer_configuration = self.configuration.borrow_mut();
73 *sanitizer_configuration = configuration;
74
75 // Step 4. Return true.
76 true
77 }
78}
79
80impl SanitizerMethods<crate::DomTypeHolder> for Sanitizer {
81 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-constructor>
82 fn Constructor(
83 cx: &mut JSContext,
84 window: &Window,
85 proto: Option<HandleObject>,
86 configuration: SanitizerConfigOrSanitizerPresets,
87 ) -> Fallible<DomRoot<Sanitizer>> {
88 let configuration = match configuration {
89 // Step 1. If configuration is a SanitizerPresets string, then:
90 SanitizerConfigOrSanitizerPresets::SanitizerPresets(configuration) => {
91 // Step 1.1. Assert: configuration is default.
92 assert_eq!(configuration, SanitizerPresets::Default);
93
94 // Step 1.2. Set configuration to the built-in safe default configuration.
95 built_in_safe_default_configuration()
96 },
97 SanitizerConfigOrSanitizerPresets::SanitizerConfig(configuration) => configuration,
98 };
99
100 // Step 2. Let valid be the return value of set a configuration with configuration and true
101 // on this.
102 // Step 3. If valid is false, then throw a TypeError.
103 let sanitizer = Sanitizer::new_with_proto(cx, window, proto, SanitizerConfig::default());
104 if !sanitizer.set_configuration(configuration, true) {
105 return Err(Error::Type(c"The configuration is invalid".into()));
106 }
107
108 Ok(sanitizer)
109 }
110
111 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-get>
112 fn Get(&self) -> SanitizerConfig {
113 // Step 1. Let config be this’s configuration.
114 let mut config = self.configuration.borrow_mut();
115
116 // Step 2. Assert: config is valid.
117 assert!(config.is_valid());
118
119 match &mut config.elements {
120 // Step 3. If config["elements"] exists:
121 Some(config_elements) => {
122 // Step 3.1. For any element of config["elements"]:
123 for element in config_elements.iter_mut() {
124 // Step 3.1.1. If element["attributes"] exists:
125 if let Some(element_attributes) = &mut element.attributes_mut() {
126 // Step 3.1.1.1. Set element["attributes"] to the result of sort in
127 // ascending order element["attributes"], with attrA being less than item
128 // attrB.
129 element_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
130 }
131
132 // Step 3.1.2. If element["removeAttributes"] exists:
133 if let Some(element_remove_attributes) = &mut element.remove_attributes_mut() {
134 // Step 3.1.2.1. Set element["removeAttributes"] to the result of sort in
135 // ascending order element["removeAttributes"], with attrA being less than
136 // item attrB.
137 element_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
138 }
139 }
140
141 // Step 3.2. Set config["elements"] to the result of sort in ascending order
142 // config["elements"], with elementA being less than item elementB.
143 config_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
144 },
145 // Step 4. Otherwise:
146 None => {
147 // Step 4.1. Set config["removeElements"] to the result of sort in ascending order
148 // config["removeElements"], with elementA being less than item elementB.
149 if let Some(config_remove_elements) = &mut config.removeElements {
150 config_remove_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
151 }
152 },
153 }
154
155 // Step 5. If config["replaceWithChildrenElements"] exists:
156 if let Some(config_replace_with_children_elements) = &mut config.replaceWithChildrenElements
157 {
158 // Step 5.1.Set config["replaceWithChildrenElements"] to the result of sort in ascending
159 // order config["replaceWithChildrenElements"], with elementA being less than item
160 // elementB.
161 config_replace_with_children_elements.sort_by(|item_a, item_b| item_a.compare(item_b));
162 }
163
164 // TODO:
165 // Step 6. If config["processingInstructions"] exists:
166 // Step 6.1. Set config["processingInstructions"] to the result of sort in ascending order
167 // config["processingInstructions"], with piA["target"] being code unit less than
168 // piB["target"].
169 // Step 7. Otherwise:
170 // Step 7.1. Set config["removeProcessingInstructions"] to the result of sort in ascending
171 // order config["removeProcessingInstructions"], with piA["target"] being code unit less
172 // than piB["target"].
173
174 match &mut config.attributes {
175 // Step 8. If config["attributes"] exists:
176 Some(config_attributes) => {
177 // Step 8.1. Set config["attributes"] to the result of sort in ascending order
178 // config["attributes"], with attrA being less than item attrB.
179 config_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
180 },
181 // Step 9. Otherwise:
182 None => {
183 // Step 9.1. Set config["removeAttributes"] to the result of sort in ascending order
184 // config["removeAttributes"], with attrA being less than item attrB.
185 if let Some(config_remove_attributes) = &mut config.removeAttributes {
186 config_remove_attributes.sort_by(|item_a, item_b| item_a.compare(item_b));
187 }
188 },
189 }
190
191 // Step 10. Return config.
192 (*config).clone()
193 }
194
195 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-allowelement>
196 fn AllowElement(&self, element: SanitizerElementWithAttributes) -> bool {
197 // Step 1. Let configuration be this’s configuration.
198 let mut configuration = self.configuration.borrow_mut();
199
200 // Step 2. Assert: configuration is valid.
201 assert!(configuration.is_valid());
202
203 // Step 3. Set element to the result of canonicalize a sanitizer element with attributes
204 // with element.
205 let mut element = element.canonicalize();
206
207 // Step 4. If configuration["elements"] exists:
208 if configuration.elements.is_some() {
209 // Step 4.1. Set modified to the result of remove element from
210 // configuration["replaceWithChildrenElements"].
211 let modified = if let Some(replace_with_children_elements) =
212 &mut configuration.replaceWithChildrenElements
213 {
214 replace_with_children_elements.remove_item(&element)
215 } else {
216 false
217 };
218
219 // Step 4.2. Comment: We need to make sure the per-element attributes do not overlap
220 // with global attributes.
221
222 match &configuration.attributes {
223 // Step 4.3. If configuration["attributes"] exists:
224 Some(configuration_attributes) => {
225 // Step 4.3.1. If element["attributes"] exists:
226 if let Some(element_attributes) = element.attributes_mut() {
227 // Step 4.3.1.1. Set element["attributes"] to remove duplicates from
228 // element["attributes"].
229 element_attributes.remove_duplicates();
230
231 // Step 4.3.1.2. Set element["attributes"] to the difference of
232 // element["attributes"] and configuration["attributes"].
233 element_attributes.difference(configuration_attributes);
234
235 // Step 4.3.1.3. If configuration["dataAttributes"] is true:
236 if configuration.dataAttributes == Some(true) {
237 // Step 4.3.1.3.1. Remove all items item from element["attributes"]
238 // where item is a custom data attribute.
239 element_attributes.retain(|attribute| {
240 !is_custom_data_attribute(
241 &attribute.name().str(),
242 attribute
243 .namespace()
244 .map(|namespace| namespace.str())
245 .as_deref(),
246 )
247 });
248 }
249 }
250
251 // Step 4.3.2. If element["removeAttributes"] exists:
252 if let Some(element_remove_attributes) = element.remove_attributes_mut() {
253 // Step 4.3.2.1. set element["removeattributes"] to remove duplicates from
254 // element["removeattributes"].
255 element_remove_attributes.remove_duplicates();
256
257 // Step 4.3.2.2. set element["removeattributes"] to the intersection of
258 // element["removeattributes"] and configuration["attributes"].
259 element_remove_attributes.intersection(configuration_attributes);
260 }
261 },
262 // Step 4.4. Otherwise:
263 None => {
264 // NOTE: To avoid borrowing `element` again at Step 4.4.1.2 and 4.4.1.3 after
265 // borrowing `element` mutably at the beginning of Step 4.4.1, we clone
266 // element["attributes"] first, and call `set_attributes` at the end of Step
267 // 4.4.1 to put it back into `element`.
268
269 // Step 4.4.1. If element["attributes"] exists:
270 if let Some(mut element_attributes) = element.attributes_mut().cloned() {
271 // Step 4.4.1.1. Set element["attributes"] to remove duplicates from
272 // element["attributes"].
273 element_attributes.remove_duplicates();
274
275 // Step 4.4.1.2. Set element["attributes"] to the difference of
276 // element["attributes"] and element["removeAttributes"] with default « ».
277 element_attributes
278 .difference(element.remove_attributes().unwrap_or_default());
279
280 // Step 4.4.1.3. Remove element["removeAttributes"].
281 element.set_remove_attributes(None);
282
283 // Step 4.4.1.4. Set element["attributes"] to the difference of
284 // element["attributes"] and configuration["removeAttributes"].
285 element_attributes.difference(
286 configuration
287 .removeAttributes
288 .as_deref()
289 .unwrap_or_default(),
290 );
291
292 element.set_attributes(Some(element_attributes));
293 }
294
295 // Step 4.4.2. If element["removeAttributes"] exists:
296 if let Some(mut element_remove_attributes) = element.remove_attributes_mut() {
297 // Step 4.4.2.1. Set element["removeAttributes"] to remove duplicates from
298 // element["removeAttributes"].
299 element_remove_attributes = element_remove_attributes.remove_duplicates();
300
301 // Step 4.4.2.2. Set element["removeAttributes"] to the difference of
302 // element["removeAttributes"] and configuration["removeAttributes"].
303 element_remove_attributes.difference(
304 configuration
305 .removeAttributes
306 .as_deref()
307 .unwrap_or_default(),
308 );
309 }
310 },
311 }
312
313 // Step 4.5. If configuration["elements"] does not contain element:
314 let configuration_elements = configuration
315 .elements
316 .as_mut()
317 .expect("Guaranteed by Step 4");
318 if !configuration_elements.contains_item(&element) {
319 // Step 4.5.1. Comment: This is the case with a global allow-list that does not yet
320 // contain element.
321
322 // Step 4.5.2. Append element to configuration["elements"].
323 configuration_elements.push(element.clone());
324
325 // Step 4.5.3. Return true.
326 return true;
327 }
328
329 // Step 4.6. Comment: This is the case with a global allow-list that already contains
330 // element.
331
332 // Step 4.7. Let current element be the item in configuration["elements"] where
333 // item["name"] equals element["name"] and item["namespace"] equals
334 // element["namespace"].
335 let current_element = configuration_elements
336 .iter()
337 .find(|item| {
338 item.name() == element.name() && item.namespace() == element.namespace()
339 })
340 .expect("Guaranteed by Step 4.5 and Step 4.5.2");
341
342 // Step 4.8. If element equals current element then return modified.
343 if element == *current_element {
344 return modified;
345 }
346
347 // Step 4.9. Remove element from configuration["elements"].
348 configuration_elements.remove_item(&element);
349
350 // Step 4.10. Append element to configuration["elements"]
351 configuration_elements.push(element);
352
353 // Step 4.11. Return true.
354 true
355 }
356 // Step 5. Otherwise:
357 else {
358 // Step 5.1. If element["attributes"] exists or element["removeAttributes"] with default
359 // « » is not empty:
360 if element.attributes().is_some() ||
361 !element.remove_attributes().unwrap_or_default().is_empty()
362 {
363 // Step 5.1.1. The user agent may report a warning to the console that this
364 // operation is not supported.
365 Console::internal_warn(
366 &self.global(),
367 "Do not support adding an element with attributes to a sanitizer \
368 whose configuration[\"elements\"] does not exist."
369 .into(),
370 );
371
372 // Step 5.1.2. Return false.
373 return false;
374 }
375
376 // Step 5.2. Set modified to the result of remove element from
377 // configuration["replaceWithChildrenElements"].
378 let modified = if let Some(replace_with_children_elements) =
379 &mut configuration.replaceWithChildrenElements
380 {
381 replace_with_children_elements.remove_item(&element)
382 } else {
383 false
384 };
385
386 // Step 5.3. If configuration["removeElements"] does not contain element:
387 if !configuration
388 .removeElements
389 .as_ref()
390 .is_some_and(|configuration_remove_elements| {
391 configuration_remove_elements.contains_item(&element)
392 })
393 {
394 // Step 5.3.1. Comment: This is the case with a global remove-list that does not
395 // contain element.
396
397 // Step 5.3.2. Return modified.
398 return modified;
399 }
400
401 // Step 5.4. Comment: This is the case with a global remove-list that contains element.
402
403 // Step 5.5. Remove element from configuration["removeElements"].
404 if let Some(configuration_remove_elements) = &mut configuration.removeElements {
405 configuration_remove_elements.remove_item(&element);
406 }
407
408 // Step 5.6. Return true.
409 true
410 }
411 }
412
413 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-removeelement>
414 fn RemoveElement(&self, element: SanitizerElement) -> bool {
415 // Remove an element with element and this’s configuration.
416 self.configuration.borrow_mut().remove_element(element)
417 }
418
419 /// <https://wicg.github.io/sanitizer-api/#dom-sanitizer-replaceelementwithchildren>
420 fn ReplaceElementWithChildren(&self, element: SanitizerElement) -> bool {
421 // Step 1. Let configuration be this’s configuration.
422 let mut configuration = self.configuration.borrow_mut();
423
424 // Step 2. Assert: configuration is valid.
425 assert!(configuration.is_valid());
426
427 // Step 3. Set element to the result of canonicalize a sanitizer element with element.
428 let element = element.canonicalize();
429
430 // Step 4. If the built-in non-replaceable elements list contains element:
431 if built_in_non_replaceable_elements_list().contains_item(&element) {
432 // Step 4.1. Return false.
433 return false;
434 }
435
436 // Step 5. If configuration["replaceWithChildrenElements"] contains element:
437 if configuration
438 .replaceWithChildrenElements
439 .as_ref()
440 .is_some_and(|configuration_replace_with_children_elements| {
441 configuration_replace_with_children_elements.contains_item(&element)
442 })
443 {
444 // Step 5.1. Return false.
445 return false;
446 }
447
448 // Step 6. Remove element from configuration["removeElements"].
449 if let Some(configuration_remove_elements) = &mut configuration.removeElements {
450 configuration_remove_elements.remove_item(&element);
451 }
452
453 // Step 7. Remove element from configuration["elements"] list.
454 if let Some(configuration_elements) = &mut configuration.elements {
455 configuration_elements.remove_item(&element);
456 }
457
458 // Step 8. Add element to configuration["replaceWithChildrenElements"].
459 if let Some(configuration_replace_with_children_elements) =
460 &mut configuration.replaceWithChildrenElements
461 {
462 configuration_replace_with_children_elements.add_item(element);
463 } else {
464 configuration.replaceWithChildrenElements = Some(vec![element]);
465 }
466
467 // Step 9. Return true.
468 true
469 }
470}
471
472trait SanitizerConfigAlgorithm {
473 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
474 fn is_valid(&self) -> bool;
475
476 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
477 fn remove_element(&mut self, element: SanitizerElement) -> bool;
478
479 /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
480 fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool);
481}
482
483impl SanitizerConfigAlgorithm for SanitizerConfig {
484 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid>
485 fn is_valid(&self) -> bool {
486 // NOTE: It’s expected that the configuration being passing in has previously been run
487 // through the canonicalize the configuration steps. We will simply assert conditions that
488 // that algorithm should have guaranteed to hold.
489
490 // Step 1. Assert: config["elements"] exists or config["removeElements"] exists.
491 assert!(self.elements.is_some() || self.removeElements.is_some());
492
493 // Step 2. If config["elements"] exists and config["removeElements"] exists, then return
494 // false.
495 if self.elements.is_some() && self.removeElements.is_some() {
496 return false;
497 }
498
499 // TODO:
500 // Step 3. Assert: Either config["processingInstructions"] exists or
501 // config["removeProcessingInstructions"] exists.
502 // Step 4. If config["processingInstructions"] exists and
503 // config["removeProcessingInstructions"] exists, then return false.
504
505 // Step 5. Assert: Either config["attributes"] exists or config["removeAttributes"] exists.
506 assert!(self.attributes.is_some() || self.removeAttributes.is_some());
507
508 // Step 6. If config["attributes"] exists and config["removeAttributes"] exists, then return
509 // false.
510 if self.attributes.is_some() && self.removeAttributes.is_some() {
511 return false;
512 }
513
514 // Step 7. Assert: All SanitizerElementNamespaceWithAttributes, SanitizerElementNamespace,
515 // SanitizerProcessingInstruction, and SanitizerAttributeNamespace items in config are
516 // canonical, meaning they have been run through canonicalize a sanitizer element,
517 // canonicalize a sanitizer processing instruction, or canonicalize a sanitizer attribute,
518 // as appropriate.
519 //
520 // NOTE: This assertion could be done by running the canonicalization again to see if there
521 // is any changes. Since it is expected to canonicalize the configuration before running
522 // this `is_valid` function, we simply skip this assert for the sake of performace.
523
524 match &self.elements {
525 // Step 8. If config["elements"] exists:
526 Some(config_elements) => {
527 // Step 8.1. If config["elements"] has duplicates, then return false.
528 if config_elements.has_duplicates() {
529 return false;
530 }
531 },
532 // Step 9. Otherwise:
533 None => {
534 // Step 9.1. If config["removeElements"] has duplicates, then return false.
535 if self
536 .removeElements
537 .as_ref()
538 .is_some_and(|config_remove_elements| config_remove_elements.has_duplicates())
539 {
540 return false;
541 }
542 },
543 }
544
545 // Step 10. If config["replaceWithChildrenElements"] exists and has duplicates, then return
546 // false.
547 if self
548 .replaceWithChildrenElements
549 .as_ref()
550 .is_some_and(|replace_with_children_elements| {
551 replace_with_children_elements.has_duplicates()
552 })
553 {
554 return false;
555 }
556
557 // TODO:
558 // Step 11. If config["processingInstructions"] exists:
559 // Step 11.1. If config["processingInstructions"] has duplicate targets, then return false.
560 // Step 12. Otherwise:
561 // Step 12.1. If config["removeProcessingInstructions"] has duplicate targets, then return
562 // false.
563
564 match &self.attributes {
565 // Step 13. If config["attributes"] exists:
566 Some(config_attributes) => {
567 // Step 13.1. If config["attributes"] has duplicates, then return false.
568 if config_attributes.has_duplicates() {
569 return false;
570 }
571 },
572 // Step 14. Otherwise:
573 None => {
574 // Step 14.1. If config["removeAttributes"] has duplicates, then return false.
575 if self
576 .removeAttributes
577 .as_ref()
578 .is_some_and(|config_remove_attributes| {
579 config_remove_attributes.has_duplicates()
580 })
581 {
582 return false;
583 }
584 },
585 }
586
587 // Step 15. If config["replaceWithChildrenElements"] exists:
588 if let Some(config_replace_with_children_elements) = &self.replaceWithChildrenElements {
589 // Step 15.1. For each element of config["replaceWithChildrenElements"]:
590 for element in config_replace_with_children_elements {
591 // Step 15.1.1. If the built-in non-replaceable elements list contains element, then
592 // return false.
593 if built_in_non_replaceable_elements_list().contains_item(element) {
594 return false;
595 }
596 }
597
598 match &self.elements {
599 // Step 15.2. If config["elements"] exists:
600 Some(config_elements) => {
601 // Step 15.2.1. If the intersection of config["elements"] and
602 // config["replaceWithChildrenElements"] is not empty, then return false.
603 if config_elements
604 .is_intersection_non_empty(config_replace_with_children_elements)
605 {
606 return false;
607 }
608 },
609 // Step 15.3. Otherwise:
610 None => {
611 // Step 15.3.1. If the intersection of config["removeElements"] and
612 // config["replaceWithChildrenElements"] is not empty, then return false.
613 if self
614 .removeElements
615 .as_ref()
616 .is_some_and(|config_remove_elements| {
617 config_remove_elements
618 .is_intersection_non_empty(config_replace_with_children_elements)
619 })
620 {
621 return false;
622 }
623 },
624 }
625 }
626
627 match &self.attributes {
628 // Step 16. If config["attributes"] exists:
629 Some(config_attributes) => {
630 // Step 16.1. Assert: config["dataAttributes"] exists.
631 assert!(self.dataAttributes.is_some());
632
633 // Step 16.2. If config["elements"] exists:
634 if let Some(config_elements) = &self.elements {
635 // Step 16.2.1. For each element of config["elements"]:
636 for element in config_elements {
637 // Step 16.2.1.1. If element["attributes"] exists and element["attributes"]
638 // has duplicates, then return false.
639 if element
640 .attributes()
641 .is_some_and(|element_attributes| element_attributes.has_duplicates())
642 {
643 return false;
644 }
645
646 // Step 16.2.1.2. If element["removeAttributes"] exists and
647 // element["removeAttributes"] has duplicates, then return false.
648 if element
649 .remove_attributes()
650 .is_some_and(|element_remove_attributes| {
651 element_remove_attributes.has_duplicates()
652 })
653 {
654 return false;
655 }
656
657 // Step 16.2.1.3. If the intersection of config["attributes"] and
658 // element["attributes"] with default « » is not empty, then return false.
659 if config_attributes
660 .is_intersection_non_empty(element.attributes().unwrap_or_default())
661 {
662 return false;
663 }
664
665 // Step 16.2.1.4. If element["removeAttributes"] with default « » is not a
666 // subset of config["attributes"], then return false.
667 if !element
668 .remove_attributes()
669 .unwrap_or_default()
670 .iter()
671 .all(|entry| config_attributes.contains_item(entry))
672 {
673 return false;
674 }
675
676 // Step 16.2.1.5. If config["dataAttributes"] is true and
677 // element["attributes"] contains a custom data attribute, then return
678 // false.
679 if self.dataAttributes == Some(true) &&
680 element.attributes().is_some_and(|attributes| {
681 attributes.iter().any(|attribute| {
682 is_custom_data_attribute(
683 &attribute.name().str(),
684 attribute
685 .namespace()
686 .map(|namespace| namespace.str())
687 .as_deref(),
688 )
689 })
690 })
691 {
692 return false;
693 }
694 }
695 }
696
697 // Step 16.3. If config["dataAttributes"] is true and config["attributes"] contains
698 // a custom data attribute, then return false.
699 if self.dataAttributes == Some(true) &&
700 config_attributes.iter().any(|attribute| {
701 is_custom_data_attribute(
702 &attribute.name().str(),
703 attribute
704 .namespace()
705 .map(|namespace| namespace.str())
706 .as_deref(),
707 )
708 })
709 {
710 return false;
711 }
712 },
713 // Step 17. Otherwise:
714 None => {
715 // Step 17.1. If config["elements"] exists:
716 if let Some(config_elements) = &self.elements {
717 // Step 17.1.1. For each element of config["elements"]:
718 for element in config_elements {
719 // Step 17.1.1.1. If element["attributes"] exists and
720 // element["removeAttributes"] exists, then return false.
721 if element.attributes().is_some() && element.remove_attributes().is_some() {
722 return false;
723 }
724
725 // Step 17.1.1.2. If element["attributes"] exist and element["attributes"]
726 // has duplicates, then return false.
727 if element
728 .attributes()
729 .as_ref()
730 .is_some_and(|element_attributes| element_attributes.has_duplicates())
731 {
732 return false;
733 }
734
735 // Step 17.1.1.3. If element["removeAttributes"] exist and
736 // element["removeAttributes"] has duplicates, then return false.
737 if element.remove_attributes().as_ref().is_some_and(
738 |element_remove_attributes| element_remove_attributes.has_duplicates(),
739 ) {
740 return false;
741 }
742
743 // Step 17.1.1.4. If the intersection of config["removeAttributes"] and
744 // element["attributes"] with default « » is not empty, then return false.
745 if self
746 .removeAttributes
747 .as_ref()
748 .is_some_and(|config_remove_attributes| {
749 config_remove_attributes.is_intersection_non_empty(
750 element.attributes().unwrap_or_default(),
751 )
752 })
753 {
754 return false;
755 }
756
757 // Step 17.1.1.5. If the intersection of config["removeAttributes"] and
758 // element["removeAttributes"] with default « » is not empty, then return
759 // false.
760 if self
761 .removeAttributes
762 .as_ref()
763 .is_some_and(|config_remove_attributes| {
764 config_remove_attributes.is_intersection_non_empty(
765 element.remove_attributes().unwrap_or_default(),
766 )
767 })
768 {
769 return false;
770 }
771 }
772 }
773
774 // Step 17.2. If config["dataAttributes"] exists, then return false.
775 if self.dataAttributes.is_some() {
776 return false;
777 }
778 },
779 }
780
781 // Step 18. Return true.
782 true
783 }
784
785 /// <https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element>
786 fn remove_element(&mut self, element: SanitizerElement) -> bool {
787 // Step 1. Assert: configuration is valid.
788 assert!(self.is_valid());
789
790 // Step 2. Set element to the result of canonicalize a sanitizer element with element.
791 let element = element.canonicalize();
792
793 // Step 3. Set modified to the result of remove element from
794 // configuration["replaceWithChildrenElements"].
795 let modified = if let Some(configuration_replace_with_children_elements) =
796 &mut self.replaceWithChildrenElements
797 {
798 configuration_replace_with_children_elements.remove_item(&element)
799 } else {
800 false
801 };
802
803 // Step 4. If configuration["elements"] exists:
804 if let Some(configuration_elements) = &mut self.elements {
805 // Step 4.1. If configuration["elements"] contains element:
806 if configuration_elements.contains_item(&element) {
807 // Step 4.1.1. Comment: We have a global allow list and it contains element.
808
809 // Step 4.1.2. Remove element from configuration["elements"].
810 configuration_elements.remove_item(&element);
811
812 // Step 4.1.3. Return true.
813 return true;
814 }
815
816 // Step 4.2. Comment: We have a global allow list and it does not contain element.
817
818 // Step 4.3. Return modified.
819 modified
820 }
821 // Step 5. Otherwise:
822 else {
823 // Step 5.1. If configuration["removeElements"] contains element:
824 if self
825 .removeElements
826 .as_mut()
827 .is_some_and(|configuration_remove_elements| {
828 configuration_remove_elements.contains_item(&element)
829 })
830 {
831 // Step 5.1.1. Comment: We have a global remove list and it already contains element.
832
833 // Step 5.1.2. Return modified.
834 return modified;
835 }
836
837 // Step 5.2. Comment: We have a global remove list and it does not contain element.
838
839 // Step 5.3. Add element to configuration["removeElements"].
840 if let Some(configuration_remove_elements) = &mut self.removeElements {
841 configuration_remove_elements.add_item(element);
842 } else {
843 self.removeElements = Some(vec![element]);
844 }
845
846 // Step 5.4. Return true.
847 true
848 }
849 }
850
851 /// <https://wicg.github.io/sanitizer-api/#sanitizer-canonicalize-the-configuration>
852 fn canonicalize(&mut self, allow_comments_pis_and_data_attributes: bool) {
853 // Step 1. If neither configuration["elements"] nor configuration["removeElements"] exist,
854 // then set configuration["removeElements"] to « ».
855 if self.elements.is_none() && self.removeElements.is_none() {
856 self.removeElements = Some(Vec::new());
857 }
858
859 // TODO:
860 // Step 2. If neither configuration["processingInstructions"] nor
861 // configuration["removeProcessingInstructions"] exist:
862 // Step 2.1. If allowCommentsPIsAndDataAttributes is true, then set
863 // configuration["removeProcessingInstructions"] to « ».
864 // Step 2.2. Otherwise, set configuration["processingInstructions"] to « ».
865
866 // Step 3. If neither configuration["attributes"] nor configuration["removeAttributes"]
867 // exist, then set configuration["removeAttributes"] to « ».
868 if self.attributes.is_none() && self.removeAttributes.is_none() {
869 self.removeAttributes = Some(Vec::new());
870 }
871
872 // Step 4. If configuration["elements"] exists:
873 if let Some(elements) = &mut self.elements {
874 // Step 4.1. Let elements be « ».
875 // Step 4.2. For each element of configuration["elements"] do:
876 // Step 4.2.1. Append the result of canonicalize a sanitizer element with attributes
877 // element to elements.
878 // Step 4.3. Set configuration["elements"] to elements.
879 *elements = elements
880 .iter()
881 .cloned()
882 .map(SanitizerElementWithAttributes::canonicalize)
883 .collect();
884 }
885
886 // Step 5. If configuration["removeElements"] exists:
887 if let Some(remove_elements) = &mut self.removeElements {
888 // Step 5.1. Let elements be « ».
889 // Step 5.2. For each element of configuration["removeElements"] do:
890 // Step 5.2.1. Append the result of canonicalize a sanitizer element element to
891 // elements.
892 // Step 5.3. Set configuration["removeElements"] to elements.
893 *remove_elements = remove_elements
894 .iter()
895 .cloned()
896 .map(SanitizerElement::canonicalize)
897 .collect();
898 }
899
900 // Step 6. If configuration["replaceWithChildrenElements"] exists:
901 if let Some(replace_with_children_elements) = &mut self.replaceWithChildrenElements {
902 // Step 6.1. Let elements be « ».
903 // Step 6.2. For each element of configuration["replaceWithChildrenElements"] do:
904 // Step 6.2.1. Append the result of canonicalize a sanitizer element element to
905 // elements.
906 // Step 6.3. Set configuration["replaceWithChildrenElements"] to elements.
907 *replace_with_children_elements = replace_with_children_elements
908 .iter()
909 .cloned()
910 .map(SanitizerElement::canonicalize)
911 .collect();
912 }
913
914 // TODO:
915 // Step 7. If configuration["processingInstructions"] exists:
916 // Step 7.1. Let processingInstructions be « ».
917 // Step 7.2. For each pi of configuration["processingInstructions"]:
918 // Step 7.2.1. Append the result of canonicalize a sanitizer processing instruction pi
919 // to processingInstructions.
920 // Step 7.3. Set configuration["processingInstructions"] to processingInstructions.
921
922 // TODO:
923 // Step 8. If configuration["removeProcessingInstructions"] exists:
924 // Step 8.1. Let processingInstructions be « ».
925 // Step 8.2. For each pi of configuration["removeProcessingInstructions"]:
926 // Step 8.2.1. Append the result of canonicalize a sanitizer processing instruction
927 // pi to processingInstructions.
928 // Step 8.3. Set configuration["removeProcessingInstructions"] to processingInstructions.
929
930 // Step 9. If configuration["attributes"] exists:
931 if let Some(attributes) = &mut self.attributes {
932 // Step 9.1. Let attributes be « ».
933 // Step 9.2. For each attribute of configuration["attributes"] do:
934 // Step 9.2.1. Append the result of canonicalize a sanitizer attribute attribute to
935 // attributes.
936 // Step 9.3. Set configuration["attributes"] to attributes.
937 *attributes = attributes
938 .iter()
939 .cloned()
940 .map(SanitizerAttribute::canonicalize)
941 .collect();
942 }
943
944 // Step 10. If configuration["removeAttributes"] exists:
945 if let Some(remove_attributes) = &mut self.removeAttributes {
946 // Step 10.1. Let attributes be « ».
947 // Step 10.2. For each attribute of configuration["removeAttributes"] do:
948 // Step 10.2.1. Append the result of canonicalize a sanitizer attribute attribute to
949 // attributes.
950 // Step 10.3. Set configuration["removeAttributes"] to attributes.
951 *remove_attributes = remove_attributes
952 .iter()
953 .cloned()
954 .map(SanitizerAttribute::canonicalize)
955 .collect();
956 }
957
958 // Step 11. If configuration["comments"] does not exist, then set configuration["comments"]
959 // to allowCommentsPIsAndDataAttributes.
960 if self.comments.is_none() {
961 self.comments = Some(allow_comments_pis_and_data_attributes);
962 }
963
964 // Step 12. If configuration["attributes"] exists and configuration["dataAttributes"] does
965 // not exist, then set configuration["dataAttributes"] to allowCommentsPIsAndDataAttributes.
966 if self.attributes.is_some() && self.dataAttributes.is_none() {
967 self.dataAttributes = Some(allow_comments_pis_and_data_attributes);
968 }
969 }
970}
971
972trait Canonicalization {
973 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
974 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
975 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
976 fn canonicalize(self) -> Self;
977}
978
979impl Canonicalization for SanitizerElementWithAttributes {
980 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes>
981 fn canonicalize(mut self) -> Self {
982 // Step 1. Let result be the result of canonicalize a sanitizer element with element.
983 let parent = match &mut self {
984 SanitizerElementWithAttributes::String(name) => {
985 SanitizerElement::String(std::mem::take(name))
986 },
987 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
988 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
989 name: std::mem::take(&mut dictionary.parent.name),
990 namespace: dictionary.parent.namespace.as_mut().map(std::mem::take),
991 })
992 },
993 };
994 let mut canonicalized_parent = parent.canonicalize();
995 let mut result = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
996 SanitizerElementNamespaceWithAttributes {
997 parent: SanitizerElementNamespace {
998 name: std::mem::take(canonicalized_parent.name_mut()),
999 namespace: canonicalized_parent.namespace_mut().map(std::mem::take),
1000 },
1001 attributes: None,
1002 removeAttributes: None,
1003 },
1004 );
1005
1006 // Step 2. If element is a dictionary:
1007 if matches!(
1008 self,
1009 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(_)
1010 ) {
1011 // Step 2.1. If element["attributes"] exists:
1012 if let Some(attributes) = self.attributes() {
1013 // Step 2.1.1. Let attributes be « ».
1014 // Step 2.1.2. For each attribute of element["attributes"]:
1015 // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute with
1016 // attribute to attributes.
1017 let attributes = attributes
1018 .iter()
1019 .cloned()
1020 .map(|attribute| attribute.canonicalize())
1021 .collect();
1022
1023 // Step 2.1.3. Set result["attributes"] to attributes.
1024 result.set_attributes(Some(attributes));
1025 }
1026
1027 // Step 2.2. If element["removeAttributes"] exists:
1028 if let Some(remove_attributes) = self.remove_attributes() {
1029 // Step 2.2.1. Let attributes be « ».
1030 // Step 2.2.2. For each attribute of element["removeAttributes"]:
1031 // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute with
1032 // attribute to attributes.
1033 let attributes = remove_attributes
1034 .iter()
1035 .cloned()
1036 .map(|attribute| attribute.canonicalize())
1037 .collect();
1038
1039 // Step 2.2.3. Set result["removeAttributes"] to attributes.
1040 result.set_remove_attributes(Some(attributes));
1041 }
1042 }
1043
1044 // Step 3. If neither result["attributes"] nor result["removeAttributes"] exist:
1045 if result.attributes().is_none() && result.remove_attributes().is_none() {
1046 // Step 3.1. Set result["removeAttributes"] to « ».
1047 result.set_remove_attributes(Some(Vec::new()));
1048 }
1049
1050 // Step 4. Return result.
1051 result
1052 }
1053}
1054
1055impl Canonicalization for SanitizerElement {
1056 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element>
1057 fn canonicalize(self) -> Self {
1058 // Return the result of canonicalize a sanitizer name with element and the HTML namespace as
1059 // the default namespace.
1060 self.canonicalize_name(Some(ns!(html).to_string()))
1061 }
1062}
1063
1064impl Canonicalization for SanitizerAttribute {
1065 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute>
1066 fn canonicalize(self) -> Self {
1067 // Return the result of canonicalize a sanitizer name with attribute and null as the default
1068 // namespace.
1069 self.canonicalize_name(None)
1070 }
1071}
1072
1073trait NameCanonicalization: NameMember {
1074 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self;
1075 fn is_string(&self) -> bool;
1076 fn is_dictionary(&self) -> bool;
1077
1078 /// <https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name>
1079 fn canonicalize_name(mut self, default_namespace: Option<String>) -> Self {
1080 // Step 1. Assert: name is either a DOMString or a dictionary.
1081 assert!(self.is_string() || self.is_dictionary());
1082
1083 // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" →
1084 // defaultNamespace]».
1085 if self.is_string() {
1086 return Self::new_dictionary(
1087 std::mem::take(self.name_mut()),
1088 default_namespace.map(DOMString::from),
1089 );
1090 }
1091
1092 // Step 3. Assert: name is a dictionary and both name["name"] and name["namespace"] exist.
1093 // NOTE: The latter is guaranteed by Rust type system.
1094 assert!(self.is_dictionary());
1095
1096 // Step 4. If name["namespace"] is the empty string, then set it to null.
1097 if self
1098 .namespace()
1099 .is_some_and(|namespace| namespace.str() == "")
1100 {
1101 self.set_namespace(None);
1102 }
1103
1104 // Step 5. Return «[
1105 // "name" → name["name"],
1106 // "namespace" → name["namespace"]
1107 // ]».
1108 Self::new_dictionary(
1109 std::mem::take(self.name_mut()),
1110 self.namespace_mut().map(std::mem::take),
1111 )
1112 }
1113}
1114
1115impl NameCanonicalization for SanitizerElement {
1116 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
1117 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace { name, namespace })
1118 }
1119
1120 fn is_string(&self) -> bool {
1121 matches!(self, SanitizerElement::String(_))
1122 }
1123
1124 fn is_dictionary(&self) -> bool {
1125 matches!(self, SanitizerElement::SanitizerElementNamespace(_))
1126 }
1127}
1128
1129impl NameCanonicalization for SanitizerAttribute {
1130 fn new_dictionary(name: DOMString, namespace: Option<DOMString>) -> Self {
1131 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
1132 name,
1133 namespace,
1134 })
1135 }
1136
1137 fn is_string(&self) -> bool {
1138 matches!(self, SanitizerAttribute::String(_))
1139 }
1140
1141 fn is_dictionary(&self) -> bool {
1142 matches!(self, SanitizerAttribute::SanitizerAttributeNamespace(_))
1143 }
1144}
1145
1146/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
1147trait NameSlice<T>
1148where
1149 T: NameMember + Canonicalization + Clone,
1150{
1151 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
1152 fn contains_item<S: NameMember>(&self, other: &S) -> bool;
1153
1154 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
1155 fn has_duplicates(&self) -> bool;
1156
1157 /// Custom version of the supporting algorithm
1158 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
1159 /// intersection is non-empty, returning early if it is non-empty for efficiency.
1160 fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
1161 where
1162 S: NameMember + Canonicalization + Clone;
1163}
1164
1165impl<T> NameSlice<T> for [T]
1166where
1167 T: NameMember + Canonicalization + Clone,
1168{
1169 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-contains>
1170 fn contains_item<S: NameMember>(&self, other: &S) -> bool {
1171 // A Sanitizer name list contains an item if there exists an entry of list that is an
1172 // ordered map, and where item["name"] equals entry["name"] and item["namespace"] equals
1173 // entry["namespace"].
1174 self.iter()
1175 .any(|entry| entry.name() == other.name() && entry.namespace() == other.namespace())
1176 }
1177
1178 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-has-duplicates>
1179 fn has_duplicates(&self) -> bool {
1180 // A list list has duplicates, if for any item of list, there is more than one entry in list
1181 // where item["name"] is entry["name"] and item["namespace"] is entry["namespace"].
1182 let mut used = HashSet::new();
1183 self.iter().any(move |entry| {
1184 !used.insert((
1185 entry.name().to_string(),
1186 entry.namespace().map(DOMString::to_string),
1187 ))
1188 })
1189 }
1190
1191 /// Custom version of the supporting algorithm
1192 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-intersection> that checks whether the
1193 /// intersection is non-empty, returning early if it is non-empty for efficiency.
1194 fn is_intersection_non_empty<S>(&self, others: &[S]) -> bool
1195 where
1196 S: NameMember + Canonicalization + Clone,
1197 {
1198 // Step 1. Let set A be « [] ».
1199 // Step 2. Let set B be « [] ».
1200 // Step 3. For each entry of A, append the result of canonicalize a sanitizer name entry to
1201 // set A.
1202 // Step 4. For each entry of B, append the result of canonicalize a sanitizer name entry to
1203 // set B.
1204 let a = self.iter().map(|entry| entry.clone().canonicalize());
1205 let b = others
1206 .iter()
1207 .map(|entry| entry.clone().canonicalize())
1208 .collect::<Vec<S>>();
1209
1210 // Step 5. Return the intersection of set A and set B.
1211 // NOTE: Instead of returning the intersection itself, return true if the intersection is
1212 // non-empty, and false otherwise.
1213 a.filter(|entry| {
1214 b.iter()
1215 .any(|other| entry.name() == other.name() && entry.namespace() == other.namespace())
1216 })
1217 .any(|_| true)
1218 }
1219}
1220
1221/// Supporting algorithms on lists of elements and lists of attributes, from the specification.
1222trait NameVec<T>
1223where
1224 T: NameMember + Canonicalization + Clone,
1225{
1226 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
1227 fn remove_item<S: NameMember>(&mut self, item: &S) -> bool;
1228
1229 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
1230 fn add_item(&mut self, name: T);
1231
1232 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
1233 fn remove_duplicates(&mut self) -> &mut Self;
1234
1235 /// Set itself to the set intersection of itself and another list.
1236 ///
1237 /// <https://infra.spec.whatwg.org/#set-intersection>
1238 fn intersection<S>(&mut self, others: &[S])
1239 where
1240 S: NameMember + Canonicalization + Clone;
1241
1242 /// <https://infra.spec.whatwg.org/#set-difference>
1243 fn difference(&mut self, others: &[T]);
1244}
1245
1246impl<T> NameVec<T> for Vec<T>
1247where
1248 T: NameMember + Canonicalization + Clone,
1249{
1250 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove>
1251 fn remove_item<S: NameMember>(&mut self, item: &S) -> bool {
1252 // Step 1. Set removed to false.
1253 let mut removed = false;
1254
1255 // Step 2. For each entry of list:
1256 // Step 2.1. If item["name"] equals entry["name"] and item["namespace"] equals entry["namespace"]:
1257 // Step 2.1.1. Remove item entry from list.
1258 // Step 2.1.2. Set removed to true.
1259 self.retain(|entry| {
1260 let matched = item.name() == entry.name() && item.namespace() == entry.namespace();
1261 if matched {
1262 removed = true;
1263 }
1264 !matched
1265 });
1266
1267 // Step 3. Return removed.
1268 removed
1269 }
1270
1271 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-add>
1272 fn add_item(&mut self, name: T) {
1273 // Step 1. If list contains name, then return.
1274 if self.contains_item(&name) {
1275 return;
1276 };
1277
1278 // Step 2. Append name to list.
1279 self.push(name);
1280 }
1281
1282 /// <https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-duplicates>
1283 fn remove_duplicates(&mut self) -> &mut Self {
1284 // Step 1. Let result be « ».
1285 // Step 2. For each entry of list, add entry to result.
1286 // Step 3. Return result.
1287 self.sort_by(|item_a, item_b| item_a.compare(item_b));
1288 self.dedup_by_key(|item| (item.name().clone(), item.namespace().cloned()));
1289 self
1290 }
1291
1292 /// Set itself to the set intersection of itself and another list.
1293 ///
1294 /// <https://infra.spec.whatwg.org/#set-intersection>
1295 fn intersection<S>(&mut self, others: &[S])
1296 where
1297 S: NameMember + Canonicalization + Clone,
1298 {
1299 // The intersection of ordered sets A and B, is the result of creating a new ordered set set
1300 // and, for each item of A, if B contains item, appending item to set.
1301 self.retain(|item| {
1302 others
1303 .iter()
1304 .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
1305 })
1306 }
1307
1308 /// Set itself to the set difference of itself and another list.
1309 ///
1310 /// <https://infra.spec.whatwg.org/#set-difference>
1311 fn difference(&mut self, others: &[T]) {
1312 // The difference of ordered sets A and B, is the result of creating a new ordered set set
1313 // and, for each item of A, if B does not contain item, appending item to set.
1314 self.retain(|item| {
1315 !others
1316 .iter()
1317 .any(|other| other.name() == item.name() && other.namespace() == item.namespace())
1318 })
1319 }
1320}
1321
1322/// Helper functions for accessing the "name" and "namespace" members of
1323/// [`SanitizerElementWithAttributes`], [`SanitizerElement`] and [`SanitizerAttribute`].
1324trait NameMember: Sized {
1325 fn name(&self) -> &DOMString;
1326 fn name_mut(&mut self) -> &mut DOMString;
1327 fn namespace(&self) -> Option<&DOMString>;
1328 fn namespace_mut(&mut self) -> Option<&mut DOMString>;
1329
1330 fn set_namespace(&mut self, namespace: Option<&str>);
1331
1332 // <https://wicg.github.io/sanitizer-api/#sanitizerconfig-less-than-item>
1333 fn is_less_than_item(&self, item_b: &Self) -> bool {
1334 let item_a = self;
1335 match item_a.namespace() {
1336 // Step 1. If itemA["namespace"] is null:
1337 None => {
1338 // Step 1.1. If itemB["namespace"] is not null, then return true.
1339 if item_b.namespace().is_some() {
1340 return true;
1341 }
1342 },
1343 // Step 2. Otherwise:
1344 Some(item_a_namespace) => {
1345 // Step 2.1. If itemB["namespace"] is null, then return false.
1346 if item_b.namespace().is_none() {
1347 return false;
1348 }
1349
1350 // Step 2.2. If itemA["namespace"] is code unit less than itemB["namespace"], then
1351 // return true.
1352 if item_b
1353 .namespace()
1354 .is_some_and(|item_b_namespace| item_a_namespace < item_b_namespace)
1355 {
1356 return true;
1357 }
1358
1359 // Step 2.3. If itemA["namespace"] is not itemB["namespace"], then return false.
1360 if item_b
1361 .namespace()
1362 .is_some_and(|item_b_namespace| item_a_namespace != item_b_namespace)
1363 {
1364 return false;
1365 }
1366 },
1367 }
1368
1369 // Step 3. Return itemA["name"] is code unit less than itemB["name"].
1370 item_a.name() < item_b.name()
1371 }
1372
1373 /// Wrapper of [`NameMember::is_less_than_item`] that returns [`std::cmp::Ordering`].
1374 fn compare(&self, other: &Self) -> Ordering {
1375 if self.is_less_than_item(other) {
1376 Ordering::Less
1377 } else {
1378 Ordering::Greater
1379 }
1380 }
1381}
1382
1383impl NameMember for SanitizerElementWithAttributes {
1384 fn name(&self) -> &DOMString {
1385 match self {
1386 SanitizerElementWithAttributes::String(name) => name,
1387 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1388 &dictionary.parent.name
1389 },
1390 }
1391 }
1392
1393 fn name_mut(&mut self) -> &mut DOMString {
1394 match self {
1395 SanitizerElementWithAttributes::String(name) => name,
1396 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1397 &mut dictionary.parent.name
1398 },
1399 }
1400 }
1401
1402 fn namespace(&self) -> Option<&DOMString> {
1403 match self {
1404 SanitizerElementWithAttributes::String(_) => None,
1405 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1406 dictionary.parent.namespace.as_ref()
1407 },
1408 }
1409 }
1410
1411 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
1412 match self {
1413 SanitizerElementWithAttributes::String(_) => None,
1414 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1415 dictionary.parent.namespace.as_mut()
1416 },
1417 }
1418 }
1419
1420 fn set_namespace(&mut self, namespace: Option<&str>) {
1421 match self {
1422 SanitizerElementWithAttributes::String(name) => {
1423 let new_instance =
1424 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1425 SanitizerElementNamespaceWithAttributes {
1426 parent: SanitizerElementNamespace {
1427 name: std::mem::take(name),
1428 namespace: namespace.map(DOMString::from),
1429 },
1430 attributes: None,
1431 removeAttributes: None,
1432 },
1433 );
1434 *self = new_instance;
1435 },
1436 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1437 dictionary.parent.namespace = namespace.map(DOMString::from);
1438 },
1439 }
1440 }
1441}
1442
1443impl NameMember for SanitizerElement {
1444 fn name(&self) -> &DOMString {
1445 match self {
1446 SanitizerElement::String(name) => name,
1447 SanitizerElement::SanitizerElementNamespace(dictionary) => &dictionary.name,
1448 }
1449 }
1450
1451 fn name_mut(&mut self) -> &mut DOMString {
1452 match self {
1453 SanitizerElement::String(name) => name,
1454 SanitizerElement::SanitizerElementNamespace(dictionary) => &mut dictionary.name,
1455 }
1456 }
1457
1458 fn namespace(&self) -> Option<&DOMString> {
1459 match self {
1460 SanitizerElement::String(_) => None,
1461 SanitizerElement::SanitizerElementNamespace(dictionary) => {
1462 dictionary.namespace.as_ref()
1463 },
1464 }
1465 }
1466
1467 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
1468 match self {
1469 SanitizerElement::String(_) => None,
1470 SanitizerElement::SanitizerElementNamespace(dictionary) => {
1471 dictionary.namespace.as_mut()
1472 },
1473 }
1474 }
1475
1476 fn set_namespace(&mut self, namespace: Option<&str>) {
1477 match self {
1478 SanitizerElement::String(name) => {
1479 let new_instance =
1480 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1481 name: std::mem::take(name),
1482 namespace: namespace.map(DOMString::from),
1483 });
1484 *self = new_instance;
1485 },
1486 SanitizerElement::SanitizerElementNamespace(dictionary) => {
1487 dictionary.namespace = namespace.map(DOMString::from);
1488 },
1489 }
1490 }
1491}
1492
1493impl NameMember for SanitizerAttribute {
1494 fn name(&self) -> &DOMString {
1495 match self {
1496 SanitizerAttribute::String(name) => name,
1497 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &dictionary.name,
1498 }
1499 }
1500
1501 fn name_mut(&mut self) -> &mut DOMString {
1502 match self {
1503 SanitizerAttribute::String(name) => name,
1504 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => &mut dictionary.name,
1505 }
1506 }
1507
1508 fn namespace(&self) -> Option<&DOMString> {
1509 match self {
1510 SanitizerAttribute::String(_) => None,
1511 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
1512 dictionary.namespace.as_ref()
1513 },
1514 }
1515 }
1516
1517 fn namespace_mut(&mut self) -> Option<&mut DOMString> {
1518 match self {
1519 SanitizerAttribute::String(_) => None,
1520 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
1521 dictionary.namespace.as_mut()
1522 },
1523 }
1524 }
1525
1526 fn set_namespace(&mut self, namespace: Option<&str>) {
1527 match self {
1528 SanitizerAttribute::String(name) => {
1529 let new_instance =
1530 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
1531 name: std::mem::take(name),
1532 namespace: namespace.map(DOMString::from),
1533 });
1534 *self = new_instance;
1535 },
1536 SanitizerAttribute::SanitizerAttributeNamespace(dictionary) => {
1537 dictionary.namespace = namespace.map(DOMString::from);
1538 },
1539 }
1540 }
1541}
1542
1543/// Helper functions for accessing the "attributes" and "removeAttributes" members of
1544/// [`SanitizerElementWithAttributes`].
1545trait AttributeMember {
1546 fn attributes(&self) -> Option<&[SanitizerAttribute]>;
1547 fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
1548 fn remove_attributes(&self) -> Option<&[SanitizerAttribute]>;
1549 fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>>;
1550
1551 fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>);
1552 fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>);
1553}
1554
1555impl AttributeMember for SanitizerElementWithAttributes {
1556 fn attributes(&self) -> Option<&[SanitizerAttribute]> {
1557 match self {
1558 SanitizerElementWithAttributes::String(_) => None,
1559 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1560 dictionary.attributes.as_deref()
1561 },
1562 }
1563 }
1564
1565 fn attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
1566 match self {
1567 SanitizerElementWithAttributes::String(_) => None,
1568 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1569 dictionary.attributes.as_mut()
1570 },
1571 }
1572 }
1573
1574 fn remove_attributes(&self) -> Option<&[SanitizerAttribute]> {
1575 match self {
1576 SanitizerElementWithAttributes::String(_) => None,
1577 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1578 dictionary.removeAttributes.as_deref()
1579 },
1580 }
1581 }
1582
1583 fn remove_attributes_mut(&mut self) -> Option<&mut Vec<SanitizerAttribute>> {
1584 match self {
1585 SanitizerElementWithAttributes::String(_) => None,
1586 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1587 dictionary.removeAttributes.as_mut()
1588 },
1589 }
1590 }
1591
1592 fn set_attributes(&mut self, attributes: Option<Vec<SanitizerAttribute>>) {
1593 match self {
1594 SanitizerElementWithAttributes::String(name) => {
1595 *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1596 SanitizerElementNamespaceWithAttributes {
1597 parent: SanitizerElementNamespace {
1598 name: std::mem::take(name),
1599 namespace: None,
1600 },
1601 attributes,
1602 removeAttributes: None,
1603 },
1604 );
1605 },
1606 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1607 dictionary.attributes = attributes;
1608 },
1609 }
1610 }
1611
1612 fn set_remove_attributes(&mut self, remove_attributes: Option<Vec<SanitizerAttribute>>) {
1613 match self {
1614 SanitizerElementWithAttributes::String(name) => {
1615 *self = SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1616 SanitizerElementNamespaceWithAttributes {
1617 parent: SanitizerElementNamespace {
1618 name: std::mem::take(name),
1619 namespace: None,
1620 },
1621 attributes: None,
1622 removeAttributes: remove_attributes,
1623 },
1624 );
1625 },
1626 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(dictionary) => {
1627 dictionary.removeAttributes = remove_attributes;
1628 },
1629 }
1630 }
1631}
1632
1633/// <https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration>
1634fn built_in_safe_default_configuration() -> SanitizerConfig {
1635 const ELEMENTS: &[(&str, &Namespace, &[&str])] = &[
1636 ("math", &ns!(mathml), &[]),
1637 ("merror", &ns!(mathml), &[]),
1638 ("mfrac", &ns!(mathml), &[]),
1639 ("mi", &ns!(mathml), &[]),
1640 ("mmultiscripts", &ns!(mathml), &[]),
1641 ("mn", &ns!(mathml), &[]),
1642 (
1643 "mo",
1644 &ns!(mathml),
1645 &[
1646 "fence",
1647 "form",
1648 "largeop",
1649 "lspace",
1650 "maxsize",
1651 "minsize",
1652 "movablelimits",
1653 "rspace",
1654 "separator",
1655 "stretchy",
1656 "symmetric",
1657 ],
1658 ),
1659 ("mover", &ns!(mathml), &["accent"]),
1660 (
1661 "mpadded",
1662 &ns!(mathml),
1663 &["depth", "height", "lspace", "voffset", "width"],
1664 ),
1665 ("mphantom", &ns!(mathml), &[]),
1666 ("mprescripts", &ns!(mathml), &[]),
1667 ("mroot", &ns!(mathml), &[]),
1668 ("mrow", &ns!(mathml), &[]),
1669 ("ms", &ns!(mathml), &[]),
1670 ("mspace", &ns!(mathml), &["depth", "height", "width"]),
1671 ("msqrt", &ns!(mathml), &[]),
1672 ("mstyle", &ns!(mathml), &[]),
1673 ("msub", &ns!(mathml), &[]),
1674 ("msubsup", &ns!(mathml), &[]),
1675 ("msup", &ns!(mathml), &[]),
1676 ("mtable", &ns!(mathml), &[]),
1677 ("mtd", &ns!(mathml), &["columnspan", "rowspan"]),
1678 ("mtext", &ns!(mathml), &[]),
1679 ("mtr", &ns!(mathml), &[]),
1680 ("munder", &ns!(mathml), &["accentunder"]),
1681 ("munderover", &ns!(mathml), &["accent", "accentunder"]),
1682 ("semantics", &ns!(mathml), &[]),
1683 ("a", &ns!(html), &["href", "hreflang", "type"]),
1684 ("abbr", &ns!(html), &[]),
1685 ("address", &ns!(html), &[]),
1686 ("article", &ns!(html), &[]),
1687 ("aside", &ns!(html), &[]),
1688 ("b", &ns!(html), &[]),
1689 ("bdi", &ns!(html), &[]),
1690 ("bdo", &ns!(html), &[]),
1691 ("blockquote", &ns!(html), &["cite"]),
1692 ("body", &ns!(html), &[]),
1693 ("br", &ns!(html), &[]),
1694 ("caption", &ns!(html), &[]),
1695 ("cite", &ns!(html), &[]),
1696 ("code", &ns!(html), &[]),
1697 ("col", &ns!(html), &["span"]),
1698 ("colgroup", &ns!(html), &["span"]),
1699 ("data", &ns!(html), &["value"]),
1700 ("dd", &ns!(html), &[]),
1701 ("del", &ns!(html), &["cite", "datetime"]),
1702 ("dfn", &ns!(html), &[]),
1703 ("div", &ns!(html), &[]),
1704 ("dl", &ns!(html), &[]),
1705 ("dt", &ns!(html), &[]),
1706 ("em", &ns!(html), &[]),
1707 ("figcaption", &ns!(html), &[]),
1708 ("figure", &ns!(html), &[]),
1709 ("footer", &ns!(html), &[]),
1710 ("h1", &ns!(html), &[]),
1711 ("h2", &ns!(html), &[]),
1712 ("h3", &ns!(html), &[]),
1713 ("h4", &ns!(html), &[]),
1714 ("h5", &ns!(html), &[]),
1715 ("h6", &ns!(html), &[]),
1716 ("head", &ns!(html), &[]),
1717 ("header", &ns!(html), &[]),
1718 ("hgroup", &ns!(html), &[]),
1719 ("hr", &ns!(html), &[]),
1720 ("html", &ns!(html), &[]),
1721 ("i", &ns!(html), &[]),
1722 ("ins", &ns!(html), &["cite", "datetime"]),
1723 ("kbd", &ns!(html), &[]),
1724 ("li", &ns!(html), &["value"]),
1725 ("main", &ns!(html), &[]),
1726 ("mark", &ns!(html), &[]),
1727 ("menu", &ns!(html), &[]),
1728 ("nav", &ns!(html), &[]),
1729 ("ol", &ns!(html), &["reversed", "start", "type"]),
1730 ("p", &ns!(html), &[]),
1731 ("pre", &ns!(html), &[]),
1732 ("q", &ns!(html), &[]),
1733 ("rp", &ns!(html), &[]),
1734 ("rt", &ns!(html), &[]),
1735 ("ruby", &ns!(html), &[]),
1736 ("s", &ns!(html), &[]),
1737 ("samp", &ns!(html), &[]),
1738 ("search", &ns!(html), &[]),
1739 ("section", &ns!(html), &[]),
1740 ("small", &ns!(html), &[]),
1741 ("span", &ns!(html), &[]),
1742 ("strong", &ns!(html), &[]),
1743 ("sub", &ns!(html), &[]),
1744 ("sup", &ns!(html), &[]),
1745 ("table", &ns!(html), &[]),
1746 ("tbody", &ns!(html), &[]),
1747 ("td", &ns!(html), &["colspan", "headers", "rowspan"]),
1748 ("tfoot", &ns!(html), &[]),
1749 (
1750 "th",
1751 &ns!(html),
1752 &["abbr", "colspan", "headers", "rowspan", "scope"],
1753 ),
1754 ("thead", &ns!(html), &[]),
1755 ("time", &ns!(html), &["datetime"]),
1756 ("title", &ns!(html), &[]),
1757 ("tr", &ns!(html), &[]),
1758 ("u", &ns!(html), &[]),
1759 ("ul", &ns!(html), &[]),
1760 ("var", &ns!(html), &[]),
1761 ("wbr", &ns!(html), &[]),
1762 ("a", &ns!(svg), &["href", "hreflang", "type"]),
1763 ("circle", &ns!(svg), &["cx", "cy", "pathLength", "r"]),
1764 ("defs", &ns!(svg), &[]),
1765 ("desc", &ns!(svg), &[]),
1766 (
1767 "ellipse",
1768 &ns!(svg),
1769 &["cx", "cy", "pathLength", "rx", "ry"],
1770 ),
1771 ("foreignObject", &ns!(svg), &["height", "width", "x", "y"]),
1772 ("g", &ns!(svg), &[]),
1773 ("line", &ns!(svg), &["pathLength", "x1", "x2", "y1", "y2"]),
1774 (
1775 "marker",
1776 &ns!(svg),
1777 &[
1778 "markerHeight",
1779 "markerUnits",
1780 "markerWidth",
1781 "orient",
1782 "preserveAspectRatio",
1783 "refX",
1784 "refY",
1785 "viewBox",
1786 ],
1787 ),
1788 ("metadata", &ns!(svg), &[]),
1789 ("path", &ns!(svg), &["d", "pathLength"]),
1790 ("polygon", &ns!(svg), &["pathLength", "points"]),
1791 ("polyline", &ns!(svg), &["pathLength", "points"]),
1792 (
1793 "rect",
1794 &ns!(svg),
1795 &["height", "pathLength", "rx", "ry", "width", "x", "y"],
1796 ),
1797 (
1798 "svg",
1799 &ns!(svg),
1800 &[
1801 "height",
1802 "preserveAspectRatio",
1803 "viewBox",
1804 "width",
1805 "x",
1806 "y",
1807 ],
1808 ),
1809 (
1810 "text",
1811 &ns!(svg),
1812 &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
1813 ),
1814 (
1815 "textPath",
1816 &ns!(svg),
1817 &[
1818 "lengthAdjust",
1819 "method",
1820 "path",
1821 "side",
1822 "spacing",
1823 "startOffset",
1824 "textLength",
1825 ],
1826 ),
1827 ("title", &ns!(svg), &[]),
1828 (
1829 "tspan",
1830 &ns!(svg),
1831 &["dx", "dy", "lengthAdjust", "rotate", "textLength", "x", "y"],
1832 ),
1833 ];
1834 const ATTRIBUTES: &[&str] = &[
1835 "alignment-baseline",
1836 "baseline-shift",
1837 "clip-path",
1838 "clip-rule",
1839 "color",
1840 "color-interpolation",
1841 "cursor",
1842 "dir",
1843 "direction",
1844 "display",
1845 "displaystyle",
1846 "dominant-baseline",
1847 "fill",
1848 "fill-opacity",
1849 "fill-rule",
1850 "font-family",
1851 "font-size",
1852 "font-size-adjust",
1853 "font-stretch",
1854 "font-style",
1855 "font-variant",
1856 "font-weight",
1857 "lang",
1858 "letter-spacing",
1859 "marker-end",
1860 "marker-mid",
1861 "marker-start",
1862 "mathbackground",
1863 "mathcolor",
1864 "mathsize",
1865 "opacity",
1866 "paint-order",
1867 "pointer-events",
1868 "scriptlevel",
1869 "shape-rendering",
1870 "stop-color",
1871 "stop-opacity",
1872 "stroke",
1873 "stroke-dasharray",
1874 "stroke-dashoffset",
1875 "stroke-linecap",
1876 "stroke-linejoin",
1877 "stroke-miterlimit",
1878 "stroke-opacity",
1879 "stroke-width",
1880 "text-anchor",
1881 "text-decoration",
1882 "text-overflow",
1883 "text-rendering",
1884 "title",
1885 "transform",
1886 "transform-origin",
1887 "unicode-bidi",
1888 "vector-effect",
1889 "visibility",
1890 "white-space",
1891 "word-spacing",
1892 "writing-mode",
1893 ];
1894
1895 let create_attribute_vec = |attributes: &[&str]| -> Vec<SanitizerAttribute> {
1896 attributes
1897 .iter()
1898 .map(|&attribute| {
1899 SanitizerAttribute::SanitizerAttributeNamespace(SanitizerAttributeNamespace {
1900 name: attribute.into(),
1901 namespace: None,
1902 })
1903 })
1904 .collect()
1905 };
1906
1907 let elements = ELEMENTS
1908 .iter()
1909 .map(|&(name, namespace, attributes)| {
1910 let attributes = create_attribute_vec(attributes);
1911 SanitizerElementWithAttributes::SanitizerElementNamespaceWithAttributes(
1912 SanitizerElementNamespaceWithAttributes {
1913 parent: SanitizerElementNamespace {
1914 name: name.into(),
1915 namespace: Some(namespace.to_string().into()),
1916 },
1917 attributes: Some(attributes),
1918 removeAttributes: None,
1919 },
1920 )
1921 })
1922 .collect();
1923
1924 let attributes = create_attribute_vec(ATTRIBUTES);
1925
1926 SanitizerConfig {
1927 elements: Some(elements),
1928 removeElements: None,
1929 replaceWithChildrenElements: None,
1930 attributes: Some(attributes),
1931 removeAttributes: None,
1932 comments: Some(false),
1933 dataAttributes: Some(false),
1934 }
1935}
1936
1937/// <https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list>
1938fn built_in_non_replaceable_elements_list() -> Vec<SanitizerElement> {
1939 vec![
1940 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1941 name: "html".into(),
1942 namespace: Some(ns!(html).to_string().into()),
1943 }),
1944 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1945 name: "svg".into(),
1946 namespace: Some(ns!(svg).to_string().into()),
1947 }),
1948 SanitizerElement::SanitizerElementNamespace(SanitizerElementNamespace {
1949 name: "math".into(),
1950 namespace: Some(ns!(mathml).to_string().into()),
1951 }),
1952 ]
1953}
1954
1955/// <https://html.spec.whatwg.org/multipage/#custom-data-attribute>
1956fn is_custom_data_attribute(name: &str, namespace: Option<&str>) -> bool {
1957 // A custom data attribute is an attribute in no namespace whose name starts with the string
1958 // "data-", has at least one character after the hyphen, is a valid attribute local name,
1959 // and contains no ASCII upper alphas.
1960 namespace.is_none() &&
1961 name.strip_prefix("data-")
1962 .is_some_and(|substring| !substring.is_empty()) &&
1963 is_valid_attribute_local_name(name) &&
1964 name.chars()
1965 .all(|code_point| !code_point.is_ascii_uppercase())
1966}