Commit c8b9b193 authored by Yifan Luo's avatar Yifan Luo Committed by Commit Bot

[Sanitizer API] Pre-element allow/block attributes.

Bug: 1116418
Change-Id: Idfc4a634a12b354c0aa96c0cf5f2516138b38235
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2475814
Commit-Queue: Yifan Luo <lyf@chromium.org>
Reviewed-by: default avatarYifan Luo <lyf@chromium.org>
Reviewed-by: default avatarDaniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#818941}
parent 3ee881d5
...@@ -29,47 +29,54 @@ Sanitizer::Sanitizer(const SanitizerConfig* config) { ...@@ -29,47 +29,54 @@ Sanitizer::Sanitizer(const SanitizerConfig* config) {
// Format dropElements to uppercase. // Format dropElements to uppercase.
drop_elements_ = default_drop_elements_; drop_elements_ = default_drop_elements_;
if (config->hasDropElements()) { if (config->hasDropElements()) {
for (const String& s : config->dropElements()) { ElementFormatter(drop_elements_, config->dropElements());
drop_elements_.insert(s.UpperASCII());
}
} }
// Format blockElements to uppercase. // Format blockElements to uppercase.
block_elements_ = default_block_elements_; block_elements_ = default_block_elements_;
if (config->hasBlockElements()) { if (config->hasBlockElements()) {
for (const String& s : config->blockElements()) { ElementFormatter(block_elements_, config->blockElements());
const String& upper_s = s.UpperASCII();
if (!drop_elements_.Contains(upper_s)) {
block_elements_.insert(upper_s);
}
}
} }
// Format allowElements to uppercase.
if (config->hasAllowElements()) { if (config->hasAllowElements()) {
// Format allowElements to uppercase.
has_allow_elements_ = true; has_allow_elements_ = true;
for (const String& s : config->allowElements()) { ElementFormatter(allow_elements_, config->allowElements());
const String& upper = s.UpperASCII();
if (!drop_elements_.Contains(upper) &&
!default_block_elements_.Contains(upper))
allow_elements_.insert(upper);
}
} }
// Format dropAttributes to lowercase. // Format dropAttributes to lowercase.
drop_attributes_ = default_drop_attributes_; drop_attributes_ = default_drop_attributes_;
if (config->hasDropAttributes()) { if (config->hasDropAttributes()) {
for (const String& s : config->dropAttributes()) { AttrFormatter(drop_attributes_, config->dropAttributes());
drop_attributes_.insert(s.LowerASCII());
}
} }
// Format allowAttributes to lowercase.
if (config->hasAllowAttributes()) { if (config->hasAllowAttributes()) {
has_allow_attributes_ = true; has_allow_attributes_ = true;
for (const String& s : config->allowAttributes()) { AttrFormatter(allow_attributes_, config->allowAttributes());
const String& lower_s = s.LowerASCII(); }
if (!default_drop_attributes_.Contains(lower_s) && }
!default_block_elements_.Contains(lower_s))
allow_attributes_.insert(lower_s); void Sanitizer::ElementFormatter(HashSet<String>& element_set,
const Vector<String>& elements) {
for (const String& s : elements) {
element_set.insert(s.UpperASCII());
}
}
void Sanitizer::AttrFormatter(
HashMap<String, Vector<String>>& attr_map,
const Vector<std::pair<String, Vector<String>>>& attrs) {
for (const std::pair<String, Vector<String>>& pair : attrs) {
const String& lower_attr = pair.first.LowerASCII();
if (pair.second.Contains("*")) {
attr_map.insert(lower_attr, Vector<String>({"*"}));
} else {
Vector<String> elements;
for (const String& s : pair.second) {
elements.push_back(s.UpperASCII());
}
attr_map.insert(lower_attr, elements);
} }
} }
} }
...@@ -140,12 +147,24 @@ DocumentFragment* Sanitizer::sanitize(ScriptState* script_state, ...@@ -140,12 +147,24 @@ DocumentFragment* Sanitizer::sanitize(ScriptState* script_state,
// element, and proceed to the next node (preorder, depth-first // element, and proceed to the next node (preorder, depth-first
// traversal). // traversal).
Element* element = To<Element>(node); Element* element = To<Element>(node);
for (const auto& name : element->getAttributeNames()) { if (has_allow_attributes_ &&
bool drop = allow_attributes_.at("*").Contains(node_name)) {
drop_attributes_.Contains(name) || } else if (drop_attributes_.at("*").Contains(node_name)) {
(has_allow_attributes_ && !allow_attributes_.Contains(name)); for (const auto& name : element->getAttributeNames()) {
if (drop)
element->removeAttribute(name); element->removeAttribute(name);
}
} else {
for (const auto& name : element->getAttributeNames()) {
bool drop = (drop_attributes_.Contains(name) &&
(drop_attributes_.at(name).Contains("*") ||
drop_attributes_.at(name).Contains(node_name))) ||
(has_allow_attributes_ &&
!(allow_attributes_.Contains(name) &&
(allow_attributes_.at(name).Contains("*") ||
allow_attributes_.at(name).Contains(node_name))));
if (drop)
element->removeAttribute(name);
}
} }
node = NodeTraversal::Next(*node, fragment); node = NodeTraversal::Next(*node, fragment);
} }
......
...@@ -30,11 +30,15 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable { ...@@ -30,11 +30,15 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable {
void Trace(Visitor*) const override; void Trace(Visitor*) const override;
private: private:
void ElementFormatter(HashSet<String>&, const Vector<String>&);
void AttrFormatter(HashMap<String, Vector<String>>&,
const Vector<std::pair<String, Vector<String>>>&);
HashSet<String> allow_elements_ = {}; HashSet<String> allow_elements_ = {};
HashSet<String> block_elements_ = {}; HashSet<String> block_elements_ = {};
HashSet<String> drop_elements_ = {}; HashSet<String> drop_elements_ = {};
HashSet<String> allow_attributes_; HashMap<String, Vector<String>> allow_attributes_ = {};
HashSet<String> drop_attributes_ = {}; HashMap<String, Vector<String>> drop_attributes_ = {};
bool has_allow_elements_ = false; bool has_allow_elements_ = false;
bool has_allow_attributes_ = false; bool has_allow_attributes_ = false;
...@@ -52,7 +56,9 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable { ...@@ -52,7 +56,9 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable {
"SVG", "TEMPLATE", "SVG", "TEMPLATE",
"THEAD", "TITLE", "THEAD", "TITLE",
"VIDEO", "XMP"}; "VIDEO", "XMP"};
const HashSet<String> default_drop_attributes_ = {"onclick", "onsubmit"}; const HashMap<String, Vector<String>> default_drop_attributes_ = {
{"onclick", Vector<String>({"*"})},
{"onsubmit", Vector<String>({"*"})}};
}; };
} // namespace blink } // namespace blink
......
...@@ -8,6 +8,6 @@ dictionary SanitizerConfig { ...@@ -8,6 +8,6 @@ dictionary SanitizerConfig {
sequence<DOMString> allowElements; sequence<DOMString> allowElements;
sequence<DOMString> blockElements; sequence<DOMString> blockElements;
sequence<DOMString> dropElements; sequence<DOMString> dropElements;
sequence<DOMString> allowAttributes; record<DOMString, sequence<DOMString>> allowAttributes;
sequence<DOMString> dropAttributes; record<DOMString, sequence<DOMString>> dropAttributes;
}; };
...@@ -66,31 +66,31 @@ ...@@ -66,31 +66,31 @@
}, "SanitizerAPI config dropElements is not editable."); }, "SanitizerAPI config dropElements is not editable.");
test(t => { test(t => {
let options = {allowAttributes: ["id"]}; let options = {allowAttributes: {"id": ["*"]}};
let s = new Sanitizer(options); let s = new Sanitizer(options);
assert_true(s instanceof Sanitizer); assert_true(s instanceof Sanitizer);
assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button id=\"btn\">balabala</button>"); assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button id=\"btn\">balabala</button>");
options.allowAttributes.push("style"); options.allowAttributes["style"] = ["*"];
assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button id=\"btn\">balabala</button>"); assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button id=\"btn\">balabala</button>");
}, "SanitizerAPI config allowAttributes is not editable."); }, "SanitizerAPI config allowAttributes is not editable.");
test(t => { test(t => {
let options = {dropAttributes: ["id"]}; let options = {dropAttributes: {"id": ["*"]}};
let s = new Sanitizer(options); let s = new Sanitizer(options);
assert_true(s instanceof Sanitizer); assert_true(s instanceof Sanitizer);
assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button style=\"color: black\">balabala</button>"); assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button style=\"color: black\">balabala</button>");
options.dropAttributes.push("style"); options.dropAttributes["style"] = ["*"];
assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button style=\"color: black\">balabala</button>"); assert_equals(s.sanitizeToString("<button id='btn' style='color: black'>balabala</button>"), "<button style=\"color: black\">balabala</button>");
}, "SanitizerAPI config dropAttributes is not editable."); }, "SanitizerAPI config dropAttributes is not editable.");
const config_names = ["dropElements", "blockElements", "allowElements", "dropAttributes", "allowAttributes"]; const config_names = ["dropElements", "blockElements", "allowElements", "dropAttributes", "allowAttributes"];
config_names.forEach(cname => { config_names.forEach(cname => {
let options = {}; let options = {};
options[cname] = []; options[cname] = cname.endsWith("Elements") ? [] : {};
test(t => { test(t => {
let s = new Sanitizer(options); let s = new Sanitizer(options);
assert_true(s instanceof Sanitizer) assert_true(s instanceof Sanitizer)
......
...@@ -25,12 +25,15 @@ PASS SanitizerAPI with config: dropElements list with invalid values}, sanitize ...@@ -25,12 +25,15 @@ PASS SanitizerAPI with config: dropElements list with invalid values}, sanitize
PASS SanitizerAPI with config: blockElements list with invalid values}, sanitize function for blockElements list with invalid values} PASS SanitizerAPI with config: blockElements list with invalid values}, sanitize function for blockElements list with invalid values}
PASS SanitizerAPI with config: allowElements list ["p"]., sanitize function for allowElements list ["p"]. 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: allowElements list has no influence to dropElements., sanitize function for allowElements list has no influence to dropElements.
PASS SanitizerAPI with config: dropAttributes list ["style"] with style attribute, sanitize function for dropAttributes list ["style"] with style attribute PASS SanitizerAPI with config: dropAttributes list {"style": ["p"]} with style attribute, sanitize function for dropAttributes list {"style": ["p"]} with style attribute
PASS SanitizerAPI with config: dropAttributes list {"*": ["a"]} with style attribute, sanitize function for dropAttributes list {"*": ["a"]} with style attribute
PASS SanitizerAPI with config: empty dropAttributes list with id attribute, sanitize function for empty dropAttributes list with id attribute PASS SanitizerAPI with config: empty dropAttributes list with id attribute, sanitize function for empty dropAttributes list with id attribute
PASS SanitizerAPI with config: dropAttributes list ["id"] with id attribute, sanitize function for dropAttributes list ["id"] with id attribute PASS SanitizerAPI with config: dropAttributes list {"id": ["*"]} with id attribute, sanitize function for dropAttributes list {"id": ["*"]} with id attribute
PASS SanitizerAPI with config: dropAttributes list ["ID"] with id attribute, sanitize function for dropAttributes list ["ID"] with id attribute PASS SanitizerAPI with config: dropAttributes list {"ID": ["*"]} with id attribute, sanitize function for dropAttributes list {"ID": ["*"]} with id attribute
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>" 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>"
PASS SanitizerAPI with config: allowAttributes list ["id"] with id attribute and onclick scripts, sanitize function for allowAttributes list ["id"] with id attribute and onclick scripts PASS SanitizerAPI with config: allowAttributes list {"id": ["div"]} with id attribute, sanitize function for allowAttributes list {"id": ["div"]} with id attribute
PASS SanitizerAPI with config: allowAttributes list {"id": ["*"]} with id attribute and onclick scripts, sanitize function for allowAttributes list {"id": ["*"]} with id attribute and onclick scripts
PASS SanitizerAPI with config: allowAttributes list {"*": ["a"]} with style attribute, sanitize function for allowAttributes list {"*": ["a"]} with style attribute
PASS SanitizerAPI with config: allowAttributes list has no influence to dropAttributes, sanitize function for allowAttributes list has no influence to dropAttributes PASS SanitizerAPI with config: allowAttributes list has no influence to dropAttributes, sanitize function for allowAttributes list has no influence to dropAttributes
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -25,12 +25,15 @@ PASS SanitizerAPI config: dropElements list with invalid values}, sanitizeToStri ...@@ -25,12 +25,15 @@ PASS SanitizerAPI config: dropElements list with invalid values}, sanitizeToStri
PASS SanitizerAPI config: blockElements list with invalid values}, sanitizeToString function for blockElements list with invalid values} PASS SanitizerAPI config: blockElements list with invalid values}, sanitizeToString function for blockElements list with invalid values}
PASS SanitizerAPI config: allowElements list ["p"]., sanitizeToString function for allowElements list ["p"]. 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: allowElements list has no influence to dropElements., sanitizeToString function for allowElements list has no influence to dropElements.
PASS SanitizerAPI config: dropAttributes list ["style"] with style attribute, sanitizeToString function for dropAttributes list ["style"] with style attribute PASS SanitizerAPI config: dropAttributes list {"style": ["p"]} with style attribute, sanitizeToString function for dropAttributes list {"style": ["p"]} with style attribute
PASS SanitizerAPI config: dropAttributes list {"*": ["a"]} with style attribute, sanitizeToString function for dropAttributes list {"*": ["a"]} with style attribute
PASS SanitizerAPI config: empty dropAttributes list with id attribute, sanitizeToString function for empty dropAttributes list with id attribute PASS SanitizerAPI config: empty dropAttributes list with id attribute, sanitizeToString function for empty dropAttributes list with id attribute
PASS SanitizerAPI config: dropAttributes list ["id"] with id attribute, sanitizeToString function for dropAttributes list ["id"] with id attribute PASS SanitizerAPI config: dropAttributes list {"id": ["*"]} with id attribute, sanitizeToString function for dropAttributes list {"id": ["*"]} with id attribute
PASS SanitizerAPI config: dropAttributes list ["ID"] with id attribute, sanitizeToString function for dropAttributes list ["ID"] with id attribute PASS SanitizerAPI config: dropAttributes list {"ID": ["*"]} with id attribute, sanitizeToString function for dropAttributes list {"ID": ["*"]} with id attribute
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>" 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>"
PASS SanitizerAPI config: allowAttributes list ["id"] with id attribute and onclick scripts, sanitizeToString function for allowAttributes list ["id"] with id attribute and onclick scripts PASS SanitizerAPI config: allowAttributes list {"id": ["div"]} with id attribute, sanitizeToString function for allowAttributes list {"id": ["div"]} with id attribute
PASS SanitizerAPI config: allowAttributes list {"id": ["*"]} with id attribute and onclick scripts, sanitizeToString function for allowAttributes list {"id": ["*"]} with id attribute and onclick scripts
PASS SanitizerAPI config: allowAttributes list {"*": ["a"]} with style attribute, sanitizeToString function for allowAttributes list {"*": ["a"]} with style attribute
PASS SanitizerAPI config: allowAttributes list has no influence to dropAttributes, sanitizeToString function for allowAttributes list has no influence to dropAttributes PASS SanitizerAPI config: allowAttributes list has no influence to dropAttributes, sanitizeToString function for allowAttributes list has no influence to dropAttributes
Harness: the test ran to completion. Harness: the test ran to completion.
...@@ -24,11 +24,14 @@ const testcases = [ ...@@ -24,11 +24,14 @@ const testcases = [
{config_input: {blockElements: [123, [], "test", "i"]}, value: "<div>balabala<i>test</i></div><test>t</test>", result: "<div>balabalatest</div>t", message: "blockElements list with invalid values}"}, {config_input: {blockElements: [123, [], "test", "i"]}, value: "<div>balabala<i>test</i></div><test>t</test>", result: "<div>balabalatest</div>t", message: "blockElements 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: {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: {dropElements: ["div"], allowElements: ["div"]}, value: "<div>test</div><c>bla", result: "bla", message: "allowElements list has no influence to dropElements."},
{config_input: {dropAttributes: ["style"]}, value: "<p style='color: black'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list [\"style\"] with style attribute"}, {config_input: {dropAttributes: {"style": ["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: []}, value: "<p id='test'>Click.</p>", result: "<p id=\"test\">Click.</p>", message: "empty dropAttributes list with id attribute"}, {config_input: {dropAttributes: {"*": ["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: ["id"]}, value: "<p id='test'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list [\"id\"] with id 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: ["ID"]}, value: "<p id='test'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list [\"ID\"] with id attribute"}, {config_input: {dropAttributes: {"id": ["*"]}}, value: "<p id='test'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list {\"id\": [\"*\"]} with id attribute"},
{config_input: {dropAttributes: ["data-attribute-with-dashes"]}, 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><script></script>", message: "dropAttributes list [\"data-attribute-with-dashes\"] with dom dataset js access."}, {config_input: {dropAttributes: {"ID": ["*"]}}, value: "<p id='test'>Click.</p>", result: "<p>Click.</p>", message: "dropAttributes list {\"ID\": [\"*\"]} with id attribute"},
{config_input: {allowAttributes: ["id"]}, 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: {dropAttributes: {"data-attribute-with-dashes": ["*"]}}, 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><script></script>", message: "dropAttributes list {\"data-attribute-with-dashes\": [\"*\"]} with dom dataset js access."},
{config_input: {dropAttributes: ["style"], allowAttributes: ["style"]}, value: "<p style='color: black'>Click.</p>", result: "<p>Click.</p>", message: "allowAttributes list has no influence to dropAttributes"}, {config_input: {allowAttributes: {"id": ["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: {"id": ["*"]}}, 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: {"style": ["*"]}, allowAttributes: {"style": ["*"]}}, value: "<p style='color: black'>Click.</p>", result: "<p>Click.</p>", message: "allowAttributes list has no influence to dropAttributes"},
]; ];
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