summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/sanitizer-api
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/sanitizer-api')
-rw-r--r--testing/web-platform/tests/sanitizer-api/META.yml5
-rw-r--r--testing/web-platform/tests/sanitizer-api/element-set-sanitized-html.https.html111
-rw-r--r--testing/web-platform/tests/sanitizer-api/idlharness.https.window.js12
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-config.https.html90
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-insecure-context.html17
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-names.https.html147
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-query-config.https.html79
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-sanitize.https.tentative.html76
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-sanitizeFor.https.tentative.html101
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-secure-context.https.html17
-rw-r--r--testing/web-platform/tests/sanitizer-api/sanitizer-unknown.https.html45
-rw-r--r--testing/web-platform/tests/sanitizer-api/support/testcases.sub.js88
12 files changed, 788 insertions, 0 deletions
diff --git a/testing/web-platform/tests/sanitizer-api/META.yml b/testing/web-platform/tests/sanitizer-api/META.yml
new file mode 100644
index 0000000000..7ac32665e1
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/META.yml
@@ -0,0 +1,5 @@
+spec: https://wicg.github.io/sanitizer-api/
+suggested_reviewers:
+ - ivanlish
+ - mozfreddyb
+ - otherdaniel
diff --git a/testing/web-platform/tests/sanitizer-api/element-set-sanitized-html.https.html b/testing/web-platform/tests/sanitizer-api/element-set-sanitized-html.https.html
new file mode 100644
index 0000000000..560e9cd635
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/element-set-sanitized-html.https.html
@@ -0,0 +1,111 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="support/testcases.sub.js"></script>
+</head>
+<body>
+<script>
+ function buildNode(element_name, markup) {
+ const e = document.createElement(element_name);
+ e.innerHTML = markup;
+ return e;
+ }
+
+ function assert_node_equals(node1, node2) {
+ assert_true(node1 instanceof Node && node1.isEqualNode(node2),
+ `Node[${node1.innerHTML}] == Node[${node2.innerHTML}]`);
+ }
+
+ for (const context of ["script", "iframe", "object", "div"]) {
+ const should_fail = context != "div";
+ test(t => {
+ let elem = document.createElement(context);
+ let probe_fn = _ => elem.setHTML("<div>Hello!</div>", new Sanitizer());
+ if (should_fail) {
+ assert_throws_js(TypeError, probe_fn);
+ } else {
+ probe_fn();
+ }
+ assert_equals(should_fail, !elem.firstChild);
+ }, `${context}.setHTML should ${should_fail ? "fail" : "pass"}.`);
+ }
+
+ for(const context of ["div", "template", "table"]) {
+ const elem1 = document.createElement(context);
+ const elem2 = document.createElement(context);
+ for (const probe of ["<em>Hello</em>", "<td>data</td>"]) {
+ elem1.setHTML(probe, new Sanitizer());
+ elem2.innerHTML = probe;
+ test(t => {
+ assert_node_equals(elem2, elem1);
+ }, `Sanitizer: <${context}>.setHTML("${probe}", ...) obeys parse context.`);
+ }
+ }
+
+ for (const testcase of testcases) {
+ const element = document.createElement("template");
+ test(t => {
+ let s = new Sanitizer(testcase.config_input);
+ element.setHTML(testcase.value, {sanitizer: s });
+ assert_node_equals(buildNode(element.localName, testcase.result), element);
+ }, "Sanitizer: Element.setHTML with config: " + testcase.message);
+ }
+
+ [
+ undefined,
+ {},
+ { sanitizer: new Sanitizer() },
+ { sanitizer: undefined },
+ { avocado: new Sanitizer() },
+ ].forEach((options, index) => {
+ test(t => {
+ const e = document.createElement("div");
+ e.setHTML("<em>bla</em><script>bla<" + "/script>", options);
+ assert_equals(e.innerHTML, "<em>bla</em>");
+ }, `Sanitizer: Element.setHTML options dictionary #${index} uses default.`);
+ });
+
+ [
+ "tomato",
+ { sanitizer: null },
+ { sanitizer: "avocado" },
+ { sanitizer: { allowElements: [ "a", "b", "c" ] } },
+ ].forEach((options, index) => {
+ test(t => {
+ assert_throws_js(TypeError, _ => {
+ document.createElement("div").setHTML("bla", options);
+ });
+ }, `Sanitizer: Element.setHTML invalid options dictionary #${index}`);
+ });
+
+ test(t => {
+ const sanitizer = new Sanitizer({allowElements: ["b"]});
+ const element = document.createElement("div");
+
+ // WebIDL magic: An IDL dictionary is mapped to a JS object. Thus, a plain
+ // Sanitizer instance will be accepted as an options dictionary. However,
+ // it will then try to read the .sanitizer property of the Sanitizer, and
+ // since that doesn't usually exist will treat it as an empty dictionary.
+ //
+ // Ref: https://webidl.spec.whatwg.org/#es-dictionary
+
+ // Sanitizer instance in the dictionary: Config is applied.
+ element.setHTML("<em>celery</em>", {sanitizer: sanitizer});
+ assert_equals(element.innerHTML, "celery");
+
+ // Same Sanitizer instance, passed directly: Is like an empty dictionary
+ // and config is not applied.
+ element.setHTML("<em>celery</em>", sanitizer);
+ assert_equals(element.innerHTML, "<em>celery</em>");
+
+ // Sanitizer-ception: Set the Sanitizer as the .sanitizer property on itself.
+ // Now the config is applied. It's magic. Just not the good kind of magic.
+ sanitizer.sanitizer = sanitizer;
+ element.setHTML("<em>celery</em>", sanitizer);
+ assert_equals(element.innerHTML, "celery");
+ }, "Sanitizer: Element.setHTML with sanitizer instance.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/idlharness.https.window.js b/testing/web-platform/tests/sanitizer-api/idlharness.https.window.js
new file mode 100644
index 0000000000..384317b8e5
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/idlharness.https.window.js
@@ -0,0 +1,12 @@
+// META: script=/resources/WebIDLParser.js
+// META: script=/resources/idlharness.js
+
+idl_test(
+ ['sanitizer-api.tentative'],
+ ['html'],
+ idl_array => {
+ idl_array.add_objects({
+ Sanitizer: ['new Sanitizer({})']
+ });
+ }
+);
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-config.https.html b/testing/web-platform/tests/sanitizer-api/sanitizer-config.https.html
new file mode 100644
index 0000000000..f60e6c9c93
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-config.https.html
@@ -0,0 +1,90 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+
+<body>
+<script>
+ test(t => {
+ let s = new Sanitizer();
+ assert_true(s instanceof Sanitizer);
+ }, "SanitizerAPI creator without config.");
+
+ test(t => {
+ let s = new Sanitizer({});
+ assert_true(s instanceof Sanitizer);
+ }, "SanitizerAPI creator with empty config.");
+
+ test(t => {
+ let s = new Sanitizer(null);
+ assert_true(s instanceof Sanitizer);
+ }, "SanitizerAPI creator with null as config.");
+
+ test(t => {
+ let s = new Sanitizer(undefined);
+ assert_true(s instanceof Sanitizer);
+ }, "SanitizerAPI creator with undefined as config.");
+
+ test(t => {
+ let s = new Sanitizer({testConfig: [1,2,3], attr: ["test", "i", "am"]});
+ assert_true(s instanceof Sanitizer);
+ }, "SanitizerAPI creator with config ignore unknown values.");
+
+ // In-depth testing of sanitization is handled in other tests. Here we
+ // do presence testing for each of the config options and test 3 things:
+ // - One case where our test string is modified,
+ // - one where it's unaffected,
+ // - that a config can't be changed afterwards.
+ // (I.e., that the Sanitizer won't hold on to a reference of the options.)
+
+ // The probe determines whether the Sanitizer modifies the probe string.
+ const probe_string = "<div id=\"i\">balabala</div><p>test</p>";
+ const probe = sanitizer => {
+ const div = document.createElement("div");
+ div.setHTML(probe_string, {sanitizer: sanitizer});
+ return probe_string == div.innerHTML;
+ };
+
+ const should_stay_the_same = {
+ allowElements: [ "div", "p" ],
+ blockElements: [ "test" ],
+ dropElements: [ "test" ],
+ allowAttributes: [{ name: "id", elements: "*"}],
+ dropAttributes: [{ name: "bla", elements: ["blubb"]}],
+ };
+ const should_modify = {
+ allowElements: [ "div", "span" ],
+ blockElements: [ "div" ],
+ dropElements: [ "p" ],
+ allowAttributes: [{ name: "id", elements: ["p"] }],
+ dropAttributes: [{ name: "id", elements: ["div"] }],
+ };
+
+ assert_array_equals(Object.keys(should_stay_the_same), Object.keys(should_modify));
+ Object.keys(should_stay_the_same).forEach(option_key => {
+ test(t => {
+ const options = {};
+ options[option_key] = should_stay_the_same[option_key];
+ const s = new Sanitizer(options);
+ assert_true(s instanceof Sanitizer);
+ assert_true(probe(s));
+ }, `SanitizerAPI: ${option_key} stays is okay.`);
+
+ const options = {};
+ options[option_key] = should_modify[option_key];
+ const s = new Sanitizer(options);
+ test(t => {
+ assert_true(s instanceof Sanitizer);
+ assert_false(probe(s));
+ }, `SanitizerAPI: ${option_key} modify is okay.`);
+
+ options[option_key] = should_stay_the_same[option_key];
+ test(t => {
+ assert_false(probe(s));
+ }, `SanitizerAPI: ${option_key} config is not kept as reference.`);
+ });
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-insecure-context.html b/testing/web-platform/tests/sanitizer-api/sanitizer-insecure-context.html
new file mode 100644
index 0000000000..4b185fd3a7
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-insecure-context.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ // Currently, the Sanitizer requires a secure context.
+ test(t => {
+ assert_false(globalThis.isSecureContext);
+ assert_equals("Sanitizer" in globalThis, globalThis.isSecureContext);
+ assert_equals("setHTML" in document.body, globalThis.isSecureContext);
+ }, "Sanitizer API in an insecure context.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-names.https.html b/testing/web-platform/tests/sanitizer-api/sanitizer-names.https.html
new file mode 100644
index 0000000000..df5dd8549d
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-names.https.html
@@ -0,0 +1,147 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ // Like assert_array_equals, but disregard element order.
+ function assert_array_same(actual, expected) {
+ assert_array_equals(actual.sort(), expected.sort());
+ }
+
+ // Element names:
+ const elems_valid = [
+ "p", "template", "span", "custom-elements", "potato",
+
+ // Arguments will be stringified, so anything that stringifies to a valid
+ // name is also valid. (E.g. null => "null")
+ null, undefined, 123
+ ];
+ const elems_invalid = [
+ "", [], ["*"], ["p"]
+ ];
+
+ // Attribute names:
+ const attrs_valid = [
+ "href", "span",
+ ];
+ const attrs_invalid = [
+ ];
+
+ const all_elems = elems_valid.concat(elems_invalid);
+ const all_attrs = attrs_valid.concat(attrs_invalid);
+ for (const item of ["allowElements", "dropElements", "blockElements"]) {
+ test(t => {
+ const sanitizer = new Sanitizer({[item]: all_elems});
+ assert_array_same(sanitizer.getConfiguration()[item],
+ elems_valid.map(x => "" + x));
+ }, `Element names in config item: ${item}`);
+ }
+ for (const item of ["allowAttributes", "dropAttributes"]) {
+ test(t => {
+ const sanitizer = new Sanitizer(
+ {[item]: Object.fromEntries(all_attrs.map(x => [x, ["*"]]))});
+ assert_array_same(Object.keys(sanitizer.getConfiguration()[item]),
+ attrs_valid.map(x => "" + x));
+ }, `Attribute names in config item: ${item}`);
+ }
+
+ // Quick sanity tests for namespaced elements.
+ // Each test case is a duo or triplet:
+ // - a Sanitizer config string for an element.
+ // - an HTML probe string.
+ // - the expected result. (If different from the probe.)
+ const SVG_NS = "http://www.w3.org/2000/svg";
+ const MATHML_NS = "http://www.w3.org/1998/Math/MathML";
+ [
+ [ "p", "<p>Hello</p>" ],
+ [ "svg", "<svg>Hello</svg>", "Hello" ],
+ [ { name: "svg", namespace: SVG_NS }, "<svg>Hello</svg>" ],
+ [ "math", "<math>Hello</math>", "Hello" ],
+ [ { name: "math", namespace: SVG_NS }, "<math>Hello</math>", "Hello" ],
+ [ { name: "math", namespace: MATHML_NS }, "<math>Hello</math>" ],
+ ].forEach(([elem, probe, expected], index) => {
+ test(t => {
+ const sanitizer = new Sanitizer({allowElements: [elem],
+ // TODO(https://github.com/WICG/sanitizer-api/issues/167)
+ allowUnknownMarkup: true});
+ const template = document.createElement("template");
+ template.setHTML(probe, {sanitizer});
+ assert_equals(template.innerHTML, expected ?? probe);
+ }, `Namespaced elements #${index}: allowElements: [${JSON.stringify(elem)}]`);
+ });
+
+ // Same for attributes:
+ const XLINK_NS = "http://www.w3.org/1999/xlink";
+ [
+ [ { name: "style", elements: "*" }, "<p style=\"bla\"></p>" ],
+ [ { name: "href", elements: "*" }, "<p href=\"bla\"></p>" ],
+ [ { name: "xlink:href", elements: "*" }, "<p xlink:href=\"bla\"></p>" ],
+ [ { name: "href", namespace: XLINK_NS, elements: "*" }, "<p xlink:href=\"bla\"></p>", "<p></p>" ],
+ [ { name: "href", namespace: XLINK_NS, elements: "*" }, "<p href='bla'></p>", "<p></p>" ],
+ [ { name: "href", elements: "*" }, "<p xlink:href='bla'></p>", "<p></p>" ],
+ ].forEach(([attr, probe, expected], index) => {
+ test(t => {
+ const sanitizer = new Sanitizer({allowAttributes: [attr],
+ // TODO(https://github.com/WICG/sanitizer-api/issues/167)
+ allowUnknownMarkup: true});
+ const template = document.createElement("template");
+ template.setHTML(probe, {sanitizer});
+ assert_equals(template.innerHTML, expected ?? probe);
+ }, `Namespaced attributes #${index}: allowAttributes: [${JSON.stringify(attr)}]`);
+ });
+
+ // Test for namespaced attribute inside namespace element
+ test(t => {
+ const probe = `<svg><a xlink:href="bla"></a></svg>`;
+
+ const sanitizer = new Sanitizer({
+ allowAttributes: [{ name: "href", namespace: XLINK_NS, elements: "*" }],
+ allowElements: [{ name: "svg", namespace: SVG_NS }, { name: "a", namespace: SVG_NS }],
+ // TODO(https://github.com/WICG/sanitizer-api/issues/167)
+ allowUnknownMarkup: true});
+ const template = document.createElement("template");
+ template.setHTML(probe, {sanitizer});
+ assert_equals(template.innerHTML, probe);
+ }, "Namespaced attribute xlink:href inside SVG tree");
+
+ // Most element and attribute names are lower-cased, but "foreign content"
+ // like SVG and MathML have some mixed-cased names.
+ [
+ [ "feBlend", "<feBlend></feBlend>" ],
+ [ "feColorMatrix", "<feColorMatrix></feColorMatrix>" ],
+ [ "textPath", "<textPath></textPath>" ],
+ ].forEach(([elem, probe], index) => {
+ const sanitize = (elem, probe) => {
+ const sanitizer = new Sanitizer({allowElements: [
+ { name: "svg", namespace: SVG_NS },
+ { name: elem, namespace: SVG_NS }
+ ],
+ // TODO(https://github.com/WICG/sanitizer-api/issues/167)
+ allowUnknownMarkup: true});
+ const template = document.createElement("template");
+ template.setHTML(`<svg>${probe}</svg>`, {sanitizer});
+ return template.content.firstElementChild.innerHTML;
+ };
+ test(t => {
+ assert_equals(sanitize(elem, probe), probe);
+ }, `Mixed-case element names #${index}: "svg:${elem}"`);
+ test(t => {
+ // Lowercase element names should be normalized to mixed-case.
+ assert_equals(sanitize(elem.toLowerCase(), probe), probe);
+ }, `Lower-case element names #${index}: "svg:${elem.toLowerCase()}"`);
+ test(t => {
+ assert_not_equals(sanitize(elem.toUpperCase(), probe), probe);
+ }, `Upper-case element names #${index}: "svg:${elem.toUpperCase()}"`);
+ test(t => {
+ const elems = ["svg:" + elem];
+ assert_array_equals(
+ new Sanitizer({allowElements: elems}).getConfiguration().allowElements,
+ elems);
+ }, `Mixed case element names #${index}: "${elem}" is preserved in config.`);
+ });
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-query-config.https.html b/testing/web-platform/tests/sanitizer-api/sanitizer-query-config.https.html
new file mode 100644
index 0000000000..60cba2d618
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-query-config.https.html
@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ function assert_deep_equals(obj1, obj2) {
+ assert_equals(typeof obj1, typeof obj2);
+ if (typeof obj1 == "string") {
+ assert_equals(obj1, obj2);
+ } else if (typeof obj1 == "boolean") {
+ assert_true(obj1 == obj2);
+ } else if (Array.isArray(obj1)) {
+ assert_equals(obj1.length, obj2.length);
+ assert_array_equals(obj1.sort(), obj2.sort());
+ } else if (typeof obj1 == "object") {
+ assert_array_equals(Object.keys(obj1).sort(), Object.keys(obj2).sort());
+ for (const k of Object.keys(obj1))
+ assert_deep_equals(obj1[k], obj2[k]);
+ }
+ }
+
+ test(t => {
+ // Quick sanity test: Test a few default values.
+ assert_in_array("div", Sanitizer.getDefaultConfiguration().allowElements);
+ assert_false(Sanitizer.getDefaultConfiguration().allowElements.includes("script"));
+ assert_false(Sanitizer.getDefaultConfiguration().allowElements.includes("noscript"));
+
+ assert_true("span" in Sanitizer.getDefaultConfiguration().allowAttributes);
+ assert_false("onclick" in Sanitizer.getDefaultConfiguration().allowAttributes);
+
+ assert_false("dropElements" in Sanitizer.getDefaultConfiguration());
+ assert_false("blockElements" in Sanitizer.getDefaultConfiguration());
+ assert_false("dropAttributes" in Sanitizer.getDefaultConfiguration());
+ assert_false(Sanitizer.getDefaultConfiguration().allowCustomElements);
+ assert_false(Sanitizer.getDefaultConfiguration().allowUnknownMarkup);
+ }, "SanitizerAPI getDefaultConfiguration()");
+
+ test(t => {
+ assert_deep_equals(Sanitizer.getDefaultConfiguration(),
+ new Sanitizer().getConfiguration());
+ }, "SanitizerAPI getConfiguration() on default created Sanitizer");
+
+ test(t => {
+ const configs = [{
+ allowElements: ["div", "span", "helloworld"],
+ dropElements: ["xxx"],
+ allowAttributes: { "class": ["*"], "color": ["span", "div"],
+ "onclick": ["*"] },
+ allowCustomElements: true,
+ allowUnknownMarkup: true,
+ },{
+ blockElements: ["table", "tbody", "th", "td"],
+ }, {
+ allowCustomElements: false,
+ }, {
+ allowUnknownMarkup: false,
+ }];
+ for (const config of configs)
+ assert_deep_equals(config, new Sanitizer(config).getConfiguration());
+
+ // Also test a mixed case variant:
+ const config_0_mixed = {
+ allowElements: ["div", "sPAn", "HelloWorld"],
+ dropElements: ["XXX"],
+ allowAttributes: { "class": ["*"], "color": ["sPAn", "div"],
+ "onclick": ["*"] },
+ allowCustomElements: true,
+ allowUnknownMarkup: true,
+ };
+ assert_deep_equals(config_0_mixed,
+ new Sanitizer(config_0_mixed).getConfiguration());
+ }, "SanitizerAPI getConfiguration() reflects creation config.");
+
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-sanitize.https.tentative.html b/testing/web-platform/tests/sanitizer-api/sanitizer-sanitize.https.tentative.html
new file mode 100644
index 0000000000..82eaeb4832
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-sanitize.https.tentative.html
@@ -0,0 +1,76 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="support/testcases.sub.js"></script>
+</head>
+
+<body>
+<script>
+ function getString(fragment) {
+ d = document.createElement("div");
+ d.replaceChildren(...fragment.cloneNode(true).childNodes);
+ return d.innerHTML;
+ }
+
+ function getFragment(markup) {
+ const d = document.createElement("div");
+ d.innerHTML = markup;
+ let f = document.createDocumentFragment();
+ f.replaceChildren(...d.childNodes);
+ return f;
+ }
+ function getDoc(markup) {
+ return new DOMParser().parseFromString(markup, "text/html");
+ }
+ function assert_node_equals(node1, node2) {
+ assert_true(node1 instanceof Node && node1.isEqualNode(node2),
+ `Node[${getString(node1)}] == Node[${getString(node2)}]`);
+ }
+
+ test(t => {
+ let s = new Sanitizer();
+ assert_throws_js(TypeError, _ => s.sanitize());
+ }, "Sanitizer.sanitize() should throw an error.");
+
+ test(t => {
+ let s = new Sanitizer();
+ assert_throws_js(TypeError, _ => s.sanitize(null));
+ }, "Sanitizer.sanitize(null).");
+
+ const probe_string = `<a href="about:blank">hello</a><script>cons` +
+ `ole.log("world!");<` + `/script>`;
+ test(t => {
+ const sanitized = new Sanitizer().sanitize(getFragment(probe_string));
+ const expected = getFragment(probe_string.substr(0, 31));
+ assert_node_equals(expected, sanitized);
+ }, "Sanitizer.sanitze(DocumentFragment)");
+
+ test(t => {
+ const sanitized = new Sanitizer().sanitize(getDoc(probe_string));
+ const expected = getFragment(probe_string.substr(0, 31));
+ assert_node_equals(expected, sanitized);
+ }, "Sanitizer.sanitze(Document)");
+
+ testcases.forEach(c => test(t => {
+ let s = new Sanitizer(c.config_input);
+ var dom = new DOMParser().parseFromString("<!DOCTYPE html><body>" + c.value, "text/html");
+ fragment = s.sanitize(dom);
+ assert_true(fragment instanceof DocumentFragment);
+
+ let result = getString(fragment);
+ assert_equals(result, c.result);
+ }, "SanitizerAPI with config: " + c.message + ", sanitize from document function for <body>"));
+
+ testcases.forEach(c => test(t => {
+ let s = new Sanitizer(c.config_input);
+ let tpl = document.createElement("template");
+ tpl.innerHTML = c.value;
+ fragment = s.sanitize(tpl.content);
+ assert_true(fragment instanceof DocumentFragment);
+ assert_equals(getString(fragment), c.result);
+ }, "SanitizerAPI with config: " + c.message + ", sanitize from document fragment function for <template>"));
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-sanitizeFor.https.tentative.html b/testing/web-platform/tests/sanitizer-api/sanitizer-sanitizeFor.https.tentative.html
new file mode 100644
index 0000000000..77ae0abb6b
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-sanitizeFor.https.tentative.html
@@ -0,0 +1,101 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+ <script src="support/testcases.sub.js"></script>
+</head>
+
+<body>
+<script>
+ function buildNode(element_name, markup) {
+ const e = document.createElement(element_name);
+ e.innerHTML = markup;
+ return e;
+ }
+
+ function toString(node) {
+ const e = document.createElement("div");
+ e.append(node.cloneNode(true));
+ return e.innerHTML;
+ }
+
+ function assert_node_equals(node1, node2) {
+ assert_equals(node1 instanceof Node, node2 instanceof Node);
+ if (!(node1 instanceof Node)) return;
+
+ node1.normalize();
+ node2.normalize();
+ assert_true(node1.isEqualNode(node2),
+ `Node[${toString(node1)}] == Node[${toString(node2)}]`);
+ if (node1 instanceof HTMLTemplateElement) {
+ assert_node_equals(node1.content, node2.content);
+ }
+ }
+
+ test(t => {
+ let s = new Sanitizer();
+ assert_throws_js(TypeError, _ => s.sanitizeFor());
+ assert_throws_js(TypeError, _ => s.sanitizeFor(null));
+ }, "Sanitizer.sanitizeFor() should throw.");
+
+ test(t => {
+ let s = new Sanitizer();
+ assert_throws_js(TypeError, _ => s.sanitizeFor("xxx"));
+ }, "Sanitizer.sanitizeFor() with one argument should throw.");
+
+ for (const context of ["script", "iframe", "object", "div"]) {
+ const should_fail = context != "div";
+ test(t => {
+ let result = new Sanitizer().sanitizeFor(context, "<div>Hello!</div>");
+ if (should_fail) {
+ assert_equals(null, result);
+ } else {
+ assert_true(result instanceof HTMLElement);
+ }
+ }, `Sanitizer.sanitizeFor("${context}", ...) should ${should_fail ? "fail" : "pass"}.`);
+ }
+
+ async_test(t => {
+ let s = new Sanitizer();
+ s.sanitizeFor("div", "<img src='https://bla/'>");
+ t.step_timeout(_ => {
+ assert_equals(performance.getEntriesByName("https://bla/").length, 0);
+ t.done();
+ }, 1000);
+ }, "Sanitizer.sanitizeFor function shouldn't load the image.");
+
+ test(t => {
+ const probe = `<a href="about:blank">hello</a><script>con` +
+ `sole.log("world!");<` + `/script>`;
+ const expected = `<a href="about:blank">hello</a>`;
+ for (const element of ["div", "template", "span", "table", "td",
+ "pumuckl", "custom-element", "linearGradient",
+ "svg", "svg:img", "svg:linearGradient"]) {
+ assert_node_equals(
+ buildNode(element, expected),
+ new Sanitizer().sanitizeFor(element, probe));
+ }
+ }, `Sanitizer.sanitizeFor(element, ..)`);
+
+ for (const context of ["div", "template", "table"]) {
+ for (const probe of ["<em>Hello</em>", "<td>data</td>"]) {
+ test(t => {
+ assert_node_equals(
+ buildNode(context, probe),
+ new Sanitizer().sanitizeFor(context, probe));
+ }, `Sanitizer.sanitizeFor("${context}", "${probe}") obeys parse context.`);
+ }
+ }
+
+ for (const testcase of testcases) {
+ test(t => {
+ let s = new Sanitizer(testcase.config_input);
+ assert_node_equals(
+ buildNode("template", testcase.result),
+ s.sanitizeFor("template", testcase.value));
+ }, "Sanitizer.sanitizeFor with config: " + testcase.message);
+ }
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-secure-context.https.html b/testing/web-platform/tests/sanitizer-api/sanitizer-secure-context.https.html
new file mode 100644
index 0000000000..0e04e04d16
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-secure-context.https.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+ // Currently, the Sanitizer requires a secure context.
+ test(t => {
+ assert_true(globalThis.isSecureContext);
+ assert_equals("Sanitizer" in globalThis, globalThis.isSecureContext);
+ assert_equals("setHTML" in document.body, globalThis.isSecureContext);
+ }, "SanitizerAPI in a secure context.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/sanitizer-unknown.https.html b/testing/web-platform/tests/sanitizer-api/sanitizer-unknown.https.html
new file mode 100644
index 0000000000..03d7c6966d
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/sanitizer-unknown.https.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<head>
+ <script src="/resources/testharness.js"></script>
+ <script src="/resources/testharnessreport.js"></script>
+</head>
+<body>
+<script>
+test(t => {
+ d = document.createElement("div")
+ d.setHTML("<hello><world>",
+ { sanitizer: new Sanitizer({allowElements: ["hello", "world"]}) });
+ assert_equals(d.innerHTML, "");
+}, "Unknown element names get blocked without allowUnknownMarkup.");
+
+test(t => {
+ d = document.createElement("div")
+ d.setHTML("<hello><world>",
+ { sanitizer: new Sanitizer({allowUnknownMarkup: true,
+ allowElements: ["hello", "world"]}) });
+ assert_equals(d.innerHTML, "<hello><world></world></hello>");
+}, "Unknown element names pass with allowUnknownMarkup.");
+
+test(t => {
+ d = document.createElement("div")
+ d.setHTML("<b hello='1' world>", { sanitizer:
+ new Sanitizer({allowAttributes: [{name: "hello", elements: "*"},
+ {name: "world", elements: "*"}]}) });
+ assert_equals(d.innerHTML, "<b></b>");
+}, "Unknown attributes names get blocked without allowUnknownMarkup.");
+
+test(t => {
+ d = document.createElement("div")
+ d.setHTML("<b hello='1' world>", { sanitizer:
+ new Sanitizer({allowUnknownMarkup: true,
+ allowAttributes: [
+ {name: "hello", elements: "*"},
+ {name: "world", elements: "*"}
+ ]})
+ });
+ assert_equals(d.innerHTML, `<b hello="1" world=""></b>`);
+}, "Unknown attribute names pass with allowUnknownMarkup.");
+</script>
+</body>
+</html>
diff --git a/testing/web-platform/tests/sanitizer-api/support/testcases.sub.js b/testing/web-platform/tests/sanitizer-api/support/testcases.sub.js
new file mode 100644
index 0000000000..9081ad2aa2
--- /dev/null
+++ b/testing/web-platform/tests/sanitizer-api/support/testcases.sub.js
@@ -0,0 +1,88 @@
+const testcases = [
+ {config_input: {}, value: "test", result: "test", message: "string"},
+ {config_input: {}, value: "<b>bla</b>", result: "<b>bla</b>", message: "html fragment"},
+ {config_input: {}, value: "<a<embla", result: "", message: "broken html"},
+ {config_input: {}, value: {}, result: "[object Object]", message: "empty object"},
+ {config_input: {}, value: 1, result: "1", message: "number"},
+ {config_input: {}, value: 000, result: "0", message: "zeros"},
+ {config_input: {}, value: 1+2, result: "3", message: "arithmetic"},
+ {config_input: {}, value: "", result: "", message: "empty string"},
+ {config_input: {}, value: undefined, result: "undefined", message: "undefined"},
+ {config_input: {}, value: "<html><head></head><body>test</body></html>", result: "test", message: "document"},
+ {config_input: {}, value: "<div>test", result: "<div>test</div>", message: "html without close tag"},
+ {config_input: {}, value: "<script>alert('i am a test')<\/script>", result: "", message: "scripts for default configs"},
+ {config_input: {}, value: "hello<script>alert('i am a test')<\/script>", result: "hello", message: "script not as root"},
+ {config_input: {}, value: "<div><b>hello<script>alert('i am a test')<\/script>", result: "<div><b>hello</b></div>", message: "script deeper in the tree"},
+ {config_input: {}, value: "<p onclick='a= 123'>Click.</p>", result: "<p>Click.</p>", message: "onclick scripts"},
+ {config_input: {}, value: "<plaintext><p>text</p>", result: "&lt;p&gt;text&lt;/p&gt;", message: "plaintext"},
+ {config_input: {}, value: "<xmp>TEXT</xmp>", result: "TEXT", message: "xmp"},
+ {config_input: {test: 123}, value: "test", result: "test", message: "invalid config_input"},
+ {config_input: {dropElements: []}, value: "test", result: "test", message: "empty dropElements list"},
+ {config_input: {dropElements: ["div"]}, value: "<div>test</div><p>bla", result: "<p>bla</p>", message: "test html without close tag with dropElements list ['div']"},
+ {config_input: {}, value: "<custom-element>test</custom-element>bla", result: "bla", message: "default behavior for custom elements"},
+ {config_input: {allowCustomElements: true}, value: "<custom-element>test</custom-element>bla", result: "testbla", message: "allow custom elements"},
+ {config_input: {allowCustomElements: true, allowElements: ["custom-element"]}, value: "<custom-element>test</custom-element>bla", result: "<custom-element>test</custom-element>bla", message: "allow custom elements with allow elements"},
+ {config_input: {allowCustomElements: false}, value: "<custom-element>test</custom-element>bla", result: "bla", message: "disallow custom elements"},
+ {config_input: {dropElements: ["custom-element"], allowCustomElements: true}, value: "<custom-element>test</custom-element>bla", result: "bla", message: "allow custom elements with drop list contains [\"custom-element\"]"},
+ {config_input: {dropElements: ["script"]}, value: "<script>alert('i am a test')<\/script>", result: "", message: "test script with [\"script\"] as dropElements list"},
+ {config_input: {dropElements: ["test-element", "i"]}, value: "<div>balabala<i>test</i></div><test-element>t</test-element>", result: "<div>balabala</div>", message: "dropElements list [\"test-element\", \"i\"]}"},
+ {config_input: {dropElements: ["dl", "p"]}, value: "<div>balabala<i>i</i><p>t</p></div>", result: "<div>balabala<i>i</i></div>", message: "dropElements list [\"dl\", \"p\"]}"},
+ {config_input: {allowElements: ["p"]}, value: "<div>test<div>p</div>tt<p>div</p></div>", result: "testptt<p>div</p>", message: "allowElements list [\"p\"]"},
+ {config_input: {dropElements: ["div"], allowElements: ["div"]}, value: "<div>test</div><p>bla", result: "bla", message: "allowElements list has no influence to dropElements"},
+ {config_input: {dropAttributes: [{name: "style", elements: ["p"]}]}, value: "<p style='color: black'>Click.</p><div style='color: white'>div</div>", result: "<p>Click.</p><div style=\"color: white\">div</div>", message: "dropAttributes list {\"style\": [\"p\"]} with style attribute"},
+ // {config_input: {dropAttributes: [{name: "*": ["a"]}}, value: "<a id='a' style='color: black'>Click.</a><div style='color: white'>div</div>", result: "<a>Click.</a><div style=\"color: white\">div</div>", message: "dropAttributes list {\"*\": [\"a\"]} with style attribute"},
+ {config_input: {dropAttributes: []}, value: "<p id='test'>Click.</p>", result: "<p id=\"test\">Click.</p>", message: "empty dropAttributes list with id attribute"},
+ {config_input: {dropAttributes: [{name: "id", elements: "*"}]}, value: "<p id='test'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list {\"id\": [\"*\"]} with id attribute"},
+ {config_input: {dropAttributes: [{name: "data-attribute-with-dashes", elements: "*"}]}, value: "<p id='p' data-attribute-with-dashes='123'>Click.</p><script>document.getElementById('p').dataset.attributeWithDashes=123;</script>", result: "<p id=\"p\">Click.</p>", message: "dropAttributes list {\"data-attribute-with-dashes\": [\"*\"]} with dom dataset js access"},
+ {config_input: {allowAttributes: [{name: "id", elements: ["div"]}]}, value: "<p id='p'>P</p><div id='div'>DIV</div>", result: "<p>P</p><div id=\"div\">DIV</div>", message: "allowAttributes list {\"id\": [\"div\"]} with id attribute"},
+ {config_input: {allowAttributes: [{name: "id", elements: "*"}]}, value: "<p id='test' onclick='a= 123'>Click.</p>", result: "<p id=\"test\">Click.</p>", message: "allowAttributes list {\"id\": [\"*\"]} with id attribute and onclick scripts"},
+ // {config_input: {allowAttributes: {"*": ["a"]}}, value: "<a id='a' style='color: black'>Click.</a><div style='color: white'>div</div>", result: "<a id=\"a\" style=\"color: black\">Click.</a><div>div</div>", message: "allowAttributes list {\"*\": [\"a\"]} with style attribute"},
+ {config_input: {dropAttributes: [{name: "style", elements: "*"}], allowAttributes: [{name: "style", elements: "*"}]}, value: "<p style='color: black'>Click.</p>", result: "<p>Click.</p>", message: "allowAttributes list has no influence to dropAttributes"},
+ {config_input: {allowElements: ["template", "div"]}, value: "<template><script>test</script><div>hello</div></template>", result: "<template><div>hello</div></template>", message: "Template element"},
+ {config_input: {}, value: "<a href='javascript:evil.com'>Click.</a>", result: "<a>Click.</a>", message: "HTMLAnchorElement with javascript protocal"},
+ {config_input: {}, value: "<a href=' javascript:evil.com'>Click.</a>", result: "<a>Click.</a>", message: "HTMLAnchorElement with javascript protocal start with space"},
+ {config_input: {}, value: "<a href='http:evil.com'>Click.</a>", result: "<a href=\"http:evil.com\">Click.</a>", message: "HTMLAnchorElement"},
+ {config_input: {}, value: "<area href='javascript:evil.com'>Click.</area>", result: "<area>Click.", message: "HTMLAreaElement with javascript protocal"},
+ {config_input: {}, value: "<area href=' javascript:evil.com'>Click.</area>", result: "<area>Click.", message: "HTMLAreaElement with javascript protocal start with space"},
+ {config_input: {}, value: "<area href='http:evil.com'>Click.</area>", result: "<area href=\"http:evil.com\">Click.", message: "HTMLAreaElement"},
+ {config_input: {}, value: "<form action='javascript:evil.com'>Click.</form>", result: "<form>Click.</form>", message: "HTMLFormElement with javascript action"},
+ {config_input: {}, value: "<form action=' javascript:evil.com'>Click.</form>", result: "<form>Click.</form>", message: "HTMLFormElement with javascript action start with space"},
+ {config_input: {}, value: "<form action='http:evil.com'>Click.</form>", result: "<form action=\"http:evil.com\">Click.</form>", message: "HTMLFormElement"},
+ {config_input: {}, value: "<input formaction='javascript:evil.com'>Click.</input>", result: "<input>Click.", message: "HTMLInputElement with javascript formaction"},
+ {config_input: {}, value: "<input formaction=' javascript:evil.com'>Click.</input>", result: "<input>Click.", message: "HTMLInputElement with javascript formaction start with space"},
+ {config_input: {}, value: "<input formaction='http:evil.com'>Click.</input>", result: "<input formaction=\"http:evil.com\">Click.", message: "HTMLInputElement"},
+ {config_input: {}, value: "<button formaction='javascript:evil.com'>Click.</button>", result: "<button>Click.</button>", message: "HTMLButtonElement with javascript formaction"},
+ {config_input: {}, value: "<button formaction=' javascript:evil.com'>Click.</button>", result: "<button>Click.</button>", message: "HTMLButtonElement with javascript formaction start with space"},
+ {config_input: {}, value: "<button formaction='http:evil.com'>Click.</button>", result: "<button formaction=\"http:evil.com\">Click.</button>", message: "HTMLButtonElement"},
+ {config_input: {}, value: "<p>Some text</p></body><!-- 1 --></html><!-- 2 --><p>Some more text</p>", result: "<p>Some text</p><p>Some more text</p>", message: "malformed HTML"},
+ {config_input: {}, value: "<p>Some text</p><!-- 1 --><!-- 2 --><p>Some more text</p>", result: "<p>Some text</p><p>Some more text</p>", message: "HTML with comments; comments not allowed"},
+ {config_input: {allowComments: true}, value: "<p>Some text</p><!-- 1 --><!-- 2 --><p>Some more text</p>", result: "<p>Some text</p><!-- 1 --><!-- 2 --><p>Some more text</p>", message: "HTML with comments; allowComments"},
+ {config_input: {allowComments: false}, value: "<p>Some text</p><!-- 1 --><!-- 2 --><p>Some more text</p>", result: "<p>Some text</p><p>Some more text</p>", message: "HTML with comments; !allowComments"},
+ {config_input: {}, value: "<p>comment<!-- hello -->in<!-- </p> -->text</p>", result: "<p>commentintext</p>", message: "HTML with comments deeper in the tree"},
+ {config_input: {allowComments: true}, value: "<p>comment<!-- hello -->in<!-- </p> -->text</p>", result: "<p>comment<!-- hello -->in<!-- </p> -->text</p>", message: "HTML with comments deeper in the tree, allowComments"},
+ {config_input: {allowComments: false}, value: "<p>comment<!-- hello -->in<!-- </p> -->text</p>", result: "<p>commentintext</p>", message: "HTML with comments deeper in the tree, !allowComments"},
+ {config_input: {allowElements: ["svg"]}, value: "<svg></svg>", result: "", message: "Unknown HTML names (HTMLUnknownElement instances) should not match elements parsed as non-HTML namespaces."},
+ {config_input: {allowElements: ["div", "svg"]}, value: "<div><svg></svg></div>", result: "<div></div>", message: "Unknown HTML names (HTMLUnknownElement instances) should not match elements parsed as non-HTML namespaces when nested."},
+
+ // Case normalization (actually: lack of)
+ {config_input: {dropElements: ["I", "DL"]}, value: "<div>balabala<dl>test</dl></div>", result: "<div>balabala<dl>test</dl></div>", message: "dropElements list [\"I\", \"DL\"]}"},
+ {config_input: {dropElements: ["i", "dl"]}, value: "<div>balabala<dl>test</dl></div>", result: "<div>balabala</div>", message: "dropElements list [\"i\", \"dl\"]}"},
+ {config_input: {dropElements: ["i", "dl"]}, value: "<DIV>balabala<DL>test</DL></DIV>", result: "<div>balabala</div>", message: "dropElements list [\"i\", \"dl\"]} with uppercase HTML"},
+ {config_input: {dropAttributes: [{name: "ID", elements: "*"}]}, value: "<p id=\"test\">Click.</p>", result: "<p id=\"test\">Click.</p>", message: "dropAttributes list {\"ID\": [\"*\"]} with id attribute"},
+ {config_input: {dropAttributes: [{name: "ID", elements: "*"}]}, value: "<p ID=\"test\">Click.</p>", result: "<p id=\"test\">Click.</p>", message: "dropAttributes list {\"ID\": [\"*\"]} with ID attribute"},
+ {config_input: {dropAttributes: [{name: "id", elements: "*"}]}, value: "<p ID=\"test\">Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list {\"id\": [\"*\"]} with ID attribute"},
+
+ // allowUnknownMarkup for elements (with and without)
+ {config_input: {dropElements: [123, "test", "i", "custom-element"]}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabala</div>", message: "dropElements with unknown elements and without allowUnknownMarkup"},
+ {config_input: {blockElements: [123, "test", "i", "custom-element"]}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabalatest</div>", message: "blockElements with unknown elements and without allowUnknownMarkup"},
+ {config_input: {allowElements: ["p", "test"]}, value: "<div>test<div>p</div>tt<p>div</p></div><test>test</test>", result: "testptt<p>div</p>", message: "allowElements with unknown elements and without allowUnknownMarkup"},
+ {config_input: {dropElements: [123, "test", "i", "custom-element"], allowUnknownMarkup: true}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabala</div>", message: "dropElements with unknown elements and with allowUnknownMarkup"},
+ {config_input: {blockElements: [123, "test", "i", "custom-element"], allowUnknownMarkup: true}, value: "<div>balabala<i>test</i></div><test>t</test><custom-element>custom-element</custom-element>", result: "<div>balabalatest</div>t", message: "blockElements with unknown elements and with allowUnknownMarkup"},
+ {config_input: {allowElements: ["p", "test"], allowUnknownMarkup: true}, value: "<div>test<div>p</div>tt<p>div</p><test>test</test></div>", result: "testptt<p>div</p><test>test</test>", message: "allowElements with unknown elements and with allowUnknownMarkup"},
+
+ // allowUnknownMarkup for attributes (with and without)
+ {config_input: {allowAttributes: [{name: "hello", elements: "*"}, {name: "world", elements: ["b"]}]}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div><b></b></div>", message: "allowAttributes unknown attributes and without allowUnknownMarkup"},
+ {config_input: {allowAttributes: [{name: "hello", elements: "*"}, {name: "world", elements: ["b"]}], allowUnknownMarkup: true}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div hello=\"1\"><b hello=\"3\" world=\"4\"></b></div>", message: "allowAttributes unknown attributes and with allowUnknownMarkup"},
+ {config_input: {dropAttributes: [{name: "hello", elements: "*"}, {name:"world", elements: ["b"]}]}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div><b></b></div>", message: "dropAttributes unknown attributes and without allowUnknownMarkup"},
+ {config_input: {dropAttributes: [{name: "hello", elements: "*"}, {name:"world", elements: ["b"]}], allowUnknownMarkup: true}, value: "<div hello='1' world='2'><b hello='3' world='4'>", result: "<div><b></b></div>", message: "dropAttributes unknown attributes and with allowUnknownMarkup"},
+];