Commit 39b52ab5 authored by Yifan Luo's avatar Yifan Luo Committed by Commit Bot

[Sanitizer API] Add allowElements list to SanitizerConfig.

Bug: 1116418
Change-Id: I1ff1f3747cc456954dba0075e6e282face479fd5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2450322
Commit-Queue: Yifan Luo <lyf@chromium.org>
Reviewed-by: default avatarDaniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#815618}
parent 5a8e03d4
...@@ -28,12 +28,24 @@ Sanitizer* Sanitizer::Create(const SanitizerConfig* config, ...@@ -28,12 +28,24 @@ Sanitizer* Sanitizer::Create(const SanitizerConfig* config,
Sanitizer::Sanitizer(const SanitizerConfig* config) Sanitizer::Sanitizer(const SanitizerConfig* config)
: config_(const_cast<SanitizerConfig*>(config)) { : config_(const_cast<SanitizerConfig*>(config)) {
// Format dropElements to uppercases. // Format dropElements to uppercases.
Vector<String> drop_elements = default_drop_elements_;
if (config->hasDropElements()) { if (config->hasDropElements()) {
Vector<String> l;
for (const String& s : config->dropElements()) { for (const String& s : config->dropElements()) {
l.push_back(s.UpperASCII()); if (!drop_elements.Contains(s.UpperASCII())) {
drop_elements.push_back(s.UpperASCII());
}
}
}
config_->setDropElements(drop_elements);
// Format allowElements to uppercases.
if (config->hasAllowElements()) {
Vector<String> l;
for (const String& s : config->allowElements()) {
if (!config_->dropElements().Contains(s))
l.push_back(s.UpperASCII());
} }
config_->setDropElements(l); config_->setAllowElements(l);
} }
// Format dropAttributes to lowercases. // Format dropAttributes to lowercases.
...@@ -70,37 +82,56 @@ DocumentFragment* Sanitizer::sanitize(ScriptState* script_state, ...@@ -70,37 +82,56 @@ DocumentFragment* Sanitizer::sanitize(ScriptState* script_state,
DCHECK(document->QuerySelector("body")); DCHECK(document->QuerySelector("body"));
fragment->ParseHTML(input, document->QuerySelector("body")); fragment->ParseHTML(input, document->QuerySelector("body"));
// Remove all the elements in the dropElements list. Node* node = fragment->firstChild();
if (config_->hasDropElements() || config_->hasDropAttributes()) {
Node* node = fragment->firstChild();
while (node) { while (node) {
// Skip non-Element nodes. // Skip non-Element nodes.
if (node->getNodeType() != Node::NodeType::kElementNode) { if (node->getNodeType() != Node::NodeType::kElementNode) {
node = NodeTraversal::Next(*node, fragment); node = NodeTraversal::Next(*node, fragment);
continue; continue;
} }
// TODO(crbug.com/1126936): Review the sanitising algorithm for non-HTMLs. // TODO(crbug.com/1126936): Review the sanitising algorithm for non-HTMLs.
String node_name = node->nodeName(); String node_name = node->nodeName().UpperASCII();
// If the current element is dropped, remove current element entirely and // If the current element is dropped, remove current element entirely and
// proceed to its next sibling. // proceed to its next sibling.
if (config_->hasDropElements() && if (config_->dropElements().Contains(node_name)) {
config_->dropElements().Contains(node_name.UpperASCII())) { Node* tmp = node;
Node* tmp = node; node = NodeTraversal::NextSkippingChildren(*node, fragment);
node = NodeTraversal::NextSkippingChildren(*node, fragment); tmp->remove();
tmp->remove(); } else if (config_->hasAllowElements() &&
} else { !config_->allowElements().Contains(node_name)) {
// Otherwise, remove any attributes to be dropped from the current // If the current element is blocked, append its children after current
// element, and proceed to the next node (preorder, depth-first // node to parent node, remove current element and proceed to the next
// traversal). // node.
if (config_->hasDropAttributes()) { Node* parent = node->parentNode();
for (auto attr : drop_attributes_) { Node* next_sibling = node->nextSibling();
To<Element>(node)->removeAttribute(attr); while (node->hasChildren()) {
} Node* n = node->firstChild();
if (next_sibling) {
parent->insertBefore(n, next_sibling, exception_state);
} else {
parent->appendChild(n, exception_state);
}
// TODO(lyf): review and make a proper decision for exceptions may
// happened here.
if (exception_state.HadException()) {
return nullptr;
}
}
Node* tmp = node;
node = NodeTraversal::Next(*node, fragment);
tmp->remove();
} else {
// Otherwise, remove any attributes to be dropped from the current
// element, and proceed to the next node (preorder, depth-first
// traversal).
if (config_->hasDropAttributes()) {
for (auto attr : drop_attributes_) {
To<Element>(node)->removeAttribute(attr);
} }
node = NodeTraversal::Next(*node, fragment);
} }
node = NodeTraversal::Next(*node, fragment);
} }
} }
......
...@@ -36,6 +36,18 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable { ...@@ -36,6 +36,18 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable {
// Vector<QualifiedName> for drop_elements. // Vector<QualifiedName> for drop_elements.
Member<SanitizerConfig> config_ = {}; Member<SanitizerConfig> config_ = {};
Vector<AtomicString> drop_attributes_ = {}; Vector<AtomicString> drop_attributes_ = {};
const Vector<String> default_drop_elements_ = {"SCRIPT", "ANNOTATION-XML",
"AUDIO", "COLGROUP",
"DESC", "FOREIGNOBJECT",
"HEAD", "IFRAME",
"MATH", "MI",
"MN", "MO",
"MS", "MTEXT",
"NOEMBED", "NOFRAMES",
"PLAINTEXT", "STYLE",
"SVG", "TEMPLATE",
"THEAD", "TITLE",
"VIDEO", "XMP"};
}; };
} // namespace blink } // namespace blink
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
// https://wicg.github.io/sanitizer-api/#sanitizer-api // https://wicg.github.io/sanitizer-api/#sanitizer-api
dictionary SanitizerConfig { dictionary SanitizerConfig {
sequence<DOMString> allowElements;
sequence<DOMString> dropElements; sequence<DOMString> dropElements;
sequence<DOMString> dropAttributes; sequence<DOMString> dropAttributes;
}; };
This is a testharness.js-based test.
PASS SanitizerAPI creator without config.
PASS SanitizerAPI creator with empty config.
PASS SanitizerAPI creator with config ignore unknown values.
PASS SanitizerAPI creator with config {"dropElements":[]}.
PASS SanitizerAPI creator with config {"blockElements":[]}.
FAIL SanitizerAPI creator with config {"allowElements":[]}. assert_equals: expected "<div>balabala<i>test</i></div>" but got "balabalatest"
PASS SanitizerAPI creator with config {"dropAttributes":[]}.
PASS SanitizerAPI creator with config {"blockAttributes":[]}.
PASS SanitizerAPI creator with config {"allowAttributes":[]}.
Harness: the test ran to completion.
...@@ -32,6 +32,17 @@ ...@@ -32,6 +32,17 @@
assert_true(s instanceof Sanitizer); assert_true(s instanceof Sanitizer);
}, "SanitizerAPI creator with config ignore unknown values."); }, "SanitizerAPI creator with config ignore unknown values.");
test(t => {
let options = {allowElements: ["div"]};
let s = new Sanitizer(options);
assert_true(s instanceof Sanitizer);
assert_equals(s.sanitizeToString("<div>balabala</div><test>test</test>"), "<div>balabala</div>test");
options.allowElements.push("test");
assert_equals(s.sanitizeToString("<div>balabala</div><test>test</test>"), "<div>balabala</div>test");
}, "SanitizerAPI config allowElements is not editable.");
test(t => { test(t => {
let options = {dropElements: ["div"]}; let options = {dropElements: ["div"]};
...@@ -54,14 +65,19 @@ ...@@ -54,14 +65,19 @@
assert_equals(s.sanitizeToString("<button id='btn' onclick='submit()'>balabala</button>"), "<button id=\"btn\">balabala</button>"); assert_equals(s.sanitizeToString("<button id='btn' onclick='submit()'>balabala</button>"), "<button id=\"btn\">balabala</button>");
}, "SanitizerAPI config dropAttributes is not editable."); }, "SanitizerAPI config dropAttributes is not editable.");
const config_names = ["dropElements", "dropAttributes"]; const config_names = ["dropElements", "allowElements", "dropAttributes"];
config_names.forEach(cname => { config_names.forEach(cname => {
let options = {}; let options = {};
options[cname] = []; options[cname] = [];
test(t => { test(t => {
let s = new Sanitizer(options); let s = new Sanitizer(options);
assert_true(s instanceof Sanitizer) assert_true(s instanceof Sanitizer)
assert_equals(s.sanitizeToString("<div>balabala<i>test</i></div>"), "<div>balabala<i>test</i></div>");
if (cname == "allowElements") {
assert_equals(s.sanitizeToString("<div id='div'>balabala<i>test</i></div>"), "balabalatest");
} else {
assert_equals(s.sanitizeToString("<div id='div'>balabala<i>test</i></div>"), "<div id=\"div\">balabala<i>test</i></div>");
}
}, "SanitizerAPI creator with config " + JSON.stringify(options) + "."); }, "SanitizerAPI creator with config " + JSON.stringify(options) + ".");
options = {}; options = {};
......
...@@ -12,7 +12,7 @@ PASS SanitizerAPI with config: undefined, sanitize function for undefined ...@@ -12,7 +12,7 @@ PASS SanitizerAPI with config: undefined, sanitize function for undefined
PASS SanitizerAPI with config: null, sanitize function for null PASS SanitizerAPI with config: null, sanitize function for null
PASS SanitizerAPI with config: document, sanitize function for document PASS SanitizerAPI with config: document, sanitize function for document
PASS SanitizerAPI with config: html without close tag, sanitize function for html without close tag PASS SanitizerAPI with config: html without close tag, sanitize function for html without close tag
FAIL SanitizerAPI with config: scripts for default configs, sanitize function for scripts for default configs assert_equals: expected "" but got "<script>alert('i am a test')</script>" PASS SanitizerAPI with config: scripts for default configs, sanitize function for scripts for default configs
FAIL SanitizerAPI with config: onclick scripts, sanitize function for onclick scripts assert_equals: expected "<p>Click.</p>" but got "<p onclick=\"a= 123\">Click.</p>" FAIL SanitizerAPI with config: onclick scripts, sanitize function for onclick scripts assert_equals: expected "<p>Click.</p>" but got "<p onclick=\"a= 123\">Click.</p>"
PASS SanitizerAPI with config: invalid config_input, sanitize function for invalid config_input PASS SanitizerAPI with config: invalid config_input, sanitize function for invalid config_input
PASS SanitizerAPI with config: empty dropElements list, sanitize function for empty dropElements list PASS SanitizerAPI with config: empty dropElements list, sanitize function for empty dropElements list
...@@ -22,10 +22,12 @@ PASS SanitizerAPI with config: dropElements list ["test", "i"]}, sanitize functi ...@@ -22,10 +22,12 @@ PASS SanitizerAPI with config: dropElements list ["test", "i"]}, sanitize functi
PASS SanitizerAPI with config: dropElements list ["I", "AM"]}, sanitize function for dropElements list ["I", "AM"]} PASS SanitizerAPI with config: dropElements list ["I", "AM"]}, sanitize function for dropElements list ["I", "AM"]}
PASS SanitizerAPI with config: dropElements list ["am", "p"]}, sanitize function for dropElements list ["am", "p"]} PASS SanitizerAPI with config: dropElements list ["am", "p"]}, sanitize function for dropElements list ["am", "p"]}
PASS SanitizerAPI with config: dropElements list with invalid values}, sanitize function for dropElements list with invalid values} PASS SanitizerAPI with config: dropElements list with invalid values}, sanitize function for dropElements list with invalid values}
PASS SanitizerAPI with config: allowElements list ["p"]., sanitize function for allowElements list ["p"].
PASS SanitizerAPI with config: allowElements list has no influence to dropElements., sanitize function for allowElements list has no influence to dropElements.
PASS SanitizerAPI with config: dropAttributes list ["onclick"] with onclick scripts, sanitize function for dropAttributes list ["onclick"] with onclick scripts PASS SanitizerAPI with config: dropAttributes list ["onclick"] with onclick scripts, sanitize function for dropAttributes list ["onclick"] with onclick scripts
PASS SanitizerAPI with config: empty dropAttributes list with onclick scripts, sanitize function for empty dropAttributes list with onclick scripts PASS SanitizerAPI with config: empty dropAttributes list with onclick scripts, sanitize function for empty dropAttributes list with onclick scripts
PASS SanitizerAPI with config: dropAttributes list ["id"] with onclick scripts, sanitize function for dropAttributes list ["id"] with onclick scripts PASS SanitizerAPI with config: dropAttributes list ["id"] with onclick scripts, sanitize function for dropAttributes list ["id"] with onclick scripts
PASS SanitizerAPI with config: dropAttributes list ["ONCLICK"] with onclick scripts, sanitize function for dropAttributes list ["ONCLICK"] with onclick scripts PASS SanitizerAPI with config: dropAttributes list ["ONCLICK"] with onclick scripts, sanitize function for dropAttributes list ["ONCLICK"] with onclick scripts
FAIL SanitizerAPI with config: dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access., sanitize function for dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access. assert_equals: expected "<p id=\"p\">Click.</p><script></script>" but got "<p id=\"p\">Click.</p><script>document.getElementById('p').dataset.attributeWithDashes=123;</script>" FAIL SanitizerAPI with config: dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access., sanitize function for dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access. assert_equals: expected "<p id=\"p\">Click.</p><script></script>" but got "<p id=\"p\">Click.</p>"
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -21,7 +21,6 @@ ...@@ -21,7 +21,6 @@
testcases.forEach(c => test(t => { testcases.forEach(c => test(t => {
let s = new Sanitizer(c.config_input); let s = new Sanitizer(c.config_input);
fragment = s.sanitize(c.value); fragment = s.sanitize(c.value);
assert_true(fragment instanceof DocumentFragment); assert_true(fragment instanceof DocumentFragment);
assert_equals(getString(fragment), c.result); assert_equals(getString(fragment), c.result);
......
...@@ -12,7 +12,7 @@ PASS SanitizerAPI config: undefined, sanitizeToString function for undefined ...@@ -12,7 +12,7 @@ PASS SanitizerAPI config: undefined, sanitizeToString function for undefined
PASS SanitizerAPI config: null, sanitizeToString function for null PASS SanitizerAPI config: null, sanitizeToString function for null
PASS SanitizerAPI config: document, sanitizeToString function for document PASS SanitizerAPI config: document, sanitizeToString function for document
PASS SanitizerAPI config: html without close tag, sanitizeToString function for html without close tag PASS SanitizerAPI config: html without close tag, sanitizeToString function for html without close tag
FAIL SanitizerAPI config: scripts for default configs, sanitizeToString function for scripts for default configs assert_equals: expected "" but got "<script>alert('i am a test')</script>" PASS SanitizerAPI config: scripts for default configs, sanitizeToString function for scripts for default configs
FAIL SanitizerAPI config: onclick scripts, sanitizeToString function for onclick scripts assert_equals: expected "<p>Click.</p>" but got "<p onclick=\"a= 123\">Click.</p>" FAIL SanitizerAPI config: onclick scripts, sanitizeToString function for onclick scripts assert_equals: expected "<p>Click.</p>" but got "<p onclick=\"a= 123\">Click.</p>"
PASS SanitizerAPI config: invalid config_input, sanitizeToString function for invalid config_input PASS SanitizerAPI config: invalid config_input, sanitizeToString function for invalid config_input
PASS SanitizerAPI config: empty dropElements list, sanitizeToString function for empty dropElements list PASS SanitizerAPI config: empty dropElements list, sanitizeToString function for empty dropElements list
...@@ -22,10 +22,12 @@ PASS SanitizerAPI config: dropElements list ["test", "i"]}, sanitizeToString fun ...@@ -22,10 +22,12 @@ PASS SanitizerAPI config: dropElements list ["test", "i"]}, sanitizeToString fun
PASS SanitizerAPI config: dropElements list ["I", "AM"]}, sanitizeToString function for dropElements list ["I", "AM"]} PASS SanitizerAPI config: dropElements list ["I", "AM"]}, sanitizeToString function for dropElements list ["I", "AM"]}
PASS SanitizerAPI config: dropElements list ["am", "p"]}, sanitizeToString function for dropElements list ["am", "p"]} PASS SanitizerAPI config: dropElements list ["am", "p"]}, sanitizeToString function for dropElements list ["am", "p"]}
PASS SanitizerAPI config: dropElements list with invalid values}, sanitizeToString function for dropElements list with invalid values} PASS SanitizerAPI config: dropElements list with invalid values}, sanitizeToString function for dropElements list with invalid values}
PASS SanitizerAPI config: allowElements list ["p"]., sanitizeToString function for allowElements list ["p"].
PASS SanitizerAPI config: allowElements list has no influence to dropElements., sanitizeToString function for allowElements list has no influence to dropElements.
PASS SanitizerAPI config: dropAttributes list ["onclick"] with onclick scripts, sanitizeToString function for dropAttributes list ["onclick"] with onclick scripts PASS SanitizerAPI config: dropAttributes list ["onclick"] with onclick scripts, sanitizeToString function for dropAttributes list ["onclick"] with onclick scripts
PASS SanitizerAPI config: empty dropAttributes list with onclick scripts, sanitizeToString function for empty dropAttributes list with onclick scripts PASS SanitizerAPI config: empty dropAttributes list with onclick scripts, sanitizeToString function for empty dropAttributes list with onclick scripts
PASS SanitizerAPI config: dropAttributes list ["id"] with onclick scripts, sanitizeToString function for dropAttributes list ["id"] with onclick scripts PASS SanitizerAPI config: dropAttributes list ["id"] with onclick scripts, sanitizeToString function for dropAttributes list ["id"] with onclick scripts
PASS SanitizerAPI config: dropAttributes list ["ONCLICK"] with onclick scripts, sanitizeToString function for dropAttributes list ["ONCLICK"] with onclick scripts PASS SanitizerAPI config: dropAttributes list ["ONCLICK"] with onclick scripts, sanitizeToString function for dropAttributes list ["ONCLICK"] with onclick scripts
FAIL SanitizerAPI config: dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access., sanitizeToString function for dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access. assert_equals: expected "<p id=\"p\">Click.</p><script></script>" but got "<p id=\"p\">Click.</p><script>document.getElementById('p').dataset.attributeWithDashes=123;</script>" FAIL SanitizerAPI config: dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access., sanitizeToString function for dropAttributes list ["data-attribute-with-dashes"] with dom dataset js access. assert_equals: expected "<p id=\"p\">Click.</p><script></script>" but got "<p id=\"p\">Click.</p>"
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -21,6 +21,8 @@ const testcases = [ ...@@ -21,6 +21,8 @@ const testcases = [
{config_input: {dropElements: ["I", "AM"]}, value: "<div>balabala<am>test</am></div>", result: "<div>balabala</div>", message: "dropElements list [\"I\", \"AM\"]}"}, {config_input: {dropElements: ["I", "AM"]}, value: "<div>balabala<am>test</am></div>", result: "<div>balabala</div>", message: "dropElements list [\"I\", \"AM\"]}"},
{config_input: {dropElements: ["am", "p"]}, value: "<div>balabala<i>i</i><p>t</p><test>a</test></div>", result: "<div>balabala<i>i</i><test>a</test></div>", message: "dropElements list [\"am\", \"p\"]}"}, {config_input: {dropElements: ["am", "p"]}, value: "<div>balabala<i>i</i><p>t</p><test>a</test></div>", result: "<div>balabala<i>i</i><test>a</test></div>", message: "dropElements list [\"am\", \"p\"]}"},
{config_input: {dropElements: [123, [], "test", "i"]}, value: "<div>balabala<i>test</i></div><test>t</test>", result: "<div>balabala</div>", message: "dropElements list with invalid values}"}, {config_input: {dropElements: [123, [], "test", "i"]}, value: "<div>balabala<i>test</i></div><test>t</test>", result: "<div>balabala</div>", message: "dropElements list with invalid values}"},
{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><c>bla", result: "bla", message: "allowElements list has no influence to dropElements."},
{config_input: {dropAttributes: ["onclick"]}, value: "<p onclick='a= 123'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list [\"onclick\"] with onclick scripts"}, {config_input: {dropAttributes: ["onclick"]}, value: "<p onclick='a= 123'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list [\"onclick\"] with onclick scripts"},
{config_input: {dropAttributes: []}, value: "<p onclick='a= 123'>Click.</p>", result: "<p onclick=\"a= 123\">Click.</p>", message: "empty dropAttributes list with onclick scripts"}, {config_input: {dropAttributes: []}, value: "<p onclick='a= 123'>Click.</p>", result: "<p onclick=\"a= 123\">Click.</p>", message: "empty dropAttributes list with onclick scripts"},
{config_input: {dropAttributes: ["id"]}, value: "<p onclick='a= 123'>Click.</p>", result: "<p onclick=\"a= 123\">Click.</p>", message: "dropAttributes list [\"id\"] with onclick scripts"}, {config_input: {dropAttributes: ["id"]}, value: "<p onclick='a= 123'>Click.</p>", result: "<p onclick=\"a= 123\">Click.</p>", message: "dropAttributes list [\"id\"] with onclick scripts"},
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment