/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "Sanitizer.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/Span.h"
#include "mozilla/StaticPtr.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/DocumentFragment.h"
#include "mozilla/dom/HTMLTemplateElement.h"
#include "mozilla/dom/SanitizerBinding.h"
#include "mozilla/dom/SanitizerDefaultConfig.h"
#include "nsContentUtils.h"
#include "nsFmtString.h"
#include "nsGenericHTMLElement.h"
#include "nsIContentInlines.h"
#include "nsNameSpaceManager.h"
namespace mozilla::dom {
using
namespace sanitizer;
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Sanitizer, mGlobal)
NS_IMPL_CYCLE_COLLECTING_ADDREF(Sanitizer)
NS_IMPL_CYCLE_COLLECTING_RELEASE(Sanitizer)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Sanitizer)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
// Map[ElementName -> ?Set[Attributes]]
using ElementsWithAttributes =
nsTHashMap<
const nsStaticAtom*, UniquePtr<StaticAtomSet>>;
StaticAutoPtr<ElementsWithAttributes> sDefaultHTMLElements;
StaticAutoPtr<ElementsWithAttributes> sDefaultMathMLElements;
StaticAutoPtr<ElementsWithAttributes> sDefaultSVGElements;
StaticAutoPtr<StaticAtomSet> sDefaultAttributes;
JSObject* Sanitizer::WrapObject(JSContext* aCx,
JS::Handle<JSObject*> aGivenProto) {
return Sanitizer_Binding::Wrap(aCx, this, aGivenProto);
}
/* static */
// https://wicg.github.io/sanitizer-api/#sanitizerconfig-get-a-sanitizer-instance-from-options
already_AddRefed<Sanitizer> Sanitizer::GetInstance(
nsIGlobalObject* aGlobal,
const OwningSanitizerOrSanitizerConfigOrSanitizerPresets& aOptions,
bool aSafe, ErrorResult& aRv) {
// Step 4. If sanitizerSpec is a string:
if (aOptions.IsSanitizerPresets()) {
// Step 4.1. Assert: sanitizerSpec is "default"
MOZ_ASSERT(aOptions.GetAsSanitizerPresets() == SanitizerPresets::
Default);
// Step 4.2. Set sanitizerSpec to the built-in safe default configuration.
// NOTE: The built-in safe default configuration is complete and not
// influenced by |safe|.
RefPtr<Sanitizer> sanitizer =
new Sanitizer(aGlobal);
sanitizer->SetDefaultConfig();
return sanitizer.forget();
}
// Step 5. Assert: sanitizerSpec is either a Sanitizer instance, or a
// dictionary. Step 6. If sanitizerSpec is a dictionary:
if (aOptions.IsSanitizerConfig()) {
// Step 6.1. Let sanitizer be a new Sanitizer instance.
RefPtr<Sanitizer> sanitizer =
new Sanitizer(aGlobal);
// Step 6.2. Let setConfigurationResult be the result of set a
// configuration with sanitizerSpec and not safe on sanitizer.
sanitizer->SetConfig(aOptions.GetAsSanitizerConfig(), !aSafe, aRv);
// Step 6.3. If setConfigurationResult is false, throw a TypeError.
if (aRv.Failed()) {
return nullptr;
}
// Step 6.4. Set sanitizerSpec to sanitizer.
return sanitizer.forget();
}
// Step 7. Assert: sanitizerSpec is a Sanitizer instance.
MOZ_ASSERT(aOptions.IsSanitizer());
// Step 8. Return sanitizerSpec.
RefPtr<Sanitizer> sanitizer = aOptions.GetAsSanitizer();
return sanitizer.forget();
}
/* static */
// https://wicg.github.io/sanitizer-api/#sanitizer-constructor
already_AddRefed<Sanitizer> Sanitizer::Constructor(
const GlobalObject& aGlobal,
const SanitizerConfigOrSanitizerPresets& aConfig, ErrorResult& aRv) {
nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
RefPtr<Sanitizer> sanitizer =
new Sanitizer(global);
// Step 1. If configuration is a SanitizerPresets string, then:
if (aConfig.IsSanitizerPresets()) {
// Step 1.1. Assert: configuration is default.
MOZ_ASSERT(aConfig.GetAsSanitizerPresets() == SanitizerPresets::
Default);
// Step 1.2. Set configuration to the built-in safe default configuration.
sanitizer->SetDefaultConfig();
// NOTE: Early return because we don't need to do any
// processing/verification of the default config.
return sanitizer.forget();
}
// Step 2. Let valid be the return value of set a configuration with
// configuration and true on this.
sanitizer->SetConfig(aConfig.GetAsSanitizerConfig(), true, aRv);
// Step 3. If valid is false, then throw a TypeError.
if (aRv.Failed()) {
return nullptr;
}
return sanitizer.forget();
}
void Sanitizer::SetDefaultConfig() {
MOZ_ASSERT(NS_IsMainThread());
AssertNoLists();
MOZ_ASSERT(!mComments);
MOZ_ASSERT(mDataAttributes.isNothing());
mIsDefaultConfig = true;
// https://wicg.github.io/sanitizer-api/#built-in-safe-default-configuration
// {
// ...
// "comments": false,
// "dataAttributes": false
// }
mComments =
false;
mDataAttributes = Some(
false);
if (sDefaultHTMLElements) {
// Already initialized.
return;
}
auto createElements = [](mozilla::Span<nsStaticAtom*
const> aElements,
nsStaticAtom*
const* aElementWithAttributes) {
auto elements =
new ElementsWithAttributes(aElements.Length());
size_t i =
0;
for (nsStaticAtom* name : aElements) {
UniquePtr<StaticAtomSet> attributes = nullptr;
// Walkthrough the element specific attribute list in lockstep.
// The last "name" in the array is a nullptr sentinel.
if (name == aElementWithAttributes[i]) {
attributes = MakeUnique<StaticAtomSet>();
while (aElementWithAttributes[++i]) {
attributes->Insert(aElementWithAttributes[i]);
}
i++;
}
elements->InsertOrUpdate(name, std::move(attributes));
}
return elements;
};
sDefaultHTMLElements =
createElements(Span(kDefaultHTMLElements), kHTMLElementWithAttributes);
sDefaultMathMLElements = createElements(Span(kDefaultMathMLElements),
kMathMLElementWithAttributes);
sDefaultSVGElements =
createElements(Span(kDefaultSVGElements), kSVGElementWithAttributes);
sDefaultAttributes =
new StaticAtomSet(std::size(kDefaultAttributes));
for (nsStaticAtom* name : kDefaultAttributes) {
sDefaultAttributes->Insert(name);
}
ClearOnShutdown(&sDefaultHTMLElements);
ClearOnShutdown(&sDefaultMathMLElements);
ClearOnShutdown(&sDefaultAttributes);
}
// https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element
template <typename SanitizerElement>
static CanonicalElement CanonicalizeElement(
const SanitizerElement& aElement) {
// return the result of canonicalize a sanitizer name with element and the
// HTML namespace as the default namespace.
// https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name
// Step 1. Assert: name is either a DOMString or a dictionary. (implicit)
// Step 2. If name is a DOMString, then return «[ "name" → name, "namespace"
// → defaultNamespace]».
if (aElement.IsString()) {
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aElement.GetAsString());
return CanonicalElement(nameAtom, nsGkAtoms::nsuri_xhtml);
}
// Step 3. Assert: name is a dictionary and both name["name"] and
// name["namespace"] exist.
const auto& elem = GetAsDictionary(aElement);
MOZ_ASSERT(!elem.mName.IsVoid());
// Step 4. If name["namespace"] is the empty string, then set it to null.
RefPtr<nsAtom> namespaceAtom;
if (!elem.mNamespace.IsEmpty()) {
namespaceAtom = NS_AtomizeMainThread(elem.mNamespace);
}
// Step 5. Return «[
// "name" → name["name"],
// "namespace" → name["namespace"]
// )
// ]».
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(elem.mName);
return CanonicalElement(nameAtom, namespaceAtom);
}
// https://html.spec.whatwg.org/#canonicalize-a-processing-instruction
template <typename SanitizerPI>
static already_AddRefed<nsAtom> CanonicalizeProcessingInstruction(
const SanitizerPI& aPI) {
// Step 1. If pi is a DOMString, then return «[ "target" → pi ]».
if (aPI.IsString()) {
RefPtr<nsAtom> piAtom = NS_AtomizeMainThread(aPI.GetAsString());
return piAtom.forget();
}
// Step 2. Assert: pi is a dictionary and pi["target"] exists.
// Step 3. Return «[ "target" → pi["target"] ]».
const auto& pi = GetAsDictionary(aPI);
return NS_AtomizeMainThread(pi.mTarget);
}
// https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-attribute
template <typename SanitizerAttribute>
static CanonicalAttribute CanonicalizeAttribute(
const SanitizerAttribute& aAttribute) {
// return the result of canonicalize a sanitizer name with attribute and
// null as the default namespace.
// https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-name
// Step 1. Assert: name is either a DOMString or a dictionary. (implicit)
// Step 2. If name is a DOMString, then return «[ "name" → name, "namespace"
// → defaultNamespace]».
if (aAttribute.IsString()) {
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(aAttribute.GetAsString());
return CanonicalAttribute(nameAtom, nullptr);
}
// Step 3. Assert: name is a dictionary and both name["name"] and
// name["namespace"] exist.
const auto& attr = aAttribute.GetAsSanitizerAttributeNamespace();
MOZ_ASSERT(!attr.mName.IsVoid());
// Step 4. If name["namespace"] is the empty string, then set it to null.
RefPtr<nsAtom> namespaceAtom;
if (!attr.mNamespace.IsEmpty()) {
namespaceAtom = NS_AtomizeMainThread(attr.mNamespace);
}
// Step 5. Return «[
// "name" → name["name"],
// "namespace" → name["namespace"],
// )
// ]».
RefPtr<nsAtom> nameAtom = NS_AtomizeMainThread(attr.mName);
return CanonicalAttribute(nameAtom, namespaceAtom);
}
// https://wicg.github.io/sanitizer-api/#canonicalize-a-sanitizer-element-with-attributes
//
// If aErrorMsg is not nullptr, this function will abort for duplicate
// attributes and set an error message, but otherwise they are ignored.
template <typename SanitizerElementWithAttributes>
static CanonicalElementAttributes CanonicalizeElementAttributes(
const SanitizerElementWithAttributes& aElement,
nsACString* aErrorMsg = nullptr) {
// Step 1. Let result be the result of canonicalize a sanitizer element with
// element.
//
// NOTE: CanonicalizeElement is the responsibility of the caller.
CanonicalElementAttributes result{};
// Step 2. If element is a dictionary:
if (aElement.IsSanitizerElementNamespaceWithAttributes()) {
auto& elem = aElement.GetAsSanitizerElementNamespaceWithAttributes();
// Step 2.1. If element["attributes"] exists:
if (elem.mAttributes.WasPassed()) {
// Step 2.1.1. Let attributes be « ».
CanonicalAttributeSet attributes;
// Step 2.1.2. For each attribute of element["attributes"]:
for (
const auto& attribute : elem.mAttributes.Value()) {
// Step 2.1.2.1. Append the result of canonicalize a sanitizer attribute
// with attribute to attributes.
CanonicalAttribute canonicalAttr = CanonicalizeAttribute(attribute);
if (!attributes.EnsureInserted(canonicalAttr)) {
if (aErrorMsg) {
aErrorMsg->Assign(
nsFmtCString(
"Duplicate attribute {} in 'attributes' of {}.",
canonicalAttr, CanonicalizeElement(aElement)));
return CanonicalElementAttributes();
}
}
}
// Step 2.1.3. Set result["attributes"] to attributes.
result.mAttributes = Some(std::move(attributes));
}
// Step 2.2. If element["attributes"] exists:
if (elem.mRemoveAttributes.WasPassed()) {
// Step 2.2.1. Let attributes be « ».
CanonicalAttributeSet attributes;
// Step 2.2.2. For each attribute of element["removeAttributes"]:
for (
const auto& attribute : elem.mRemoveAttributes.Value()) {
// Step 2.2.2.1. Append the result of canonicalize a sanitizer attribute
// with attribute to attributes.
CanonicalAttribute canonicalAttr = CanonicalizeAttribute(attribute);
if (!attributes.EnsureInserted(canonicalAttr)) {
if (aErrorMsg) {
aErrorMsg->Assign(nsFmtCString(
"Duplicate attribute {} in 'removeAttributes' of {}.",
canonicalAttr, CanonicalizeElement(aElement)));
return CanonicalElementAttributes();
}
}
}
// Step 2.2.3. Set result["removeAttributes"] to attributes.
result.mRemoveAttributes = Some(std::move(attributes));
}
}
// Step 3. If neither result["attributes"] nor
// result["removeAttributes"] exist:
if (!result.mAttributes && !result.mRemoveAttributes) {
// Step 3.1. Set result["removeAttributes"] to « ».
CanonicalAttributeSet set{};
result.mRemoveAttributes = Some(std::move(set));
}
// Step 4. Return result.
return result;
}
// https://html.spec.whatwg.org/#canonicalize-the-configuration
void Sanitizer::CanonicalizeConfiguration(
const SanitizerConfig& aConfig,
bool aAllowCommentsPIsAndDataAttributes,
ErrorResult& aRv) {
// This function is only called while constructing a new Sanitizer object.
AssertNoLists();
// Step 1. If neither configuration["elements"] nor
// configuration["removeElements"] exists, then set
// configuration["removeElements"] to an empty list.
if (!aConfig.mElements.WasPassed() && !aConfig.mRemoveElements.WasPassed()) {
mRemoveElements.emplace();
}
// Step 2. If neither configuration["attributes"] nor
// configuration["removeAttributes"] exists, then set
// configuration["removeAttributes"] to an empty list.
if (!aConfig.mAttributes.WasPassed() &&
!aConfig.mRemoveAttributes.WasPassed()) {
mRemoveAttributes.emplace();
}
// Step 3. If neither configuration["processingInstructions"] nor
// configuration["removeProcessingInstructions"] exists:
if (!aConfig.mProcessingInstructions.WasPassed() &&
!aConfig.mRemoveProcessingInstructions.WasPassed()) {
// Step 3.1. If allowCommentsPIsAndDataAttributes is true, then set
// configuration["removeProcessingInstructions"] to an empty list.
if (aAllowCommentsPIsAndDataAttributes) {
mRemoveProcessingInstructions.emplace();
}
else {
// Step 3.2. Otherwise, set configuration["processingInstructions"] to an
// empty list.
mProcessingInstructions.emplace();
}
}
// Step 4. If configuration["elements"] exists:
if (aConfig.mElements.WasPassed()) {
// Step 4.1. Let newElements be « ».
CanonicalElementMap elements;
nsAutoCString errorMsg;
// Step 4.2. For each element of configuration["elements"], append the
// result of canonicalizing element to newElements.
for (
const auto& element : aConfig.mElements.Value()) {
CanonicalElement elementName = CanonicalizeElement(element);
if (elements.Contains(elementName)) {
aRv.ThrowTypeError(
nsFmtCString(
"Duplicate element {} in 'elements'.", elementName));
return;
}
CanonicalElementAttributes elementAttributes =
CanonicalizeElementAttributes(element, &errorMsg);
if (!errorMsg.IsEmpty()) {
aRv.ThrowTypeError(errorMsg);
return;
}
elements.InsertOrUpdate(elementName, std::move(elementAttributes));
}
// Step 4.3. Set configuration["elements"] to newElements.
mElements = Some(std::move(elements));
}
// Step 5. If configuration["removeElements"] exists, then set
// configuration["removeElements"] to the result of canonicalizing
// configuration["removeElements"].
if (aConfig.mRemoveElements.WasPassed()) {
CanonicalElementSet elements;
for (
const auto& element : aConfig.mRemoveElements.Value()) {
CanonicalElement canonical = CanonicalizeElement(element);
if (!elements.EnsureInserted(canonical)) {
aRv.ThrowTypeError(nsFmtCString(
"Duplicate element {} in 'removeElements'.", canonical));
return;
}
}
mRemoveElements = Some(std::move(elements));
}
// Step 6. If configuration["attributes"] exists, then set
// configuration["attributes"] to the result of canonicalizing
// configuration["attributes"].
if (aConfig.mAttributes.WasPassed()) {
CanonicalAttributeSet attributes;
for (
const auto& attribute : aConfig.mAttributes.Value()) {
CanonicalAttribute canonical = CanonicalizeAttribute(attribute);
if (!attributes.EnsureInserted(canonical)) {
aRv.ThrowTypeError(
nsFmtCString(
"Duplicate attribute {} in 'attributes'.", canonical));
return;
}
}
mAttributes = Some(std::move(attributes));
}
// Step 7. If configuration["removeAttributes"] exists, then set
// configuration["removeAttributes"] to the result of canonicalizing
// configuration["removeAttributes"].
if (aConfig.mRemoveAttributes.WasPassed()) {
CanonicalAttributeSet attributes;
for (
const auto& attribute : aConfig.mRemoveAttributes.Value()) {
CanonicalAttribute canonical = CanonicalizeAttribute(attribute);
if (!attributes.EnsureInserted(canonical)) {
aRv.ThrowTypeError(nsFmtCString(
"Duplicate attribute {} in 'removeAttributes'.", canonical));
return;
}
}
mRemoveAttributes = Some(std::move(attributes));
}
// Step 8. If configuration["replaceWithChildrenElements"] exists, then set
// configuration["replaceWithChildrenElements"] to the result of
// canonicalizing configuration["replaceWithChildrenElements"].
if (aConfig.mReplaceWithChildrenElements.WasPassed()) {
CanonicalElementSet elements;
for (
const auto& element : aConfig.mReplaceWithChildrenElements.Value()) {
CanonicalElement canonical = CanonicalizeElement(element);
if (!elements.EnsureInserted(canonical)) {
aRv.ThrowTypeError(nsFmtCString(
"Duplicate element {} in 'replaceWithChildrenElements'.",
canonical));
return;
}
}
mReplaceWithChildrenElements = Some(std::move(elements));
}
// Step 9. If configuration["processingInstructions"] exists, then set
// configuration["processingInstructions"] to the result of canonicalizing
// configuration["processingInstructions"].
if (aConfig.mProcessingInstructions.WasPassed()) {
CanonicalPISet pis;
for (
const auto& pi : aConfig.mProcessingInstructions.Value()) {
RefPtr<nsAtom> canonical = CanonicalizeProcessingInstruction(pi);
if (!pis.EnsureInserted(canonical)) {
nsAutoCString name;
canonical->ToUTF8String(name);
aRv.ThrowTypeError(
nsFmtCString(
"Duplicate processing instruction \"{}\
" in "
"'processingInstructions'.",
name));
return;
}
}
mProcessingInstructions = Some(std::move(pis));
}
// Step 10. If configuration["removeProcessingInstructions"] exists, then set
// configuration["removeProcessingInstructions"] to the result of
// canonicalizing configuration["removeProcessingInstructions"].
if (aConfig.mRemoveProcessingInstructions.WasPassed()) {
CanonicalPISet pis;
for (
const auto& pi : aConfig.mRemoveProcessingInstructions.Value()) {
RefPtr<nsAtom> canonical = CanonicalizeProcessingInstruction(pi);
if (!pis.EnsureInserted(canonical)) {
nsAutoCString name;
canonical->ToUTF8String(name);
aRv.ThrowTypeError(
nsFmtCString(
"Duplicate processing instruction \"{}\
" in "
"'removeProcessingInstructions'.",
name));
return;
}
}
mRemoveProcessingInstructions = Some(std::move(pis));
}
// Step 11. If configuration["comments"] does not exist, then set it to
// allowCommentsPIsAndDataAttributes.
if (aConfig.mComments.WasPassed()) {
// NOTE: We always need to copy this property if it exists.
mComments = aConfig.mComments.Value();
}
else {
mComments = aAllowCommentsPIsAndDataAttributes;
}
// Step 12. If configuration["attributes"] exists and
// configuration["dataAttributes"] does not exist, then set it to
// allowCommentsPIsAndDataAttributes.
if (aConfig.mDataAttributes.WasPassed()) {
// NOTE: We always need to copy this property if it exists.
mDataAttributes = Some(aConfig.mDataAttributes.Value());
}
else if (aConfig.mAttributes.WasPassed()) {
mDataAttributes = Some(aAllowCommentsPIsAndDataAttributes);
}
}
// https://wicg.github.io/sanitizer-api/#built-in-non-replaceable-elements-list
// The built-in non-replaceable elements list
static bool IsNonReplaceableElement(
const CanonicalElement& aElement) {
return aElement ==
CanonicalElement(nsGkAtoms::html, nsGkAtoms::nsuri_xhtml) ||
aElement == CanonicalElement(nsGkAtoms::svg, nsGkAtoms::nsuri_svg) ||
aElement == CanonicalElement(nsGkAtoms::math, nsGkAtoms::nsuri_mathml);
}
// https://wicg.github.io/sanitizer-api/#sanitizerconfig-valid
void Sanitizer::IsValid(ErrorResult& aRv) {
// Step 1. The config has either an elements or a removeElements key, but
// not both.
MOZ_ASSERT(mElements || mRemoveElements,
"Must have either due to CanonicalizeConfiguration");
if (mElements && mRemoveElements) {
aRv.ThrowTypeError(
"'elements' and 'removeElements' are not allowed at the same time.");
return;
}
// Assert: Either config["processingInstructions"] exists or
// config["removeProcessingInstructions"] exists.
MOZ_ASSERT(mProcessingInstructions || mRemoveProcessingInstructions);
// If config["processingInstructions"] exists and
// config["removeProcessingInstructions"] exists, then return false.
if (mProcessingInstructions && mRemoveProcessingInstructions) {
aRv.ThrowTypeError(
"'processingInstructions' and 'removeProcessingInstructions' are not "
"allowed at the same time.");
return;
}
// Step 2. The config has either an attributes or a removeAttributes key,
// but not both.
MOZ_ASSERT(mAttributes || mRemoveAttributes,
"Must have either due to CanonicalizeConfiguration");
if (mAttributes && mRemoveAttributes) {
aRv.ThrowTypeError(
"'attributes' and 'removeAttributes' are not allowed at the same "
"time.");
return;
}
// Step 3. Assert: All SanitizerElementNamespaceWithAttributes,
// SanitizerElementNamespace, and SanitizerAttributeNamespace items in
// config are canonical, meaning they have been run through canonicalize a
// sanitizer element or canonicalize a sanitizer attribute, as appropriate.
// Step 4. None of config[elements], config[removeElements],
// config[replaceWithChildrenElements], config[attributes], or
// config[removeAttributes], if they exist, has duplicates.
//
// NOTE: The Sanitizer::CanonicalizeConfiguration method would have already
// thrown an error for duplicate elements/attributes by this point. The map
// and sets can't have duplicates by definition.
// Step 11. If config["replaceWithChildrenElements"] exists:
if (mReplaceWithChildrenElements) {
// Step 11.1. For each element of config["replaceWithChildrenElements"]:
for (
const CanonicalElement& element : *mReplaceWithChildrenElements) {
// Step 11.1.1. If the built-in non-replaceable elements list contains
// element, then return false.
if (IsNonReplaceableElement(element)) {
aRv.ThrowTypeError(nsFmtCString(
"Element {} is not allowed in 'replaceWithChildrenElements'",
element));
return;
}
}
// Step 11.2. If config["elements"] exists:
if (mElements) {
// Step 11.2.1. If the intersection of config["elements"] and
// config["replaceWithChildrenElements"] is not empty, then return false.
for (
const CanonicalElement& name : mElements->Keys()) {
if (mReplaceWithChildrenElements->Contains(name)) {
aRv.ThrowTypeError(
nsFmtCString(
"Element {} can't be in both 'elements' "
"and 'replaceWithChildrenElements'.",
name));
return;
}
}
}
else {
// Step 11.3. Otherwise:
// Step 11.3.1. If the intersection of config["removeElements"] and
// config["replaceWithChildrenElements"] is not empty, then return false.
for (
const CanonicalElement& name : *mRemoveElements) {
if (mReplaceWithChildrenElements->Contains(name)) {
aRv.ThrowTypeError(
nsFmtCString(
"Element {} can't be in both 'removeElements' and "
"'replaceWithChildrenElements'.",
name));
return;
}
}
}
}
// Step 7. If config[attributes] exists:
if (mAttributes) {
// Step 7.1. If config[elements] exists:
if (mElements) {
// Step 7.1.1 For any element in config[elements]:
for (
const auto& entry : *mElements) {
const CanonicalElementAttributes& elemAttributes = entry.GetData();
MOZ_ASSERT(
elemAttributes.mAttributes || elemAttributes.mRemoveAttributes,
"Canonical elements must at least have removeAttributes");
// Step 7.1.1.1. Neither element[attributes] or
// element[removeAttributes], if they exist, has duplicates.
//
// NOTE: The Sanitizer::CanonicalizeConfiguration (specifically
// CanonicalizeElementAttributes) method would have already thrown an
// error for duplicate attributes by this point. The attribute sets
// can't have duplicates by definition.
// Step 7.1.1.2. The intersection of config[attributes] and
// element[attributes] with default « [] » is empty.
if (elemAttributes.mAttributes) {
for (
const CanonicalAttribute& name : *elemAttributes.mAttributes) {
if (mAttributes->Contains(name)) {
aRv.ThrowTypeError(nsFmtCString(
"Attribute {} can't be part of both the 'attributes' of "
"the element {} and the global 'attributes'.",
name, entry.GetKey()));
return;
}
}
}
// Step 7.1.1.3. element[removeAttributes] is a subset of
// config[attributes].
if (elemAttributes.mRemoveAttributes) {
for (
const CanonicalAttribute& name :
*elemAttributes.mRemoveAttributes) {
if (!mAttributes->Contains(name)) {
aRv.ThrowTypeError(nsFmtCString(
"Attribute {} can't be in 'removeAttributes' of the "
"element {} but not in the global 'attributes'.",
name, entry.GetKey()));
return;
}
}
}
MOZ_ASSERT(mDataAttributes.isSome(),
"mDataAttributes exists iff mAttributes exists");
// Step 7.1.1.4. If dataAttributes is true:
if (*mDataAttributes && elemAttributes.mAttributes) {
// TODO: Merge with loop above?
// Step 7.1.1.4.1. element[attributes] does not contain a custom
// data attribute.
for (
const CanonicalAttribute& name : *elemAttributes.mAttributes) {
if (name.IsDataAttribute()) {
aRv.ThrowTypeError(nsFmtCString(
"Data attribute {} in the 'attributes' of the element {} "
"is redundant with 'dataAttributes' being true.",
name, entry.GetKey()));
return;
}
}
}
}
}
MOZ_ASSERT(mDataAttributes.isSome(),
"mDataAttributes exists iff mAttributes exists");
// Step 7.2. If dataAttributes is true:
if (*mDataAttributes) {
// Step 7.2.1. config[attributes] does not contain a custom data
// attribute.
for (
const CanonicalAttribute& name : *mAttributes) {
if (name.IsDataAttribute()) {
aRv.ThrowTypeError(
nsFmtCString(
"Data attribute {} in the global 'attributes' is "
"redundant with 'dataAttributes' being true.",
name));
return;
}
}
}
}
// Step 8. If config[removeAttributes] exists:
if (mRemoveAttributes) {
// Step 8.1. If config[elements] exists, then for any element in
// config[elements]:
if (mElements) {
for (
const auto& entry : *mElements) {
const CanonicalElementAttributes& elemAttributes = entry.GetData();
// Step 8.1.1. Not both element[attributes] and
// element[removeAttributes] exist.
if (elemAttributes.mAttributes && elemAttributes.mRemoveAttributes) {
return aRv.ThrowTypeError(
nsFmtCString(
"Element {} can't have both 'attributes' "
"and 'removeAttributes'.",
entry.GetKey()));
}
// Step 8.1.2. Neither element[attributes] nor
// element[removeAttributes], if they exist, has duplicates.
//
// NOTE: The Sanitizer::CanonicalizeConfiguration (specifically
// CanonicalizeElementAttributes) method would have already thrown an
// error for duplicate attributes by this point. The attribute sets
// can't have duplicates by definition.
// Step 8.1.3. The intersection of config[removeAttributes] and
// element[attributes] with default « [] » is empty.
if (elemAttributes.mAttributes) {
for (
const CanonicalAttribute& name : *elemAttributes.mAttributes) {
if (mRemoveAttributes->Contains(name)) {
aRv.ThrowTypeError(nsFmtCString(
"Attribute {} can't be in 'attributes' of the element {} "
"while in the global 'removeAttributes'.",
name, entry.GetKey()));
return;
}
}
}
// Step 8.1.4. The intersection of config[removeAttributes] and
// element[removeAttributes] with default « [] » is empty.
if (elemAttributes.mRemoveAttributes) {
for (
const CanonicalAttribute& name :
*elemAttributes.mRemoveAttributes) {
if (mRemoveAttributes->Contains(name)) {
aRv.ThrowTypeError(
nsFmtCString(
"Attribute {} can't be part of both the "
"'removeAttributes' of the element {} and the "
"global 'removeAttributes'.",
name, entry.GetKey()));
return;
}
}
}
}
}
// Step 8.2. config[dataAttributes] does not exist.
if (mDataAttributes) {
aRv.ThrowTypeError(
"'removeAttributes' and 'dataAttributes' aren't allowed at the "
"same time.");
}
}
}
void Sanitizer::AssertIsValid() {
#ifdef DEBUG
IgnoredErrorResult rv;
IsValid(rv);
MOZ_ASSERT(!rv.Failed());
#endif
}
// https://html.spec.whatwg.org/#configure-a-sanitizer
void Sanitizer::SetConfig(
const SanitizerConfig& aConfig,
bool aAllowCommentsPIsAndDataAttributes,
ErrorResult& aRv) {
// Step 1. Canonicalize configuration with allowCommentsPIsAndDataAttributes.
CanonicalizeConfiguration(aConfig, aAllowCommentsPIsAndDataAttributes, aRv);
if (aRv.Failed()) {
return;
}
// Step 2. If configuration is not valid, then throw a TypeError.
IsValid(aRv);
if (aRv.Failed()) {
return;
}
// Step 3. Set sanitizer's configuration to configuration.
// Note: This was already done in CanonicalizeConfiguration.
}
// Turn the lazy default config into real lists that can be
// modified or queried via get().
void Sanitizer::MaybeMaterializeDefaultConfig() {
if (!mIsDefaultConfig) {
AssertIsValid();
return;
}
AssertNoLists();
CanonicalElementMap elements;
auto insertElements = [&elements](
mozilla::Span<nsStaticAtom*
const> aElements,
nsStaticAtom* aNamespace,
nsStaticAtom*
const* aElementWithAttributes) {
size_t i =
0;
for (nsStaticAtom* name : aElements) {
CanonicalElementAttributes elementAttributes{};
if (name == aElementWithAttributes[i]) {
CanonicalAttributeSet attributes;
while (aElementWithAttributes[++i]) {
attributes.Insert(
CanonicalAttribute(aElementWithAttributes[i], nullptr));
}
i++;
elementAttributes.mAttributes = Some(std::move(attributes));
}
else {
// In the default config all elements have a (maybe empty) `attributes`
// list.
CanonicalAttributeSet set{};
elementAttributes.mAttributes = Some(std::move(set));
}
CanonicalElement elementName(name, aNamespace);
elements.InsertOrUpdate(elementName, std::move(elementAttributes));
}
};
insertElements(Span(kDefaultHTMLElements), nsGkAtoms::nsuri_xhtml,
kHTMLElementWithAttributes);
insertElements(Span(kDefaultMathMLElements), nsGkAtoms::nsuri_mathml,
kMathMLElementWithAttributes);
insertElements(Span(kDefaultSVGElements), nsGkAtoms::nsuri_svg,
kSVGElementWithAttributes);
mElements = Some(std::move(elements));
mProcessingInstructions = Some(CanonicalPISet{});
CanonicalAttributeSet attributes;
for (nsStaticAtom* name : kDefaultAttributes) {
attributes.Insert(CanonicalAttribute(name, nullptr));
}
mAttributes = Some(std::move(attributes));
mIsDefaultConfig =
false;
}
// https://wicg.github.io/sanitizer-api/#dom-sanitizer-get
void Sanitizer::Get(SanitizerConfig& aConfig) {
MaybeMaterializeDefaultConfig();
// Step 1. Let config be this’s configuration.
// Step 2. If config["elements"] exists:
if (mElements) {
nsTArray<OwningStringOrSanitizerElementNamespaceWithAttributes> elements;
// Step 2.1. For any element of config["elements"]:
for (
const auto& entry : *mElements) {
// Step 2.1.1. If element["attributes"] exists:
// Step 2.1.2. If element["removeAttributes"] exists:
// ...
// (The attributes are sorted by the ToSanitizerAttributes call in
// ToSanitizerElementNamespaceWithAttributes)
OwningStringOrSanitizerElementNamespaceWithAttributes owning;
owning.SetAsSanitizerElementNamespaceWithAttributes() =
entry.GetKey().ToSanitizerElementNamespaceWithAttributes(
entry.GetData());
// Step 2.2. Set config["elements"] to the result of sort in ascending
// order config["elements"], with elementA being less than item elementB.
// (Instead of sorting at the end, we sort during insertion)
elements.InsertElementSorted(owning,
SanitizerComparator<decltype(owning)>());
}
aConfig.mElements.Construct(std::move(elements));
}
else {
// Step 3. If config["removeElements"] exists:
// Step 3.1. Set config["removeElements"] to the result of sort in ascending
// order config["removeElements"], with elementA being less than item
// elementB.
nsTArray<OwningStringOrSanitizerElementNamespace> removeElements;
for (
const CanonicalElement& canonical : *mRemoveElements) {
OwningStringOrSanitizerElementNamespace owning;
owning.SetAsSanitizerElementNamespace() =
canonical.ToSanitizerElementNamespace();
removeElements.InsertElementSorted(
owning, SanitizerComparator<decltype(owning)>());
}
aConfig.mRemoveElements.Construct(std::move(removeElements));
}
// Step 4. If config["replaceWithChildrenElements"] exists:
if (mReplaceWithChildrenElements) {
// Step 4.1. Set config["replaceWithChildrenElements"] to the result of sort
// in ascending order config["replaceWithChildrenElements"], with elementA
// being less than item elementB.
nsTArray<OwningStringOrSanitizerElementNamespace>
replaceWithChildrenElements;
for (
const CanonicalElement& canonical : *mReplaceWithChildrenElements) {
OwningStringOrSanitizerElementNamespace owning;
owning.SetAsSanitizerElementNamespace() =
canonical.ToSanitizerElementNamespace();
replaceWithChildrenElements.InsertElementSorted(
owning, SanitizerComparator<decltype(owning)>());
}
aConfig.mReplaceWithChildrenElements.Construct(
std::move(replaceWithChildrenElements));
}
if (mProcessingInstructions) {
nsTArray<OwningStringOrSanitizerProcessingInstruction>
processingInstructions;
for (
const RefPtr<nsAtom>& canonical : *mProcessingInstructions) {
SanitizerProcessingInstruction pi;
canonical->ToString(pi.mTarget);
OwningStringOrSanitizerProcessingInstruction owning;
owning.SetAsSanitizerProcessingInstruction() = pi;
processingInstructions.InsertElementSorted(owning, PIComparator());
}
aConfig.mProcessingInstructions.Construct(
std::move(processingInstructions));
}
else {
nsTArray<OwningStringOrSanitizerProcessingInstruction>
removeProcessingInstructions;
for (
const RefPtr<nsAtom>& canonical : *mRemoveProcessingInstructions) {
SanitizerProcessingInstruction pi;
canonical->ToString(pi.mTarget);
OwningStringOrSanitizerProcessingInstruction owning;
owning.SetAsSanitizerProcessingInstruction() = pi;
removeProcessingInstructions.InsertElementSorted(owning, PIComparator());
}
aConfig.mRemoveProcessingInstructions.Construct(
std::move(removeProcessingInstructions));
}
// Step 5. If config["attributes"] exists:
if (mAttributes) {
// Step 5.1. Set config["attributes"] to the result of sort in ascending
// order config["attributes"], with attrA being less than item attrB.
// (Sorting is done by ToSanitizerAttributes)
aConfig.mAttributes.Construct(ToSanitizerAttributes(*mAttributes));
}
else {
// Step 6. If config["removeAttributes"] exists:
// Step 6.1. Set config["removeAttributes"] to the result of sort in
// ascending order config["removeAttributes"], with attrA being less than
// item attrB.
aConfig.mRemoveAttributes.Construct(
ToSanitizerAttributes(*mRemoveAttributes));
}
// (In the spec these already exist in the |config| and don't need sorting)
aConfig.mComments.Construct(mComments);
if (mDataAttributes) {
aConfig.mDataAttributes.Construct(*mDataAttributes);
}
// Step 7. Return config.
}
// https://wicg.github.io/sanitizer-api/#sanitizerconfig-allow-an-element
bool Sanitizer::AllowElement(
const StringOrSanitizerElementNamespaceWithAttributes& aElement) {
MaybeMaterializeDefaultConfig();
// Step 1. Set element to the result of canonicalize a sanitizer element
// with attributes with element.
CanonicalElement elementName = CanonicalizeElement(aElement);
// NOTE: Duplicate attributes are removed/ignored.
CanonicalElementAttributes elementAttributes =
CanonicalizeElementAttributes(aElement);
// Step 2 If configuration["elements"] exists:
if (mElements) {
// Step 2.1. Set modified to the result of remove element from
// configuration["replaceWithChildrenElements"].
bool modified =
mReplaceWithChildrenElements
? mReplaceWithChildrenElements->EnsureRemoved(elementName)
:
false;
// Step 2.2. Comment: We need to make sure the per-element attributes do
// not overlap with global attributes.
// Step 2.3. If configuration["attributes"] exists:
if (mAttributes) {
// Step 2.3.1. If element["attributes"] exists:
if (elementAttributes.mAttributes) {
CanonicalAttributeSet attributes;
for (
const CanonicalAttribute& attr : *elementAttributes.mAttributes) {
// Step 2.3.1.1. Set element["attributes"] to remove duplicates from
// element["attributes"].
MOZ_ASSERT(!attributes.Contains(attr));
// Step 2.3.1.2. Set element["attributes"] to the difference of
// element["attributes"] and configuration["attributes"].
if (mAttributes->Contains(attr)) {
continue;
}
// Step 2.3.1.3. If configuration["dataAttributes"] is true:
MOZ_ASSERT(mDataAttributes.isSome(),
"mDataAttributes exists iff mAttributes");
if (*mDataAttributes) {
// Step 2.3.1.3.1 Remove all items item from element["attributes"]
// where item is a custom data attribute.
if (attr.IsDataAttribute()) {
continue;
}
}
attributes.Insert(attr.Clone());
}
elementAttributes.mAttributes = Some(std::move(attributes));
}
// Step 2.3.2. If element["removeAttributes"] exists:
if (elementAttributes.mRemoveAttributes) {
CanonicalAttributeSet removeAttributes;
for (
const CanonicalAttribute& attr :
*elementAttributes.mRemoveAttributes) {
// Step 2.3.2.1. Set element["removeAttributes"] to remove duplicates
// from element["removeAttributes"].
//
// NOTE: CanonicalizeElementAttributes removed all duplicates for us.
MOZ_ASSERT(!removeAttributes.Contains(attr));
// Step 2.3.2.2. Set element["removeAttributes"] to the intersection
// of element["removeAttributes"] and configuration["attributes"].
if (!mAttributes->Contains(attr)) {
continue;
}
removeAttributes.Insert(attr.Clone());
}
elementAttributes.mRemoveAttributes = Some(std::move(removeAttributes));
}
}
else {
// Step 2.4. Otherwise:
// Step 2.4.1. If element["attributes"] exists:
if (elementAttributes.mAttributes) {
CanonicalAttributeSet attributes;
for (
const CanonicalAttribute& attr : *elementAttributes.mAttributes) {
// Step 2.4.1.1. Set element["attributes"] to remove duplicates from
// element["attributes"].
//
// NOTE: CanonicalizeElementAttributes removed all duplicates for us.
MOZ_ASSERT(!attributes.Contains(attr));
// Step 2.4.1.2. Set element["attributes"] to the difference of
// element["attributes"] and element["removeAttributes"] with default
// « ».
if (elementAttributes.mRemoveAttributes &&
elementAttributes.mRemoveAttributes->Contains(attr)) {
continue;
}
// Step 2.4.1.4. Set element["attributes"] to the difference of
// element["attributes"] and configuration["removeAttributes"].
if (mRemoveAttributes->Contains(attr)) {
continue;
}
attributes.Insert(attr.Clone());
}
elementAttributes.mAttributes = Some(std::move(attributes));
// Step 2.4.1.3. Remove element["removeAttributes"].
elementAttributes.mRemoveAttributes = Nothing();
}
// Step 2.4.2. If element["removeAttributes"] exists:
if (elementAttributes.mRemoveAttributes) {
CanonicalAttributeSet removeAttributes;
for (
const CanonicalAttribute& attr :
*elementAttributes.mRemoveAttributes) {
// Step 2.4.2.1. Set element["removeAttributes"] to remove duplicates
// from element["removeAttributes"].
MOZ_ASSERT(!removeAttributes.Contains(attr));
// Step 2.4.2.2. Set element["removeAttributes"] to the difference of
// element["removeAttributes"] and configuration["removeAttributes"].
if (mRemoveAttributes->Contains(attr)) {
continue;
}
removeAttributes.Insert(attr.Clone());
}
elementAttributes.mRemoveAttributes = Some(std::move(removeAttributes));
}
}
// Step 2.5. If configuration["elements"] does not contain element:
const CanonicalElementAttributes* existingElementAttributes =
mElements->Lookup(elementName).DataPtrOrNull();
if (!existingElementAttributes) {
// Step 2.5.1. Comment: This is the case with a global allow-list that
// does not yet contain element.
// Step 2.5.2. Append element to configuration["elements"].
mElements->InsertOrUpdate(elementName, std::move(elementAttributes));
// Step 2.5.3. Return true.
return true;
}
// Step 2.6. Comment: This is the case with a global allow-list that
// already contains element.
// Step 2.7. Let current element be the item in configuration["elements"]
// where item[name] equals element[name] and item[namespace] equals
// element[namespace]. (implict with existingElementAttributes)
// Step 2.8. If element equals current element then return modified.
if (elementAttributes.Equals(*existingElementAttributes)) {
return modified;
}
// Step 2.9. Remove element from configuration["elements"].
// Step 2.10. Append element to configuration["elements"].
mElements->InsertOrUpdate(elementName, std::move(elementAttributes));
// Step 2.11. Return true.
return true;
}
// Step 3. Otherwise:
// Step 3.1. If element["attributes"] exists or element["removeAttributes"]
// with default « » is not empty:
if (elementAttributes.mAttributes ||
(elementAttributes.mRemoveAttributes &&
!elementAttributes.mRemoveAttributes->IsEmpty())) {
// Step 3.1.1. The user agent may report a warning to the console that this
// operation is not supported.
if (
auto* win = mGlobal->GetAsInnerWindow()) {
nsContentUtils::ReportToConsole(
nsIScriptError::warningFlag,
"Sanitizer"_ns, win->GetDoc(),
PropertiesFile::SECURITY_PROPERTIES,
"SanitizerAllowElementIgnored2");
}
// Step 3.1.2. Return false.
return false;
}
// Step 3.2. Set modified to the result of remove element from
// configuration["replaceWithChildrenElements"].
bool modified = mReplaceWithChildrenElements
? mReplaceWithChildrenElements->EnsureRemoved(elementName)
:
false;
// Step 3.3. If configuration["removeElements"] does not contain element:
if (!mRemoveElements->Contains(elementName)) {
// Step 3.3.1. Comment: This is the case with a global remove-list that
// does not contain element.
// Step 3.3.2. Return modified.
return modified;
}
// Step 3.4. Comment: This is the case with a global remove-list that
// contains element.
// Step 3.5. Remove element from configuration["removeElements"].
mRemoveElements->Remove(elementName);
// Step 3.6. Return true.
return true;
}
// https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-element
bool Sanitizer::RemoveElement(
const StringOrSanitizerElementNamespace& aElement) {
MaybeMaterializeDefaultConfig();
// Step 1. Set element to the result of canonicalize a sanitizer element
// with element.
CanonicalElement element = CanonicalizeElement(aElement);
return RemoveElementCanonical(std::move(element));
}
bool Sanitizer::RemoveElementCanonical(CanonicalElement&& aElement) {
// Step 2. Set modified to the result of remove element from
// configuration["replaceWithChildrenElements"].
bool modified = mReplaceWithChildrenElements
? mReplaceWithChildrenElements->EnsureRemoved(aElement)
:
false;
// Step 3. If configuration["elements"] exists:
if (mElements) {
// Step 3.1. If configuration["elements"] contains element:
if (mElements->Contains(aElement)) {
// Step 3.1.1. Comment: We have a global allow list and it contains
// element.
// Step 3.1.2. Remove element from configuration["elements"].
mElements->Remove(aElement);
// Step 3.1.3. Return true.
return true;
}
// Step 3.2. Comment: We have a global allow list and it does not contain
// element.
// Step 3.3. Return modified.
return modified;
}
// Step 4. Otherwise:
// Step 4.1. If configuration["removeElements"] contains element:
if (mRemoveElements->Contains(aElement)) {
// Step 4.1.1. Comment: We have a global remove list and it already
// contains element.
// Step 4.1.2. Return modified.
return modified;
}
// Step 4.2. Comment: We have a global remove list and it does not contain
// element.
// Step 4.3. Add element to configuration["removeElements"].
mRemoveElements->Insert(std::move(aElement));
// Step 4.4. Return true.
return true;
}
// https://wicg.github.io/sanitizer-api/#sanitizer-replace-an-element-with-its-children
bool Sanitizer::ReplaceElementWithChildren(
const StringOrSanitizerElementNamespace& aElement) {
// Step 1. Let configuration be this’s configuration.
// Step 2. Assert: configuration is valid.
MaybeMaterializeDefaultConfig();
// Step 3. Set element to the result of canonicalize a sanitizer element with
// element.
CanonicalElement element = CanonicalizeElement(aElement);
// Step 4. If the built-in non-replaceable elements list contains element:
if (IsNonReplaceableElement(element)) {
// Step 4.1. Return false.
return false;
}
// Step 5. If configuration["replaceWithChildrenElements"] contains element:
if (mReplaceWithChildrenElements &&
mReplaceWithChildrenElements->Contains(element)) {
// Step 5.1. Return false.
return false;
}
// Step 6. Remove element from configuration["removeElements"].
if (mRemoveElements) {
mRemoveElements->Remove(element);
}
else {
// Step 7. Remove element from configuration["elements"] list.
mElements->Remove(element);
}
// Step 8. Add element to configuration["replaceWithChildrenElements"].
if (!mReplaceWithChildrenElements) {
mReplaceWithChildrenElements.emplace();
}
mReplaceWithChildrenElements->Insert(std::move(element));
// Step 9. Return true.
return true;
}
// https://html.spec.whatwg.org/#dom-sanitizer-allowprocessinginstruction
bool Sanitizer::AllowProcessingInstruction(
const StringOrSanitizerProcessingInstruction& aPI) {
// Step 1. Let configuration be this's configuration.
// Step 2. Assert: configuration is valid.
MaybeMaterializeDefaultConfig();
// Step 3. Set pi to the result of canonicalizing pi.
RefPtr<nsAtom> pi = CanonicalizeProcessingInstruction(aPI);
// Step 4. If configuration["processingInstructions"] exists:
if (mProcessingInstructions) {
// Step 4.1. If configuration["processingInstructions"] contains pi, then
// return false.
//
// Step 4.2. Append pi to configuration["processingInstructions"].
//
// Step 4.3. Return true.
return mProcessingInstructions->EnsureInserted(pi);
}
// Step 5. Otherwise:
// Step 5.1. If configuration["removeProcessingInstructions"] contains pi:
if (mRemoveProcessingInstructions->Contains(pi)) {
// Step 5.1.1. Remove pi from configuration["removeProcessingInstructions"].
mRemoveProcessingInstructions->Remove(pi);
// Step 5.1.2. Return true.
return true;
}
// Step 5.2. Return false.
return false;
}
// https://html.spec.whatwg.org/#dom-sanitizer-removeprocessinginstruction
bool Sanitizer::RemoveProcessingInstruction(
const StringOrSanitizerProcessingInstruction& aPI) {
// Step 1. Let configuration be this's configuration.
// Step 2. Assert: configuration is valid.
MaybeMaterializeDefaultConfig();
// Step 3. Set pi to the result of canonicalizing pi.
RefPtr<nsAtom> pi = CanonicalizeProcessingInstruction(aPI);
// Step 4. If configuration["processingInstructions"] exists:
if (mProcessingInstructions) {
// Step 4.1. If configuration["processingInstructions"] contains pi:
if (mProcessingInstructions->Contains(pi)) {
// Step 4.1.1. Remove pi from configuration["processingInstructions"].
mProcessingInstructions->Remove(pi);
// Step 4.1.2. Return true.
return true;
}
// Step 4.2. Return false.
return false;
}
// Step 5. Otherwise:
// Step 5.1. If configuration["removeProcessingInstructions"] contains pi,
// then return false. Step 5.2. Append pi to
// configuration["removeProcessingInstructions"]. Step 5.3. Return true.
return mRemoveProcessingInstructions->EnsureInserted(pi);
}
// https://wicg.github.io/sanitizer-api/#sanitizer-allow-an-attribute
bool Sanitizer::AllowAttribute(
const StringOrSanitizerAttributeNamespace& aAttribute) {
MaybeMaterializeDefaultConfig();
// Step 1. Set attribute to the result of canonicalize a sanitizer attribute
// with attribute.
CanonicalAttribute attribute = CanonicalizeAttribute(aAttribute);
// Step 2. If configuration["attributes"] exists:
if (mAttributes) {
// Step 2.1. Comment: If we have a global allow-list, we need to add
// attribute.
MOZ_ASSERT(mDataAttributes.isSome(),
"mDataAttributes exists iff mAttributes exists");
// Step 2.2. If configuration["dataAttributes"] is true and attribute is a
// custom data attribute, then return false.
if (*mDataAttributes && attribute.IsDataAttribute()) {
return false;
}
// Step 2.3. If configuration["attributes"] contains attribute
// return false.
if (mAttributes->Contains(attribute)) {
return false;
}
// Step 2.3. Comment: Fix-up per-element allow and remove lists.
// Step 2.5. If configuration["elements"] exists:
if (mElements) {
// Step 2.5.1. For each element in configuration["elements"]:
for (
auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
CanonicalElementAttributes& elemAttributes = iter.Data();
// Step 2.5.1.1. If element["attributes"] with default « [] » contains
// attribute:
if (elemAttributes.mAttributes &&
elemAttributes.mAttributes->Contains(attribute)) {
// Step 2.5.1.1.1. Remove attribute from element["attributes"].
elemAttributes.mAttributes->Remove(attribute);
}
// Step 2.5.1.2. Assert: element["removeAttributes"] with default « []
// » does not contain attribute.
MOZ_ASSERT_IF(elemAttributes.mRemoveAttributes,
!elemAttributes.mRemoveAttributes->Contains(attribute));
}
}
// Step 2.6. Append attribute to configuration["attributes"]
mAttributes->Insert(std::move(attribute));
// Step 2.7. Return true.
return true;
}
// Step 3. Otherwise:
// Step 3.1. Comment: If we have a global remove-list, we need to remove
// attribute.
// Step 3.2. If configuration["removeAttributes"] does not contain
// attribute:
if (!mRemoveAttributes->Contains(attribute)) {
// Step 3.2.1. Return false.
return false;
}
// Step 3.3. Remove attribute from configuration["removeAttributes"].
mRemoveAttributes->Remove(attribute);
// Step 3.4. Return true.
return true;
}
// https://wicg.github.io/sanitizer-api/#sanitizer-remove-an-attribute
bool Sanitizer::RemoveAttribute(
const StringOrSanitizerAttributeNamespace& aAttribute) {
// Step 1. Assert: configuration is valid.
MaybeMaterializeDefaultConfig();
// Step 2. Set attribute to the result of canonicalize a sanitizer attribute
// with attribute.
CanonicalAttribute attribute = CanonicalizeAttribute(aAttribute);
return RemoveAttributeCanonical(std::move(attribute));
}
bool Sanitizer::RemoveAttributeCanonical(CanonicalAttribute&& aAttribute) {
// Step 3. If configuration["attributes"] exists:
if (mAttributes) {
// Step 3.1. Comment: If we have a global allow-list, we need to remove
// attribute.
// Step 3.2. Set modified to the result of remove attribute from
// configuration["attributes"].
bool modified = mAttributes->EnsureRemoved(aAttribute);
// Step 3.3. Comment: Fix-up per-element allow and remove lists.
// Step 3.4. If configuration["elements"] exists:
if (mElements) {
// Step 3.4.1. For each element of configuration["elements"]:
for (
auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
CanonicalElementAttributes& elemAttributes = iter.Data();
// Step 3.4.1.1. If element["attributes"] with default « » contains
// attribute:
if (elemAttributes.mAttributes &&
elemAttributes.mAttributes->Contains(aAttribute)) {
// Step 3.4.1.1.1. Set modified to true.
modified = true;
// Step 3.4.1.1.2. Remove attribute from element["attributes"].
elemAttributes.mAttributes->Remove(aAttribute);
}
// Step 3.4.1.2. If element["removeAttributes"] with default « [] »
// contains attribute:
if (elemAttributes.mRemoveAttributes &&
elemAttributes.mRemoveAttributes->Contains(aAttribute)) {
// Step 3.4.1.2.1. Assert: modified is true.
MOZ_ASSERT(modified,
"Must have removed attribute from mAttributes already");
// Step 3.4.1.2.2. Remove attribute from
// element["removeAttributes"].
elemAttributes.mRemoveAttributes->Remove(aAttribute);
}
}
}
// Step 3.5. Return modified.
return modified;
}
// Step 4. Otherwise:
// Step 4.1. Comment: If we have a global remove-list, we need to add
// attribute.
// Step 4.2. If configuration["removeAttributes"] contains attribute return
// false.
if (mRemoveAttributes->Contains(aAttribute)) {
return false;
}
// Step 4.3. Comment: Fix-up per-element allow and remove lists.
// Step 4.4. If configuration["elements"] exists:
if (mElements) {
// Step 4.4.1. For each element in configuration["elements"]:
for (
auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
CanonicalElementAttributes& elemAttributes = iter.Data();
// Step 4.4.1.1. If element["attributes"] with default « [] » contains
// attribute:
if (elemAttributes.mAttributes &&
elemAttributes.mAttributes->Contains(aAttribute)) {
// Step 4.4.1.1.1. Remove attribute from element["attributes"].
elemAttributes.mAttributes->Remove(aAttribute);
}
// Step 4.4.1.2. If element["removeAttributes"] with default « [] »
// contains attribute:
if (elemAttributes.mRemoveAttributes &&
elemAttributes.mRemoveAttributes->Contains(aAttribute)) {
// Step 4.4.1.2.1. Remove attribute from element["removeAttributes"].
elemAttributes.mRemoveAttributes->Remove(aAttribute);
}
}
}
// Step 4.5. Append attribute to configuration["removeAttributes"].
mRemoveAttributes->Insert(std::move(aAttribute));
// Step 4.6. Return true.
return true;
}
// https://wicg.github.io/sanitizer-api/#sanitizer-set-comments
bool Sanitizer::SetComments(
bool aAllow) {
// The sanitize algorithm optimized for the default config supports
// comments both being allowed and disallowed.
// Step 1. If configuration["comments"] exists and configuration["comments"]
// equals allow, then return false;
if (mComments == aAllow) {
return false;
}
// Step 2. Set configuration["comments"] to allow.
mComments = aAllow;
// Step 3. Return true.
return true;
}
// https://wicg.github.io/sanitizer-api/#sanitizer-set-data-attributes
bool Sanitizer::SetDataAttributes(
bool aAllow) {
// Same as above for data-attributes.
// Step 1. If configuration["attributes"] does not exist, then return false.
// Note: The default config has "attributes".
if (!mIsDefaultConfig && !mAttributes) {
return false;
}
MOZ_ASSERT(mDataAttributes.isSome(),
"mDataAttributes exists iff mAttributes exists (or in the default "
"config)");
// Step 2. If configuration["dataAttributes"] equals allow, then return false.
if (*mDataAttributes == aAllow) {
return false;
}
// Step 3. If allow is true:
if (!mIsDefaultConfig && aAllow) {
// Note: The default config does not contain any data-attributes.
// Step 3.1. Remove any items attr from configuration["attributes"] where
// attr is a custom data attribute.
mAttributes->RemoveIf([](
const CanonicalAttribute& aAttribute) {
return aAttribute.IsDataAttribute();
});
// Step 3.2. If configuration["elements"] exists:
if (mElements) {
// Step 3.2.1. For each element in configuration["elements"]:
for (
auto iter = mElements->Iter(); !iter.Done(); iter.Next()) {
CanonicalElementAttributes& elemAttributes = iter.Data();
// Step 3.2.1.1. If element[attributes] exists:
if (elemAttributes.mAttributes) {
// Step 3.2.1.1.1. Remove any items attr from element[attributes]
// where attr is a custom data attribute.
elemAttributes.mAttributes->RemoveIf(
[](
const CanonicalAttribute& aAttribute) {
return aAttribute.IsDataAttribute();
});
}
}
}
}
// Step 4. Set configuration["dataAttributes"] to allow.
mDataAttributes = Some(aAllow);
// Step 5. Return true.
return true;
}
// https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration
// The built-in safe baseline configuration
#define FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT) \
ELEMENT(XHTML, xhtml, base) \
ELEMENT(XHTML, xhtml, embed) \
ELEMENT(XHTML, xhtml, frame) \
ELEMENT(XHTML, xhtml, iframe) \
ELEMENT(XHTML, xhtml, object) \
ELEMENT(XHTML, xhtml, script) \
ELEMENT(SVG, svg, script) \
ELEMENT(SVG, svg, use)
// https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe
bool Sanitizer::RemoveUnsafe() {
// XXX this should be a no-op for the default config now.
MaybeMaterializeDefaultConfig();
// Step 1. Assert: The key set of built-in safe baseline configuration equals
// «[ "removeElements", "removeAttributes" ] ».
// Step 2. Let result be false.
bool result =
false;
// Step 3. For each element in built-in safe baseline
// configuration[removeElements]:
#define ELEMENT(_, NSURI, LOCAL_NAME) \
/* Step 3.1. Call remove an element element from configuration. */ \
if (RemoveElementCanonical(CanonicalElement(nsGkAtoms::LOCAL_NAME, \
nsGkAtoms::nsuri_
##NSURI))) { \
/* Step 3.2. If the call returned true, set result to true. */ \
result = true; \
}
FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT)
#undef ELEMENT
// Step 4. For each attribute in built-in safe baseline
// configuration[removeAttributes]:
// (This is an empty list)
// Step 5. For each attribute listed in event handler content attributes:
// TODO: Consider sorting these.
nsContentUtils::ForEachEventAttributeName(
EventNameType_All & ~EventNameType_XUL,
[self = MOZ_KnownLive(this), &result](nsAtom* aName) {
// Step 5.1. Call remove an attribute attribute from configuration.
if (self->RemoveAttributeCanonical(
CanonicalAttribute(aName, nullptr))) {
// Step 5.2. If the call returned true, set result to true.
result = true;
}
});
// Step 6. Return result.
return result;
}
// https://wicg.github.io/sanitizer-api/#sanitize
void Sanitizer::Sanitize(nsINode* aNode,
bool aSafe, ErrorResult& aRv) {
MOZ_ASSERT(aNode->OwnerDoc()->IsLoadedAsData(),
"SanitizeChildren relies on the document being inert to be safe");
// Step 1. Let configuration be the value of sanitizer’s configuration.
// Step 2. If safe is true, then set configuration to the result of calling
// remove unsafe on configuration.
//
// Optimization: We really don't want to make a copy of the configuration
// here, so we instead explictly remove the handful elements and
// attributes that are part of "remove unsafe" in the
// SanitizeChildren() and SanitizeAttributes() methods.
// Step 3. Call sanitize core on node, configuration, and with
// handleJavascriptNavigationUrls set to safe.
if (mIsDefaultConfig) {
AssertNoLists();
SanitizeChildren<true>(aNode, aSafe);
}
else {
AssertIsValid();
SanitizeChildren<
false>(aNode, aSafe);
}
}
static RefPtr<nsAtom> ToNamespace(int32_t aNamespaceID) {
if (aNamespaceID == kNameSpaceID_None) {
return nullptr;
}
RefPtr<nsAtom> atom =
nsNameSpaceManager::GetInstance()->NameSpaceURIAtom(aNamespaceID);
return atom;
}
static bool IsUnsafeElement(nsAtom* aLocalName, int32_t aNamespaceID) {
#define ELEMENT(NSID, _, LOCAL_NAME) \
if (aNamespaceID == kNameSpaceID_
##NSID) { \
if (aLocalName == nsGkAtoms::LOCAL_NAME) { \
return true; \
} \
}
FOR_EACH_BASELINE_REMOVE_ELEMENT(ELEMENT)
#undef ELEMENT
return false;
}
// https://wicg.github.io/sanitizer-api/#sanitize-core
// Step 2.4.9.5. If handleJavascriptNavigationUrls:
static bool RemoveJavascriptNavigationURLAttribute(Element* aElement,
nsAtom* aLocalName,
int32_t aNamespaceID) {
// https://wicg.github.io/sanitizer-api/#contains-a-javascript-url
auto containsJavascriptURL = [&]() {
nsAutoString value;
if (!aElement->GetAttr(aNamespaceID, aLocalName, value)) {
return false;
}
// Step 1. Let url be the result of running the basic URL parser on
// attribute’s value.
nsCOMPtr<nsIURI> uri;
if (NS_FAILED(NS_NewURI(getter_AddRefs(uri), value))) {
// Step 2. If url is failure, then return false.
return false;
}
// Step 3. Return whether url’s scheme is "javascript".
return uri->SchemeIs(
"javascript");
};
// Step 1. If «[elementName, attrName]» matches an entry in the built-in
// navigating URL attributes list, and if attribute contains a javascript:
// URL, then remove attribute from child.
if ((aElement->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area) &&
aLocalName == nsGkAtoms::href && aNamespaceID == kNameSpaceID_None) ||
(aElement->IsAnyOfHTMLElements(nsGkAtoms::button, nsGkAtoms::input) &&
aLocalName == nsGkAtoms::formaction &&
aNamespaceID == kNameSpaceID_None) ||
(aElement->IsHTMLElement(nsGkAtoms::form) &&
aLocalName == nsGkAtoms::action && aNamespaceID == kNameSpaceID_None) ||
(aElement->IsHTMLElement(nsGkAtoms::iframe) &&
aLocalName == nsGkAtoms::src && aNamespaceID == kNameSpaceID_None) ||
(aElement->IsSVGElement(nsGkAtoms::a) && aLocalName == nsGkAtoms::href &&
(aNamespaceID == kNameSpaceID_None ||
aNamespaceID == kNameSpaceID_XLink))) {
if (containsJavascriptURL()) {
return true;
}
};
// Step 2. If child’s namespace is the MathML Namespace and attr’s local
// name is "href" and attr’s namespace is null or the XLink namespace and
// attr contains a javascript: URL, then remove attr.
if (aElement->IsMathMLElement() && aLocalName == nsGkAtoms::href &&
(aNamespaceID == kNameSpaceID_None ||
aNamespaceID == kNameSpaceID_XLink)) {
if (containsJavascriptURL()) {
return true;
}
}
// Step 3. If the built-in animating URL attributes list contains
// «[elementName, attrName]» and attr’s value is "href" or "xlink:href",
// then remove attr.
if (aLocalName == nsGkAtoms::attributeName &&
aNamespaceID == kNameSpaceID_None &&
aElement->IsAnyOfSVGElements(
nsGkAtoms::animate, nsGkAtoms::animateTransform, nsGkAtoms::set)) {
nsAutoString value;
if (!aElement->GetAttr(aNamespaceID, aLocalName, value)) {
return false;
}
return value.EqualsLiteral(
"href") || StringEndsWith(value, u
":href"_ns);
}
return false;
}
// https://wicg.github.io/sanitizer-api/#sanitize-core
template <
bool IsDefaultConfig>
void Sanitizer::SanitizeChildren(nsINode* aNode,
bool aSafe)
const {
// Step 1. For each child in current’s children:
nsCOMPtr<nsIContent> next = nullptr;
for (nsCOMPtr<nsIContent> child = aNode->GetFirstChild(); child;
child = next) {
next = child->GetNextSibling();
// Step 1.1. Assert: child implements Text, Comment, Element, or
// DocumentType.
// TODO(bug 2044714): PI
MOZ_ASSERT(child->IsText() || child->IsComment() || child->IsElement() ||
child->NodeType() == nsINode::DOCUMENT_TYPE_NODE);
// Step 1.2. If child is a DocumentType or Text node, then continue.
if (child->NodeType() == nsINode::DOCUMENT_TYPE_NODE || child->IsText()) {
continue;
}
// Step 1.3. If child is a Comment node:
if (child->IsComment()) {
// Step 1.3.1. If configuration["comments"] is not true, then remove
// child.
if (!mComments) {
child->Remove();
}
// Step 1.3.2. Continue.
continue;
}
// Step 1.4. If child is a ProcessingInstruction node:
// TODO(bug 2044714): Implement this when the HTML parser can produce PI
// nodes.
// Step 1.5. Otherwise:
MOZ_ASSERT(child->IsElement());
// Step 1.5.1. Let elementName be a SanitizerElementNamespace with child’s
// local name and namespace.
nsAtom* nameAtom = child->NodeInfo()->NameAtom();
int32_t namespaceID = child->NodeInfo()->NamespaceID();
// Make sure this is optimized away when using the default config.
Maybe<CanonicalElement> elementName;
std::conditional_t<IsDefaultConfig, StaticAtomSet*,
CanonicalElementAttributes*>
elementAttributes = nullptr;
if constexpr (!IsDefaultConfig) {
elementName.emplace(nameAtom, ToNamespace(namespaceID));
// Optimization: Remove unsafe elements before doing anything else.
// https://wicg.github.io/sanitizer-api/#built-in-safe-baseline-configuration
//
// We have to do this _before_ handling the
// "replaceWithChildrenElements" list, because the "remove an element"
// call in removeUnsafe() would implicitly remove it from the list.
//
// The default config's "elements" allow list does not contain any
// unsafe elements so we can skip this.
if (aSafe && IsUnsafeElement(nameAtom, namespaceID)) {
child->Remove();
continue;
}
// Step 1.5.2. If configuration["replaceWithChildrenElements"] exists
// and if configuration["replaceWithChildrenElements"] contains
// elementName:
if (mReplaceWithChildrenElements &&
mReplaceWithChildrenElements->Contains(*elementName)) {
// Note: This follows nsTreeSanitizer by first inserting the
// child's children in place of the current child and then
// continueing the sanitization from the first inserted grandchild.
nsCOMPtr<nsIContent> parent = child->GetParent();
MOZ_DIAGNOSTIC_ASSERT(parent);
nsCOMPtr<nsIContent> firstChild = child->GetFirstChild();
nsCOMPtr<nsIContent> newChild = firstChild;
for (; newChild; newChild = child->GetFirstChild()) {
ErrorResult rv;
parent->InsertBefore(*newChild, child, rv);
if (rv.Failed()) {
// TODO: Abort?
break;
}
}
child->Remove();
if (firstChild) {
next = firstChild;
}
continue;
}
// Step 1.5.3. If configuration["removeElements"] exists and
// configuration["removeElements"] contains elementName:
if (mRemoveElements) {
if (mRemoveElements->Contains(*elementName)) {
// Step 1.5.3.1. Remove child.
child->Remove();
// Step 1.5.3.2.Continue.
continue;
}
}
// Step 1.5.4. If configuration["elements"] exists and
// configuration["elements"] does not contain elementName:
if (mElements) {
elementAttributes = mElements->Lookup(*elementName).DataPtrOrNull();
if (!elementAttributes) {
// Step 1.5.4.1. Remove child.
child->Remove();
// Step 1.5.4.2. Continue.
continue;
}
}
}
else {
// (The default config has no replaceWithChildrenElements or
// removeElements)
// Step 1.5.4. If configuration["elements"] exists and
// configuration["elements"] does not contain elementName:
bool found =
false;
if (nameAtom->IsStatic()) {
ElementsWithAttributes* elements = nullptr;
if (namespaceID == kNameSpaceID_XHTML) {
elements = sDefaultHTMLElements;
}
else if (namespaceID == kNameSpaceID_MathML) {
elements = sDefaultMathMLElements;
}
else if (namespaceID == kNameSpaceID_SVG) {
elements = sDefaultSVGElements;
}
if (elements) {
if (
auto lookup = elements->Lookup(nameAtom->AsStatic())) {
found = true;
// This is the nullptr for elements without specific allowed
// attributes.
elementAttributes = lookup->get();
}
}
}
if (!found) {
// Step 1.5.4.1. Remove child.
child->Remove();
// Step 1.5.4.2. Continue.
continue;
}
MOZ_ASSERT(!IsUnsafeElement(nameAtom, namespaceID),
"The default config has no unsafe elements");
}
// Step 1.5.5. If elementName equals «[ "name" → "template", "namespace" →
// HTML namespace ]», then call sanitize core on child’s template contents
// with configuration and handleJavascriptNavigationUrls.
if (
auto* templateEl = HTMLTemplateElement::FromNode(child)) {
RefPtr<DocumentFragment> frag = templateEl->Content();
SanitizeChildren<IsDefaultConfig>(frag, aSafe);
}
// Step 1.5.6. If child is a shadow host, then call sanitize core on child’s
// shadow root with configuration and handleJavascriptNavigationUrls.
if (RefPtr<ShadowRoot> shadow = child->GetShadowRoot();
shadow && !shadow->IsUAWidget()) {
SanitizeChildren<IsDefaultConfig>(shadow, aSafe);
}
// Step 1.6.7. If child’s is value is not null:
if (CustomElementData* data = child->AsElement()->GetCustomElementData();
data && data->GetIs(child->AsElement())) [[unlikely]] {
// Step 1.6.7.1. Let isAttrName be «[ "name" → "is", "namespace" → null
// ]».
// Step 1.6.7.2. If is attribute allowed for isAttrName given
// configuration, and elementName is blocked:
if (IsDefaultConfig ||
!IsAttributeAllowed(elementAttributes, nsGkAtoms::is,
kNameSpaceID_None, aSafe)) {
// Step 1.6.7.2.1. Step Set child’s custom element state to "undefined".
// Step 1.6.7.2.2. Set child’s custom element definition to null.
// Step 1.6.7.2.3. Set child’s is value to null.
child->AsElement()->ClearCustomElementData();
}
}
// Step 1.6.7. For each attribute in child’s attribute list:
int32_t attrCount = int32_t(child->AsElement()->GetAttrCount());
for (int32_t i = attrCount -
1; i >=
0; --i) {
// Step 1.6.7.1. Let attrName be a SanitizerAttributeNamespace with
// attribute’s local name and namespace.
const nsAttrName* attr = child->AsElement()->GetAttrNameAt(i);
RefPtr<nsAtom> attrLocalName = attr->LocalName();
int32_t attrNs = attr->NamespaceID();
// Step 1.6.7.2. If is attribute allowed for attrName given configuration,
// and elementName is blocked, then remove attribute.
bool remove =
!IsAttributeAllowed(elementAttributes, attrLocalName, attrNs, aSafe);
// Step 1.6.7.3. If handleJavascriptNavigationUrls:
if (aSafe && !remove) {
remove = RemoveJavascriptNavigationURLAttribute(child->AsElement(),
attrLocalName, attrNs);
}
if (remove) {
child->AsElement()->UnsetAttr(attrNs, attrLocalName,
/* aNotify */ false);
// XXX Copied from nsTreeSanitizer.
// In case the attribute removal shuffled the attribute order, start the
// loop again.
--attrCount;
i = attrCount;
// i will be decremented immediately thanks to the for
// loop
}
}
// Step 1.5.10. Call sanitize core on child with configuration and
// handleJavascriptNavigationUrls.
// TODO: Optimization: Remove recusion similar to nsTreeSanitizer
SanitizeChildren<IsDefaultConfig>(child, aSafe);
}
}
static inline bool IsDataAttribute(nsAtom* aName, int32_t aNamespaceID) {
return StringBeginsWith(nsDependentAtomString(aName), u
"data-"_ns) &&
aNamespaceID == kNameSpaceID_None;
}
// IsAttributeAllowed specialized for the default config.
bool Sanitizer::IsAttributeAllowed(StaticAtomSet* aElementAttributes,
nsAtom* aAttrLocalName, int32_t aAttrNs,
bool)
const {
MOZ_ASSERT(mIsDefaultConfig);
// Step 1. Let elementWithLocalAttributes be an empty ordered map.
// Step 2. If configuration["elements"] exists and configuration["elements"]
// contains elementName:
// ...
// NOTE: This was lookup was already done outside of this function.
// Step 3. If elementWithLocalAttributes["removeAttributes"] with default « »
// contains attrName:
// NOTE: No local removeAttributes in the default config.
// Step 4. If configuration["attributes"] exists:
// NOTE: Always true in the default config.
// Step 4.1. Let the boolean globallyAllowed be whether
// configuration["attributes"] contains attrName.
// NOTE: All attributes allowed by the default config are in the "null"
// namespace.
bool globallyAllowed = aAttrNs == kNameSpaceID_None &&
sDefaultAttributes->Contains(aAttrLocalName);
// Step 4.2. Let the boolean locallyAllowed be whether
// elementWithLocalAttributes["attributes"] with default « » contains
// attrName.
bool locallyAllowed = aAttrNs == kNameSpaceID_None && aElementAttributes &&
aElementAttributes->Contains(aAttrLocalName);
// Step 4.3. Let the boolean isDataAttributeAllowed be whether both, "data-"
// is a code unit prefix of attrName[name] and attrName[namespace] is null,
// and configuration["dataAttributes"] is true.
bool isDataAttributeAllowed =
*mDataAttributes && IsDataAttribute(aAttrLocalName, aAttrNs);
// Step 4.4. If neither globallyAllowed nor locallyAllowed nor
// isDataAttributeAllowed, return blocked.
if (!globallyAllowed && !locallyAllowed && !isDataAttributeAllowed) {
return false;
}
// 5. Otherwise:
// ...
// The default config attribute allow lists don't contain event
// handler attributes.
MOZ_ASSERT(!nsContentUtils::IsEventAttributeName(
aAttrLocalName, EventNameType_All & ~EventNameType_XUL));
// Step 6. Return allowed.
return true;
}
bool Sanitizer::IsAttributeAllowed(
CanonicalElementAttributes* aElementAttributes, nsAtom* aAttrLocalName,
int32_t aAttrNs,
bool aSafe)
const {
MOZ_ASSERT(!mIsDefaultConfig);
// Step 1. Let elementWithLocalAttributes be an empty ordered map.
// Step 2. If configuration["elements"] exists and configuration["elements"]
// contains elementName:
// ...
// NOTE: This was lookup was already done outside of this function.
// Optimization: Remove unsafe event handler content attributes here, because
// we didn't clone the config and called removeUnsafe on it before.
// https://wicg.github.io/sanitizer-api/#sanitizerconfig-remove-unsafe
if (aSafe && aAttrNs == kNameSpaceID_None &&
nsContentUtils::IsEventAttributeName(
aAttrLocalName, EventNameType_All & ~EventNameType_XUL)) {
return false;
}
CanonicalAttribute attrName(aAttrLocalName, ToNamespace(aAttrNs));
// Step 3. If elementWithLocalAttributes["removeAttributes"] with default « »
// contains attrName:
if (aElementAttributes && aElementAttributes->mRemoveAttributes &&
aElementAttributes->mRemoveAttributes->Contains(attrName)) {
// Step 3.1. Return blocked.
return false;
}
// Step 4. If configuration["attributes"] exists:
if (mAttributes) {
// Step 4.1. Let the boolean globallyAllowed be whether
// configuration["attributes"] contains attrName.
bool globallyAllowed = mAttributes->Contains(attrName);
// Step 4.2. Let the boolean locallyAllowed be whether
// elementWithLocalAttributes["attributes"] with default « » contains
// attrName.
bool locallyAllowed = aElementAttributes &&
aElementAttributes->mAttributes &&
aElementAttributes->mAttributes->Contains(attrName);
// Step 4.3. Let the boolean isDataAttributeAllowed be whether both, "data-"
// is a code unit prefix of attrName[name] and attrName[namespace] is null,
// and configuration["dataAttributes"] is true.
bool isDataAttributeAllowed =
*mDataAttributes && IsDataAttribute(aAttrLocalName, aAttrNs);
// Step 4.4. If neither globallyAllowed nor locallyAllowed nor
// isDataAttributeAllowed, return blocked.
if (!globallyAllowed && !locallyAllowed && !isDataAttributeAllowed) {
return false;
}
}
// 5. Otherwise:
else {
// Step 5.1. If elementWithLocalAttributes["attributes"] exists and
// elementWithLocalAttributes["attributes"] does not contain attrName:
if (aElementAttributes && aElementAttributes->mAttributes &&
!aElementAttributes->mAttributes->Contains(attrName)) {
// Step 5.1.1. Return blocked.
return false;
}
// Step 5.2. If configuration["removeAttributes"] contains attrName:
if (mRemoveAttributes->Contains(attrName)) {
// Step 5.2.1. Return blocked.
return false;
}
}
// Step 6. Return allowed.
return true;
}
}
// namespace mozilla::dom