blob: a6ab2cbe6b459c9476c451bf4eacd6c761669b49 [file] [log] [blame]
// Copyright 2014 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/frame/csp/csp_directive_list.h"
#include <memory>
#include <utility>
#include <vector>
#include "third_party/blink/renderer/bindings/core/v8/source_location.h"
#include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/space_split_string.h"
#include "third_party/blink/renderer/core/execution_context/security_context.h"
#include "third_party/blink/renderer/core/frame/deprecation.h"
#include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/core/frame/use_counter.h"
#include "third_party/blink/renderer/core/html/html_script_element.h"
#include "third_party/blink/renderer/core/inspector/console_message.h"
#include "third_party/blink/renderer/platform/crypto.h"
#include "third_party/blink/renderer/platform/network/content_security_policy_parsers.h"
#include "third_party/blink/renderer/platform/weborigin/kurl.h"
#include "third_party/blink/renderer/platform/wtf/text/base64.h"
#include "third_party/blink/renderer/platform/wtf/text/parsing_utilities.h"
#include "third_party/blink/renderer/platform/wtf/text/string_builder.h"
#include "third_party/blink/renderer/platform/wtf/text/string_utf8_adaptor.h"
#include "third_party/blink/renderer/platform/wtf/text/wtf_string.h"
namespace blink {
namespace {
String GetSha256String(const String& content) {
DigestValue digest;
StringUTF8Adaptor utf8_content(content);
bool digest_success = ComputeDigest(kHashAlgorithmSha256, utf8_content.Data(),
utf8_content.length(), digest);
if (!digest_success) {
return "sha256-...";
}
return "sha256-" + Base64Encode(reinterpret_cast<char*>(digest.data()),
digest.size(), kBase64DoNotInsertLFs);
}
ContentSecurityPolicyHashAlgorithm ConvertHashAlgorithmToCSPHashAlgorithm(
IntegrityAlgorithm algorithm) {
switch (algorithm) {
case IntegrityAlgorithm::kSha256:
return kContentSecurityPolicyHashAlgorithmSha256;
case IntegrityAlgorithm::kSha384:
return kContentSecurityPolicyHashAlgorithmSha384;
case IntegrityAlgorithm::kSha512:
return kContentSecurityPolicyHashAlgorithmSha512;
case IntegrityAlgorithm::kEd25519:
return kContentSecurityPolicyHashAlgorithmEd25519;
}
NOTREACHED();
return kContentSecurityPolicyHashAlgorithmNone;
}
// IntegrityMetadata (from SRI) has base64-encoded digest values, but CSP uses
// binary format. This converts from the former to the latter.
bool ParseBase64Digest(String base64, DigestValue* hash) {
Vector<char> hash_vector;
// We accept base64url-encoded data here by normalizing it to base64.
if (!Base64Decode(NormalizeToBase64(base64), hash_vector))
return false;
if (hash_vector.IsEmpty() || hash_vector.size() > kMaxDigestSize)
return false;
hash->Append(reinterpret_cast<uint8_t*>(hash_vector.data()),
hash_vector.size());
return true;
}
} // namespace
CSPDirectiveList::CSPDirectiveList(ContentSecurityPolicy* policy,
ContentSecurityPolicyHeaderType type,
ContentSecurityPolicyHeaderSource source)
: policy_(policy),
header_type_(type),
header_source_(source),
has_sandbox_policy_(false),
strict_mixed_content_checking_enforced_(false),
upgrade_insecure_requests_(false),
treat_as_public_address_(false),
require_sri_for_(RequireSRIForToken::kNone),
use_reporting_api_(false) {}
CSPDirectiveList* CSPDirectiveList::Create(
ContentSecurityPolicy* policy,
const UChar* begin,
const UChar* end,
ContentSecurityPolicyHeaderType type,
ContentSecurityPolicyHeaderSource source,
bool should_parse_wasm_eval) {
CSPDirectiveList* directives = new CSPDirectiveList(policy, type, source);
directives->Parse(begin, end, should_parse_wasm_eval);
if (!directives->CheckEval(directives->OperativeDirective(
ContentSecurityPolicy::DirectiveType::kScriptSrc))) {
String message =
"Refused to evaluate a string as JavaScript because 'unsafe-eval' is "
"not an allowed source of script in the following Content Security "
"Policy directive: \"" +
directives
->OperativeDirective(
ContentSecurityPolicy::DirectiveType::kScriptSrc)
->GetText() +
"\".\n";
directives->SetEvalDisabledErrorMessage(message);
}
if (directives->IsReportOnly() &&
source != kContentSecurityPolicyHeaderSourceMeta &&
directives->ReportEndpoints().IsEmpty())
policy->ReportMissingReportURI(String(begin, end - begin));
return directives;
}
void CSPDirectiveList::ReportViolation(
const String& directive_text,
const ContentSecurityPolicy::DirectiveType effective_type,
const String& console_message,
const KURL& blocked_url,
ResourceRequest::RedirectStatus redirect_status) const {
String message =
IsReportOnly() ? "[Report Only] " + console_message : console_message;
policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource,
kErrorMessageLevel, message));
policy_->ReportViolation(directive_text, effective_type, message, blocked_url,
report_endpoints_, use_reporting_api_, header_,
header_type_, ContentSecurityPolicy::kURLViolation,
std::unique_ptr<SourceLocation>(),
nullptr, // localFrame
redirect_status);
}
void CSPDirectiveList::ReportViolationWithFrame(
const String& directive_text,
const ContentSecurityPolicy::DirectiveType effective_type,
const String& console_message,
const KURL& blocked_url,
LocalFrame* frame) const {
String message =
IsReportOnly() ? "[Report Only] " + console_message : console_message;
policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource,
kErrorMessageLevel, message),
frame);
policy_->ReportViolation(directive_text, effective_type, message, blocked_url,
report_endpoints_, use_reporting_api_, header_,
header_type_, ContentSecurityPolicy::kURLViolation,
std::unique_ptr<SourceLocation>(), frame);
}
void CSPDirectiveList::ReportViolationWithLocation(
const String& directive_text,
const ContentSecurityPolicy::DirectiveType effective_type,
const String& console_message,
const KURL& blocked_url,
const String& context_url,
const WTF::OrdinalNumber& context_line,
Element* element,
const String& source) const {
String message =
IsReportOnly() ? "[Report Only] " + console_message : console_message;
std::unique_ptr<SourceLocation> source_location =
SourceLocation::Capture(context_url, context_line.OneBasedInt(), 0);
policy_->LogToConsole(ConsoleMessage::Create(kSecurityMessageSource,
kErrorMessageLevel, message,
source_location->Clone()));
policy_->ReportViolation(directive_text, effective_type, message, blocked_url,
report_endpoints_, use_reporting_api_, header_,
header_type_,
ContentSecurityPolicy::kInlineViolation,
std::move(source_location), nullptr, // localFrame
RedirectStatus::kNoRedirect, element, source);
}
void CSPDirectiveList::ReportEvalViolation(
const String& directive_text,
const ContentSecurityPolicy::DirectiveType effective_type,
const String& message,
const KURL& blocked_url,
ScriptState* script_state,
const ContentSecurityPolicy::ExceptionStatus exception_status,
const String& content) const {
String report_message = IsReportOnly() ? "[Report Only] " + message : message;
// Print a console message if it won't be redundant with a
// JavaScript exception that the caller will throw. (Exceptions will
// never get thrown in report-only mode because the caller won't see
// a violation.)
if (IsReportOnly() ||
exception_status == ContentSecurityPolicy::kWillNotThrowException) {
ConsoleMessage* console_message = ConsoleMessage::Create(
kSecurityMessageSource, kErrorMessageLevel, report_message);
policy_->LogToConsole(console_message);
}
policy_->ReportViolation(directive_text, effective_type, message, blocked_url,
report_endpoints_, use_reporting_api_, header_,
header_type_, ContentSecurityPolicy::kEvalViolation,
std::unique_ptr<SourceLocation>(), nullptr,
RedirectStatus::kFollowedRedirect, nullptr, content);
}
bool CSPDirectiveList::CheckEval(SourceListDirective* directive) const {
return !directive || directive->AllowEval();
}
bool CSPDirectiveList::CheckWasmEval(SourceListDirective* directive) const {
return !directive || directive->AllowWasmEval();
}
bool CSPDirectiveList::IsMatchingNoncePresent(SourceListDirective* directive,
const String& nonce) const {
return directive && directive->AllowNonce(nonce);
}
bool CSPDirectiveList::AreAllMatchingHashesPresent(
SourceListDirective* directive,
const IntegrityMetadataSet& hashes) const {
if (!directive || hashes.IsEmpty())
return false;
for (const std::pair<String, IntegrityAlgorithm>& hash : hashes) {
// Convert the hash from integrity metadata format to CSP format.
CSPHashValue csp_hash;
csp_hash.first = ConvertHashAlgorithmToCSPHashAlgorithm(hash.second);
if (!ParseBase64Digest(hash.first, &csp_hash.second))
return false;
// All integrity hashes must be listed in the CSP.
if (!directive->AllowHash(csp_hash))
return false;
}
return true;
}
bool CSPDirectiveList::CheckHash(SourceListDirective* directive,
const CSPHashValue& hash_value) const {
return !directive || directive->AllowHash(hash_value);
}
bool CSPDirectiveList::CheckUnsafeHashesAllowed(
SourceListDirective* directive) const {
return !directive || directive->AllowUnsafeHashes();
}
bool CSPDirectiveList::CheckDynamic(SourceListDirective* directive) const {
return !directive || directive->AllowDynamic();
}
void CSPDirectiveList::ReportMixedContent(
const KURL& mixed_url,
ResourceRequest::RedirectStatus redirect_status) const {
if (StrictMixedContentChecking()) {
policy_->ReportViolation(
ContentSecurityPolicy::GetDirectiveName(
ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent),
ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent, String(),
mixed_url, report_endpoints_, use_reporting_api_, header_, header_type_,
ContentSecurityPolicy::kURLViolation, std::unique_ptr<SourceLocation>(),
nullptr, // contextFrame,
redirect_status);
}
}
bool CSPDirectiveList::CheckSource(
SourceListDirective* directive,
const KURL& url,
ResourceRequest::RedirectStatus redirect_status) const {
// If |url| is empty, fall back to the policy URL to ensure that <object>'s
// without a `src` can be blocked/allowed, as they can still load plugins
// even though they don't actually have a URL.
return !directive || directive->Allows(url.IsEmpty() ? policy_->Url() : url,
redirect_status);
}
bool CSPDirectiveList::CheckAncestors(SourceListDirective* directive,
LocalFrame* frame) const {
if (!frame || !directive)
return true;
for (Frame* current = frame->Tree().Parent(); current;
current = current->Tree().Parent()) {
// The |current| frame might be a remote frame which has no URL, so use
// its origin instead. This should suffice for this check since it
// doesn't do path comparisons. See https://crbug.com/582544.
//
// TODO(mkwst): Move this check up into the browser process. See
// https://crbug.com/555418.
KURL url(NullURL(),
current->GetSecurityContext()->GetSecurityOrigin()->ToString());
if (!directive->Allows(url, ResourceRequest::RedirectStatus::kNoRedirect))
return false;
}
return true;
}
bool CSPDirectiveList::CheckRequestWithoutIntegrity(
WebURLRequest::RequestContext context) const {
if (require_sri_for_ == RequireSRIForToken::kNone)
return true;
// SRI specification
// (https://w3c.github.io/webappsec-subresource-integrity/#apply-algorithm-to-request)
// says to match token with request's destination with the token.
// Keep this logic aligned with ContentSecurityPolicy::allowRequest
if ((require_sri_for_ & RequireSRIForToken::kScript) &&
(context == WebURLRequest::kRequestContextScript ||
context == WebURLRequest::kRequestContextImport ||
context == WebURLRequest::kRequestContextServiceWorker ||
context == WebURLRequest::kRequestContextSharedWorker ||
context == WebURLRequest::kRequestContextWorker)) {
return false;
}
if ((require_sri_for_ & RequireSRIForToken::kStyle) &&
context == WebURLRequest::kRequestContextStyle)
return false;
return true;
}
bool CSPDirectiveList::CheckRequestWithoutIntegrityAndReportViolation(
WebURLRequest::RequestContext context,
const KURL& url,
ResourceRequest::RedirectStatus redirect_status) const {
if (CheckRequestWithoutIntegrity(context))
return true;
String resource_type;
switch (context) {
case WebURLRequest::kRequestContextScript:
case WebURLRequest::kRequestContextImport:
resource_type = "script";
break;
case WebURLRequest::kRequestContextStyle:
resource_type = "stylesheet";
break;
case WebURLRequest::kRequestContextServiceWorker:
resource_type = "service worker";
break;
case WebURLRequest::kRequestContextSharedWorker:
resource_type = "shared worker";
break;
case WebURLRequest::kRequestContextWorker:
resource_type = "worker";
break;
default:
break;
}
ReportViolation(ContentSecurityPolicy::GetDirectiveName(
ContentSecurityPolicy::DirectiveType::kRequireSRIFor),
ContentSecurityPolicy::DirectiveType::kRequireSRIFor,
"Refused to load the " + resource_type + " '" +
url.ElidedString() +
"' because 'require-sri-for' directive requires "
"integrity attribute be present for all " +
resource_type + "s.",
url, redirect_status);
return DenyIfEnforcingPolicy();
}
bool CSPDirectiveList::AllowRequestWithoutIntegrity(
WebURLRequest::RequestContext context,
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
if (reporting_policy == SecurityViolationReportingPolicy::kReport)
return CheckRequestWithoutIntegrityAndReportViolation(context, url,
redirect_status);
return DenyIfEnforcingPolicy() || CheckRequestWithoutIntegrity(context);
}
bool CSPDirectiveList::CheckMediaType(MediaListDirective* directive,
const String& type,
const String& type_attribute) const {
if (!directive)
return true;
if (type_attribute.IsEmpty() || type_attribute.StripWhiteSpace() != type)
return false;
return directive->Allows(type);
}
bool CSPDirectiveList::CheckEvalAndReportViolation(
SourceListDirective* directive,
const String& console_message,
ScriptState* script_state,
ContentSecurityPolicy::ExceptionStatus exception_status,
const String& content) const {
if (CheckEval(directive))
return true;
String suffix = String();
if (directive == default_src_)
suffix =
" Note that 'script-src' was not explicitly set, so 'default-src' is "
"used as a fallback.";
ReportEvalViolation(
directive->GetText(), ContentSecurityPolicy::DirectiveType::kScriptSrc,
console_message + "\"" + directive->GetText() + "\"." + suffix + "\n",
KURL(), script_state, exception_status,
directive->AllowReportSample() ? content : g_empty_string);
if (!IsReportOnly()) {
policy_->ReportBlockedScriptExecutionToInspector(directive->GetText());
return false;
}
return true;
}
bool CSPDirectiveList::CheckWasmEvalAndReportViolation(
SourceListDirective* directive,
const String& console_message,
ScriptState* script_state,
ContentSecurityPolicy::ExceptionStatus exception_status,
const String& content) const {
if (CheckWasmEval(directive))
return true;
String suffix = String();
if (directive == default_src_) {
suffix =
" Note that 'script-src' was not explicitly set, so 'default-src' is "
"used as a fallback.";
}
ReportEvalViolation(
directive->GetText(), ContentSecurityPolicy::DirectiveType::kScriptSrc,
console_message + "\"" + directive->GetText() + "\"." + suffix + "\n",
KURL(), script_state, exception_status,
directive->AllowReportSample() ? content : g_empty_string);
if (!IsReportOnly()) {
policy_->ReportBlockedScriptExecutionToInspector(directive->GetText());
return false;
}
return true;
}
bool CSPDirectiveList::CheckMediaTypeAndReportViolation(
MediaListDirective* directive,
const String& type,
const String& type_attribute,
const String& console_message) const {
if (CheckMediaType(directive, type, type_attribute))
return true;
String message = console_message + "\'" + directive->GetText() + "\'.";
if (type_attribute.IsEmpty())
message = message +
" When enforcing the 'plugin-types' directive, the plugin's "
"media type must be explicitly declared with a 'type' attribute "
"on the containing element (e.g. '<object type=\"[TYPE GOES "
"HERE]\" ...>').";
// 'RedirectStatus::NoRedirect' is safe here, as we do the media type check
// before actually loading data; this means that we shouldn't leak redirect
// targets, as we won't have had a chance to redirect yet.
ReportViolation(
directive->GetText(), ContentSecurityPolicy::DirectiveType::kPluginTypes,
message + "\n", NullURL(), ResourceRequest::RedirectStatus::kNoRedirect);
return DenyIfEnforcingPolicy();
}
bool CSPDirectiveList::CheckInlineAndReportViolation(
SourceListDirective* directive,
const String& console_message,
Element* element,
const String& source,
const String& context_url,
const WTF::OrdinalNumber& context_line,
bool is_script,
const String& hash_value,
ContentSecurityPolicy::DirectiveType effective_type) const {
if (!directive || directive->AllowAllInline())
return true;
String suffix = String();
if (directive->AllowInline() && directive->IsHashOrNoncePresent()) {
// If inline is allowed, but a hash or nonce is present, we ignore
// 'unsafe-inline'. Throw a reasonable error.
suffix =
" Note that 'unsafe-inline' is ignored if either a hash or nonce value "
"is present in the source list.";
} else {
suffix =
" Either the 'unsafe-inline' keyword, a hash ('" + hash_value +
"'), or a nonce ('nonce-...') is required to enable inline execution.";
if (directive == default_src_)
suffix = suffix + " Note also that '" +
String(is_script ? "script" : "style") +
"-src' was not explicitly set, so 'default-src' is used as a "
"fallback.";
}
ReportViolationWithLocation(
directive->GetText(), effective_type,
console_message + "\"" + directive->GetText() + "\"." + suffix + "\n",
KURL(), context_url, context_line, element,
directive->AllowReportSample() ? source : g_empty_string);
if (!IsReportOnly()) {
if (is_script)
policy_->ReportBlockedScriptExecutionToInspector(directive->GetText());
return false;
}
return true;
}
bool CSPDirectiveList::CheckSourceAndReportViolation(
SourceListDirective* directive,
const KURL& url,
const ContentSecurityPolicy::DirectiveType effective_type,
ResourceRequest::RedirectStatus redirect_status) const {
if (!directive)
return true;
// We ignore URL-based whitelists if we're allowing dynamic script injection.
if (CheckSource(directive, url, redirect_status) && !CheckDynamic(directive))
return true;
// We should never have a violation against `child-src` or `default-src`
// directly; the effective directive should always be one of the explicit
// fetch directives.
DCHECK_NE(ContentSecurityPolicy::DirectiveType::kChildSrc, effective_type);
DCHECK_NE(ContentSecurityPolicy::DirectiveType::kDefaultSrc, effective_type);
String prefix = "Refused to ";
if (ContentSecurityPolicy::DirectiveType::kBaseURI == effective_type)
prefix = prefix + "set the document's base URI to '";
else if (ContentSecurityPolicy::DirectiveType::kWorkerSrc == effective_type)
prefix = prefix + "create a worker from '";
else if (ContentSecurityPolicy::DirectiveType::kConnectSrc == effective_type)
prefix = prefix + "connect to '";
else if (ContentSecurityPolicy::DirectiveType::kFontSrc == effective_type)
prefix = prefix + "load the font '";
else if (ContentSecurityPolicy::DirectiveType::kFormAction == effective_type)
prefix = prefix + "send form data to '";
else if (ContentSecurityPolicy::DirectiveType::kFrameSrc == effective_type)
prefix = prefix + "frame '";
else if (ContentSecurityPolicy::DirectiveType::kImgSrc == effective_type)
prefix = prefix + "load the image '";
else if (ContentSecurityPolicy::DirectiveType::kMediaSrc == effective_type)
prefix = prefix + "load media from '";
else if (ContentSecurityPolicy::DirectiveType::kManifestSrc == effective_type)
prefix = prefix + "load manifest from '";
else if (ContentSecurityPolicy::DirectiveType::kObjectSrc == effective_type)
prefix = prefix + "load plugin data from '";
else if (ContentSecurityPolicy::DirectiveType::kPrefetchSrc == effective_type)
prefix = prefix + "prefetch content from '";
else if (ContentSecurityPolicy::IsScriptDirective(effective_type))
prefix = prefix + "load the script '";
else if (ContentSecurityPolicy::IsStyleDirective(effective_type))
prefix = prefix + "load the stylesheet '";
else if (ContentSecurityPolicy::DirectiveType::kNavigateTo == effective_type)
prefix = prefix + "navigate to '";
String suffix = String();
if (CheckDynamic(directive))
suffix =
" 'strict-dynamic' is present, so host-based whitelisting is disabled.";
String directive_name = directive->GetName();
String effective_directive_name =
ContentSecurityPolicy::GetDirectiveName(effective_type);
if (directive_name != effective_directive_name) {
suffix = suffix + " Note that '" + effective_directive_name +
"' was not explicitly set, so '" + directive_name +
"' is used as a fallback.";
}
ReportViolation(directive->GetText(), effective_type,
prefix + url.ElidedString() +
"' because it violates the following Content Security "
"Policy directive: \"" +
directive->GetText() + "\"." + suffix + "\n",
url, redirect_status);
return DenyIfEnforcingPolicy();
}
bool CSPDirectiveList::CheckAncestorsAndReportViolation(
SourceListDirective* directive,
LocalFrame* frame,
const KURL& url) const {
if (CheckAncestors(directive, frame))
return true;
ReportViolationWithFrame(
directive->GetText(),
ContentSecurityPolicy::DirectiveType::kFrameAncestors,
"Refused to display '" + url.ElidedString() +
"' in a frame because an ancestor violates the "
"following Content Security Policy directive: "
"\"" +
directive->GetText() + "\".",
url, frame);
return DenyIfEnforcingPolicy();
}
bool CSPDirectiveList::AllowJavaScriptURLs(
Element* element,
const String& source,
const String& context_url,
const WTF::OrdinalNumber& context_line,
SecurityViolationReportingPolicy reporting_policy) const {
SourceListDirective* directive =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcElem);
if (reporting_policy == SecurityViolationReportingPolicy::kReport) {
return CheckInlineAndReportViolation(
directive,
"Refused to run the JavaScript URL because it violates the following "
"Content Security Policy directive: ",
element, source, context_url, context_line, true, "sha256-...",
ContentSecurityPolicy::DirectiveType::kScriptSrcElem);
}
return !directive || directive->AllowAllInline();
}
bool CSPDirectiveList::AllowInlineEventHandlers(
Element* element,
const String& source,
const String& context_url,
const WTF::OrdinalNumber& context_line,
SecurityViolationReportingPolicy reporting_policy) const {
SourceListDirective* directive =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcAttr);
if (reporting_policy == SecurityViolationReportingPolicy::kReport) {
return CheckInlineAndReportViolation(
directive,
"Refused to execute inline event handler because it violates the "
"following Content Security Policy directive: ",
element, source, context_url, context_line, true, "sha256-...",
ContentSecurityPolicy::DirectiveType::kScriptSrcAttr);
}
return !directive || directive->AllowAllInline();
}
bool CSPDirectiveList::AllowInlineScript(
Element* element,
const String& context_url,
const String& nonce,
const WTF::OrdinalNumber& context_line,
SecurityViolationReportingPolicy reporting_policy,
const String& content) const {
SourceListDirective* directive =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcElem);
if (IsMatchingNoncePresent(directive, nonce))
return true;
if (element && IsHTMLScriptElement(element) &&
!ToHTMLScriptElement(element)->Loader()->IsParserInserted() &&
AllowDynamic(ContentSecurityPolicy::DirectiveType::kScriptSrcElem)) {
return true;
}
if (reporting_policy == SecurityViolationReportingPolicy::kReport) {
return CheckInlineAndReportViolation(
directive,
"Refused to execute inline script because it violates the following "
"Content Security Policy directive: ",
element, content, context_url, context_line, true,
GetSha256String(content),
ContentSecurityPolicy::DirectiveType::kScriptSrcElem);
}
return !directive || directive->AllowAllInline();
}
bool CSPDirectiveList::AllowInlineStyle(
Element* element,
const String& context_url,
const String& nonce,
const WTF::OrdinalNumber& context_line,
SecurityViolationReportingPolicy reporting_policy,
const String& content,
ContentSecurityPolicy::InlineType inline_type) const {
ContentSecurityPolicy::DirectiveType effective_type =
inline_type == ContentSecurityPolicy::InlineType::kAttribute
? ContentSecurityPolicy::DirectiveType::kStyleSrcAttr
: ContentSecurityPolicy::DirectiveType::kStyleSrcElem;
SourceListDirective* directive = OperativeDirective(effective_type);
if (IsMatchingNoncePresent(directive, nonce))
return true;
if (reporting_policy == SecurityViolationReportingPolicy::kReport) {
return CheckInlineAndReportViolation(
directive,
"Refused to apply inline style because it violates the following "
"Content Security Policy directive: ",
element, content, context_url, context_line, false,
GetSha256String(content), effective_type);
}
return !directive || directive->AllowAllInline();
}
bool CSPDirectiveList::AllowEval(
ScriptState* script_state,
SecurityViolationReportingPolicy reporting_policy,
ContentSecurityPolicy::ExceptionStatus exception_status,
const String& content) const {
if (reporting_policy == SecurityViolationReportingPolicy::kReport) {
return CheckEvalAndReportViolation(
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc),
"Refused to evaluate a string as JavaScript because 'unsafe-eval' is "
"not an allowed source of script in the following Content Security "
"Policy directive: ",
script_state, exception_status, content);
}
return CheckEval(
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc));
}
bool CSPDirectiveList::AllowWasmEval(
ScriptState* script_state,
SecurityViolationReportingPolicy reporting_policy,
ContentSecurityPolicy::ExceptionStatus exception_status,
const String& content) const {
if (reporting_policy == SecurityViolationReportingPolicy::kReport) {
return CheckWasmEvalAndReportViolation(
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc),
"Refused to compile or instantiate WebAssembly module because "
"'wasm-eval' is not an allowed source of script in the following "
"Content Security Policy directive: ",
script_state, exception_status, content);
}
return CheckWasmEval(
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrc));
}
bool CSPDirectiveList::AllowPluginType(
const String& type,
const String& type_attribute,
const KURL& url,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckMediaTypeAndReportViolation(
plugin_types_.Get(), type, type_attribute,
"Refused to load '" + url.ElidedString() + "' (MIME type '" +
type_attribute +
"') because it violates the following Content Security "
"Policy Directive: ")
: CheckMediaType(plugin_types_.Get(), type, type_attribute);
}
bool CSPDirectiveList::AllowScriptFromSource(
const KURL& url,
const String& nonce,
const IntegrityMetadataSet& hashes,
ParserDisposition parser_disposition,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
SourceListDirective* directive =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kScriptSrcElem);
if (IsMatchingNoncePresent(directive, nonce))
return true;
if (parser_disposition == kNotParserInserted &&
AllowDynamic(ContentSecurityPolicy::DirectiveType::kScriptSrcElem))
return true;
if (AreAllMatchingHashesPresent(directive, hashes))
return true;
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
directive, url,
ContentSecurityPolicy::DirectiveType::kScriptSrcElem,
redirect_status)
: CheckSource(directive, url, redirect_status);
}
bool CSPDirectiveList::AllowObjectFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
if (url.ProtocolIsAbout())
return true;
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kObjectSrc),
url, ContentSecurityPolicy::DirectiveType::kObjectSrc,
redirect_status)
: CheckSource(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kObjectSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowPrefetchFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kPrefetchSrc),
url, ContentSecurityPolicy::DirectiveType::kPrefetchSrc,
redirect_status)
: CheckSource(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kPrefetchSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowFrameFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
if (url.ProtocolIsAbout())
return true;
// 'frame-src' overrides 'child-src', which overrides the default
// sources. So, we do this nested set of calls to 'operativeDirective()' to
// grab 'frame-src' if it exists, 'child-src' if it doesn't, and 'defaut-src'
// if neither are available.
SourceListDirective* directive =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kFrameSrc);
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
directive, url,
ContentSecurityPolicy::DirectiveType::kFrameSrc,
redirect_status)
: CheckSource(directive, url, redirect_status);
}
bool CSPDirectiveList::AllowImageFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kImgSrc),
url, ContentSecurityPolicy::DirectiveType::kImgSrc,
redirect_status)
: CheckSource(OperativeDirective(
ContentSecurityPolicy::DirectiveType::kImgSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowStyleFromSource(
const KURL& url,
const String& nonce,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
SourceListDirective* directive =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kStyleSrcElem);
if (IsMatchingNoncePresent(directive, nonce))
return true;
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
directive, url,
ContentSecurityPolicy::DirectiveType::kStyleSrcElem,
redirect_status)
: CheckSource(directive, url, redirect_status);
}
bool CSPDirectiveList::AllowFontFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kFontSrc),
url, ContentSecurityPolicy::DirectiveType::kFontSrc,
redirect_status)
: CheckSource(OperativeDirective(
ContentSecurityPolicy::DirectiveType::kFontSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowMediaFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kMediaSrc),
url, ContentSecurityPolicy::DirectiveType::kMediaSrc,
redirect_status)
: CheckSource(OperativeDirective(
ContentSecurityPolicy::DirectiveType::kMediaSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowManifestFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kManifestSrc),
url, ContentSecurityPolicy::DirectiveType::kManifestSrc,
redirect_status)
: CheckSource(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kManifestSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowConnectToSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kConnectSrc),
url, ContentSecurityPolicy::DirectiveType::kConnectSrc,
redirect_status)
: CheckSource(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kConnectSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowFormAction(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kFormAction),
url, ContentSecurityPolicy::DirectiveType::kFormAction,
redirect_status)
: CheckSource(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kFormAction),
url, redirect_status);
}
bool CSPDirectiveList::AllowBaseURI(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
bool result =
reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kBaseURI),
url, ContentSecurityPolicy::DirectiveType::kBaseURI,
redirect_status)
: CheckSource(OperativeDirective(
ContentSecurityPolicy::DirectiveType::kBaseURI),
url, redirect_status);
if (result &&
!CheckSource(
OperativeDirective(ContentSecurityPolicy::DirectiveType::kBaseURI),
url, redirect_status)) {
UseCounter::Count(policy_->GetDocument(),
WebFeature::kBaseWouldBeBlockedByDefaultSrc);
}
return result;
}
bool CSPDirectiveList::AllowTrustedTypePolicy(const String& policy_name) const {
if (trusted_types_->Allows(policy_name))
return true;
ReportViolation(
"trusted-types", ContentSecurityPolicy::DirectiveType::kTrustedTypes,
String::Format(
"Refused to create a TrustedTypePolicy named '%s' because "
"it violates the following Content Security Policy directive: "
"\"%s\".",
policy_name.Utf8().data(),
trusted_types_.Get()->GetText().Utf8().data()),
KURL(), RedirectStatus::kNoRedirect);
return DenyIfEnforcingPolicy();
}
bool CSPDirectiveList::AllowWorkerFromSource(
const KURL& url,
ResourceRequest::RedirectStatus redirect_status,
SecurityViolationReportingPolicy reporting_policy) const {
if (AllowDynamicWorker())
return true;
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckSourceAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kWorkerSrc),
url, ContentSecurityPolicy::DirectiveType::kWorkerSrc,
redirect_status)
: CheckSource(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kWorkerSrc),
url, redirect_status);
}
bool CSPDirectiveList::AllowAncestors(
LocalFrame* frame,
const KURL& url,
SecurityViolationReportingPolicy reporting_policy) const {
return reporting_policy == SecurityViolationReportingPolicy::kReport
? CheckAncestorsAndReportViolation(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kFrameAncestors),
frame, url)
: CheckAncestors(
OperativeDirective(
ContentSecurityPolicy::DirectiveType::kFrameAncestors),
frame);
}
bool CSPDirectiveList::AllowHash(
const CSPHashValue& hash_value,
const ContentSecurityPolicy::InlineType type,
const ContentSecurityPolicy::DirectiveType directive_type) const {
if (type == ContentSecurityPolicy::InlineType::kAttribute) {
if (!policy_->ExperimentalFeaturesEnabled())
return false;
if (!CheckUnsafeHashesAllowed(OperativeDirective(directive_type)))
return false;
}
return CheckHash(OperativeDirective(directive_type), hash_value);
}
bool CSPDirectiveList::AllowScriptHash(
const CSPHashValue& hash_value,
ContentSecurityPolicy::InlineType type) const {
return AllowHash(hash_value, type,
type == ContentSecurityPolicy::InlineType::kAttribute
? ContentSecurityPolicy::DirectiveType::kScriptSrcAttr
: ContentSecurityPolicy::DirectiveType::kScriptSrcElem);
}
bool CSPDirectiveList::AllowStyleHash(
const CSPHashValue& hash_value,
ContentSecurityPolicy::InlineType type) const {
return AllowHash(hash_value, type,
type == ContentSecurityPolicy::InlineType::kAttribute
? ContentSecurityPolicy::DirectiveType::kStyleSrcAttr
: ContentSecurityPolicy::DirectiveType::kStyleSrcElem);
}
bool CSPDirectiveList::AllowDynamic(
ContentSecurityPolicy::DirectiveType directive_type) const {
return CheckDynamic(OperativeDirective(directive_type));
}
bool CSPDirectiveList::AllowDynamicWorker() const {
SourceListDirective* worker_src =
OperativeDirective(ContentSecurityPolicy::DirectiveType::kWorkerSrc);
return CheckDynamic(worker_src);
}
const String& CSPDirectiveList::PluginTypesText() const {
DCHECK(HasPluginTypes());
return plugin_types_->GetText();
}
bool CSPDirectiveList::ShouldSendCSPHeader(ResourceType type) const {
// TODO(mkwst): Revisit this once the CORS prefetch issue with the 'CSP'
// header is worked out, one way or another:
// https://github.com/whatwg/fetch/issues/52
return false;
}
// policy = directive-list
// directive-list = [ directive *( ";" [ directive ] ) ]
//
void CSPDirectiveList::Parse(const UChar* begin,
const UChar* end,
bool should_parse_wasm_eval) {
header_ = String(begin, end - begin).StripWhiteSpace();
if (begin == end)
return;
const UChar* position = begin;
while (position < end) {
const UChar* directive_begin = position;
SkipUntil<UChar>(position, end, ';');
String name, value;
if (ParseDirective(directive_begin, position, &name, &value)) {
DCHECK(!name.IsEmpty());
AddDirective(name, value);
}
DCHECK(position == end || *position == ';');
SkipExactly<UChar>(position, end, ';');
}
}
// directive = *WSP [ directive-name [ WSP directive-value ] ]
// directive-name = 1*( ALPHA / DIGIT / "-" )
// directive-value = *( WSP / <VCHAR except ";"> )
//
bool CSPDirectiveList::ParseDirective(const UChar* begin,
const UChar* end,
String* name,
String* value) {
DCHECK(name->IsEmpty());
DCHECK(value->IsEmpty());
const UChar* position = begin;
SkipWhile<UChar, IsASCIISpace>(position, end);
// Empty directive (e.g. ";;;"). Exit early.
if (position == end)
return false;
const UChar* name_begin = position;
SkipWhile<UChar, IsCSPDirectiveNameCharacter>(position, end);
// The directive-name must be non-empty.
if (name_begin == position) {
SkipWhile<UChar, IsNotASCIISpace>(position, end);
policy_->ReportUnsupportedDirective(
String(name_begin, position - name_begin));
return false;
}
*name = String(name_begin, position - name_begin);
if (position == end)
return true;
if (!SkipExactly<UChar, IsASCIISpace>(position, end)) {
SkipWhile<UChar, IsNotASCIISpace>(position, end);
policy_->ReportUnsupportedDirective(
String(name_begin, position - name_begin));
return false;
}
SkipWhile<UChar, IsASCIISpace>(position, end);
const UChar* value_begin = position;
SkipWhile<UChar, IsCSPDirectiveValueCharacter>(position, end);
if (position != end) {
policy_->ReportInvalidDirectiveValueCharacter(
*name, String(value_begin, end - value_begin));
return false;
}
// The directive-value may be empty.
if (value_begin == position)
return true;
*value = String(value_begin, position - value_begin);
return true;
}
void CSPDirectiveList::ParseRequireSRIFor(const String& name,
const String& value) {
if (require_sri_for_ != 0) {
policy_->ReportDuplicateDirective(name);
return;
}
StringBuilder token_errors;
unsigned number_of_token_errors = 0;
Vector<UChar> characters;
value.AppendTo(characters);
const UChar* position = characters.data();
const UChar* end = position + characters.size();
while (position < end) {
SkipWhile<UChar, IsASCIISpace>(position, end);
const UChar* token_begin = position;
SkipWhile<UChar, IsNotASCIISpace>(position, end);
if (token_begin < position) {
String token = String(token_begin, position - token_begin);
if (EqualIgnoringASCIICase(token, "script")) {
require_sri_for_ |= RequireSRIForToken::kScript;
} else if (EqualIgnoringASCIICase(token, "style")) {
require_sri_for_ |= RequireSRIForToken::kStyle;
} else {
if (number_of_token_errors)
token_errors.Append(", \'");
else
token_errors.Append('\'');
token_errors.Append(token);
token_errors.Append('\'');
number_of_token_errors++;
}
}
}
if (number_of_token_errors == 0)
return;
String invalid_tokens_error_message;
if (number_of_token_errors > 1)
token_errors.Append(" are invalid 'require-sri-for' tokens.");
else
token_errors.Append(" is an invalid 'require-sri-for' token.");
invalid_tokens_error_message = token_errors.ToString();
DCHECK(!invalid_tokens_error_message.IsEmpty());
policy_->ReportInvalidRequireSRIForTokens(invalid_tokens_error_message);
}
void CSPDirectiveList::ParseReportTo(const String& name, const String& value) {
if (!use_reporting_api_) {
use_reporting_api_ = true;
report_endpoints_.clear();
}
if (!report_endpoints_.IsEmpty()) {
policy_->ReportDuplicateDirective(name);
return;
}
ParseAndAppendReportEndpoints(value);
}
void CSPDirectiveList::ParseReportURI(const String& name, const String& value) {
// report-to supersedes report-uri
if (use_reporting_api_)
return;
if (!report_endpoints_.IsEmpty()) {
policy_->ReportDuplicateDirective(name);
return;
}
// Remove report-uri in meta policies, per
// https://html.spec.whatwg.org/#attr-meta-http-equiv-content-security-policy.
if (header_source_ == kContentSecurityPolicyHeaderSourceMeta) {
policy_->ReportInvalidDirectiveInMeta(name);
return;
}
ParseAndAppendReportEndpoints(value);
}
void CSPDirectiveList::ParseAndAppendReportEndpoints(const String& value) {
Vector<UChar> characters;
value.AppendTo(characters);
const UChar* position = characters.data();
const UChar* end = position + characters.size();
while (position < end) {
SkipWhile<UChar, IsASCIISpace>(position, end);
const UChar* endpoint_begin = position;
SkipWhile<UChar, IsNotASCIISpace>(position, end);
if (endpoint_begin < position) {
String endpoint = String(endpoint_begin, position - endpoint_begin);
report_endpoints_.push_back(endpoint);
}
}
if (report_endpoints_.size() > 1) {
UseCounter::Count(policy_->GetDocument(),
WebFeature::kReportUriMultipleEndpoints);
} else {
UseCounter::Count(policy_->GetDocument(),
WebFeature::kReportUriSingleEndpoint);
}
}
template <class CSPDirectiveType>
void CSPDirectiveList::SetCSPDirective(const String& name,
const String& value,
Member<CSPDirectiveType>& directive,
bool should_parse_wasm_eval) {
if (directive) {
policy_->ReportDuplicateDirective(name);
return;
}
// Remove frame-ancestors directives in meta policies, per
// https://www.w3.org/TR/CSP2/#delivery-html-meta-element.
if (header_source_ == kContentSecurityPolicyHeaderSourceMeta &&
ContentSecurityPolicy::GetDirectiveType(name) ==
ContentSecurityPolicy::DirectiveType::kFrameAncestors) {
policy_->ReportInvalidDirectiveInMeta(name);
return;
}
directive = new CSPDirectiveType(name, value, policy_);
}
void CSPDirectiveList::ApplySandboxPolicy(const String& name,
const String& sandbox_policy) {
// Remove sandbox directives in meta policies, per
// https://www.w3.org/TR/CSP2/#delivery-html-meta-element.
if (header_source_ == kContentSecurityPolicyHeaderSourceMeta) {
policy_->ReportInvalidDirectiveInMeta(name);
return;
}
if (IsReportOnly()) {
policy_->ReportInvalidInReportOnly(name);
return;
}
if (has_sandbox_policy_) {
policy_->ReportDuplicateDirective(name);
return;
}
has_sandbox_policy_ = true;
String invalid_tokens;
SpaceSplitString policy_tokens =
SpaceSplitString(AtomicString(sandbox_policy));
policy_->EnforceSandboxFlags(
ParseSandboxPolicy(policy_tokens, invalid_tokens));
if (!invalid_tokens.IsNull())
policy_->ReportInvalidSandboxFlags(invalid_tokens);
}
void CSPDirectiveList::TreatAsPublicAddress(const String& name,
const String& value) {
if (IsReportOnly()) {
policy_->ReportInvalidInReportOnly(name);
return;
}
if (treat_as_public_address_) {
policy_->ReportDuplicateDirective(name);
return;
}
treat_as_public_address_ = true;
policy_->TreatAsPublicAddress();
if (!value.IsEmpty())
policy_->ReportValueForEmptyDirective(name, value);
}
void CSPDirectiveList::RequireTrustedTypes(const String& name,
const String& value) {
if (IsReportOnly()) {
policy_->ReportInvalidInReportOnly(name);
return;
}
if (trusted_types_) {
policy_->ReportDuplicateDirective(name);
return;
}
policy_->RequireTrustedTypes();
trusted_types_ = new StringListDirective(name, value, policy_);
}
void CSPDirectiveList::EnforceStrictMixedContentChecking(const String& name,
const String& value) {
if (strict_mixed_content_checking_enforced_) {
policy_->ReportDuplicateDirective(name);
return;
}
if (!value.IsEmpty())
policy_->ReportValueForEmptyDirective(name, value);
strict_mixed_content_checking_enforced_ = true;
if (!IsReportOnly())
policy_->EnforceStrictMixedContentChecking();
}
void CSPDirectiveList::EnableInsecureRequestsUpgrade(const String& name,
const String& value) {
if (IsReportOnly()) {
policy_->ReportInvalidInReportOnly(name);
return;
}
if (upgrade_insecure_requests_) {
policy_->ReportDuplicateDirective(name);
return;
}
upgrade_insecure_requests_ = true;
policy_->UpgradeInsecureRequests();
if (!value.IsEmpty())
policy_->ReportValueForEmptyDirective(name, value);
}
void CSPDirectiveList::AddDirective(const String& name, const String& value) {
DCHECK(!name.IsEmpty());
ContentSecurityPolicy::DirectiveType type =
ContentSecurityPolicy::GetDirectiveType(name);
if (type == ContentSecurityPolicy::DirectiveType::kDefaultSrc) {
SetCSPDirective<SourceListDirective>(name, value, default_src_);
// TODO(mkwst) It seems unlikely that developers would use different
// algorithms for scripts and styles. We may want to combine the
// usesScriptHashAlgorithms() and usesStyleHashAlgorithms.
policy_->UsesScriptHashAlgorithms(default_src_->HashAlgorithmsUsed());
policy_->UsesStyleHashAlgorithms(default_src_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrc) {
SetCSPDirective<SourceListDirective>(name, value, script_src_);
policy_->UsesScriptHashAlgorithms(script_src_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrcAttr &&
policy_->ExperimentalFeaturesEnabled()) {
SetCSPDirective<SourceListDirective>(name, value, script_src_attr_);
policy_->UsesScriptHashAlgorithms(script_src_attr_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kScriptSrcElem &&
policy_->ExperimentalFeaturesEnabled()) {
SetCSPDirective<SourceListDirective>(name, value, script_src_elem_);
policy_->UsesScriptHashAlgorithms(script_src_elem_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kObjectSrc) {
SetCSPDirective<SourceListDirective>(name, value, object_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kFrameAncestors) {
SetCSPDirective<SourceListDirective>(name, value, frame_ancestors_);
} else if (type == ContentSecurityPolicy::DirectiveType::kFrameSrc) {
SetCSPDirective<SourceListDirective>(name, value, frame_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kImgSrc) {
SetCSPDirective<SourceListDirective>(name, value, img_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrc) {
SetCSPDirective<SourceListDirective>(name, value, style_src_);
policy_->UsesStyleHashAlgorithms(style_src_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrcAttr &&
policy_->ExperimentalFeaturesEnabled()) {
SetCSPDirective<SourceListDirective>(name, value, style_src_attr_);
policy_->UsesStyleHashAlgorithms(style_src_attr_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kStyleSrcElem &&
policy_->ExperimentalFeaturesEnabled()) {
SetCSPDirective<SourceListDirective>(name, value, style_src_elem_);
policy_->UsesStyleHashAlgorithms(style_src_elem_->HashAlgorithmsUsed());
} else if (type == ContentSecurityPolicy::DirectiveType::kFontSrc) {
SetCSPDirective<SourceListDirective>(name, value, font_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kMediaSrc) {
SetCSPDirective<SourceListDirective>(name, value, media_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kConnectSrc) {
SetCSPDirective<SourceListDirective>(name, value, connect_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kSandbox) {
ApplySandboxPolicy(name, value);
} else if (type == ContentSecurityPolicy::DirectiveType::kReportURI) {
ParseReportURI(name, value);
} else if (type == ContentSecurityPolicy::DirectiveType::kBaseURI) {
SetCSPDirective<SourceListDirective>(name, value, base_uri_);
} else if (type == ContentSecurityPolicy::DirectiveType::kChildSrc) {
SetCSPDirective<SourceListDirective>(name, value, child_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kWorkerSrc) {
SetCSPDirective<SourceListDirective>(name, value, worker_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kFormAction) {
SetCSPDirective<SourceListDirective>(name, value, form_action_);
} else if (type == ContentSecurityPolicy::DirectiveType::kPluginTypes) {
SetCSPDirective<MediaListDirective>(name, value, plugin_types_);
} else if (type ==
ContentSecurityPolicy::DirectiveType::kUpgradeInsecureRequests) {
EnableInsecureRequestsUpgrade(name, value);
} else if (type ==
ContentSecurityPolicy::DirectiveType::kBlockAllMixedContent) {
EnforceStrictMixedContentChecking(name, value);
} else if (type == ContentSecurityPolicy::DirectiveType::kManifestSrc) {
SetCSPDirective<SourceListDirective>(name, value, manifest_src_);
} else if (type == ContentSecurityPolicy::DirectiveType::kNavigateTo) {
SetCSPDirective<SourceListDirective>(name, value, navigate_to_);
} else if (type ==
ContentSecurityPolicy::DirectiveType::kTreatAsPublicAddress) {
TreatAsPublicAddress(name, value);
} else if (policy_->ExperimentalFeaturesEnabled()) {
if (type == ContentSecurityPolicy::DirectiveType::kRequireSRIFor) {
ParseRequireSRIFor(name, value);
} else if (type == ContentSecurityPolicy::DirectiveType::kReportTo) {
ParseReportTo(name, value);
} else if (type == ContentSecurityPolicy::DirectiveType::kTrustedTypes &&
RuntimeEnabledFeatures::TrustedDOMTypesEnabled()) {
RequireTrustedTypes(name, value);
} else if (type == ContentSecurityPolicy::DirectiveType::kPrefetchSrc) {
SetCSPDirective<SourceListDirective>(name, value, prefetch_src_);
} else {
policy_->ReportUnsupportedDirective(name);
}
} else {
policy_->ReportUnsupportedDirective(name);
}
}
ContentSecurityPolicy::DirectiveType CSPDirectiveList::FallbackDirective(
const ContentSecurityPolicy::DirectiveType current_directive,
const ContentSecurityPolicy::DirectiveType original_directive) const {
switch (current_directive) {
case ContentSecurityPolicy::DirectiveType::kConnectSrc:
case ContentSecurityPolicy::DirectiveType::kFontSrc:
case ContentSecurityPolicy::DirectiveType::kImgSrc:
case ContentSecurityPolicy::DirectiveType::kManifestSrc:
case ContentSecurityPolicy::DirectiveType::kMediaSrc:
case ContentSecurityPolicy::DirectiveType::kPrefetchSrc:
case ContentSecurityPolicy::DirectiveType::kObjectSrc:
case ContentSecurityPolicy::DirectiveType::kScriptSrc:
case ContentSecurityPolicy::DirectiveType::kStyleSrc:
return ContentSecurityPolicy::DirectiveType::kDefaultSrc;
case ContentSecurityPolicy::DirectiveType::kScriptSrcAttr:
case ContentSecurityPolicy::DirectiveType::kScriptSrcElem:
return ContentSecurityPolicy::DirectiveType::kScriptSrc;
case ContentSecurityPolicy::DirectiveType::kStyleSrcAttr:
case ContentSecurityPolicy::DirectiveType::kStyleSrcElem:
return ContentSecurityPolicy::DirectiveType::kStyleSrc;
case ContentSecurityPolicy::DirectiveType::kFrameSrc:
case ContentSecurityPolicy::DirectiveType::kWorkerSrc:
return ContentSecurityPolicy::DirectiveType::kChildSrc;
// Because the fallback chain of child-src can be different if we are
// checking a worker or a frame request, we need to know the original type
// of the request to decide. These are the fallback chains for worker-src
// and frame-src specifically.
// worker-src > child-src > script-src > default-src
// frame-src > child-src > default-src
// Since there are some situations and tests that will operate on the
// `child-src` directive directly (like for example the EE subsumption
// algorithm), we consider the child-src > default-src fallback path as the
// "default" and the worker-src fallback path as an exception.
case ContentSecurityPolicy::DirectiveType::kChildSrc:
if (original_directive ==
ContentSecurityPolicy::DirectiveType::kWorkerSrc)
return ContentSecurityPolicy::DirectiveType::kScriptSrc;
return ContentSecurityPolicy::DirectiveType::kDefaultSrc;
default:
return ContentSecurityPolicy::DirectiveType::kUndefined;
}
}
SourceListDirective* CSPDirectiveList::OperativeDirective(
const ContentSecurityPolicy::DirectiveType type,
ContentSecurityPolicy::DirectiveType original_type) const {
if (type == ContentSecurityPolicy::DirectiveType::kUndefined) {
return nullptr;
}
SourceListDirective* directive;
if (original_type == ContentSecurityPolicy::DirectiveType::kUndefined) {
original_type = type;
}
switch (type) {
case ContentSecurityPolicy::DirectiveType::kBaseURI:
directive = base_uri_;
break;
case ContentSecurityPolicy::DirectiveType::kDefaultSrc:
directive = default_src_;
break;
case ContentSecurityPolicy::DirectiveType::kFrameAncestors:
directive = frame_ancestors_;
break;
case ContentSecurityPolicy::DirectiveType::kFormAction:
directive = form_action_;
break;
case ContentSecurityPolicy::DirectiveType::kNavigateTo:
directive = navigate_to_;
break;
case ContentSecurityPolicy::DirectiveType::kChildSrc:
directive = child_src_;
break;
case ContentSecurityPolicy::DirectiveType::kConnectSrc:
directive = connect_src_;
break;
case ContentSecurityPolicy::DirectiveType::kFontSrc:
directive = font_src_;
break;
case ContentSecurityPolicy::DirectiveType::kImgSrc:
directive = img_src_;
break;
case ContentSecurityPolicy::DirectiveType::kManifestSrc:
directive = manifest_src_;
break;
case ContentSecurityPolicy::DirectiveType::kMediaSrc:
directive = media_src_;
break;
case ContentSecurityPolicy::DirectiveType::kObjectSrc:
directive = object_src_;
break;
case ContentSecurityPolicy::DirectiveType::kPrefetchSrc:
directive = prefetch_src_;
break;
case ContentSecurityPolicy::DirectiveType::kScriptSrc:
directive = script_src_;
break;
case ContentSecurityPolicy::DirectiveType::kScriptSrcAttr:
directive = policy_->ExperimentalFeaturesEnabled() ? script_src_attr_
: script_src_;
break;
case ContentSecurityPolicy::DirectiveType::kScriptSrcElem:
directive = policy_->ExperimentalFeaturesEnabled() ? script_src_elem_
: script_src_;
break;
case ContentSecurityPolicy::DirectiveType::kStyleSrc:
directive = style_src_;
break;
case ContentSecurityPolicy::DirectiveType::kStyleSrcAttr:
directive =
policy_->ExperimentalFeaturesEnabled() ? style_src_attr_ : style_src_;
break;
case ContentSecurityPolicy::DirectiveType::kStyleSrcElem:
directive =
policy_->ExperimentalFeaturesEnabled() ? style_src_elem_ : style_src_;
break;
case ContentSecurityPolicy::DirectiveType::kFrameSrc:
directive = frame_src_;
break;
case ContentSecurityPolicy::DirectiveType::kWorkerSrc:
directive = worker_src_;
break;
default:
return nullptr;
}
// if the directive does not exist, rely on the fallback directive
return directive ? directive
: OperativeDirective(FallbackDirective(type, original_type),
original_type);
}
SourceListDirectiveVector CSPDirectiveList::GetSourceVector(
const ContentSecurityPolicy::DirectiveType type,
const CSPDirectiveListVector& policies) {
SourceListDirectiveVector source_list_directives;
for (const auto& policy : policies) {
if (SourceListDirective* directive = policy->OperativeDirective(type)) {
if (directive->IsNone())
return SourceListDirectiveVector(1, directive);
source_list_directives.push_back(directive);
}
}
return source_list_directives;
}
bool CSPDirectiveList::Subsumes(const CSPDirectiveListVector& other) {
// A white-list of directives that we consider for subsumption.
// See more about source lists here:
// https://w3c.github.io/webappsec-csp/#framework-directive-source-list
static ContentSecurityPolicy::DirectiveType directives[] = {
ContentSecurityPolicy::DirectiveType::kChildSrc,
ContentSecurityPolicy::DirectiveType::kConnectSrc,
ContentSecurityPolicy::DirectiveType::kFontSrc,
ContentSecurityPolicy::DirectiveType::kFrameSrc,
ContentSecurityPolicy::DirectiveType::kImgSrc,
ContentSecurityPolicy::DirectiveType::kManifestSrc,
ContentSecurityPolicy::DirectiveType::kMediaSrc,
ContentSecurityPolicy::DirectiveType::kObjectSrc,
ContentSecurityPolicy::DirectiveType::kScriptSrc,
ContentSecurityPolicy::DirectiveType::kScriptSrcAttr,
ContentSecurityPolicy::DirectiveType::kScriptSrcElem,
ContentSecurityPolicy::DirectiveType::kStyleSrc,
ContentSecurityPolicy::DirectiveType::kStyleSrcAttr,
ContentSecurityPolicy::DirectiveType::kStyleSrcElem,
ContentSecurityPolicy::DirectiveType::kWorkerSrc,
ContentSecurityPolicy::DirectiveType::kBaseURI,
ContentSecurityPolicy::DirectiveType::kFrameAncestors,
ContentSecurityPolicy::DirectiveType::kFormAction,
ContentSecurityPolicy::DirectiveType::kNavigateTo};
for (const auto& directive : directives) {
// There should only be one SourceListDirective for each directive in
// Embedding-CSP.
SourceListDirectiveVector required_list =
GetSourceVector(directive, CSPDirectiveListVector(1, this));
if (!required_list.size())
continue;
SourceListDirective* required = required_list[0];
// Aggregate all serialized source lists of the returned CSP into a vector
// based on a directive type, defaulting accordingly (for example, to
// `default-src`).
SourceListDirectiveVector returned = GetSourceVector(directive, other);
// TODO(amalika): Add checks for plugin-types, sandbox, disown-opener,
// navigation-to, worker-src.
if (!required->Subsumes(returned))
return false;
}
if (!HasPluginTypes())
return true;
HeapVector<Member<MediaListDirective>> plugin_types_other;
for (const auto& policy : other) {
if (policy->HasPluginTypes())
plugin_types_other.push_back(policy->plugin_types_);
}
return plugin_types_->Subsumes(plugin_types_other);
}
WebContentSecurityPolicy CSPDirectiveList::ExposeForNavigationalChecks() const {
WebContentSecurityPolicy policy;
policy.disposition = static_cast<WebContentSecurityPolicyType>(header_type_);
policy.source = static_cast<WebContentSecurityPolicySource>(header_source_);
std::vector<WebContentSecurityPolicyDirective> directives;
for (const auto& directive :
{child_src_, default_src_, form_action_, frame_src_, navigate_to_}) {
if (directive) {
directives.push_back(WebContentSecurityPolicyDirective{
directive->DirectiveName(),
directive->ExposeForNavigationalChecks()});
}
}
if (upgrade_insecure_requests_) {
directives.push_back(WebContentSecurityPolicyDirective{
blink::WebString("upgrade-insecure-requests"),
WebContentSecurityPolicySourceList()});
}
policy.directives = directives;
std::vector<WebString> report_endpoints;
for (const auto& report_endpoint : ReportEndpoints()) {
report_endpoints.push_back(report_endpoint);
}
policy.use_reporting_api = use_reporting_api_;
policy.report_endpoints = report_endpoints;
policy.header = Header();
return policy;
}
void CSPDirectiveList::Trace(blink::Visitor* visitor) {
visitor->Trace(policy_);
visitor->Trace(plugin_types_);
visitor->Trace(base_uri_);
visitor->Trace(child_src_);
visitor->Trace(connect_src_);
visitor->Trace(default_src_);
visitor->Trace(font_src_);
visitor->Trace(form_action_);
visitor->Trace(frame_ancestors_);
visitor->Trace(frame_src_);
visitor->Trace(img_src_);
visitor->Trace(media_src_);
visitor->Trace(manifest_src_);
visitor->Trace(object_src_);
visitor->Trace(prefetch_src_);
visitor->Trace(script_src_);
visitor->Trace(script_src_attr_);
visitor->Trace(script_src_elem_);
visitor->Trace(style_src_);
visitor->Trace(style_src_attr_);
visitor->Trace(style_src_elem_);
visitor->Trace(worker_src_);
visitor->Trace(navigate_to_);
visitor->Trace(trusted_types_);
}
} // namespace blink