blob: 638295dcea55b4c711cf56e3e27d3cc0acfef4c6 [file] [log] [blame]
// Copyright 2018 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 "third_party/blink/renderer/core/trustedtypes/trusted_type_policy_factory.h"
#include "third_party/blink/public/mojom/web_feature/web_feature.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_value.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_trusted_html.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_trusted_script.h"
#include "third_party/blink/renderer/bindings/core/v8/v8_trusted_script_url.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/events/event.h"
#include "third_party/blink/renderer/core/events/before_create_policy_event.h"
#include "third_party/blink/renderer/core/execution_context/execution_context.h"
#include "third_party/blink/renderer/core/frame/csp/content_security_policy.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/probe/core_probes.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_type_policy.h"
#include "third_party/blink/renderer/core/trustedtypes/trusted_types_util.h"
#include "third_party/blink/renderer/platform/bindings/exception_state.h"
#include "third_party/blink/renderer/platform/bindings/v8_dom_wrapper.h"
#include "third_party/blink/renderer/platform/instrumentation/use_counter.h"
#include "third_party/blink/renderer/platform/runtime_enabled_features.h"
#include "third_party/blink/renderer/platform/wtf/text/string_hash.h"
namespace blink {
TrustedTypePolicy* TrustedTypePolicyFactory::createPolicy(
const String& policy_name,
ExceptionState& exception_state) {
return createPolicy(policy_name,
MakeGarbageCollected<TrustedTypePolicyOptions>(),
exception_state);
}
TrustedTypePolicy* TrustedTypePolicyFactory::createPolicy(
const String& policy_name,
const TrustedTypePolicyOptions* policy_options,
ExceptionState& exception_state) {
if (RuntimeEnabledFeatures::TrustedTypeBeforePolicyCreationEventEnabled()) {
DispatchEventResult result =
DispatchEvent(*BeforeCreatePolicyEvent::Create(policy_name));
if (result != DispatchEventResult::kNotCanceled) {
exception_state.ThrowDOMException(
DOMExceptionCode::kNotAllowedError,
"The policy creation has been canceled.");
return nullptr;
}
}
if (!GetExecutionContext()) {
exception_state.ThrowDOMException(DOMExceptionCode::kInvalidStateError,
"The document is detached.");
return nullptr;
}
UseCounter::Count(GetExecutionContext(),
WebFeature::kTrustedTypesCreatePolicy);
if (RuntimeEnabledFeatures::TrustedDOMTypesEnabled(GetExecutionContext()) &&
GetExecutionContext()->GetContentSecurityPolicy()) {
ContentSecurityPolicy::AllowTrustedTypePolicyDetails violation_details =
ContentSecurityPolicy::AllowTrustedTypePolicyDetails::kAllowed;
bool disallowed = !GetExecutionContext()
->GetContentSecurityPolicy()
->AllowTrustedTypePolicy(
policy_name, policy_map_.Contains(policy_name),
violation_details);
if (violation_details != ContentSecurityPolicy::ContentSecurityPolicy::
AllowTrustedTypePolicyDetails::kAllowed) {
// We may report a violation here even when disallowed is false
// in case policy is a report-only one.
probe::OnContentSecurityPolicyViolation(
GetExecutionContext(),
ContentSecurityPolicy::ContentSecurityPolicyViolationType::
kTrustedTypesPolicyViolation);
}
if (disallowed) {
// For a better error message, we'd like to disambiguate between
// "disallowed" and "disallowed because of a duplicate name".
bool disallowed_because_of_duplicate_name =
violation_details ==
ContentSecurityPolicy::AllowTrustedTypePolicyDetails::
kDisallowedDuplicateName;
const String message =
disallowed_because_of_duplicate_name
? "Policy with name \"" + policy_name + "\" already exists."
: "Policy \"" + policy_name + "\" disallowed.";
exception_state.ThrowTypeError(message);
return nullptr;
}
}
UseCounter::Count(GetExecutionContext(),
WebFeature::kTrustedTypesPolicyCreated);
if (policy_name == "default") {
DCHECK(!policy_map_.Contains("default"));
UseCounter::Count(GetExecutionContext(),
WebFeature::kTrustedTypesDefaultPolicyCreated);
}
auto* policy = MakeGarbageCollected<TrustedTypePolicy>(
policy_name, const_cast<TrustedTypePolicyOptions*>(policy_options));
policy_map_.insert(policy_name, policy);
return policy;
}
TrustedTypePolicy* TrustedTypePolicyFactory::defaultPolicy() const {
return policy_map_.at("default");
}
TrustedTypePolicyFactory::TrustedTypePolicyFactory(ExecutionContext* context)
: ExecutionContextClient(context),
empty_html_(MakeGarbageCollected<TrustedHTML>("")),
empty_script_(MakeGarbageCollected<TrustedScript>("")) {}
const WrapperTypeInfo*
TrustedTypePolicyFactory::GetWrapperTypeInfoFromScriptValue(
ScriptState* script_state,
const ScriptValue& script_value) {
v8::Local<v8::Value> value = script_value.V8Value();
if (value.IsEmpty() || !value->IsObject() ||
!V8DOMWrapper::IsWrapper(script_state->GetIsolate(), value))
return nullptr;
v8::Local<v8::Object> object = value.As<v8::Object>();
return ToWrapperTypeInfo(object);
}
bool TrustedTypePolicyFactory::isHTML(ScriptState* script_state,
const ScriptValue& script_value) {
const WrapperTypeInfo* wrapper_type_info =
GetWrapperTypeInfoFromScriptValue(script_state, script_value);
return wrapper_type_info &&
wrapper_type_info->Equals(V8TrustedHTML::GetWrapperTypeInfo());
}
bool TrustedTypePolicyFactory::isScript(ScriptState* script_state,
const ScriptValue& script_value) {
const WrapperTypeInfo* wrapper_type_info =
GetWrapperTypeInfoFromScriptValue(script_state, script_value);
return wrapper_type_info &&
wrapper_type_info->Equals(V8TrustedScript::GetWrapperTypeInfo());
}
bool TrustedTypePolicyFactory::isScriptURL(ScriptState* script_state,
const ScriptValue& script_value) {
const WrapperTypeInfo* wrapper_type_info =
GetWrapperTypeInfoFromScriptValue(script_state, script_value);
return wrapper_type_info &&
wrapper_type_info->Equals(V8TrustedScriptURL::GetWrapperTypeInfo());
}
TrustedHTML* TrustedTypePolicyFactory::emptyHTML() const {
return empty_html_.Get();
}
TrustedScript* TrustedTypePolicyFactory::emptyScript() const {
return empty_script_.Get();
}
const struct {
const char* element;
const char* property;
const char* element_namespace;
SpecificTrustedType type;
// We use this table for both attributes and properties. Most are both,
// because DOM "reflects" the attributes onto their specified poperties.
// We use is_not_* so that the default value initialization (false) will
// match the common case and we only need to explicitly provide the uncommon
// values.
bool is_not_property : 1;
bool is_not_attribute : 1;
} kTypeTable[] = {
{"embed", "src", nullptr, SpecificTrustedType::kScriptURL},
{"iframe", "srcdoc", nullptr, SpecificTrustedType::kHTML},
{"object", "codeBase", nullptr, SpecificTrustedType::kScriptURL},
{"object", "data", nullptr, SpecificTrustedType::kScriptURL},
{"script", "innerText", nullptr, SpecificTrustedType::kScript, false, true},
{"script", "src", nullptr, SpecificTrustedType::kScriptURL},
{"script", "text", nullptr, SpecificTrustedType::kScript, false, true},
{"script", "textContent", nullptr, SpecificTrustedType::kScript, false,
true},
{"*", "innerHTML", nullptr, SpecificTrustedType::kHTML, false, true},
{"*", "outerHTML", nullptr, SpecificTrustedType::kHTML, false, true},
{"*", "on*", nullptr, SpecificTrustedType::kScript, true, false},
};
// Does a type table entry match a property?
// (Properties are evaluated by JavaScript and are case-sensitive.)
bool EqualsProperty(decltype(*kTypeTable)& left,
const String& tag,
const String& attr,
const String& ns) {
DCHECK_EQ(tag.LowerASCII(), tag);
return (left.element == tag || !strcmp(left.element, "*")) &&
(left.property == attr ||
(!strcmp(left.property, "on*") && attr.StartsWith("on"))) &&
left.element_namespace == ns && !left.is_not_property;
}
// Does a type table entry match an attribute?
// (Attributes get queried by calling acecssor methods on the DOM. These are
// case-insensitivem, because DOM.)
bool EqualsAttribute(decltype(*kTypeTable)& left,
const String& tag,
const String& attr,
const String& ns) {
DCHECK_EQ(tag.LowerASCII(), tag);
return (left.element == tag || !strcmp(left.element, "*")) &&
(String(left.property).LowerASCII() == attr.LowerASCII() ||
(!strcmp(left.property, "on*") && attr.StartsWith("on"))) &&
left.element_namespace == ns && !left.is_not_attribute;
}
String getTrustedTypeName(SpecificTrustedType type) {
switch (type) {
case SpecificTrustedType::kHTML:
return "TrustedHTML";
case SpecificTrustedType::kScript:
return "TrustedScript";
case SpecificTrustedType::kScriptURL:
return "TrustedScriptURL";
case SpecificTrustedType::kNone:
return String();
}
}
typedef bool (*PropertyEqualsFn)(decltype(*kTypeTable)&,
const String&,
const String&,
const String&);
String FindTypeInTypeTable(const String& tagName,
const String& propertyName,
const String& elementNS,
PropertyEqualsFn equals) {
SpecificTrustedType type = SpecificTrustedType::kNone;
for (auto* it = std::cbegin(kTypeTable); it != std::cend(kTypeTable); it++) {
if ((*equals)(*it, tagName, propertyName, elementNS)) {
type = it->type;
break;
}
}
return getTrustedTypeName(type);
}
String TrustedTypePolicyFactory::getPropertyType(
const String& tagName,
const String& propertyName,
const String& elementNS) const {
return FindTypeInTypeTable(tagName.LowerASCII(), propertyName, elementNS,
&EqualsProperty);
}
String TrustedTypePolicyFactory::getAttributeType(
const String& tagName,
const String& attributeName,
const String& tagNS,
const String& attributeNS) const {
return FindTypeInTypeTable(tagName.LowerASCII(), attributeName, tagNS,
&EqualsAttribute);
}
String TrustedTypePolicyFactory::getPropertyType(
const String& tagName,
const String& propertyName) const {
return getPropertyType(tagName, propertyName, String());
}
String TrustedTypePolicyFactory::getAttributeType(
const String& tagName,
const String& attributeName) const {
return getAttributeType(tagName, attributeName, String(), String());
}
String TrustedTypePolicyFactory::getAttributeType(const String& tagName,
const String& attributeName,
const String& tagNS) const {
return getAttributeType(tagName, attributeName, tagNS, String());
}
ScriptValue TrustedTypePolicyFactory::getTypeMapping(
ScriptState* script_state) const {
return getTypeMapping(script_state, String());
}
ScriptValue TrustedTypePolicyFactory::getTypeMapping(ScriptState* script_state,
const String& ns) const {
// Create three-deep dictionary of properties, like so:
// {tagname: { ["attributes"|"properties"]: { attribute: type }}}
if (!ns.IsEmpty())
return ScriptValue();
v8::HandleScope handle_scope(script_state->GetIsolate());
v8::Local<v8::Object> top = v8::Object::New(script_state->GetIsolate());
v8::Local<v8::Object> properties;
v8::Local<v8::Object> attributes;
const char* element = nullptr;
for (const auto& iter : kTypeTable) {
if (properties.IsEmpty() || !element || strcmp(iter.element, element)) {
element = iter.element;
v8::Local<v8::Object> middle =
v8::Object::New(script_state->GetIsolate());
top->Set(script_state->GetContext(),
V8String(script_state->GetIsolate(), iter.element), middle)
.Check();
properties = v8::Object::New(script_state->GetIsolate());
middle
->Set(script_state->GetContext(),
V8String(script_state->GetIsolate(), "properties"), properties)
.Check();
attributes = v8::Object::New(script_state->GetIsolate());
middle
->Set(script_state->GetContext(),
V8String(script_state->GetIsolate(), "attributes"), attributes)
.Check();
}
if (!iter.is_not_property) {
properties
->Set(script_state->GetContext(),
V8String(script_state->GetIsolate(), iter.property),
V8String(script_state->GetIsolate(),
getTrustedTypeName(iter.type)))
.Check();
}
if (!iter.is_not_attribute) {
attributes
->Set(script_state->GetContext(),
V8String(script_state->GetIsolate(), iter.property),
V8String(script_state->GetIsolate(),
getTrustedTypeName(iter.type)))
.Check();
}
}
return ScriptValue(script_state->GetIsolate(), top);
}
void TrustedTypePolicyFactory::CountTrustedTypeAssignmentError() {
if (!hadAssignmentError) {
UseCounter::Count(GetExecutionContext(),
WebFeature::kTrustedTypesAssignmentError);
hadAssignmentError = true;
}
}
const AtomicString& TrustedTypePolicyFactory::InterfaceName() const {
return event_target_names::kTrustedTypePolicyFactory;
}
ExecutionContext* TrustedTypePolicyFactory::GetExecutionContext() const {
return ExecutionContextClient::GetExecutionContext();
}
void TrustedTypePolicyFactory::Trace(Visitor* visitor) const {
EventTargetWithInlineData::Trace(visitor);
ExecutionContextClient::Trace(visitor);
visitor->Trace(empty_html_);
visitor->Trace(empty_script_);
visitor->Trace(policy_map_);
}
} // namespace blink