Bug 1959727 - Add the sanitizer option to setHTMLUnsafe. r=emilio

Differential Revision: https://phabricator.services.mozilla.com/D247109
This commit is contained in:
Tom Schuster
2025-05-08 03:18:12 +00:00
committed by tschuster@mozilla.com
parent 1201316c8c
commit ae7f13512d
12 changed files with 131 additions and 48 deletions

View File

@@ -5493,10 +5493,12 @@ EditorBase* Element::GetExtantEditor() const {
}
void Element::SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aError) {
nsContentUtils::SetHTMLUnsafe(this, this, aHTML, false /*aIsShadowRoot*/,
aSubjectPrincipal, aError);
nsContentUtils::SetHTMLUnsafe(this, this, aHTML, aOptions,
false /*aIsShadowRoot*/, aSubjectPrincipal,
aError);
}
// https://html.spec.whatwg.org/#event-beforematch

View File

@@ -119,6 +119,7 @@ struct URLValue;
namespace dom {
struct CheckVisibilityOptions;
struct CustomElementData;
struct SetHTMLUnsafeOptions;
struct SetHTMLOptions;
struct GetHTMLOptions;
struct GetAnimationsOptions;
@@ -2270,6 +2271,7 @@ class Element : public FragmentOrElement {
MOZ_CAN_RUN_SCRIPT
virtual void SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aError);

View File

@@ -885,11 +885,13 @@ nsresult ShadowRoot::Clone(dom::NodeInfo* aNodeInfo, nsINode** aResult) const {
}
void ShadowRoot::SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aError) {
RefPtr<Element> host = GetHost();
nsContentUtils::SetHTMLUnsafe(this, host, aHTML, true /*aIsShadowRoot*/,
aSubjectPrincipal, aError);
nsContentUtils::SetHTMLUnsafe(this, host, aHTML, aOptions,
true /*aIsShadowRoot*/, aSubjectPrincipal,
aError);
}
void ShadowRoot::GetInnerHTML(

View File

@@ -252,6 +252,7 @@ class ShadowRoot final : public DocumentFragment, public DocumentOrShadowRoot {
MOZ_CAN_RUN_SCRIPT
void SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal, ErrorResult& aError);
// @param aInnerHTML will always be of type `NullIsEmptyString`.

View File

@@ -197,6 +197,7 @@
#include "mozilla/dom/PContentChild.h"
#include "mozilla/dom/PrototypeList.h"
#include "mozilla/dom/ReferrerPolicyBinding.h"
#include "mozilla/dom/Sanitizer.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/Selection.h"
#include "mozilla/dom/ShadowRoot.h"
@@ -5862,13 +5863,107 @@ uint32_t computeSanitizationFlags(nsIPrincipal* aPrincipal, int32_t aFlags) {
return sanitizationFlags;
}
// https://wicg.github.io/sanitizer-api/#set-and-filter-html
static void SetAndFilterHTML(
FragmentOrElement* aTarget, Element* aContext, const nsAString& aHTML,
const OwningSanitizerOrSanitizerConfigOrSanitizerPresets& aSanitizerOptions,
const bool aSafe, ErrorResult& aError) {
RefPtr<Document> doc = aTarget->OwnerDoc();
// Step 1. If safe and contextElements local name is "script" and
// contextElements namespace is the HTML namespace or the SVG namespace, then
// return.
if (aSafe && (aContext->IsHTMLElement(nsGkAtoms::script) ||
aContext->IsSVGElement(nsGkAtoms::script))) {
nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, "DOM"_ns, doc,
nsContentUtils::eDOM_PROPERTIES,
"SetHTMLScript");
return;
}
// Step 2. Let sanitizer be the result of calling get a sanitizer instance
// from options with options and safe.
nsCOMPtr<nsIGlobalObject> global = aTarget->GetOwnerGlobal();
if (!global) {
aError.ThrowInvalidStateError("Missing owner global.");
return;
}
RefPtr<Sanitizer> sanitizer =
Sanitizer::GetInstance(global, aSanitizerOptions, aSafe, aError);
if (aError.Failed()) {
return;
}
// Batch possible DOMSubtreeModified events.
mozAutoSubtreeModified subtree(doc, nullptr);
aTarget->FireNodeRemovedForChildren();
// Needed when innerHTML is used in combination with contenteditable
mozAutoDocUpdate updateBatch(doc, true);
// Remove childnodes.
nsAutoMutationBatch mb(aTarget, true, false);
aTarget->RemoveAllChildren(true);
mb.RemovalDone();
nsAutoScriptLoaderDisabler sld(doc);
// Step 3. Let newChildren be the result of the HTML fragment parsing
// algorithm steps given contextElement, html, and true.
// Step 4. Let fragment be a new DocumentFragment whose node document is
// contextElements node document.
// Step 5. For each node in newChildren, append node to fragment.
// We MUST NOT cause any requests during parsing, so we'll
// create an inert Document and parse into a new DocumentFragment.
RefPtr<Document> inertDoc = nsContentUtils::CreateInertHTMLDocument(doc);
if (!inertDoc) {
aError = NS_ERROR_FAILURE;
return;
}
RefPtr<DocumentFragment> fragment = new (inertDoc->NodeInfoManager())
DocumentFragment(inertDoc->NodeInfoManager());
nsAtom* contextLocalName = aContext->NodeInfo()->NameAtom();
int32_t contextNameSpaceID = aContext->GetNameSpaceID();
aError = nsContentUtils::ParseFragmentHTML(aHTML, fragment, contextLocalName,
contextNameSpaceID, false, true);
if (aError.Failed()) {
return;
}
// Suppress assertion about node removal mutation events that can't have
// listeners anyway, because no one has had the chance to register
// mutation listeners on the fragment that comes from the parser.
nsAutoScriptBlockerSuppressNodeRemoved scriptBlocker;
int32_t oldChildCount = static_cast<int32_t>(aTarget->GetChildCount());
// Step 6. Run sanitize on fragment using sanitizer and safe.
sanitizer->Sanitize(fragment, aSafe, aError);
if (aError.Failed()) {
return;
}
// Step 7. Replace all with fragment within target.
aTarget->AppendChild(*fragment, aError);
if (aError.Failed()) {
return;
}
mb.NodesAdded();
nsContentUtils::FireMutationEventsForDirectParsing(doc, aTarget,
oldChildCount);
}
/* static */
void nsContentUtils::SetHTMLUnsafe(FragmentOrElement* aTarget,
Element* aContext,
const TrustedHTMLOrString& aSource,
bool aIsShadowRoot,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aError) {
void nsContentUtils::SetHTMLUnsafe(
FragmentOrElement* aTarget, Element* aContext,
const TrustedHTMLOrString& aSource, const SetHTMLUnsafeOptions& aOptions,
bool aIsShadowRoot, nsIPrincipal* aSubjectPrincipal, ErrorResult& aError) {
constexpr nsLiteralString elementSink = u"Element setHTMLUnsafe"_ns;
constexpr nsLiteralString shadowRootSink = u"ShadowRoot setHTMLUnsafe"_ns;
Maybe<nsAutoString> compliantStringHolder;
@@ -5881,6 +5976,13 @@ void nsContentUtils::SetHTMLUnsafe(FragmentOrElement* aTarget,
return;
}
// Fallback to the more optimized code below without a sanitizer.
if (aOptions.mSanitizer.WasPassed()) {
return SetAndFilterHTML(aTarget, aContext, *compliantString,
aOptions.mSanitizer.Value(), /* aSafe */ false,
aError);
}
RefPtr<DocumentFragment> fragment;
{
MOZ_ASSERT(!sFragmentParsingActive,

View File

@@ -192,6 +192,7 @@ class MessageBroadcaster;
class NodeInfo;
class OwningFileOrUSVStringOrFormData;
class Selection;
struct SetHTMLUnsafeOptions;
enum class ShadowRootMode : uint8_t;
class ShadowRoot;
struct StructuredSerializeOptions;
@@ -1894,6 +1895,7 @@ class nsContentUtils {
static void SetHTMLUnsafe(mozilla::dom::FragmentOrElement* aTarget,
Element* aContext,
const mozilla::dom::TrustedHTMLOrString& aSource,
const mozilla::dom::SetHTMLUnsafeOptions& aOptions,
bool aIsShadowRoot, nsIPrincipal* aSubjectPrincipal,
mozilla::ErrorResult& aError);
/**

View File

@@ -101,11 +101,13 @@ bool HTMLTemplateElement::ParseAttribute(int32_t aNamespaceID,
}
void HTMLTemplateElement::SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aError) {
RefPtr<DocumentFragment> content = mContent;
nsContentUtils::SetHTMLUnsafe(content, this, aHTML, false /*aIsShadowRoot*/,
aSubjectPrincipal, aError);
nsContentUtils::SetHTMLUnsafe(content, this, aHTML, aOptions,
false /*aIsShadowRoot*/, aSubjectPrincipal,
aError);
}
} // namespace mozilla::dom

View File

@@ -74,6 +74,7 @@ class HTMLTemplateElement final : public nsGenericHTMLElement {
MOZ_CAN_RUN_SCRIPT
void SetHTMLUnsafe(const TrustedHTMLOrString& aHTML,
const SetHTMLUnsafeOptions& aOptions,
nsIPrincipal* aSubjectPrincipal,
ErrorResult& aError) final;

View File

@@ -405,9 +405,8 @@ dictionary GetHTMLOptions {
partial interface Element {
// https://html.spec.whatwg.org/#dom-element-sethtmlunsafe
/* TODO: optional SetHTMLUnsafeOptions options = {} */
[NeedsSubjectPrincipal=NonSystem, Throws]
undefined setHTMLUnsafe((TrustedHTML or DOMString) html);
undefined setHTMLUnsafe((TrustedHTML or DOMString) html, optional SetHTMLUnsafeOptions options = {});
DOMString getHTML(optional GetHTMLOptions options = {});
};

View File

@@ -14,11 +14,11 @@ enum SanitizerPresets { "default" };
dictionary SetHTMLOptions {
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = "default";
};
/*
dictionary SetHTMLUnsafeOptions {
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer = {};
// TODO: = {}; (Using optional to easily detect a missing sanitizer)
[Pref="dom.security.sanitizer.enabled"]
(Sanitizer or SanitizerConfig or SanitizerPresets) sanitizer;
};
*/
dictionary SanitizerElementNamespace {
required DOMString name;

View File

@@ -60,7 +60,7 @@ interface ShadowRoot : DocumentFragment
partial interface ShadowRoot {
// https://html.spec.whatwg.org/#dom-shadowroot-sethtmlunsafe
[NeedsSubjectPrincipal=NonSystem, Throws]
undefined setHTMLUnsafe((TrustedHTML or DOMString) html);
undefined setHTMLUnsafe((TrustedHTML or DOMString) html, optional SetHTMLUnsafeOptions options = {});
DOMString getHTML(optional GetHTMLOptions options = {});
};

View File

@@ -1,43 +1,25 @@
[sanitizer-basic-filtering.tentative.html]
[setHTMLUnsafe testcase elements/1, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTML testcase elements/1, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/1, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTML testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/2, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/3, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[parseHTMLUnsafe testcase elements/4, "<div><p>Hello <b>World!</b>"]
expected: FAIL
[setHTMLUnsafe testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTMLUnsafe testcase attributes/1, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[setHTMLUnsafe testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
[parseHTMLUnsafe testcase attributes/2, "<p id="hello" style="font-weight: bold">x"]
expected: FAIL
@@ -50,18 +32,12 @@
[parseHTMLUnsafe testcase attributes-per-element/0, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[setHTMLUnsafe testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[parseHTML testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[parseHTMLUnsafe testcase attributes-per-element/1, "<div style="font-weight: bold" class="bourgeoisie">"]
expected: FAIL
[setHTMLUnsafe testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
[parseHTMLUnsafe testcase comments/1, "a <!-- comment --> b"]
expected: FAIL
@@ -89,9 +65,6 @@
[setHTML testcase namespaces/0, "<svg><rect></svg><math><mi>x"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/1, "<svg><rect>"]
expected: FAIL
[parseHTML testcase namespaces/1, "<svg><rect>"]
expected: FAIL
@@ -113,9 +86,6 @@
[parseHTMLUnsafe testcase namespaces/3, "<svg><rect>"]
expected: FAIL
[setHTMLUnsafe testcase namespaces/4, "<math><mi>x"]
expected: FAIL
[parseHTML testcase namespaces/4, "<math><mi>x"]
expected: FAIL