1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148
|
<!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.
123
];
const elems_invalid = [
"", {name: ""},
];
// Attribute names:
const attrs_valid = [
"href", "span",
];
const attrs_invalid = [
"", {name: ""},
];
for (const item of ["elements", "removeElements", "replaceWithChildrenElements"]) {
test(t => {
const sanitizer = new Sanitizer({[item]: elems_valid});
assert_array_same(sanitizer.get()[item].map(x => x.name),
elems_valid.map(x => "" + x));
}, `Element names in config item: ${item}`);
test(t => {
assert_throws_js(TypeError, _ => {
new Sanitizer({[item]: elems_valid.concat(elems_invalid)});
});
}, `Invalid element names in config item: ${item}`);
}
for (const item of ["attributes", "removeAttributes"]) {
test(t => {
const sanitizer = new Sanitizer({[item]: attrs_valid});
assert_array_same(sanitizer.get()[item].map(x => x.name),
attrs_valid.map(x => "" + x));
}, `Attribute names in config item: ${item}`);
test(t => {
assert_throws_js(TypeError, _ => {
new Sanitizer({[item]: attrs_valid.concat(attrs_invalid)});
});
}, `Invalid 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>", "" ],
[ { name: "svg", namespace: SVG_NS }, "<svg>Hello</svg>" ],
[ "math", "<math>Hello</math>", "" ],
[ { name: "math", namespace: SVG_NS }, "<math>Hello</math>", "" ],
[ { name: "math", namespace: MATHML_NS }, "<math>Hello</math>" ],
].forEach(([elem, probe, expected], index) => {
test(t => {
const div = document.createElement("div");
div.setHTML(probe, {sanitizer: {elements: [elem]}});
assert_equals(div.innerHTML, expected ?? probe);
}, `Namespaced elements #${index}: elements: [${JSON.stringify(elem)}]`);
});
// Same for attributes:
const XLINK_NS = "http://www.w3.org/1999/xlink";
[
[ { name: "style"}, "<p style=\"bla\"></p>" ],
[ { name: "href"}, "<p href=\"bla\"></p>" ],
// In HTML content, the HTML parser parses "xlink:href" as an attribute
// named "xlink:href" in the null namespace.
[ { name: "xlink:href"}, "<p xlink:href=\"bla\"></p>" ],
[ { name: "href", namespace: XLINK_NS}, "<p xlink:href=\"bla\"></p>", "<p></p>" ],
[ { name: "href", namespace: XLINK_NS}, "<p href='bla'></p>", "<p></p>" ],
[ { name: "href"}, "<p xlink:href='bla'></p>", "<p></p>" ],
// For "foreign elements" like <svg>, the HTML parser parses "xlink:href" as
// an attribtue named "href" in the XLink namespace.
[ { name: "xlink:href"}, "<svg xlink:href=\"bla\"></svg>", "<svg></svg>" ],
[ { name: "href", namespace: XLINK_NS}, "<svg xlink:href=\"bla\"></svg>" ],
[ { name: "href", namespace: XLINK_NS}, "<svg href='bla'></svg>", "<svg></svg>" ],
[ { name: "href"}, "<svg xlink:href='bla'></svg>", "<svg></svg>" ],
].forEach(([attr, probe, expected], index) => {
test(t => {
const options = {attributes: [attr],
elements: ["p", {name: "svg", namespace: SVG_NS}]};
const template = document.createElement("template");
template.setHTML(probe, {sanitizer: options});
assert_equals(template.content.firstElementChild.outerHTML, expected ?? probe);
}, `Namespaced attributes #${index}: attributes: [${JSON.stringify(attr)}]`);
});
// Test for namespaced attribute inside namespace element
test(t => {
const probe = `<svg><a xlink:href="bla"></a></svg>`;
const options = {
elements: [
{name: "svg", namespace: SVG_NS},
{name: "a", namespace: SVG_NS, attributes: [
{ name: "href", namespace: XLINK_NS }
]}
]};
const template = document.createElement("template");
template.setHTML(probe, {sanitizer: options});
assert_equals(template.innerHTML, probe);
}, "Namespaced attribute xlink:href inside SVG tree");
// Names are case-senstive. Most element and attribute names are
// lower-cased, but "foreign content" like SVG and MathML have some
// mixed-cased names. Check this is supported.
[
[ "feBlend", "<feBlend></feBlend>" ],
[ "feColorMatrix", "<feColorMatrix></feColorMatrix>" ],
[ "textPath", "<textPath></textPath>" ],
].forEach(([elem, probe], index) => {
const sanitize = (elem, probe) => {
const options = {elements: [
{ name: "svg", namespace: SVG_NS },
{ name: elem, namespace: SVG_NS }
]};
const template = document.createElement("template");
template.setHTML(`<svg>${probe}</svg>`, {sanitizer: options});
return template.content.firstElementChild.innerHTML;
};
test(t => {
assert_equals(sanitize(elem, probe), probe);
}, `Mixed-case element names #${index}: "svg:${elem}"`);
});
</script>
</body>
</html>
|