blob: 32e2fb694c84d61e783c09d8b2428fa5572836c0 [file] [log] [blame]
// Copyright 2020 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#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/element.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/text/atomic_string.h"
#include "third_party/blink/renderer/platform/wtf/vector.h"
namespace blink {
Sanitizer* Sanitizer::Create(const SanitizerConfig* config,
ExceptionState& exception_state) {
return MakeGarbageCollected<Sanitizer>(config);
}
Sanitizer::Sanitizer(const SanitizerConfig* config)
: config_(const_cast<SanitizerConfig*>(config)) {
// Format dropElements to uppercase.
Vector<String> drop_elements = default_drop_elements_;
if (config->hasDropElements()) {
for (const String& s : config->dropElements()) {
if (!drop_elements.Contains(s.UpperASCII())) {
drop_elements.push_back(s.UpperASCII());
}
}
}
config_->setDropElements(drop_elements);
// Format allowElements to uppercase.
if (config->hasAllowElements()) {
Vector<String> l;
for (const String& s : config->allowElements()) {
if (!config_->dropElements().Contains(s))
l.push_back(s.UpperASCII());
}
config_->setAllowElements(l);
}
// Format dropAttributes to lowercase.
if (config->hasDropAttributes()) {
drop_attributes_ = default_drop_attributes_;
for (const String& s : config->dropAttributes()) {
drop_attributes_.push_back(WTF::AtomicString(s.LowerASCII()));
}
} else if (config->hasAllowAttributes()) {
Vector<String> l;
for (const String& s : config->allowAttributes()) {
if (!default_drop_attributes_.Contains(s))
l.push_back(s.LowerASCII());
}
config_->setAllowAttributes(l);
} else {
drop_attributes_ = default_drop_attributes_;
}
}
Sanitizer::~Sanitizer() = default;
String Sanitizer::sanitizeToString(ScriptState* script_state,
const String& input,
ExceptionState& exception_state) {
return CreateMarkup(sanitize(script_state, input, exception_state),
kChildrenOnly);
}
DocumentFragment* Sanitizer::sanitize(ScriptState* script_state,
const String& input,
ExceptionState& exception_state) {
LocalDOMWindow* window = LocalDOMWindow::From(script_state);
if (!window) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"Cannot find current DOM window.");
return nullptr;
}
Document* document = window->document();
DocumentFragment* fragment = document->createDocumentFragment();
DCHECK(document->QuerySelector("body"));
fragment->ParseHTML(input, document->QuerySelector("body"));
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().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);
}
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).
Element* element = To<Element>(node);
for (const auto& name : element->getAttributeNames()) {
bool drop = drop_attributes_.Contains(name) ||
(config_->hasAllowAttributes() &&
!config_->allowAttributes().Contains(name));
if (drop)
element->removeAttribute(name);
}
node = NodeTraversal::Next(*node, fragment);
}
}
return fragment;
}
void Sanitizer::Trace(Visitor* visitor) const {
ScriptWrappable::Trace(visitor);
visitor->Trace(config_);
}
} // namespace blink