blob: 9e032eac81e42b992849b2833b3d594096f05d65 [file] [log] [blame]
// Copyright 2021 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "third_party/blink/renderer/modules/sanitizer_api/config_util.h"
#include "third_party/blink/renderer/modules/sanitizer_api/builtins.h"
#include "third_party/blink/renderer/modules/sanitizer_api/sanitizer_config_impl.h"
#include "third_party/blink/renderer/platform/wtf/std_lib_extras.h"
#include "third_party/blink/renderer/platform/wtf/text/character_visitor.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
namespace blink {
SanitizerConfigImpl FromAPI(const SanitizerConfig* config) {
if (!config) {
return GetDefaultConfig();
}
SanitizerConfigImpl impl;
impl.allow_custom_elements_ =
config->hasAllowCustomElements() && config->allowCustomElements();
impl.allow_unknown_markup_ =
config->hasAllowUnknownMarkup() && config->allowUnknownMarkup();
impl.allow_comments_ = config->hasAllowComments() && config->allowComments();
// Format dropElements to lower case.
if (config->hasDropElements()) {
impl.drop_elements_ = FromAPI(config->dropElements());
}
// Format blockElements to lower case.
if (config->hasBlockElements()) {
impl.block_elements_ = FromAPI(config->blockElements());
}
// Format allowElements to lower case.
if (config->hasAllowElements()) {
impl.allow_elements_ = FromAPI(config->allowElements());
} else {
impl.allow_elements_ = GetDefaultConfig().allow_elements_;
}
// Format dropAttributes to lowercase.
if (config->hasDropAttributes()) {
impl.drop_attributes_ = FromAPI(config->dropAttributes());
}
// Format allowAttributes to lowercase.
if (config->hasAllowAttributes()) {
impl.allow_attributes_ = FromAPI(config->allowAttributes());
} else {
impl.allow_attributes_ = GetDefaultConfig().allow_attributes_;
}
impl.had_allow_elements_ = config->hasAllowElements();
impl.had_allow_attributes_ = config->hasAllowAttributes();
impl.had_allow_custom_elements_ = config->hasAllowCustomElements();
impl.had_allow_unknown_markup_ = config->hasAllowUnknownMarkup();
return impl;
}
SanitizerConfigImpl::ElementList FromAPI(const Vector<String>& elements) {
SanitizerConfigImpl::ElementList result;
for (const String& element : elements) {
const auto name = ElementFromAPI(element);
if (!IsInvalid(name))
result.insert(name);
}
return result;
}
SanitizerConfigImpl::AttributeList FromAPI(
const Vector<std::pair<String, Vector<String>>>& attrs) {
SanitizerConfigImpl::AttributeList result;
for (const std::pair<String, Vector<String>>& pair : attrs) {
const auto attr = AttributeOrWildcardFromAPI(pair.first);
if (!IsInvalid(attr)) {
result.insert(attr, pair.second.Contains("*") ? WildcardList()
: FromAPI(pair.second));
}
}
return result;
}
bool IsValidCharacter(UChar ch) {
// TODO(vogelheim): Sync well-formedness with Sanitizer spec
// The Sanitizer spec doesn't say much (yet. The HTML spec is a bit
// obtuse, but it seems to allow all XML names. The HTML parser however
// allows only ascii. Here, we settle for the simplest, most restrictive
// variant. May it's too restrictive, though.
// TODO(vogelheim): "HTML parser allows only ascii" is no longer true.
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') ||
(ch >= '0' && ch <= '9') || ch == '-' || ch == '_';
}
bool AllValidCharacters(const String& name) {
return WTF::VisitCharacters(name,
[&](const auto* chars, unsigned len) -> bool {
for (unsigned i = 0; i < len; i++) {
if (!IsValidCharacter(chars[i])) {
return false;
}
}
return true;
});
}
bool IsValidName(const String& name) {
return !name.empty() && AllValidCharacters(name);
}
String ElementFromAPI(const String& name) {
if (!IsValidName(name))
return Invalid();
return name;
}
String AttributeFromAPI(const String& name) {
if (!IsValidName(name))
return Invalid();
return name;
}
String AttributeOrWildcardFromAPI(const String& name) {
return (name == "*") ? Wildcard() : AttributeFromAPI(name);
}
SanitizerConfig* ToAPI(const SanitizerConfigImpl& impl) {
SanitizerConfig* config = SanitizerConfig::Create();
if (impl.had_allow_elements_) {
config->setAllowElements(ToAPI(impl.allow_elements_));
}
if (!impl.drop_elements_.empty()) {
config->setDropElements(ToAPI(impl.drop_elements_));
}
if (!impl.block_elements_.empty()) {
config->setBlockElements(ToAPI(impl.block_elements_));
}
if (impl.had_allow_attributes_) {
config->setAllowAttributes(ToAPI(impl.allow_attributes_));
}
if (!impl.drop_attributes_.empty()) {
config->setDropAttributes(ToAPI(impl.drop_attributes_));
}
if (impl.had_allow_unknown_markup_)
config->setAllowUnknownMarkup(impl.allow_unknown_markup_);
if (impl.had_allow_custom_elements_)
config->setAllowCustomElements(impl.allow_custom_elements_);
return config;
}
String ToAPI(const String& name) {
DCHECK(!IsInvalid(name));
return name;
}
Vector<String> ToAPI(const SanitizerConfigImpl::ElementList& set) {
Vector<String> result;
for (const auto& element : set)
result.push_back(ToAPI(element));
return result;
}
Vector<std::pair<String, Vector<String>>> ToAPI(
const SanitizerConfigImpl::AttributeList& attr_list) {
Vector<std::pair<String, Vector<String>>> result;
for (const auto& item : attr_list) {
result.push_back(std::make_pair(ToAPI(item.key), ToAPI(item.value)));
}
return result;
}
String Wildcard() {
return "*";
}
String Invalid() {
return String();
}
SanitizerConfigImpl::ElementList WildcardList() {
return {Wildcard()};
}
bool IsWildcard(const String& name) {
return name == "*";
}
bool IsInvalid(const String& name) {
return name.IsNull();
}
bool IsWildcardList(const SanitizerConfigImpl::ElementList& list) {
// ElementList construction should ensure that a wildcard list contains only
// the wildcard, and we don't have mixed lists with proper element names and
// wildcards in them.
DCHECK(!list.Contains(Wildcard()) || list.size() == 1);
return list.size() == 1 && IsWildcard(*list.begin());
}
bool Match(const String& element_name,
const SanitizerConfigImpl::ElementList& elements) {
// We only match against actual element names, not against "*" or empty
// strings.
DCHECK(!IsWildcard(element_name));
DCHECK(!IsInvalid(element_name));
return elements.Contains(element_name) || IsWildcardList(elements);
}
bool Match(const String& attribute_name,
const String& element_name,
const SanitizerConfigImpl::AttributeList& attributes) {
DCHECK(!IsInvalid(attribute_name));
const auto iter = attributes.find(attribute_name);
return iter != attributes.end() && Match(element_name, iter->value);
}
} // namespace blink