Sanitizer.cpp (74655B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "Sanitizer.h" 8 9 #include "mozilla/ClearOnShutdown.h" 10 #include "mozilla/Span.h" 11 #include "mozilla/StaticPtr.h" 12 #include "mozilla/dom/BindingDeclarations.h" 13 #include "mozilla/dom/BindingUtils.h" 14 #include "mozilla/dom/DocumentFragment.h" 15 #include "mozilla/dom/HTMLTemplateElement.h" 16 #include "mozilla/dom/SanitizerBinding.h" 17 #include "mozilla/dom/SanitizerDefaultConfig.h" 18 #include "nsContentUtils.h" 19 #include "nsFmtString.h" 20 #include "nsGenericHTMLElement.h" 21 #include "nsIContentInlines.h" 22 #include "nsNameSpaceManager.h" 23 24 namespace mozilla::dom { 25 using namespace sanitizer; 26 27 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Sanitizer, mGlobal) 28 29 NS_IMPL_CYCLE_COLLECTING_ADDREF(Sanitizer) 30 NS_IMPL_CYCLE_COLLECTING_RELEASE(Sanitizer) 31 32 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Sanitizer) 33 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 34 NS_INTERFACE_MAP_ENTRY(nsISupports) 35 NS_INTERFACE_MAP_END 36 37 // Map[ElementName -> ?Set[Attributes]] 38 using ElementsWithAttributes = 39 nsTHashMap<const nsStaticAtom*, UniquePtr<StaticAtomSet>>; 40 41 StaticAutoPtr<ElementsWithAttributes> sDefaultHTMLElements; 42 StaticAutoPtr<ElementsWithAttributes> sDefaultMathMLElements; 43 StaticAutoPtr<ElementsWithAttributes> sDefaultSVGElements; 44 StaticAutoPtr<StaticAtomSet> sDefaultAttributes; 45 46 JSObject* Sanitizer::WrapObject(JSContext* aCx, 47 JS::Handle<JSObject*> aGivenProto) { 48 return Sanitizer_Binding::Wrap(aCx, this, aGivenProto); 49 } 50 51 /* static */ 52 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-get-a-sanitizer-instance-from-options 53 already_AddRefed<Sanitizer> Sanitizer::GetInstance( 54 nsIGlobalObject* aGlobal, 55 const OwningSanitizerOrSanitizerConfigOrSanitizerPresets& aOptions, 56 bool aSafe, ErrorResult& aRv) { 57 // Step 4. If sanitizerSpec is a string: 58 if (aOptions.IsSanitizerPresets()) { 59 // Step 4.1. Assert: sanitizerSpec is "default" 60 MOZ_ASSERT(aOptions.GetAsSanitizerPresets() == SanitizerPresets::Default); 61 62 // Step 4.2. Set sanitizerSpec to the built-in safe default configuration. 63 // NOTE: The built-in safe default configuration is complete and not 64 // influenced by |safe|. 65 RefPtr<Sanitizer> sanitizer = new Sanitizer(aGlobal); 66 sanitizer->SetDefaultConfig(); 67 return sanitizer.forget(); 68 } 69 70 // Step 5. Assert: sanitizerSpec is either a Sanitizer instance, or a 71 // dictionary. Step 6. If sanitizerSpec is a dictionary: 72 if (aOptions.IsSanitizerConfig()) { 73 // Step 6.1. Let sanitizer be a new Sanitizer instance. 74 RefPtr<Sanitizer> sanitizer = new Sanitizer(aGlobal); 75 76 // Step 6.2. Let setConfigurationResult be the result of set a 77 // configuration with sanitizerSpec and not safe on sanitizer. 78 sanitizer->SetConfig(aOptions.GetAsSanitizerConfig(), !aSafe, aRv); 79 80 // Step 6.3. If setConfigurationResult is false, throw a TypeError. 81 if (aRv.Failed()) { 82 return nullptr; 83 } 84 85 // Step 6.4. Set sanitizerSpec to sanitizer. 86 return sanitizer.forget(); 87 } 88 89 // Step 7. Assert: sanitizerSpec is a Sanitizer instance. 90 MOZ_ASSERT(aOptions.IsSanitizer()); 91 92 // Step 8. Return sanitizerSpec. 93 RefPtr<Sanitizer> sanitizer = aOptions.GetAsSanitizer(); 94 return sanitizer.forget(); 95 } 96 97 /* static */ 98 // https://wicg.github.io/sanitizer-api/#sanitizer-constructor 99 already_AddRefed<Sanitizer> Sanitizer::Constructor( 100 const GlobalObject& aGlobal, 101 const SanitizerConfigOrSanitizerPresets& aConfig, ErrorResult& aRv) { 102 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 103 RefPtr<Sanitizer> sanitizer = new Sanitizer(global); 104 105 // Step 1. If configuration is a SanitizerPresets string, then: 106 if (aConfig.IsSanitizerPresets()) { 107 // Step 1.1. Assert: configuration is default. 108 MOZ_ASSERT(aConfig.GetAsSanitizerPresets() == SanitizerPresets::Default); 109 110 // Step 1.2. Set configuration to the built-in safe default configuration. 111 sanitizer->SetDefaultConfig(); 112 113 // NOTE: Early return because we don't need to do any 114 // processing/verification of the default config. 115 return sanitizer.forget(); 116 } 117 118 // Step 2. Let valid be the return value of set a configuration with 119 // configuration and true on this. 120 sanitizer->SetConfig(aConfig.GetAsSanitizerConfig(), true, aRv); 121 122 // Step 3. If valid is false, then throw a TypeError. 123 if (aRv.Failed()) { 124 return nullptr; 125 } 126 127 return sanitizer.forget(); 128 } 129 130 void Sanitizer::SetDefaultConfig() { 131 MOZ_ASSERT(NS_IsMainThread()); 132 AssertNoLists(); 133 MOZ_ASSERT(!mComments); 134 MOZ_ASSERT(mDataAttributes.isNothing()); 135 136 mIsDefaultConfig = true; 137 138 // https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration 139 // { 140 // ... 141 // "comments": false, 142 // "dataAttributes": false 143 // } 144 mComments = false; 145 mDataAttributes = Some(false); 146 147 if (sDefaultHTMLElements) { 148 // Already initialized. 149 return; 150 } 151 152 auto createElements = [](mozilla::Span<nsStaticAtom* const> aElements, 153 nsStaticAtom* const* aElementWithAttributes) { 154 auto elements = new ElementsWithAttributes(aElements.Length()); 155 156 size_t i = 0; 157 for (nsStaticAtom* name : aElements) { 158 UniquePtr<StaticAtomSet> attributes = nullptr; 159 160 // Walkthrough the element specific attribute list in lockstep. 161 // The last "name" in the array is a nullptr sentinel. 162 if (name == aElementWithAttributes[i]) { 163 attributes = MakeUnique<StaticAtomSet>(); 164 while (aElementWithAttributes[++i]) { 165 attributes->Insert(aElementWithAttributes[i]); 166 } 167 i++; 168 } 169 170 elements->InsertOrUpdate(name, std::move(attributes)); 171 } 172 173 return elements; 174 }; 175 176 sDefaultHTMLElements = 177 createElements(Span(kDefaultHTMLElements), kHTMLElementWithAttributes); 178 sDefaultMathMLElements = createElements(Span(kDefaultMathMLElements), 179 kMathMLElementWithAttributes); 180 sDefaultSVGElements = 181 createElements(Span(kDefaultSVGElements), kSVGElementWithAttributes); 182 183 sDefaultAttributes = new StaticAtomSet(std::size(kDefaultAttributes)); 184 for (nsStaticAtom* name : kDefaultAttributes) { 185 sDefaultAttributes->Insert(name); 186 } 187 188 ClearOnShutdown(&sDefaultHTMLElements); 189 ClearOnShutdown(&sDefaultMathMLElements); 190 ClearOnShutdown(&sDefaultAttributes); 191 } 192 193 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element 194 template <typename SanitizerElement> 195 static CanonicalElement CanonicalizeElement(const SanitizerElement& aElement) { 196 // return the result of canonicalize a sanitizer name with element and the 197 // HTML namespace as the default namespace. 198 199 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name 200 // Step 1. Assert: name is either a DOMString or a dictionary. (implicit) 201 202 // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" 203 // → defaultNamespace]». 204 if (aElement.IsString()) { 205 RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aElement.GetAsString()); 206 return CanonicalElement(nameAtom, nsGkAtoms::nsuri_xhtml); 207 } 208 209 // Step 3. Assert: name is a dictionary and both name["name"] and 210 // name["namespace"] exist. 211 const auto& elem = GetAsDictionary(aElement); 212 MOZ_ASSERT(!elem.mName.IsVoid()); 213 214 // Step 4. If name["namespace"] is the empty string, then set it to null. 215 RefPtr<nsAtom> namespaceAtom; 216 if (!elem.mNamespace.IsEmpty()) { 217 namespaceAtom = NS_AtomizeMainThread(elem.mNamespace); 218 } 219 220 // Step 5. Return «[ 221 // "name" → name["name"], 222 // "namespace" → name["namespace"] 223 // ) 224 // ]». 225 RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(elem.mName); 226 return CanonicalElement(nameAtom, namespaceAtom); 227 } 228 229 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute 230 template <typename SanitizerAttribute> 231 static CanonicalAttribute CanonicalizeAttribute( 232 const SanitizerAttribute& aAttribute) { 233 // return the result of canonicalize a sanitizer name with attribute and 234 // null as the default namespace. 235 236 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name 237 // Step 1. Assert: name is either a DOMString or a dictionary. (implicit) 238 239 // Step 2. If name is a DOMString, then return «[ "name" → name, "namespace" 240 // → defaultNamespace]». 241 if (aAttribute.IsString()) { 242 RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aAttribute.GetAsString()); 243 return CanonicalAttribute(nameAtom, nullptr); 244 } 245 246 // Step 3. Assert: name is a dictionary and both name["name"] and 247 // name["namespace"] exist. 248 const auto& attr = aAttribute.GetAsSanitizerAttributeNamespace(); 249 MOZ_ASSERT(!attr.mName.IsVoid()); 250 251 // Step 4. If name["namespace"] is the empty string, then set it to null. 252 RefPtr<nsAtom> namespaceAtom; 253 if (!attr.mNamespace.IsEmpty()) { 254 namespaceAtom = NS_AtomizeMainThread(attr.mNamespace); 255 } 256 257 // Step 5. Return «[ 258 // "name" → name["name"], 259 // "namespace" → name["namespace"], 260 // ) 261 // ]». 262 RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(attr.mName); 263 return CanonicalAttribute(nameAtom, namespaceAtom); 264 } 265 266 // https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes 267 // 268 // If aErrorMsg is not nullptr, this function will abort for duplicate 269 // attributes and set an error message, but otherwise they are ignored. 270 template <typename SanitizerElementWithAttributes> 271 static CanonicalElementAttributes CanonicalizeElementAttributes( 272 const SanitizerElementWithAttributes& aElement, 273 nsACString* aErrorMsg = nullptr) { 274 // Step 1. Let result be the result of canonicalize a sanitizer element with 275 // element. 276 // 277 // NOTE: CanonicalizeElement is the responsibility of the caller. 278 CanonicalElementAttributes result{}; 279 280 // Step 2. If element is a dictionary: 281 if (aElement.IsSanitizerElementNamespaceWithAttributes()) { 282 auto& elem = aElement.GetAsSanitizerElementNamespaceWithAttributes(); 283 284 // Step 2.1. If element["attributes"] exists: 285 if (elem.mAttributes.WasPassed()) { 286 // Step 2.1.1. Let attributes be « ». 287 CanonicalAttributeSet attributes; 288 289 // Step 2.1.2. For each attribute of element["attributes"]: 290 for (const auto& attribute : elem.mAttributes.Value()) { 291 // Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute 292 // with attribute to attributes. 293 CanonicalAttribute canonicalAttr = CanonicalizeAttribute(attribute); 294 if (!attributes.EnsureInserted(canonicalAttr)) { 295 if (aErrorMsg) { 296 aErrorMsg->Assign(nsFmtCString( 297 FMT_STRING("Duplicate attribute {} in 'attributes' of {}."), 298 canonicalAttr, CanonicalizeElement(aElement))); 299 return CanonicalElementAttributes(); 300 } 301 } 302 } 303 304 // Step 2.1.3. Set result["attributes"] to attributes. 305 result.mAttributes = Some(std::move(attributes)); 306 } 307 308 // Step 2.2. If element["attributes"] exists: 309 if (elem.mRemoveAttributes.WasPassed()) { 310 // Step 2.2.1. Let attributes be « ». 311 CanonicalAttributeSet attributes; 312 313 // Step 2.2.2. For each attribute of element["removeAttributes"]: 314 for (const auto& attribute : elem.mRemoveAttributes.Value()) { 315 // Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute 316 // with attribute to attributes. 317 CanonicalAttribute canonicalAttr = CanonicalizeAttribute(attribute); 318 if (!attributes.EnsureInserted(canonicalAttr)) { 319 if (aErrorMsg) { 320 aErrorMsg->Assign(nsFmtCString( 321 FMT_STRING( 322 "Duplicate attribute {} in 'removeAttributes' of {}."), 323 canonicalAttr, CanonicalizeElement(aElement))); 324 return CanonicalElementAttributes(); 325 } 326 } 327 } 328 329 // Step 2.2.3. Set result["removeAttributes"] to attributes. 330 result.mRemoveAttributes = Some(std::move(attributes)); 331 } 332 } 333 334 // Step 3. If neither result["attributes"] nor 335 // result["removeAttributes"] exist: 336 if (!result.mAttributes && !result.mRemoveAttributes) { 337 // Step 3.1. Set result["removeAttributes"] to « ». 338 CanonicalAttributeSet set{}; 339 result.mRemoveAttributes = Some(std::move(set)); 340 } 341 342 // Step 4. Return result. 343 return result; 344 } 345 346 // https://wicg.github.io/sanitizer-api/#configuration-canonicalize 347 void Sanitizer::CanonicalizeConfiguration(const SanitizerConfig& aConfig, 348 bool aAllowCommentsAndDataAttributes, 349 ErrorResult& aRv) { 350 // This function is only called while constructing a new Sanitizer object. 351 AssertNoLists(); 352 353 // Step 1. If neither configuration["elements"] nor 354 // configuration["removeElements"] exist, then set 355 // configuration["removeElements"] to « [] ». 356 if (!aConfig.mElements.WasPassed() && !aConfig.mRemoveElements.WasPassed()) { 357 mRemoveElements.emplace(); 358 } 359 360 // Step 2. If neither configuration["attributes"] nor 361 // configuration["removeAttributes"] exist, then set 362 // configuration["removeAttributes"] to « [] ». 363 if (!aConfig.mAttributes.WasPassed() && 364 !aConfig.mRemoveAttributes.WasPassed()) { 365 mRemoveAttributes.emplace(); 366 } 367 368 // Step 3. If configuration["elements"] exists: 369 if (aConfig.mElements.WasPassed()) { 370 // Step 3.1. Let elements be « [] » 371 CanonicalElementMap elements; 372 373 nsAutoCString errorMsg; 374 // Step 3.2. For each element of configuration["elements"] do: 375 for (const auto& element : aConfig.mElements.Value()) { 376 // Step 3.3.2.1. Append the result of canonicalize a sanitizer element 377 // with attributes element to elements. 378 CanonicalElement elementName = CanonicalizeElement(element); 379 if (elements.Contains(elementName)) { 380 aRv.ThrowTypeError(nsFmtCString( 381 FMT_STRING("Duplicate element {} in 'elements'."), elementName)); 382 return; 383 } 384 385 CanonicalElementAttributes elementAttributes = 386 CanonicalizeElementAttributes(element, &errorMsg); 387 if (!errorMsg.IsEmpty()) { 388 aRv.ThrowTypeError(errorMsg); 389 return; 390 } 391 392 elements.InsertOrUpdate(elementName, std::move(elementAttributes)); 393 } 394 395 // Step 3.3. Set configuration["elements"] to elements. 396 mElements = Some(std::move(elements)); 397 } 398 399 // Step 4. If configuration["removeElements"] exists: 400 if (aConfig.mRemoveElements.WasPassed()) { 401 // Step 4.1. Let elements be « [] » 402 CanonicalElementSet elements; 403 404 // Step 4.2. For each element of configuration["removeElements"] do: 405 for (const auto& element : aConfig.mRemoveElements.Value()) { 406 // Step 4.2.1. Append the result of canonicalize a sanitizer element 407 // element to elements. 408 CanonicalElement canonical = CanonicalizeElement(element); 409 if (!elements.EnsureInserted(canonical)) { 410 aRv.ThrowTypeError(nsFmtCString( 411 FMT_STRING("Duplicate element {} in 'removeElements'."), 412 canonical)); 413 return; 414 } 415 } 416 417 // Step 4.3. Set configuration["removeElements"] to elements. 418 mRemoveElements = Some(std::move(elements)); 419 } 420 421 // Step 5. If configuration["replaceWithChildrenElements"] exists: 422 if (aConfig.mReplaceWithChildrenElements.WasPassed()) { 423 // Step 5.1. Let elements be « [] » 424 CanonicalElementSet elements; 425 426 // Step 5.2. For each element of 427 // configuration["replaceWithChildrenElements"] do: 428 for (const auto& element : aConfig.mReplaceWithChildrenElements.Value()) { 429 // Step 5.2.1. Append the result of canonicalize a sanitizer element 430 // element to elements. 431 CanonicalElement canonical = CanonicalizeElement(element); 432 if (!elements.EnsureInserted(canonical)) { 433 aRv.ThrowTypeError(nsFmtCString( 434 FMT_STRING( 435 "Duplicate element {} in 'replaceWithChildrenElements'."), 436 canonical)); 437 return; 438 } 439 } 440 441 // Step 5.3. Set configuration["replaceWithChildrenElements"] to elements. 442 mReplaceWithChildrenElements = Some(std::move(elements)); 443 } 444 445 // Step 6. If configuration["attributes"] exists: 446 if (aConfig.mAttributes.WasPassed()) { 447 // Step 6.1. Let attributes be « [] » 448 CanonicalAttributeSet attributes; 449 450 // Step 6.2. For each attribute of configuration["attributes"] do: 451 for (const auto& attribute : aConfig.mAttributes.Value()) { 452 // Step 6.2.1. Append the result of canonicalize a sanitizer attribute 453 // attribute to attributes. 454 CanonicalAttribute canonical = CanonicalizeAttribute(attribute); 455 if (!attributes.EnsureInserted(canonical)) { 456 aRv.ThrowTypeError(nsFmtCString( 457 FMT_STRING("Duplicate attribute {} in 'attributes'."), canonical)); 458 return; 459 } 460 } 461 462 // Step 6.3. Set configuration["attributes"] to attributes. 463 mAttributes = Some(std::move(attributes)); 464 } 465 466 // Step 7. If configuration["removeAttributes"] exists: 467 if (aConfig.mRemoveAttributes.WasPassed()) { 468 // Step 7.1. Let attributes be « [] » 469 CanonicalAttributeSet attributes; 470 471 // Step 7.2. For each attribute of configuration["removeAttributes"] do: 472 for (const auto& attribute : aConfig.mRemoveAttributes.Value()) { 473 // Step 7.2.2. Append the result of canonicalize a sanitizer attribute 474 // attribute to attributes. 475 CanonicalAttribute canonical = CanonicalizeAttribute(attribute); 476 if (!attributes.EnsureInserted(canonical)) { 477 aRv.ThrowTypeError(nsFmtCString( 478 FMT_STRING("Duplicate attribute {} in 'removeAttributes'."), 479 canonical)); 480 return; 481 } 482 } 483 484 // Step 7.3. Set configuration["removeAttributes"] to attributes. 485 mRemoveAttributes = Some(std::move(attributes)); 486 } 487 488 // Step 8. If configuration["comments"] does not exist, then set 489 // configuration["comments"] to allowCommentsAndDataAttributes. 490 if (aConfig.mComments.WasPassed()) { 491 // NOTE: We always need to copy this property if it exists. 492 mComments = aConfig.mComments.Value(); 493 } else { 494 mComments = aAllowCommentsAndDataAttributes; 495 } 496 497 // Step 9. If configuration["attributes"] exists and 498 // configuration["dataAttributes"] does not exist, then set 499 // configuration["dataAttributes"] to allowCommentsAndDataAttributes. 500 if (aConfig.mDataAttributes.WasPassed()) { 501 // NOTE: We always need to copy this property if it exists. 502 mDataAttributes = Some(aConfig.mDataAttributes.Value()); 503 } else if (aConfig.mAttributes.WasPassed()) { 504 mDataAttributes = Some(aAllowCommentsAndDataAttributes); 505 } 506 } 507 508 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid 509 void Sanitizer::IsValid(ErrorResult& aRv) { 510 // Step 1. The config has either an elements or a removeElements key, but 511 // not both. 512 MOZ_ASSERT(mElements || mRemoveElements, 513 "Must have either due to CanonicalizeConfiguration"); 514 if (mElements && mRemoveElements) { 515 aRv.ThrowTypeError( 516 "'elements' and 'removeElements' are not allowed at the same time."); 517 return; 518 } 519 520 // Step 2. The config has either an attributes or a removeAttributes key, 521 // but not both. 522 MOZ_ASSERT(mAttributes || mRemoveAttributes, 523 "Must have either due to CanonicalizeConfiguration"); 524 if (mAttributes && mRemoveAttributes) { 525 aRv.ThrowTypeError( 526 "'attributes' and 'removeAttributes' are not allowed at the same " 527 "time."); 528 return; 529 } 530 531 // Step 3. Assert: All SanitizerElementNamespaceWithAttributes, 532 // SanitizerElementNamespace, and SanitizerAttributeNamespace items in 533 // config are canonical, meaning they have been run through canonicalize a 534 // sanitizer element or canonicalize a sanitizer attribute, as appropriate. 535 536 // Step 4. None of config[elements], config[removeElements], 537 // config[replaceWithChildrenElements], config[attributes], or 538 // config[removeAttributes], if they exist, has duplicates. 539 // 540 // NOTE: The Sanitizer::CanonicalizeConfiguration method would have already 541 // thrown an error for duplicate elements/attributes by this point. The map 542 // and sets can't have duplicates by definition. 543 544 // Step 5. If both config[elements] and config[replaceWithChildrenElements] 545 // exist, then the intersection of config[elements] and 546 // config[replaceWithChildrenElements] is empty. 547 if (mElements && mReplaceWithChildrenElements) { 548 for (const CanonicalElement& name : mElements->Keys()) { 549 if (mReplaceWithChildrenElements->Contains(name)) { 550 aRv.ThrowTypeError( 551 nsFmtCString(FMT_STRING("Element {} can't be in both 'elements' " 552 "and 'replaceWithChildrenElements'."), 553 name)); 554 return; 555 } 556 } 557 } 558 559 // Step 6. If both config[removeElements] and 560 // config[replaceWithChildrenElements] exist, then the intersection of 561 // config[removeElements] and config[replaceWithChildrenElements] is empty. 562 if (mRemoveElements && mReplaceWithChildrenElements) { 563 for (const CanonicalElement& name : *mRemoveElements) { 564 if (mReplaceWithChildrenElements->Contains(name)) { 565 aRv.ThrowTypeError(nsFmtCString( 566 FMT_STRING("Element {} can't be in both 'removeElements' and " 567 "'replaceWithChildrenElements'."), 568 name)); 569 return; 570 } 571 } 572 } 573 574 // Step 7. If config[attributes] exists: 575 if (mAttributes) { 576 // Step 7.1. If config[elements] exists: 577 if (mElements) { 578 // Step 7.1.1 For any element in config[elements]: 579 for (const auto& entry : *mElements) { 580 const CanonicalElementAttributes& elemAttributes = entry.GetData(); 581 MOZ_ASSERT( 582 elemAttributes.mAttributes || elemAttributes.mRemoveAttributes, 583 "Canonical elements must at least have removeAttributes"); 584 585 // Step 7.1.1.1. Neither element[attributes] or 586 // element[removeAttributes], if they exist, has duplicates. 587 // 588 // NOTE: The Sanitizer::CanonicalizeConfiguration (specifically 589 // CanonicalizeElementAttributes) method would have already thrown an 590 // error for duplicate attributes by this point. The attribute sets 591 // can't have duplicates by definition. 592 593 // Step 7.1.1.2. The intersection of config[attributes] and 594 // element[attributes] with default « [] » is empty. 595 if (elemAttributes.mAttributes) { 596 for (const CanonicalAttribute& name : *elemAttributes.mAttributes) { 597 if (mAttributes->Contains(name)) { 598 aRv.ThrowTypeError(nsFmtCString( 599 FMT_STRING( 600 "Attribute {} can't be part of both the 'attributes' of " 601 "the element {} and the global 'attributes'."), 602 name, entry.GetKey())); 603 return; 604 } 605 } 606 } 607 608 // Step 7.1.1.3. element[removeAttributes] is a subset of 609 // config[attributes]. 610 if (elemAttributes.mRemoveAttributes) { 611 for (const CanonicalAttribute& name : 612 *elemAttributes.mRemoveAttributes) { 613 if (!mAttributes->Contains(name)) { 614 aRv.ThrowTypeError(nsFmtCString( 615 FMT_STRING( 616 "Attribute {} can't be in 'removeAttributes' of the " 617 "element {} but not in the global 'attributes'."), 618 name, entry.GetKey())); 619 return; 620 } 621 } 622 } 623 624 MOZ_ASSERT(mDataAttributes.isSome(), 625 "mDataAttributes exists iff mAttributes exists"); 626 627 // Step 7.1.1.4. If dataAttributes is true: 628 if (*mDataAttributes && elemAttributes.mAttributes) { 629 // TODO: Merge with loop above? 630 // Step 7.1.1.4.1. element[attributes] does not contain a custom 631 // data attribute. 632 for (const CanonicalAttribute& name : *elemAttributes.mAttributes) { 633 if (name.IsDataAttribute()) { 634 aRv.ThrowTypeError(nsFmtCString( 635 FMT_STRING( 636 "Data attribute {} in the 'attributes' of the element {} " 637 "is redundant with 'dataAttributes' being true."), 638 name, entry.GetKey())); 639 return; 640 } 641 } 642 } 643 } 644 } 645 646 MOZ_ASSERT(mDataAttributes.isSome(), 647 "mDataAttributes exists iff mAttributes exists"); 648 649 // Step 7.2. If dataAttributes is true: 650 if (*mDataAttributes) { 651 // Step 7.2.1. config[attributes] does not contain a custom data 652 // attribute. 653 for (const CanonicalAttribute& name : *mAttributes) { 654 if (name.IsDataAttribute()) { 655 aRv.ThrowTypeError(nsFmtCString( 656 FMT_STRING("Data attribute {} in the global 'attributes' is " 657 "redundant with 'dataAttributes' being true."), 658 name)); 659 return; 660 } 661 } 662 } 663 } 664 665 // Step 8. If config[removeAttributes] exists: 666 if (mRemoveAttributes) { 667 // Step 8.1. If config[elements] exists, then for any element in 668 // config[elements]: 669 if (mElements) { 670 for (const auto& entry : *mElements) { 671 const CanonicalElementAttributes& elemAttributes = entry.GetData(); 672 673 // Step 8.1.1. Not both element[attributes] and 674 // element[removeAttributes] exist. 675 if (elemAttributes.mAttributes && elemAttributes.mRemoveAttributes) { 676 return aRv.ThrowTypeError( 677 nsFmtCString(FMT_STRING("Element {} can't have both 'attributes' " 678 "and 'removeAttributes'."), 679 entry.GetKey())); 680 } 681 682 // Step 8.1.2. Neither element[attributes] nor 683 // element[removeAttributes], if they exist, has duplicates. 684 // 685 // NOTE: The Sanitizer::CanonicalizeConfiguration (specifically 686 // CanonicalizeElementAttributes) method would have already thrown an 687 // error for duplicate attributes by this point. The attribute sets 688 // can't have duplicates by definition. 689 690 // Step 8.1.3. The intersection of config[removeAttributes] and 691 // element[attributes] with default « [] » is empty. 692 if (elemAttributes.mAttributes) { 693 for (const CanonicalAttribute& name : *elemAttributes.mAttributes) { 694 if (mRemoveAttributes->Contains(name)) { 695 aRv.ThrowTypeError(nsFmtCString( 696 FMT_STRING( 697 "Attribute {} can't be in 'attributes' of the element {} " 698 "while in the global 'removeAttributes'."), 699 name, entry.GetKey())); 700 return; 701 } 702 } 703 } 704 705 // Step 8.1.4. The intersection of config[removeAttributes] and 706 // element[removeAttributes] with default « [] » is empty. 707 if (elemAttributes.mRemoveAttributes) { 708 for (const CanonicalAttribute& name : 709 *elemAttributes.mRemoveAttributes) { 710 if (mRemoveAttributes->Contains(name)) { 711 aRv.ThrowTypeError(nsFmtCString( 712 FMT_STRING("Attribute {} can't be part of both the " 713 "'removeAttributes' of the element {} and the " 714 "global 'removeAttributes'."), 715 name, entry.GetKey())); 716 return; 717 } 718 } 719 } 720 } 721 } 722 723 // Step 8.2. config[dataAttributes] does not exist. 724 if (mDataAttributes) { 725 aRv.ThrowTypeError( 726 "'removeAttributes' and 'dataAttributes' aren't allowed at the " 727 "same time."); 728 } 729 } 730 } 731 732 void Sanitizer::AssertIsValid() { 733 #ifdef DEBUG 734 IgnoredErrorResult rv; 735 IsValid(rv); 736 MOZ_ASSERT(!rv.Failed()); 737 #endif 738 } 739 740 // https://wicg.github.io/sanitizer-api/#sanitizer-set-a-configuration 741 void Sanitizer::SetConfig(const SanitizerConfig& aConfig, 742 bool aAllowCommentsAndDataAttributes, 743 ErrorResult& aRv) { 744 // Step 1. Canonicalize configuration with allowCommentsAndDataAttributes. 745 CanonicalizeConfiguration(aConfig, aAllowCommentsAndDataAttributes, aRv); 746 if (aRv.Failed()) { 747 return; 748 } 749 750 // Step 2. If configuration is not valid, then return false. 751 IsValid(aRv); 752 if (aRv.Failed()) { 753 return; 754 } 755 756 // Step 3. Set sanitizer’s configuration to configuration. 757 // Note: This was already done in CanonicalizeConfiguration. 758 // Step 4. Return true. (implicit) 759 } 760 761 // Turn the lazy default config into real lists that can be 762 // modified or queried via get(). 763 void Sanitizer::MaybeMaterializeDefaultConfig() { 764 if (!mIsDefaultConfig) { 765 AssertIsValid(); 766 return; 767 } 768 769 AssertNoLists(); 770 771 CanonicalElementMap elements; 772 auto insertElements = [&elements]( 773 mozilla::Span<nsStaticAtom* const> aElements, 774 nsStaticAtom* aNamespace, 775 nsStaticAtom* const* aElementWithAttributes) { 776 size_t i = 0; 777 for (nsStaticAtom* name : aElements) { 778 CanonicalElementAttributes elementAttributes{}; 779 780 if (name == aElementWithAttributes[i]) { 781 CanonicalAttributeSet attributes; 782 while (aElementWithAttributes[++i]) { 783 attributes.Insert( 784 CanonicalAttribute(aElementWithAttributes[i], nullptr)); 785 } 786 i++; 787 elementAttributes.mAttributes = Some(std::move(attributes)); 788 } else { 789 // In the default config all elements have a (maybe empty) `attributes` 790 // list. 791 CanonicalAttributeSet set{}; 792 elementAttributes.mAttributes = Some(std::move(set)); 793 } 794 795 CanonicalElement elementName(name, aNamespace); 796 elements.InsertOrUpdate(elementName, std::move(elementAttributes)); 797 } 798 }; 799 insertElements(Span(kDefaultHTMLElements), nsGkAtoms::nsuri_xhtml, 800 kHTMLElementWithAttributes); 801 insertElements(Span(kDefaultMathMLElements), nsGkAtoms::nsuri_mathml, 802 kMathMLElementWithAttributes); 803 insertElements(Span(kDefaultSVGElements), nsGkAtoms::nsuri_svg, 804 kSVGElementWithAttributes); 805 mElements = Some(std::move(elements)); 806 807 CanonicalAttributeSet attributes; 808 for (nsStaticAtom* name : kDefaultAttributes) { 809 attributes.Insert(CanonicalAttribute(name, nullptr)); 810 } 811 mAttributes = Some(std::move(attributes)); 812 813 mIsDefaultConfig = false; 814 } 815 816 // https://wicg.github.io/sanitizer-api/#dom-sanitizer-get 817 void Sanitizer::Get(SanitizerConfig& aConfig) { 818 MaybeMaterializeDefaultConfig(); 819 820 // Step 1. Let config be this’s configuration. 821 // Step 2. If config["elements"] exists: 822 if (mElements) { 823 nsTArray<OwningStringOrSanitizerElementNamespaceWithAttributes> elements; 824 // Step 2.1. For any element of config["elements"]: 825 for (const auto& entry : *mElements) { 826 // Step 2.1.1. If element["attributes"] exists: 827 // Step 2.1.2. If element["removeAttributes"] exists: 828 // ... 829 // (The attributes are sorted by the ToSanitizerAttributes call in 830 // ToSanitizerElementNamespaceWithAttributes) 831 OwningStringOrSanitizerElementNamespaceWithAttributes owning; 832 owning.SetAsSanitizerElementNamespaceWithAttributes() = 833 entry.GetKey().ToSanitizerElementNamespaceWithAttributes( 834 entry.GetData()); 835 836 // Step 2.2. Set config["elements"] to the result of sort in ascending 837 // order config["elements"], with elementA being less than item elementB. 838 // (Instead of sorting at the end, we sort during insertion) 839 elements.InsertElementSorted(owning, 840 SanitizerComparator<decltype(owning)>()); 841 } 842 aConfig.mElements.Construct(std::move(elements)); 843 } else { 844 // Step 3. If config["removeElements"] exists: 845 // Step 3.1. Set config["removeElements"] to the result of sort in ascending 846 // order config["removeElements"], with elementA being less than item 847 // elementB. 848 nsTArray<OwningStringOrSanitizerElementNamespace> removeElements; 849 for (const CanonicalElement& canonical : *mRemoveElements) { 850 OwningStringOrSanitizerElementNamespace owning; 851 owning.SetAsSanitizerElementNamespace() = 852 canonical.ToSanitizerElementNamespace(); 853 removeElements.InsertElementSorted( 854 owning, SanitizerComparator<decltype(owning)>()); 855 } 856 aConfig.mRemoveElements.Construct(std::move(removeElements)); 857 } 858 859 // Step 4. If config["replaceWithChildrenElements"] exists: 860 if (mReplaceWithChildrenElements) { 861 // Step 4.1. Set config["replaceWithChildrenElements"] to the result of sort 862 // in ascending order config["replaceWithChildrenElements"], with elementA 863 // being less than item elementB. 864 nsTArray<OwningStringOrSanitizerElementNamespace> 865 replaceWithChildrenElements; 866 for (const CanonicalElement& canonical : *mReplaceWithChildrenElements) { 867 OwningStringOrSanitizerElementNamespace owning; 868 owning.SetAsSanitizerElementNamespace() = 869 canonical.ToSanitizerElementNamespace(); 870 replaceWithChildrenElements.InsertElementSorted( 871 owning, SanitizerComparator<decltype(owning)>()); 872 } 873 aConfig.mReplaceWithChildrenElements.Construct( 874 std::move(replaceWithChildrenElements)); 875 } 876 877 // Step 5. If config["attributes"] exists: 878 if (mAttributes) { 879 // Step 5.1. Set config["attributes"] to the result of sort in ascending 880 // order config["attributes"], with attrA being less than item attrB. 881 // (Sorting is done by ToSanitizerAttributes) 882 aConfig.mAttributes.Construct(ToSanitizerAttributes(*mAttributes)); 883 } else { 884 // Step 6. If config["removeAttributes"] exists: 885 // Step 6.1. Set config["removeAttributes"] to the result of sort in 886 // ascending order config["removeAttributes"], with attrA being less than 887 // item attrB. 888 aConfig.mRemoveAttributes.Construct( 889 ToSanitizerAttributes(*mRemoveAttributes)); 890 } 891 892 // (In the spec these already exist in the |config| and don't need sorting) 893 aConfig.mComments.Construct(mComments); 894 if (mDataAttributes) { 895 aConfig.mDataAttributes.Construct(*mDataAttributes); 896 } 897 898 // Step 7. Return config. 899 } 900 901 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-allow-an-element 902 bool Sanitizer::AllowElement( 903 const StringOrSanitizerElementNamespaceWithAttributes& aElement) { 904 MaybeMaterializeDefaultConfig(); 905 906 // Step 1. Set element to the result of canonicalize a sanitizer element 907 // with attributes with element. 908 CanonicalElement elementName = CanonicalizeElement(aElement); 909 // NOTE: Duplicate attributes are removed/ignored. 910 CanonicalElementAttributes elementAttributes = 911 CanonicalizeElementAttributes(aElement); 912 913 // Step 2 If configuration["elements"] exists: 914 if (mElements) { 915 // Step 2.1. Set modified to the result of remove element from 916 // configuration["replaceWithChildrenElements"]. 917 bool modified = 918 mReplaceWithChildrenElements 919 ? mReplaceWithChildrenElements->EnsureRemoved(elementName) 920 : false; 921 922 // Step 2.2. Comment: We need to make sure the per-element attributes do 923 // not overlap with global attributes. 924 925 // Step 2.3. If configuration["attributes"] exists: 926 if (mAttributes) { 927 // Step 2.3.1. If element["attributes"] exists: 928 if (elementAttributes.mAttributes) { 929 CanonicalAttributeSet attributes; 930 for (const CanonicalAttribute& attr : *elementAttributes.mAttributes) { 931 // Step 2.3.1.1. Set element["attributes"] to remove duplicates from 932 // element["attributes"]. 933 MOZ_ASSERT(!attributes.Contains(attr)); 934 935 // Step 2.3.1.2. Set element["attributes"] to the difference of 936 // element["attributes"] and configuration["attributes"]. 937 if (mAttributes->Contains(attr)) { 938 continue; 939 } 940 941 // Step 2.3.1.3. If configuration["dataAttributes"] is true: 942 MOZ_ASSERT(mDataAttributes.isSome(), 943 "mDataAttributes exists iff mAttributes"); 944 if (*mDataAttributes) { 945 // Step 2.3.1.3.1 Remove all items item from element["attributes"] 946 // where item is a custom data attribute. 947 if (attr.IsDataAttribute()) { 948 continue; 949 } 950 } 951 952 attributes.Insert(attr.Clone()); 953 } 954 elementAttributes.mAttributes = Some(std::move(attributes)); 955 } 956 957 // Step 2.3.2. If element["removeAttributes"] exists: 958 if (elementAttributes.mRemoveAttributes) { 959 CanonicalAttributeSet removeAttributes; 960 for (const CanonicalAttribute& attr : 961 *elementAttributes.mRemoveAttributes) { 962 // Step 2.3.2.1. Set element["removeAttributes"] to remove duplicates 963 // from element["removeAttributes"]. 964 // 965 // NOTE: CanonicalizeElementAttributes removed all duplicates for us. 966 MOZ_ASSERT(!removeAttributes.Contains(attr)); 967 968 // Step 2.3.2.2. Set element["removeAttributes"] to the intersection 969 // of element["removeAttributes"] and configuration["attributes"]. 970 if (!mAttributes->Contains(attr)) { 971 continue; 972 } 973 974 removeAttributes.Insert(attr.Clone()); 975 } 976 elementAttributes.mRemoveAttributes = Some(std::move(removeAttributes)); 977 } 978 } else { 979 // Step 2.4. Otherwise: 980 981 // Step 2.4.1. If element["attributes"] exists: 982 if (elementAttributes.mAttributes) { 983 CanonicalAttributeSet attributes; 984 for (const CanonicalAttribute& attr : *elementAttributes.mAttributes) { 985 // Step 2.4.1.1. Set element["attributes"] to remove duplicates from 986 // element["attributes"]. 987 // 988 // NOTE: CanonicalizeElementAttributes removed all duplicates for us. 989 MOZ_ASSERT(!attributes.Contains(attr)); 990 991 // Step 2.4.1.2. Set element["attributes"] to the difference of 992 // element["attributes"] and element["removeAttributes"] with default 993 // « ». 994 if (elementAttributes.mRemoveAttributes && 995 elementAttributes.mRemoveAttributes->Contains(attr)) { 996 continue; 997 } 998 999 // Step 2.4.1.4. Set element["attributes"] to the difference of 1000 // element["attributes"] and configuration["removeAttributes"]. 1001 if (mRemoveAttributes->Contains(attr)) { 1002 continue; 1003 } 1004 1005 attributes.Insert(attr.Clone()); 1006 } 1007 elementAttributes.mAttributes = Some(std::move(attributes)); 1008 1009 // Step 2.4.1.3. Remove element["removeAttributes"]. 1010 elementAttributes.mRemoveAttributes = Nothing(); 1011 } 1012 1013 // Step 2.4.2. If element["removeAttributes"] exists: 1014 if (elementAttributes.mRemoveAttributes) { 1015 CanonicalAttributeSet removeAttributes; 1016 for (const CanonicalAttribute& attr : 1017 *elementAttributes.mRemoveAttributes) { 1018 // Step 2.4.2.1. Set element["removeAttributes"] to remove duplicates 1019 // from element["removeAttributes"]. 1020 MOZ_ASSERT(!removeAttributes.Contains(attr)); 1021 1022 // Step 2.4.2.2. Set element["removeAttributes"] to the difference of 1023 // element["removeAttributes"] and configuration["removeAttributes"]. 1024 if (mRemoveAttributes->Contains(attr)) { 1025 continue; 1026 } 1027 1028 removeAttributes.Insert(attr.Clone()); 1029 } 1030 elementAttributes.mRemoveAttributes = Some(std::move(removeAttributes)); 1031 } 1032 } 1033 1034 // Step 2.5. If configuration["elements"] does not contain element: 1035 const CanonicalElementAttributes* existingElementAttributes = 1036 mElements->Lookup(elementName).DataPtrOrNull(); 1037 if (!existingElementAttributes) { 1038 // Step 2.5.1. Comment: This is the case with a global allow-list that 1039 // does not yet contain element. 1040 1041 // Step 2.5.2. Append element to configuration["elements"]. 1042 mElements->InsertOrUpdate(elementName, std::move(elementAttributes)); 1043 1044 // Step 2.5.3. Return true. 1045 return true; 1046 } 1047 1048 // Step 2.6. Comment: This is the case with a global allow-list that 1049 // already contains element. 1050 1051 // Step 2.7. Let current element be the item in configuration["elements"] 1052 // where item[name] equals element[name] and item[namespace] equals 1053 // element[namespace]. (implict with existingElementAttributes) 1054 1055 // Step 2.8. If element equals current element then return modified. 1056 if (elementAttributes.Equals(*existingElementAttributes)) { 1057 return modified; 1058 } 1059 1060 // Step 2.9. Remove element from configuration["elements"]. 1061 // Step 2.10. Append element to configuration["elements"]. 1062 mElements->InsertOrUpdate(elementName, std::move(elementAttributes)); 1063 1064 // Step 2.11. Return true. 1065 return true; 1066 } 1067 1068 // Step 3. Otherwise: 1069 // Step 3.1. If element["attributes"] exists or element["removeAttributes"] 1070 // with default « » is not empty: 1071 if (elementAttributes.mAttributes || 1072 (elementAttributes.mRemoveAttributes && 1073 !elementAttributes.mRemoveAttributes->IsEmpty())) { 1074 // Step 3.1.1. The user agent may report a warning to the console that this 1075 // operation is not supported. 1076 if (auto* win = mGlobal->GetAsInnerWindow()) { 1077 nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, 1078 "Sanitizer"_ns, win->GetDoc(), 1079 nsContentUtils::eSECURITY_PROPERTIES, 1080 "SanitizerAllowElementIgnored2"); 1081 } 1082 1083 // Step 3.1.2. Return false. 1084 return false; 1085 } 1086 1087 // Step 3.2. Set modified to the result of remove element from 1088 // configuration["replaceWithChildrenElements"]. 1089 bool modified = mReplaceWithChildrenElements 1090 ? mReplaceWithChildrenElements->EnsureRemoved(elementName) 1091 : false; 1092 1093 // Step 3.3. If configuration["removeElements"] does not contain element: 1094 if (!mRemoveElements->Contains(elementName)) { 1095 // Step 3.3.1. Comment: This is the case with a global remove-list that 1096 // does not contain element. 1097 1098 // Step 3.3.2. Return modified. 1099 return modified; 1100 } 1101 1102 // Step 3.4. Comment: This is the case with a global remove-list that 1103 // contains element. 1104 1105 // Step 3.5. Remove element from configuration["removeElements"]. 1106 mRemoveElements->Remove(elementName); 1107 1108 // Step 3.6. Return true. 1109 return true; 1110 } 1111 1112 // https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element 1113 bool Sanitizer::RemoveElement( 1114 const StringOrSanitizerElementNamespace& aElement) { 1115 MaybeMaterializeDefaultConfig(); 1116 1117 // Step 1. Set element to the result of canonicalize a sanitizer element 1118 // with element. 1119 CanonicalElement element = CanonicalizeElement(aElement); 1120 1121 return RemoveElementCanonical(std::move(element)); 1122 } 1123 1124 bool Sanitizer::RemoveElementCanonical(CanonicalElement&& aElement) { 1125 // Step 2. Set modified to the result of remove element from 1126 // configuration["replaceWithChildrenElements"]. 1127 bool modified = mReplaceWithChildrenElements 1128 ? mReplaceWithChildrenElements->EnsureRemoved(aElement) 1129 : false; 1130 1131 // Step 3. If configuration["elements"] exists: 1132 if (mElements) { 1133 // Step 3.1. If configuration["elements"] contains element: 1134 if (mElements->Contains(aElement)) { 1135 // Step 3.1.1. Comment: We have a global allow list and it contains 1136 // element. 1137 1138 // Step 3.1.2. Remove element from configuration["elements"]. 1139 mElements->Remove(aElement); 1140 1141 // Step 3.1.3. Return true. 1142 return true; 1143 } 1144 1145 // Step 3.2. Comment: We have a global allow list and it does not contain 1146 // element. 1147 1148 // Step 3.3. Return modified. 1149 return modified; 1150 } 1151 1152 // Step 4. Otherwise: 1153 // Step 4.1. If configuration["removeElements"] contains element: 1154 if (mRemoveElements->Contains(aElement)) { 1155 // Step 4.1.1. Comment: We have a global remove list and it already 1156 // contains element. 1157 1158 // Step 4.1.2. Return modified. 1159 return modified; 1160 } 1161 1162 // Step 4.2. Comment: We have a global remove list and it does not contain 1163 // element. 1164 1165 // Step 4.3. Add element to configuration["removeElements"]. 1166 mRemoveElements->Insert(std::move(aElement)); 1167 1168 // Step 4.4. Return true. 1169 return true; 1170 } 1171 1172 // https://wicg.github.io/sanitizer-api/#sanitizer-replace-an-element-with-its-children 1173 bool Sanitizer::ReplaceElementWithChildren( 1174 const StringOrSanitizerElementNamespace& aElement) { 1175 MaybeMaterializeDefaultConfig(); 1176 1177 // Step 1. Set element to the result of canonicalize a sanitizer element 1178 // with element. 1179 CanonicalElement element = CanonicalizeElement(aElement); 1180 1181 // Step 2. If configuration["replaceWithChildrenElements"] contains element: 1182 if (mReplaceWithChildrenElements && 1183 mReplaceWithChildrenElements->Contains(element)) { 1184 // Step 2.1. Return false. 1185 return false; 1186 } 1187 1188 // Step 3. Remove element from configuration["removeElements"]. 1189 if (mRemoveElements) { 1190 mRemoveElements->Remove(element); 1191 } else { 1192 // Step 4. Remove element from configuration["elements"] list. 1193 mElements->Remove(element); 1194 } 1195 1196 // Step 5. Add element to configuration["replaceWithChildrenElements"]. 1197 if (!mReplaceWithChildrenElements) { 1198 mReplaceWithChildrenElements.emplace(); 1199 } 1200 mReplaceWithChildrenElements->Insert(std::move(element)); 1201 1202 // Step 6. Return true. 1203 return true; 1204 } 1205 1206 // https://wicg.github.io/sanitizer-api/#sanitizer-allow-an-attribute 1207 bool Sanitizer::AllowAttribute( 1208 const StringOrSanitizerAttributeNamespace& aAttribute) { 1209 MaybeMaterializeDefaultConfig(); 1210 1211 // Step 1. Set attribute to the result of canonicalize a sanitizer attribute 1212 // with attribute. 1213 CanonicalAttribute attribute = CanonicalizeAttribute(aAttribute); 1214 1215 // Step 2. If configuration["attributes"] exists: 1216 if (mAttributes) { 1217 // Step 2.1. Comment: If we have a global allow-list, we need to add 1218 // attribute. 1219 1220 MOZ_ASSERT(mDataAttributes.isSome(), 1221 "mDataAttributes exists iff mAttributes exists"); 1222 1223 // Step 2.2. If configuration["dataAttributes"] is true and attribute is a 1224 // custom data attribute, then return false. 1225 if (*mDataAttributes && attribute.IsDataAttribute()) { 1226 return false; 1227 } 1228 1229 // Step 2.3. If configuration["attributes"] contains attribute 1230 // return false. 1231 if (mAttributes->Contains(attribute)) { 1232 return false; 1233 } 1234 1235 // Step 2.3. Comment: Fix-up per-element allow and remove lists. 1236 1237 // Step 2.5. If configuration["elements"] exists: 1238 if (mElements) { 1239 // Step 2.5.1. For each element in configuration["elements"]: 1240 for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) { 1241 CanonicalElementAttributes& elemAttributes = iter.Data(); 1242 1243 // Step 2.5.1.1. If element["attributes"] with default « [] » contains 1244 // attribute: 1245 if (elemAttributes.mAttributes && 1246 elemAttributes.mAttributes->Contains(attribute)) { 1247 // Step 2.5.1.1.1. Remove attribute from element["attributes"]. 1248 elemAttributes.mAttributes->Remove(attribute); 1249 } 1250 1251 // Step 2.5.1.2. Assert: element["removeAttributes"] with default « [] 1252 // » does not contain attribute. 1253 MOZ_ASSERT_IF(elemAttributes.mRemoveAttributes, 1254 !elemAttributes.mRemoveAttributes->Contains(attribute)); 1255 } 1256 } 1257 1258 // Step 2.6. Append attribute to configuration["attributes"] 1259 mAttributes->Insert(std::move(attribute)); 1260 1261 // Step 2.7. Return true. 1262 return true; 1263 } 1264 1265 // Step 3. Otherwise: 1266 1267 // Step 3.1. Comment: If we have a global remove-list, we need to remove 1268 // attribute. 1269 1270 // Step 3.2. If configuration["removeAttributes"] does not contain 1271 // attribute: 1272 if (!mRemoveAttributes->Contains(attribute)) { 1273 // Step 3.2.1. Return false. 1274 return false; 1275 } 1276 1277 // Step 3.3. Remove attribute from configuration["removeAttributes"]. 1278 mRemoveAttributes->Remove(attribute); 1279 1280 // Step 3.4. Return true. 1281 return true; 1282 } 1283 1284 // https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute 1285 bool Sanitizer::RemoveAttribute( 1286 const StringOrSanitizerAttributeNamespace& aAttribute) { 1287 MaybeMaterializeDefaultConfig(); 1288 1289 // Step 1. Set attribute to the result of canonicalize a sanitizer attribute 1290 // with attribute. 1291 CanonicalAttribute attribute = CanonicalizeAttribute(aAttribute); 1292 1293 return RemoveAttributeCanonical(std::move(attribute)); 1294 } 1295 1296 bool Sanitizer::RemoveAttributeCanonical(CanonicalAttribute&& aAttribute) { 1297 // Step 2. If configuration["attributes"] exists: 1298 if (mAttributes) { 1299 // Step 2.1. Comment: If we have a global allow-list, we need to add 1300 // attribute. 1301 1302 // Step 2.2. If configuration["attributes"] does not contain attribute: 1303 if (!mAttributes->Contains(aAttribute)) { 1304 // Step 2.2.1. Return false. 1305 return false; 1306 } 1307 1308 // Step 2.3. Comment: Fix-up per-element allow and remove lists. 1309 1310 // Step 2.4. If configuration["elements"] exists: 1311 if (mElements) { 1312 // Step 2.4.1. For each element in configuration["elements"]: 1313 for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) { 1314 CanonicalElementAttributes& elemAttributes = iter.Data(); 1315 // Step 2.4.1.1. If element["removeAttributes"] with default « [] » 1316 // contains attribute: 1317 if (elemAttributes.mRemoveAttributes && 1318 elemAttributes.mRemoveAttributes->Contains(aAttribute)) { 1319 // Step 2.4.1.1.1. Remove attribute from 1320 // element["removeAttributes"]. 1321 elemAttributes.mRemoveAttributes->Remove(aAttribute); 1322 } 1323 } 1324 } 1325 1326 // Step 2.5. Remove attribute from configuration["attributes"]. 1327 mAttributes->Remove(aAttribute); 1328 1329 // Step 2.6. Return true. 1330 return true; 1331 } 1332 1333 // Step 3. Otherwise: 1334 // Step 3.1. Comment: If we have a global remove-list, we need to add 1335 // attribute. 1336 1337 // Step 3.2. If configuration["removeAttributes"] contains attribute return 1338 // false. 1339 if (mRemoveAttributes->Contains(aAttribute)) { 1340 return false; 1341 } 1342 1343 // Step 3.3. Comment: Fix-up per-element allow and remove lists. 1344 // Step 3.4. If configuration["elements"] exists: 1345 if (mElements) { 1346 // Step 3.4.1. For each element in configuration["elements"]: 1347 for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) { 1348 CanonicalElementAttributes& elemAttributes = iter.Data(); 1349 // Step 3.4.1.1. If element["attributes"] with default « [] » contains 1350 // attribute: 1351 if (elemAttributes.mAttributes && 1352 elemAttributes.mAttributes->Contains(aAttribute)) { 1353 // Step 3.4.1.1.1. Remove attribute from element["attributes"]. 1354 elemAttributes.mAttributes->Remove(aAttribute); 1355 } 1356 1357 // Step 3.4.1.2. If element["removeAttributes"] with default « [] » 1358 // contains attribute: 1359 if (elemAttributes.mRemoveAttributes && 1360 elemAttributes.mRemoveAttributes->Contains(aAttribute)) { 1361 // Step 3.4.1.2.1. Remove attribute from element["removeAttributes"]. 1362 elemAttributes.mRemoveAttributes->Remove(aAttribute); 1363 } 1364 } 1365 } 1366 1367 // Step 3.5. Append attribute to configuration["removeAttributes"]. 1368 mRemoveAttributes->Insert(std::move(aAttribute)); 1369 1370 // Step 3.6. Return true. 1371 return true; 1372 } 1373 1374 // https://wicg.github.io/sanitizer-api/#sanitizer-set-comments 1375 bool Sanitizer::SetComments(bool aAllow) { 1376 // The sanitize algorithm optimized for the default config supports 1377 // comments both being allowed and disallowed. 1378 1379 // Step 1. If configuration["comments"] exists and configuration["comments"] 1380 // equals allow, then return false; 1381 if (mComments == aAllow) { 1382 return false; 1383 } 1384 1385 // Step 2. Set configuration["comments"] to allow. 1386 mComments = aAllow; 1387 1388 // Step 3. Return true. 1389 return true; 1390 } 1391 1392 // https://wicg.github.io/sanitizer-api/#sanitizer-set-data-attributes 1393 bool Sanitizer::SetDataAttributes(bool aAllow) { 1394 // Same as above for data-attributes. 1395 1396 // Step 1. If configuration["attributes"] does not exist, then return false. 1397 // Note: The default config has "attributes". 1398 if (!mIsDefaultConfig && !mAttributes) { 1399 return false; 1400 } 1401 1402 MOZ_ASSERT(mDataAttributes.isSome(), 1403 "mDataAttributes exists iff mAttributes exists (or in the default " 1404 "config)"); 1405 1406 // Step 2. If configuration["dataAttributes"] equals allow, then return false. 1407 if (*mDataAttributes == aAllow) { 1408 return false; 1409 } 1410 1411 // Step 3. If allow is true: 1412 if (!mIsDefaultConfig && aAllow) { 1413 // Note: The default config does not contain any data-attributes. 1414 1415 // Step 3.1. Remove any items attr from configuration["attributes"] where 1416 // attr is a custom data attribute. 1417 mAttributes->RemoveIf([](const CanonicalAttribute& aAttribute) { 1418 return aAttribute.IsDataAttribute(); 1419 }); 1420 1421 // Step 3.2. If configuration["elements"] exists: 1422 if (mElements) { 1423 // Step 3.2.1. For each element in configuration["elements"]: 1424 for (auto iter = mElements->Iter(); !iter.Done(); iter.Next()) { 1425 CanonicalElementAttributes& elemAttributes = iter.Data(); 1426 // Step 3.2.1.1. If element[attributes] exists: 1427 if (elemAttributes.mAttributes) { 1428 // Step 3.2.1.1.1. Remove any items attr from element[attributes] 1429 // where attr is a custom data attribute. 1430 elemAttributes.mAttributes->RemoveIf( 1431 [](const CanonicalAttribute& aAttribute) { 1432 return aAttribute.IsDataAttribute(); 1433 }); 1434 } 1435 } 1436 } 1437 } 1438 1439 // Step 4. Set configuration["dataAttributes"] to allow. 1440 mDataAttributes = Some(aAllow); 1441 1442 // Step 5. Return true. 1443 return true; 1444 } 1445 1446 // https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration 1447 // The built-in safe baseline configuration 1448 #define FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT) \ 1449 ELEMENT(XHTML, xhtml, embed) \ 1450 ELEMENT(XHTML, xhtml, frame) \ 1451 ELEMENT(XHTML, xhtml, iframe) \ 1452 ELEMENT(XHTML, xhtml, object) \ 1453 ELEMENT(XHTML, xhtml, script) \ 1454 ELEMENT(SVG, svg, script) \ 1455 ELEMENT(SVG, svg, use) 1456 1457 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe 1458 bool Sanitizer::RemoveUnsafe() { 1459 // XXX this should be a no-op for the default config now. 1460 MaybeMaterializeDefaultConfig(); 1461 1462 // Step 1. Assert: The key set of built-in safe baseline configuration equals 1463 // «[ "removeElements", "removeAttributes" ] ». 1464 1465 // Step 2. Let result be false. 1466 bool result = false; 1467 1468 // Step 3. For each element in built-in safe baseline 1469 // configuration[removeElements]: 1470 #define ELEMENT(_, NSURI, LOCAL_NAME) \ 1471 /* Step 3.1. Call remove an element element from configuration. */ \ 1472 if (RemoveElementCanonical(CanonicalElement(nsGkAtoms::LOCAL_NAME, \ 1473 nsGkAtoms::nsuri_##NSURI))) { \ 1474 /* Step 3.2. If the call returned true, set result to true. */ \ 1475 result = true; \ 1476 } 1477 1478 FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT) 1479 1480 #undef ELEMENT 1481 1482 // Step 4. For each attribute in built-in safe baseline 1483 // configuration[removeAttributes]: 1484 // (This is an empty list) 1485 1486 // Step 5. For each attribute listed in event handler content attributes: 1487 // TODO: Consider sorting these. 1488 nsContentUtils::ForEachEventAttributeName( 1489 EventNameType_All & ~EventNameType_XUL, 1490 [self = MOZ_KnownLive(this), &result](nsAtom* aName) { 1491 // Step 5.1. Call remove an attribute attribute from configuration. 1492 if (self->RemoveAttributeCanonical( 1493 CanonicalAttribute(aName, nullptr))) { 1494 // Step 5.2. If the call returned true, set result to true. 1495 result = true; 1496 } 1497 }); 1498 1499 // Step 6. Return result. 1500 return result; 1501 } 1502 1503 // https://wicg.github.io/sanitizer-api/#sanitize 1504 void Sanitizer::Sanitize(nsINode* aNode, bool aSafe, ErrorResult& aRv) { 1505 MOZ_ASSERT(aNode->OwnerDoc()->IsLoadedAsData(), 1506 "SanitizeChildren relies on the document being inert to be safe"); 1507 1508 // Step 1. Let configuration be the value of sanitizer’s configuration. 1509 1510 // Step 2. If safe is true, then set configuration to the result of calling 1511 // remove unsafe on configuration. 1512 // 1513 // Optimization: We really don't want to make a copy of the configuration 1514 // here, so we instead explictly remove the handful elements and 1515 // attributes that are part of "remove unsafe" in the 1516 // SanitizeChildren() and SanitizeAttributes() methods. 1517 1518 // Step 3. Call sanitize core on node, configuration, and with 1519 // handleJavascriptNavigationUrls set to safe. 1520 if (mIsDefaultConfig) { 1521 AssertNoLists(); 1522 SanitizeChildren<true>(aNode, aSafe); 1523 } else { 1524 AssertIsValid(); 1525 SanitizeChildren<false>(aNode, aSafe); 1526 } 1527 } 1528 1529 static RefPtr<nsAtom> ToNamespace(int32_t aNamespaceID) { 1530 if (aNamespaceID == kNameSpaceID_None) { 1531 return nullptr; 1532 } 1533 1534 RefPtr<nsAtom> atom = 1535 nsNameSpaceManager::GetInstance()->NameSpaceURIAtom(aNamespaceID); 1536 return atom; 1537 } 1538 1539 static bool IsUnsafeElement(nsAtom* aLocalName, int32_t aNamespaceID) { 1540 #define ELEMENT(NSID, _, LOCAL_NAME) \ 1541 if (aNamespaceID == kNameSpaceID_##NSID) { \ 1542 if (aLocalName == nsGkAtoms::LOCAL_NAME) { \ 1543 return true; \ 1544 } \ 1545 } 1546 1547 FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT) 1548 1549 #undef ELEMENT 1550 1551 return false; 1552 } 1553 1554 // https://wicg.github.io/sanitizer-api/#sanitize-core 1555 template <bool IsDefaultConfig> 1556 void Sanitizer::SanitizeChildren(nsINode* aNode, bool aSafe) { 1557 // Step 1. For each child in current’s children: 1558 nsCOMPtr<nsIContent> next = nullptr; 1559 for (nsCOMPtr<nsIContent> child = aNode->GetFirstChild(); child; 1560 child = next) { 1561 next = child->GetNextSibling(); 1562 1563 // Step 1.1. Assert: child implements Text, Comment, Element, or 1564 // DocumentType. 1565 MOZ_ASSERT(child->IsText() || child->IsComment() || child->IsElement() || 1566 child->NodeType() == nsINode::DOCUMENT_TYPE_NODE); 1567 1568 // Step 1.2. If child implements DocumentType, then continue. 1569 if (child->NodeType() == nsINode::DOCUMENT_TYPE_NODE) { 1570 continue; 1571 } 1572 1573 // Step 1.3. If child implements Text, then continue. 1574 if (child->IsText()) { 1575 continue; 1576 } 1577 1578 // Step 1.4. If child implements Comment: 1579 if (child->IsComment()) { 1580 // Step 1.4.1 If configuration["comments"] is not true, then remove 1581 // child. 1582 if (!mComments) { 1583 child->RemoveFromParent(); 1584 } 1585 continue; 1586 } 1587 1588 // Step 1.5. Otherwise: 1589 MOZ_ASSERT(child->IsElement()); 1590 1591 // Step 1.5.1. Let elementName be a SanitizerElementNamespace with child’s 1592 // local name and namespace. 1593 nsAtom* nameAtom = child->NodeInfo()->NameAtom(); 1594 int32_t namespaceID = child->NodeInfo()->NamespaceID(); 1595 // Make sure this is optimized away when using the default config. 1596 Maybe<CanonicalElement> elementName; 1597 // This is only used for the default config case. 1598 [[maybe_unused]] StaticAtomSet* elementAttributes = nullptr; 1599 if constexpr (!IsDefaultConfig) { 1600 elementName.emplace(nameAtom, ToNamespace(namespaceID)); 1601 1602 // Optimization: Remove unsafe elements before doing anything else. 1603 // https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration 1604 // 1605 // We have to do this _before_ handling the 1606 // "replaceWithChildrenElements" list, because the "remove an element" 1607 // call in removeUnsafe() would implicitly remove it from the list. 1608 // 1609 // The default config's "elements" allow list does not contain any 1610 // unsafe elements so we can skip this. 1611 if (aSafe && IsUnsafeElement(nameAtom, namespaceID)) { 1612 child->RemoveFromParent(); 1613 continue; 1614 } 1615 1616 // Step 1.5.2. If configuration["replaceWithChildrenElements"] exists 1617 // and if configuration["replaceWithChildrenElements"] contains 1618 // elementName: 1619 if (mReplaceWithChildrenElements && 1620 mReplaceWithChildrenElements->Contains(*elementName) && 1621 // Temporary fix for Bug 2004112 1622 // To be specified by https://github.com/WICG/sanitizer-api/issues/365 1623 !!child->GetParent()) { 1624 // Note: This follows nsTreeSanitizer by first inserting the 1625 // child's children in place of the current child and then 1626 // continueing the sanitization from the first inserted grandchild. 1627 nsCOMPtr<nsIContent> parent = child->GetParent(); 1628 nsCOMPtr<nsIContent> firstChild = child->GetFirstChild(); 1629 nsCOMPtr<nsIContent> newChild = firstChild; 1630 for (; newChild; newChild = child->GetFirstChild()) { 1631 ErrorResult rv; 1632 parent->InsertBefore(*newChild, child, rv); 1633 if (rv.Failed()) { 1634 // TODO: Abort? 1635 break; 1636 } 1637 } 1638 1639 child->RemoveFromParent(); 1640 if (firstChild) { 1641 next = firstChild; 1642 } 1643 continue; 1644 } 1645 1646 // Step 1.5.3. If configuration["removeElements"] exists and 1647 // configuration["removeElements"] contains elementName: 1648 if (mRemoveElements) { 1649 if (mRemoveElements->Contains(*elementName)) { 1650 // Step 1.5.3.1. Remove child. 1651 child->RemoveFromParent(); 1652 // Step 1.5.3.2.Continue. 1653 continue; 1654 } 1655 } 1656 1657 // Step 1.5.4. If configuration["elements"] exists and 1658 // configuration["elements"] does not contain elementName: 1659 if (mElements) { 1660 if (!mElements->Contains(*elementName)) { 1661 // Step 1.5.4.1. Remove child. 1662 child->RemoveFromParent(); 1663 // Step 1.5.4.2. Continue. 1664 continue; 1665 } 1666 } 1667 } else { 1668 // (The default config has no replaceWithChildrenElements or 1669 // removeElements) 1670 1671 // Step 1.5.4. If configuration["elements"] exists and 1672 // configuration["elements"] does not contain elementName: 1673 1674 bool found = false; 1675 if (nameAtom->IsStatic()) { 1676 ElementsWithAttributes* elements = nullptr; 1677 if (namespaceID == kNameSpaceID_XHTML) { 1678 elements = sDefaultHTMLElements; 1679 } else if (namespaceID == kNameSpaceID_MathML) { 1680 elements = sDefaultMathMLElements; 1681 } else if (namespaceID == kNameSpaceID_SVG) { 1682 elements = sDefaultSVGElements; 1683 } 1684 if (elements) { 1685 if (auto lookup = elements->Lookup(nameAtom->AsStatic())) { 1686 found = true; 1687 // This is the nullptr for elements without specific allowed 1688 // attributes. 1689 elementAttributes = lookup->get(); 1690 } 1691 } 1692 } 1693 if (!found) { 1694 // Step 1.5.4.1. Remove child. 1695 child->RemoveFromParent(); 1696 // Step 1.5.4.2. Continue. 1697 continue; 1698 } 1699 1700 MOZ_ASSERT(!IsUnsafeElement(nameAtom, namespaceID), 1701 "The default config has no unsafe elements"); 1702 } 1703 1704 // Step 1.5.5. If elementName equals «[ "name" → "template", "namespace" → 1705 // HTML namespace ]», then call sanitize core on child’s template contents 1706 // with configuration and handleJavascriptNavigationUrls. 1707 if (auto* templateEl = HTMLTemplateElement::FromNode(child)) { 1708 RefPtr<DocumentFragment> frag = templateEl->Content(); 1709 SanitizeChildren<IsDefaultConfig>(frag, aSafe); 1710 } 1711 1712 // Step 1.5.6. If child is a shadow host, then call sanitize core on child’s 1713 // shadow root with configuration and handleJavascriptNavigationUrls. 1714 if (RefPtr<ShadowRoot> shadow = child->GetShadowRoot()) { 1715 SanitizeChildren<IsDefaultConfig>(shadow, aSafe); 1716 } 1717 1718 // Step 1.5.7-9. 1719 if constexpr (!IsDefaultConfig) { 1720 SanitizeAttributes(child->AsElement(), *elementName, aSafe); 1721 } else { 1722 SanitizeDefaultConfigAttributes(child->AsElement(), elementAttributes, 1723 aSafe); 1724 } 1725 1726 // Step 1.5.10. Call sanitize core on child with configuration and 1727 // handleJavascriptNavigationUrls. 1728 // TODO: Optimization: Remove recusion similar to nsTreeSanitizer 1729 SanitizeChildren<IsDefaultConfig>(child, aSafe); 1730 } 1731 } 1732 1733 static inline bool IsDataAttribute(nsAtom* aName, int32_t aNamespaceID) { 1734 return StringBeginsWith(nsDependentAtomString(aName), u"data-"_ns) && 1735 aNamespaceID == kNameSpaceID_None; 1736 } 1737 1738 // https://wicg.github.io/sanitizer-api/#sanitize-core 1739 // Step 2.4.9.5. If handleJavascriptNavigationUrls: 1740 static bool RemoveJavascriptNavigationURLAttribute(Element* aElement, 1741 nsAtom* aLocalName, 1742 int32_t aNamespaceID) { 1743 // https://wicg.github.io/sanitizer-api/#contains-a-javascript-url 1744 auto containsJavascriptURL = [&]() { 1745 nsAutoString value; 1746 if (!aElement->GetAttr(aNamespaceID, aLocalName, value)) { 1747 return false; 1748 } 1749 1750 // Step 1. Let url be the result of running the basic URL parser on 1751 // attribute’s value. 1752 nsCOMPtr<nsIURI> uri; 1753 if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), value))) { 1754 // Step 2. If url is failure, then return false. 1755 return false; 1756 } 1757 1758 // Step 3. Return whether url’s scheme is "javascript". 1759 return uri->SchemeIs("javascript"); 1760 }; 1761 1762 // Step 1. If «[elementName, attrName]» matches an entry in the built-in 1763 // navigating URL attributes list, and if attribute contains a javascript: 1764 // URL, then remove attribute from child. 1765 if ((aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area, 1766 nsGkAtoms::base) && 1767 aLocalName == nsGkAtoms::href && aNamespaceID == kNameSpaceID_None) || 1768 (aElement->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input) && 1769 aLocalName == nsGkAtoms::formaction && 1770 aNamespaceID == kNameSpaceID_None) || 1771 (aElement->IsHTMLElement(nsGkAtoms::form) && 1772 aLocalName == nsGkAtoms::action && aNamespaceID == kNameSpaceID_None) || 1773 (aElement->IsHTMLElement(nsGkAtoms::iframe) && 1774 aLocalName == nsGkAtoms::src && aNamespaceID == kNameSpaceID_None) || 1775 (aElement->IsSVGElement(nsGkAtoms::a) && aLocalName == nsGkAtoms::href && 1776 (aNamespaceID == kNameSpaceID_None || 1777 aNamespaceID == kNameSpaceID_XLink))) { 1778 if (containsJavascriptURL()) { 1779 return true; 1780 } 1781 }; 1782 1783 // Step 2. If child’s namespace is the MathML Namespace and attr’s local 1784 // name is "href" and attr’s namespace is null or the XLink namespace and 1785 // attr contains a javascript: URL, then remove attr. 1786 if (aElement->IsMathMLElement() && aLocalName == nsGkAtoms::href && 1787 (aNamespaceID == kNameSpaceID_None || 1788 aNamespaceID == kNameSpaceID_XLink)) { 1789 if (containsJavascriptURL()) { 1790 return true; 1791 } 1792 } 1793 1794 // Step 3. If the built-in animating URL attributes list contains 1795 // «[elementName, attrName]» and attr’s value is "href" or "xlink:href", 1796 // then remove attr. 1797 if (aLocalName == nsGkAtoms::attributeName && 1798 aNamespaceID == kNameSpaceID_None && 1799 aElement->IsAnyOfSVGElements(nsGkAtoms::animate, nsGkAtoms::animateMotion, 1800 nsGkAtoms::animateTransform, 1801 nsGkAtoms::set)) { 1802 nsAutoString value; 1803 if (!aElement->GetAttr(aNamespaceID, aLocalName, value)) { 1804 return false; 1805 } 1806 1807 return value.EqualsLiteral("href") || value.EqualsLiteral("xlink:href"); 1808 } 1809 1810 return false; 1811 } 1812 1813 void Sanitizer::SanitizeAttributes(Element* aChild, 1814 const CanonicalElement& aElementName, 1815 bool aSafe) { 1816 MOZ_ASSERT(!mIsDefaultConfig); 1817 1818 // https://wicg.github.io/sanitizer-api/#sanitize-core 1819 // Substeps of 1.5. that are relevant to attributes. 1820 1821 // Step 7. Let elementWithLocalAttributes be « [] ». 1822 // Step 8. If configuration["elements"] exists and configuration["elements"] 1823 // contains elementName: 1824 // Step 8.1. Set elementWithLocalAttributes to 1825 // configuration["elements"][elementName]. 1826 const CanonicalElementAttributes* elementAttributes = 1827 mElements ? mElements->Lookup(aElementName).DataPtrOrNull() : nullptr; 1828 1829 // Step 9. For each attribute in child’s attribute list: 1830 int32_t count = int32_t(aChild->GetAttrCount()); 1831 for (int32_t i = count - 1; i >= 0; --i) { 1832 // Step 9.1. Let attrName be a SanitizerAttributeNamespace with attribute’s 1833 // local name and namespace. 1834 const nsAttrName* attr = aChild->GetAttrNameAt(i); 1835 RefPtr<nsAtom> attrLocalName = attr->LocalName(); 1836 int32_t attrNs = attr->NamespaceID(); 1837 CanonicalAttribute attrName(attrLocalName, ToNamespace(attrNs)); 1838 1839 bool remove = false; 1840 // Optimization: Remove unsafe event handler content attributes. 1841 // https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe 1842 if (aSafe && attrNs == kNameSpaceID_None && 1843 nsContentUtils::IsEventAttributeName( 1844 attrLocalName, EventNameType_All & ~EventNameType_XUL)) { 1845 remove = true; 1846 } 1847 1848 // Step 9.2. If elementWithLocalAttributes["removeAttributes"] with default 1849 // « [] » contains attrName: 1850 else if (elementAttributes && elementAttributes->mRemoveAttributes && 1851 elementAttributes->mRemoveAttributes->Contains(attrName)) { 1852 // Step 9.2.1. Remove attribute. 1853 remove = true; 1854 } 1855 1856 // Step 9.3. Otherwise, if configuration["attributes"] exists: 1857 else if (mAttributes) { 1858 // Step 9.3.1. If configuration["attributes"] does not contain attrName 1859 // and elementWithLocalAttributes["attributes"] with default « [] » does 1860 // not contain attrName, and if "data-" is not a code unit prefix of 1861 // attribute’s local name and namespace is not null or 1862 // configuration["dataAttributes"] is not true: 1863 MOZ_ASSERT(mDataAttributes.isSome(), 1864 "mDataAttributes exists iff mAttributes exists"); 1865 if (!mAttributes->Contains(attrName) && 1866 !(elementAttributes && elementAttributes->mAttributes && 1867 elementAttributes->mAttributes->Contains(attrName)) && 1868 !(*mDataAttributes && IsDataAttribute(attrLocalName, attrNs))) { 1869 // Step 9.3.1.1. Remove attribute. 1870 remove = true; 1871 } 1872 } 1873 1874 // Step 9.4. Otherwise: 1875 else { 1876 // Step 9.4.1. If elementWithLocalAttributes["attributes"] exists and 1877 // elementWithLocalAttributes["attributes"] does not contain attrName: 1878 if (elementAttributes && elementAttributes->mAttributes && 1879 !elementAttributes->mAttributes->Contains(attrName)) { 1880 // Step 9.4.1.1. Remove attribute. 1881 remove = true; 1882 } 1883 1884 // Step 9.4.2. Otherwise, if configuration["removeAttributes"] contains 1885 // attrName: 1886 else if (mRemoveAttributes->Contains(attrName)) { 1887 // Step 9.4.2.1. Remove attribute. 1888 remove = true; 1889 } 1890 } 1891 1892 // Step 5. If handleJavascriptNavigationUrls: 1893 if (aSafe && !remove) { 1894 remove = 1895 RemoveJavascriptNavigationURLAttribute(aChild, attrLocalName, attrNs); 1896 } 1897 1898 if (remove) { 1899 aChild->UnsetAttr(attr->NamespaceID(), attr->LocalName(), false); 1900 1901 // XXX Copied from nsTreeSanitizer. 1902 // In case the attribute removal shuffled the attribute order, start the 1903 // loop again. 1904 --count; 1905 i = count; // i will be decremented immediately thanks to the for loop 1906 } 1907 } 1908 } 1909 1910 void Sanitizer::SanitizeDefaultConfigAttributes( 1911 Element* aChild, StaticAtomSet* aElementAttributes, bool aSafe) { 1912 MOZ_ASSERT(mIsDefaultConfig); 1913 1914 // https://wicg.github.io/sanitizer-api/#sanitize-core 1915 // Substeps of 1.5. that are relevant to attributes. 1916 1917 // Step 7-8. (aElementAttributes passed as an argument) 1918 1919 // Step 9. For each attribute in child’s attribute list: 1920 int32_t count = int32_t(aChild->GetAttrCount()); 1921 for (int32_t i = count - 1; i >= 0; --i) { 1922 // Step 1. Let attrName be a SanitizerAttributeNamespace with attribute’s 1923 // local name and namespace. 1924 const nsAttrName* attr = aChild->GetAttrNameAt(i); 1925 RefPtr<nsAtom> attrLocalName = attr->LocalName(); 1926 int32_t attrNs = attr->NamespaceID(); 1927 1928 // Step 2. If elementWithLocalAttributes["removeAttributes"] with default « 1929 // [] » contains attrName: 1930 // (No local removeAttributes in the default config) 1931 1932 // Step 3. Otherwise, if configuration["attributes"] exists: 1933 // Step 3.1. If configuration["attributes"] does not contain attrName and 1934 // elementWithLocalAttributes["attributes"] with default « [] » does not 1935 // contain attrName, and if "data-" is not a code unit prefix of attribute’s 1936 // local name and namespace is not null or configuration["dataAttributes"] 1937 // is not true: 1938 bool remove = false; 1939 // Note: All attributes allowed by the default config are in the "null" 1940 // namespace. 1941 MOZ_ASSERT(mDataAttributes.isSome(), 1942 "mDataAttributes always exists in the default config"); 1943 if (attrNs != kNameSpaceID_None || 1944 (!sDefaultAttributes->Contains(attrLocalName) && 1945 !(aElementAttributes && aElementAttributes->Contains(attrLocalName)) && 1946 !(*mDataAttributes && IsDataAttribute(attrLocalName, attrNs)))) { 1947 // Step 3.1.1. Remove attribute. 1948 remove = true; 1949 } 1950 1951 // Step 4. Otherwise: 1952 // (not applicable) 1953 1954 // Step 5. If handleJavascriptNavigationUrls: 1955 else if (aSafe) { 1956 // TODO: This could be further optimized, because the default config 1957 // at the moment only allows <a href>. 1958 remove = 1959 RemoveJavascriptNavigationURLAttribute(aChild, attrLocalName, attrNs); 1960 } 1961 1962 // The default config attribute allow lists don't contain event 1963 // handler attributes. 1964 MOZ_ASSERT_IF(!remove, 1965 !nsContentUtils::IsEventAttributeName( 1966 attrLocalName, EventNameType_All & ~EventNameType_XUL)); 1967 1968 if (remove) { 1969 aChild->UnsetAttr(attr->NamespaceID(), attr->LocalName(), false); 1970 1971 // XXX Copied from nsTreeSanitizer. 1972 // In case the attribute removal shuffled the attribute order, start the 1973 // loop again. 1974 --count; 1975 i = count; // i will be decremented immediately thanks to the for loop 1976 } 1977 } 1978 } 1979 1980 } // namespace mozilla::dom