diff options
Diffstat (limited to 'chromium/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc')
-rw-r--r-- | chromium/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc | 244 |
1 files changed, 175 insertions, 69 deletions
diff --git a/chromium/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc b/chromium/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc index 320636766ff..adcc31578a2 100644 --- a/chromium/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc +++ b/chromium/third_party/blink/renderer/modules/sanitizer_api/sanitizer.cc @@ -4,61 +4,92 @@ #include "sanitizer.h" +#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h" #include "third_party/blink/renderer/bindings/core/v8/v8_node_filter.h" +#include "third_party/blink/renderer/bindings/core/v8/v8_parse_from_string_options.h" #include "third_party/blink/renderer/bindings/modules/v8/string_or_document_fragment_or_document.h" #include "third_party/blink/renderer/bindings/modules/v8/string_or_trusted_html_or_document_fragment_or_document.h" #include "third_party/blink/renderer/bindings/modules/v8/v8_sanitizer_config.h" #include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document_fragment.h" +#include "third_party/blink/renderer/core/dom/document_init.h" +#include "third_party/blink/renderer/core/dom/dom_implementation.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/dom/range.h" #include "third_party/blink/renderer/core/editing/serializers/serialization.h" +#include "third_party/blink/renderer/core/execution_context/execution_context.h" #include "third_party/blink/renderer/core/frame/local_dom_window.h" +#include "third_party/blink/renderer/core/html/custom/custom_element.h" +#include "third_party/blink/renderer/core/html/html_collection.h" +#include "third_party/blink/renderer/core/html/html_element.h" +#include "third_party/blink/renderer/core/html/html_head_element.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_html.h" #include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h" +#include "third_party/blink/renderer/core/xml/dom_parser.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/instrumentation/use_counter.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, +Sanitizer* Sanitizer::Create(ExecutionContext* execution_context, + const SanitizerConfig* config, ExceptionState& exception_state) { - return MakeGarbageCollected<Sanitizer>(config); + return MakeGarbageCollected<Sanitizer>(execution_context, config); } -Sanitizer::Sanitizer(const SanitizerConfig* config) { +Sanitizer::Sanitizer(ExecutionContext* execution_context, + const SanitizerConfig* config) + : allow_custom_elements_(config->allowCustomElements()) { + bool use_default_config = true; + if (config->allowCustomElements()) { + use_default_config = false; + } + // Format dropElements to uppercase. drop_elements_ = default_drop_elements_; if (config->hasDropElements()) { ElementFormatter(drop_elements_, config->dropElements()); + use_default_config = false; } // Format blockElements to uppercase. block_elements_ = default_block_elements_; if (config->hasBlockElements()) { ElementFormatter(block_elements_, config->blockElements()); + use_default_config = false; } // Format allowElements to uppercase. if (config->hasAllowElements()) { has_allow_elements_ = true; ElementFormatter(allow_elements_, config->allowElements()); + use_default_config = false; } // Format dropAttributes to lowercase. drop_attributes_ = default_drop_attributes_; if (config->hasDropAttributes()) { AttrFormatter(drop_attributes_, config->dropAttributes()); + use_default_config = false; } // Format allowAttributes to lowercase. if (config->hasAllowAttributes()) { has_allow_attributes_ = true; AttrFormatter(allow_attributes_, config->allowAttributes()); + use_default_config = false; + } + + if (use_default_config) { + // TODO(lyf): Add unit tests for counters. + UseCounter::Count(execution_context, + WebFeature::kSanitizerAPIDefaultConfiguration); } } @@ -74,8 +105,8 @@ void Sanitizer::AttrFormatter( 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>({"*"})); + if (pair.second == kVectorStar || pair.second.Contains("*")) { + attr_map.insert(lower_attr, kVectorStar); } else { Vector<String> elements; for (const String& s : pair.second) { @@ -129,18 +160,37 @@ DocumentFragment* Sanitizer::SanitizeImpl( DocumentFragment* fragment = nullptr; LocalDOMWindow* window = LocalDOMWindow::From(script_state); + if (!window) { + exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, + "Cannot find current DOM window."); + return nullptr; + } if (input.IsDocumentFragment()) { + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIFromFragment); fragment = input.GetAsDocumentFragment(); - } else if (window) { - Document* document = window->document(); - if (input.IsString() || input.IsNull()) { - fragment = document->createDocumentFragment(); - DCHECK(document->QuerySelector("body")); - fragment->ParseHTML(input.GetAsString(), document->QuerySelector("body")); - } else { - fragment = document->createDocumentFragment(); - fragment->appendChild(input.GetAsDocument()->documentElement()); - } + } else if (input.IsString() || input.IsNull()) { + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIFromString); + + Document* document = + window->document() + ? window->document()->implementation().createHTMLDocument() + : DOMParser::Create(script_state) + ->parseFromString("<!DOCTYPE html><html><body></body></html>", + "text/html", + ParseFromStringOptions::Create()); + // TODO(https://crbug.com/1178774): Behavior difference need further + // investgate. + fragment = document->createRange()->createContextualFragment( + input.GetAsString(), exception_state); + } else if (input.IsDocument()) { + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIFromDocument); + + fragment = input.GetAsDocument()->createDocumentFragment(); + fragment->CloneChildNodesFrom(*(input.GetAsDocument()->body()), + CloneChildrenFlag::kClone); } else { exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError, "Cannot find current DOM window."); @@ -157,67 +207,123 @@ DocumentFragment* Sanitizer::SanitizeImpl( } // 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 (drop_elements_.Contains(node_name)) { - Node* tmp = node; - node = NodeTraversal::NextSkippingChildren(*node, fragment); - tmp->remove(); - } else if (block_elements_.Contains(node_name) || - (has_allow_elements_ && !allow_elements_.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(); + // 1. Let |name| be |element|'s tag name. + String name = node->nodeName().UpperASCII(); + // 2. Classify elements into one of three kinds: kCustom, kUnknown, kRegular + ElementKind kind = ElementKind::kRegular; + if (CustomElement::IsValidName(AtomicString(name.LowerASCII()), false)) { + kind = ElementKind::kCustom; + } else if (IsA<HTMLElement>(node) && + To<HTMLElement>(node)->IsHTMLUnknownElement()) { + kind = ElementKind::kUnknown; + } + + // 3. If |kind| is `regular` and if |name| is not contained in the + // default element allow list, then 'drop' + if (kind == ElementKind::kRegular && + !default_allow_elements_.Contains(name)) { + node = DropElement(node, fragment); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); + } else if (kind == ElementKind::kCustom && !allow_custom_elements_) { + // 4. If |kind| is `custom` and if allow_custom_elements_ is unset or set + // to anything other than `true`, then 'drop'. + node = DropElement(node, fragment); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); + } else if (drop_elements_.Contains(name)) { + // 5. If |name| is in |config|'s [=element drop list=] then 'drop'. + node = DropElement(node, fragment); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); + } else if (block_elements_.Contains(name)) { + // 6. If |name| is in |config|'s [=element block list=] then 'block'. + node = BlockElement(node, fragment, exception_state); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); + } else if (has_allow_elements_ && !allow_elements_.Contains(name)) { + // 7. if |config| has a non-empty [=element allow list=] and |name| is + // not in |config|'s [=element allow list=] then 'block'. + node = BlockElement(node, fragment, exception_state); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); } 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); - if (has_allow_attributes_ && - allow_attributes_.at("*").Contains(node_name)) { - } else if (drop_attributes_.at("*").Contains(node_name)) { - for (const auto& name : element->getAttributeNames()) { - element->removeAttribute(name); - } - } else { - for (const auto& name : element->getAttributeNames()) { - // Attributes in drop list or not in allow list while allow list - // exists will be dropped. - 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 = KeepElement(node, fragment, name, window); } } return fragment; } +// If the current element needs to be dropped, remove current element entirely +// and proceed to its next sibling. +Node* Sanitizer::DropElement(Node* node, DocumentFragment* fragment) { + Node* tmp = node; + node = NodeTraversal::NextSkippingChildren(*node, fragment); + tmp->remove(); + return node; +} + +// If the current element should be blocked, append its children after current +// node to parent node, remove current element and proceed to the next node. +Node* Sanitizer::BlockElement(Node* node, + DocumentFragment* fragment, + ExceptionState& exception_state) { + 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(); + return node; +} + +// Remove any attributes to be dropped from the current element, and proceed to +// the next node (preorder, depth-first traversal). +Node* Sanitizer::KeepElement(Node* node, + DocumentFragment* fragment, + String& node_name, + LocalDOMWindow* window) { + Element* element = To<Element>(node); + if (has_allow_attributes_ && allow_attributes_.at("*").Contains(node_name)) { + } else if (drop_attributes_.at("*").Contains(node_name)) { + for (const auto& name : element->getAttributeNames()) { + element->removeAttribute(name); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); + } + } else { + for (const auto& name : element->getAttributeNames()) { + // Attributes in drop list or not in allow list while allow list + // exists will be dropped. + bool drop = (drop_attributes_.Contains(name) && + (drop_attributes_.at(name) == kVectorStar || + drop_attributes_.at(name).Contains(node_name))) || + (has_allow_attributes_ && + !(allow_attributes_.Contains(name) && + (allow_attributes_.at(name) == kVectorStar || + allow_attributes_.at(name).Contains(node_name)))); + if (drop) { + element->removeAttribute(name); + UseCounter::Count(window->GetExecutionContext(), + WebFeature::kSanitizerAPIActionTaken); + } + } + } + node = NodeTraversal::Next(*node, fragment); + return node; +} + void Sanitizer::Trace(Visitor* visitor) const { ScriptWrappable::Trace(visitor); } |