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,
Sanitizer::Sanitizer(const SanitizerConfig* config)
: config_(const_cast<SanitizerConfig*>(config)) {
// Format dropElements to uppercases.
Vector<String> drop_elements = default_drop_elements_;
if (config->hasDropElements()) {
Vector<String> l;
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.
......@@ -70,37 +82,56 @@ DocumentFragment* Sanitizer::sanitize(ScriptState* script_state,
DCHECK(document->QuerySelector("body"));
fragment->ParseHTML(input, document->QuerySelector("body"));
// Remove all the elements in the dropElements list.
if (config_->hasDropElements() || config_->hasDropAttributes()) {
Node* node = fragment->firstChild();
Node* node = fragment->firstChild();
while (node) {
// Skip non-Element nodes.
if (node->getNodeType() != Node::NodeType::kElementNode) {
node = NodeTraversal::Next(*node, fragment);
continue;
}
while (node) {
// Skip non-Element nodes.
if (node->getNodeType() != Node::NodeType::kElementNode) {
node = NodeTraversal::Next(*node, fragment);
continue;
}
// TODO(crbug.com/1126936): Review the sanitising algorithm for non-HTMLs.
String node_name = node->nodeName();
// If the current element is dropped, remove current element entirely and
// proceed to its next sibling.
if (config_->hasDropElements() &&
config_->dropElements().Contains(node_name.UpperASCII())) {
Node* tmp = node;
node = NodeTraversal::NextSkippingChildren(*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);
}
// TODO(crbug.com/1126936): Review the sanitising algorithm for non-HTMLs.
String node_name = node->nodeName().UpperASCII();
// If the current element is dropped, remove current element entirely and
// proceed to its next sibling.
if (config_->dropElements().Contains(node_name)) {
Node* tmp = node;
node = NodeTraversal::NextSkippingChildren(*node, fragment);
tmp->remove();
} else if (config_->hasAllowElements() &&
!config_->allowElements().Contains(node_name)) {
// If the current element is blocked, append its children after current
// node to parent node, remove current element and proceed to the next
// node.
Node* parent = node->parentNode();
Node* next_sibling = node->nextSibling();
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 {
// Vector<QualifiedName> for drop_elements.
Member<SanitizerConfig> config_ = {};
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
......
......@@ -5,6 +5,7 @@
// https://wicg.github.io/sanitizer-api/#sanitizer-api
dictionary SanitizerConfig {
sequence<DOMString> allowElements;
sequence<DOMString> dropElements;
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 @@
assert_true(s instanceof Sanitizer);
}, "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 => {
let options = {dropElements: ["div"]};
......@@ -54,14 +65,19 @@
assert_equals(s.sanitizeToString("<button id='btn' onclick='submit()'>balabala</button>"), "<button id=\"btn\">balabala</button>");
}, "SanitizerAPI config dropAttributes is not editable.");
const config_names = ["dropElements", "dropAttributes"];
const config_names = ["dropElements", "allowElements", "dropAttributes"];
config_names.forEach(cname => {
let options = {};
options[cname] = [];
test(t => {
let s = new Sanitizer(options);
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) + ".");
options = {};
......
......@@ -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: document, sanitize function for document
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>"
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
......@@ -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 ["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: 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: 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 ["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.
......@@ -21,7 +21,6 @@
testcases.forEach(c => test(t => {
let s = new Sanitizer(c.config_input);
fragment = s.sanitize(c.value);
assert_true(fragment instanceof DocumentFragment);
assert_equals(getString(fragment), c.result);
......
......@@ -12,7 +12,7 @@ PASS SanitizerAPI config: undefined, sanitizeToString function for undefined
PASS SanitizerAPI config: null, sanitizeToString function for null
PASS SanitizerAPI config: document, sanitizeToString function for document
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>"
PASS SanitizerAPI config: invalid config_input, sanitizeToString function for invalid config_input
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
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 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: 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 ["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.
......@@ -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: ["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: {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: []}, 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"},
......
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