Commit 4df80911 authored by Yifan Luo's avatar Yifan Luo Committed by Commit Bot

[Sanitizer API] Add dropElements to config.

Bug: 1116418
Change-Id: If435e43ced3287398982aa2bc9719bc5498a40ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2401162
Commit-Queue: Yifan Luo <lyf@chromium.org>
Reviewed-by: default avatarMike West <mkwst@chromium.org>
Reviewed-by: default avatarDaniel Vogelheim <vogelheim@chromium.org>
Cr-Commit-Position: refs/heads/master@{#806614}
parent 7db3923f
......@@ -62,7 +62,8 @@ class NodeTraversal {
// Like next, but skips children and starts with the next sibling.
CORE_EXPORT static Node* NextSkippingChildren(const Node&);
static Node* NextSkippingChildren(const Node&, const Node* stay_within);
CORE_EXPORT static Node* NextSkippingChildren(const Node&,
const Node* stay_within);
static Node* FirstWithin(const Node& current) { return current.firstChild(); }
......
......@@ -4,13 +4,17 @@
#include "sanitizer.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_node_filter.h"
#include "third_party/blink/renderer/bindings/modules/v8/v8_sanitizer_config.h"
#include "third_party/blink/renderer/core/dom/document_fragment.h"
#include "third_party/blink/renderer/core/dom/node.h"
#include "third_party/blink/renderer/core/dom/node_traversal.h"
#include "third_party/blink/renderer/core/editing/serializers/serialization.h"
#include "third_party/blink/renderer/core/frame/local_dom_window.h"
#include "third_party/blink/renderer/platform/bindings/exception_messages.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
......@@ -20,7 +24,16 @@ Sanitizer* Sanitizer::Create(const SanitizerConfig* config,
}
Sanitizer::Sanitizer(const SanitizerConfig* config)
: config_(const_cast<SanitizerConfig*>(config)) {}
: config_(const_cast<SanitizerConfig*>(config)) {
// Format dropElements to uppercases.
if (config->hasDropElements()) {
Vector<String> l;
for (const String& s : config->dropElements()) {
l.push_back(s.UpperASCII());
}
config_->setDropElements(l);
}
}
Sanitizer::~Sanitizer() = default;
......@@ -44,9 +57,38 @@ DocumentFragment* Sanitizer::sanitize(ScriptState* script_state,
DocumentFragment* fragment = document->createDocumentFragment();
DCHECK(document->QuerySelector("body"));
fragment->ParseHTML(input, document->QuerySelector("body"));
// Remove all the elements in the dropElements list.
if (config_->hasDropElements()) {
Node* node = fragment->firstChild();
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_->dropElements().Contains(node_name.UpperASCII())) {
Node* tmp = node;
node = NodeTraversal::NextSkippingChildren(*node, fragment);
tmp->remove();
} else {
// Otherwise, proceed to the next node (preorder, depth-first
// traversal).
node = NodeTraversal::Next(*node, fragment);
}
}
}
return fragment;
}
// TODO(lyf): https://github.com/WICG/sanitizer-api/issues/34
SanitizerConfig* Sanitizer::creationOptions() const {
return config_;
}
......
......@@ -32,6 +32,8 @@ class MODULES_EXPORT Sanitizer final : public ScriptWrappable {
void Trace(Visitor*) const override;
private:
// TODO(lyf): Make config_ read-only. The creationOptions getter which
// asks for the pointer is forbidened by a read-only variable.
Member<SanitizerConfig> config_ = {};
};
......
......@@ -4,4 +4,6 @@
// https://github.com/WICG/sanitizer-api
dictionary SanitizerConfig {};
dictionary SanitizerConfig {
sequence<DOMString>? dropElements;
};
......@@ -8,4 +8,6 @@
[CallWith=ScriptState, RaisesException] DOMString sanitizeToString(DOMString input);
};
dictionary SanitizerConfig {};
dictionary SanitizerConfig {
sequence<DOMString>? dropElements;
};
......@@ -8,52 +8,81 @@
<body>
<script>
const default_option ={dropElements: null};
test(t => {
let s = new Sanitizer();
assert_true(s instanceof Sanitizer);
assert_object_equals(s.creationOptions, {});
assert_object_equals(s.creationOptions, default_option);
}, "SanitizerAPI creator without config.");
test(t => {
let s = new Sanitizer({});
assert_true(s instanceof Sanitizer);
assert_object_equals(s.creationOptions, {});
assert_object_equals(s.creationOptions, default_option);
}, "SanitizerAPI creator with empty config.");
test(t => {
let s = new Sanitizer(null);
assert_true(s instanceof Sanitizer);
assert_object_equals(s.creationOptions, {});
assert_object_equals(s.creationOptions, default_option);
}, "SanitizerAPI creator with null as config.");
test(t => {
let s = new Sanitizer(undefined);
assert_true(s instanceof Sanitizer);
assert_object_equals(s.creationOptions, {});
assert_object_equals(s.creationOptions, default_option);
}, "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);
assert_object_equals(s.creationOptions, {});
assert_object_equals(s.creationOptions, default_option);
}, "SanitizerAPI creator with config ignore unknown values.");
test(t => {
let options = {};
let options = {dropElements: ["div"]};
let s = new Sanitizer(options);
assert_true(s instanceof Sanitizer);
assert_object_equals(s.creationOptions, {});
assert_object_equals(s.creationOptions, {dropElements: ["DIV"]});
options.testConfig = [1,2,3];
assert_object_equals(s.creationOptions, {});
options.dropElements.push("test");
assert_object_equals(s.creationOptions, {dropElements: ["DIV"]});
s.creationOptions = {testConfig: [1,2,3]};
assert_object_equals(s.creationOptions, {});
s.creationOptions = {dropElements: ["test", "t"]};
assert_object_equals(s.creationOptions, {dropElements: ["DIV"]});
s.creationOptions['testConfig'] = [1,2,3];
assert_object_equals(s.creationOptions, {});
s.creationOptions['dropElements'] = [1,2,3];
assert_object_equals(s.creationOptions, {dropElements: ["DIV"]});
}, "SanitizerAPI config is not editable.");
test(t => {
let s = new Sanitizer({dropElements: []});
assert_true(s instanceof Sanitizer);
assert_equals(s.sanitizeToString("<div>balabala<i>test</i></div>"), "<div>balabala<i>test</i></div>");
}, "SanitizerAPI creator with config {dropElements: []}.")
test(t => {
let s = new Sanitizer({dropElements: null});
assert_true(s instanceof Sanitizer);
assert_true(s.creationOptions instanceof Object);
assert_object_equals(s.creationOptions, default_option);
}, "SanitizerAPI creator with config {dropElements: null}.")
test(t => {
let s = new Sanitizer({dropElements: undefined});
assert_true(s instanceof Sanitizer);
assert_true(s.creationOptions instanceof Object);
assert_object_equals(s.creationOptions, default_option);
}, "SanitizerAPI creator with config {dropElements: undefined}.");
test(t => {
assert_throws_js(TypeError, _ => {let s = new Sanitizer({dropElements: 123})});
}, "SanitizerAPI creator with config {dropElements: 123}.");
test(t => {
assert_throws_js(TypeError, _ => {let s = new Sanitizer({dropElements: "div"})});
}, "SanitizerAPI creator with config {dropElements: div}.");
</script>
</body>
</html>
......@@ -13,5 +13,13 @@ PASS SanitizerAPI sanitize function for document
PASS SanitizerAPI sanitize function for html without close tag
FAIL SanitizerAPI sanitize function for scripts assert_equals: expected "" but got "<script>alert('i am a test')</script>"
FAIL SanitizerAPI sanitize function for onclick scripts assert_equals: expected "<p>Click.</p>" but got "<p onclick=\"a= 123\">Click.</p>"
PASS SanitizerAPI sanitize function for invalid config_input
PASS SanitizerAPI sanitize function for empty dropElements list
PASS SanitizerAPI sanitize function for test html without close tag with dropElements list ['div']
PASS SanitizerAPI sanitize function for test script with ["script"] as dropElements list
PASS SanitizerAPI sanitize function for dropElements list ["test", "i"]}
PASS SanitizerAPI sanitize function for dropElements list ["I", "AM"]}
PASS SanitizerAPI sanitize function for dropElements list ["am", "p"]}
PASS SanitizerAPI sanitize function for dropElements list with invalid values}
Harness: the test ran to completion.
......@@ -13,9 +13,12 @@
d.appendChild(fragment);
return d.innerHTML;
}
const s = new Sanitizer({});
testcases.forEach(c => test(t => {
let s = new Sanitizer(c.config_input);
assert_true(s.creationOptions instanceof Object);
assert_object_equals(s.creationOptions, c.config_value);
fragment = s.sanitize(c.value);
assert_true(fragment instanceof DocumentFragment);
assert_equals(getString(fragment), c.result);
......
......@@ -13,5 +13,13 @@ PASS SanitizerAPI sanitizeToString function for document
PASS SanitizerAPI sanitizeToString function for html without close tag
FAIL SanitizerAPI sanitizeToString function for scripts assert_equals: expected "" but got "<script>alert('i am a test')</script>"
FAIL SanitizerAPI sanitizeToString function for onclick scripts assert_equals: expected "<p>Click.</p>" but got "<p onclick=\"a= 123\">Click.</p>"
PASS SanitizerAPI sanitizeToString function for invalid config_input
PASS SanitizerAPI sanitizeToString function for empty dropElements list
PASS SanitizerAPI sanitizeToString function for test html without close tag with dropElements list ['div']
PASS SanitizerAPI sanitizeToString function for test script with ["script"] as dropElements list
PASS SanitizerAPI sanitizeToString function for dropElements list ["test", "i"]}
PASS SanitizerAPI sanitizeToString function for dropElements list ["I", "AM"]}
PASS SanitizerAPI sanitizeToString function for dropElements list ["am", "p"]}
PASS SanitizerAPI sanitizeToString function for dropElements list with invalid values}
Harness: the test ran to completion.
......@@ -8,9 +8,10 @@
<body>
<script>
const s = new Sanitizer({});
testcases.forEach(c => test(t => {
let s = new Sanitizer(c.config_input);
assert_true(s.creationOptions instanceof Object);
assert_object_equals(s.creationOptions, c.config_value);
assert_equals(s.sanitizeToString(c.value), c.result);
}, "SanitizerAPI sanitizeToString function for " + c.message));
</script>
......
const testcases = [
{value: "test", result: "test", message: "string"},
{value: "<b>bla</b>", result: "<b>bla</b>", message: "html fragment"},
{value: "<a<embla", result: "", message: "broken html"},
{value: {}, result: "[object Object]", message: "empty object"},
{value: 1, result: "1", message: "number"},
{value: 000, result: "0", message: "zeros"},
{value: 1+2, result: "3", message: "arithmetic"},
{value: "", result: "", message: "empty string"},
{value: undefined, result: "undefined", message: "undefined"},
{value: null, result: "null", message: "null"},
{value: "<html><head></head><body>test</body></html>", result: "test", message: "document"},
{value: "<div>test", result: "<div>test</div>", message: "html without close tag"},
{value: "<script>alert('i am a test')<\/script>", result: "", message: "scripts"},
{value: "<p onclick='a= 123'>Click.</p>", result: "<p>Click.</p>", message: "onclick scripts"}
{config_input: {}, config_value: {dropElements: null}, value: "test", result: "test", message: "string"},
{config_input: {}, config_value: {dropElements: null}, value: "<b>bla</b>", result: "<b>bla</b>", message: "html fragment"},
{config_input: {}, config_value: {dropElements: null}, value: "<a<embla", result: "", message: "broken html"},
{config_input: {}, config_value: {dropElements: null}, value: {}, result: "[object Object]", message: "empty object"},
{config_input: {}, config_value: {dropElements: null}, value: 1, result: "1", message: "number"},
{config_input: {}, config_value: {dropElements: null}, value: 000, result: "0", message: "zeros"},
{config_input: {}, config_value: {dropElements: null}, value: 1+2, result: "3", message: "arithmetic"},
{config_input: {}, config_value: {dropElements: null}, value: "", result: "", message: "empty string"},
{config_input: {}, config_value: {dropElements: null}, value: undefined, result: "undefined", message: "undefined"},
{config_input: {}, config_value: {dropElements: null}, value: null, result: "null", message: "null"},
{config_input: {}, config_value: {dropElements: null}, value: "<html><head></head><body>test</body></html>", result: "test", message: "document"},
{config_input: {}, config_value: {dropElements: null}, value: "<div>test", result: "<div>test</div>", message: "html without close tag"},
{config_input: {}, config_value: {dropElements: null}, value: "<script>alert('i am a test')<\/script>", result: "", message: "scripts"},
{config_input: {}, config_value: {dropElements: null}, value: "<p onclick='a= 123'>Click.</p>", result: "<p>Click.</p>", message: "onclick scripts"},
{config_input: {test: 123}, config_value: {dropElements: null}, value: "test", result: "test", message: "invalid config_input"},
{config_input: {dropElements: []}, config_value: {dropElements:[]}, value: "test", result: "test", message: "empty dropElements list"},
{config_input: {dropElements: ["div"]}, config_value: {dropElements:["DIV"]}, value: "<div>test</div><c>bla", result: "<c>bla</c>", message: "test html without close tag with dropElements list ['div']"},
{config_input: {dropElements: ["script"]}, config_value: {dropElements:["SCRIPT"]}, value: "<script>alert('i am a test')<\/script>", result: "", message: "test script with [\"script\"] as dropElements list"},
{config_input: {dropElements: ["test", "i"]}, config_value: {dropElements:["TEST","I"]}, value: "<div>balabala<i>test</i></div><test>t</test>", result: "<div>balabala</div>", message: "dropElements list [\"test\", \"i\"]}"},
{config_input: {dropElements: ["I", "AM"]}, config_value: {dropElements:["I", "AM"]}, value: "<div>balabala<am>test</am></div>", result: "<div>balabala</div>", message: "dropElements list [\"I\", \"AM\"]}"},
{config_input: {dropElements: ["am", "p"]}, config_value: {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"]}, config_value: {dropElements:["123","","TEST","I"]}, value: "<div>balabala<i>test</i></div><test>t</test>", result: "<div>balabala</div>", message: "dropElements list with invalid values}"}
];
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