blob: 37081e5f0a3f9aa0f09c8285f6df360d9f73e12a [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 "core/frame/csp/CSPDirectiveList.h"
#include "bindings/core/v8/SourceLocation.h"
#include "core/dom/Document.h"
#include "core/dom/SecurityContext.h"
#include "core/dom/SpaceSplitString.h"
#include "core/frame/LocalFrame.h"
#include "core/frame/UseCounter.h"
#include "core/inspector/ConsoleMessage.h"
#include "platform/Crypto.h"
#include "platform/ParsingUtilities.h"
#include "platform/RuntimeEnabledFeatures.h"
#include "platform/network/ContentSecurityPolicyParsers.h"
#include "platform/weborigin/KURL.h"
#include "wtf/text/Base64.h"
#include "wtf/text/StringUTF8Adaptor.h"
#include "wtf/text/WTFString.h"
namespace blink {
namespace {
String getSha256String(const String& content)
{
DigestValue digest;
StringUTF8Adaptor utf8Content(content);
bool digestSuccess = computeDigest(HashAlgorithmSha256, utf8Content.data(), utf8Content.length(), digest);
if (!digestSuccess) {
return "sha256-...";
}
return "sha256-" + base64Encode(reinterpret_cast<char*>(digest.data()), digest.size(), Base64DoNotInsertLFs);
}
template<typename CharType> inline bool isASCIIAlphanumericOrHyphen(CharType c)
{
return isASCIIAlphanumeric(c) || c == '-';
}
} // namespace
CSPDirectiveList::CSPDirectiveList(ContentSecurityPolicy* policy, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source)
: m_policy(policy)
, m_headerType(type)
, m_headerSource(source)
, m_reportOnly(false)
, m_hasSandboxPolicy(false)
, m_reflectedXSSDisposition(ReflectedXSSUnset)
, m_didSetReferrerPolicy(false)
, m_referrerPolicy(ReferrerPolicyDefault)
, m_strictMixedContentCheckingEnforced(false)
, m_upgradeInsecureRequests(false)
, m_treatAsPublicAddress(false)
, m_requireSRIFor(RequireSRIForToken::None)
{
m_reportOnly = type == ContentSecurityPolicyHeaderTypeReport;
}
CSPDirectiveList* CSPDirectiveList::create(ContentSecurityPolicy* policy, const UChar* begin, const UChar* end, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source)
{
CSPDirectiveList* directives = new CSPDirectiveList(policy, type, source);
directives->parse(begin, end);
if (!directives->checkEval(directives->operativeDirective(directives->m_scriptSrc.get()))) {
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(directives->m_scriptSrc.get())->text() + "\".\n";
directives->setEvalDisabledErrorMessage(message);
}
if (directives->isReportOnly() && source != ContentSecurityPolicyHeaderSourceMeta && directives->reportEndpoints().isEmpty())
policy->reportMissingReportURI(String(begin, end - begin));
return directives;
}
void CSPDirectiveList::reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, ResourceRequest::RedirectStatus redirectStatus) const
{
String message = m_reportOnly ? "[Report Only] " + consoleMessage : consoleMessage;
m_policy->logToConsole(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message));
m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportEndpoints, m_header, ContentSecurityPolicy::URLViolation, nullptr, redirectStatus);
}
void CSPDirectiveList::reportViolationWithFrame(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, LocalFrame* frame) const
{
String message = m_reportOnly ? "[Report Only] " + consoleMessage : consoleMessage;
m_policy->logToConsole(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message), frame);
m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportEndpoints, m_header, ContentSecurityPolicy::URLViolation, frame);
}
void CSPDirectiveList::reportViolationWithLocation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, const String& contextURL, const WTF::OrdinalNumber& contextLine) const
{
String message = m_reportOnly ? "[Report Only] " + consoleMessage : consoleMessage;
m_policy->logToConsole(ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, message, SourceLocation::capture(contextURL, contextLine.oneBasedInt(), 0)));
m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportEndpoints, m_header, ContentSecurityPolicy::InlineViolation, nullptr, RedirectStatus::NoRedirect, contextLine.oneBasedInt());
}
void CSPDirectiveList::reportViolationWithState(const String& directiveText, const String& effectiveDirective, const String& message, const KURL& blockedURL, ScriptState* scriptState, const ContentSecurityPolicy::ExceptionStatus exceptionStatus) const
{
String reportMessage = m_reportOnly ? "[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 (m_reportOnly || exceptionStatus == ContentSecurityPolicy::WillNotThrowException) {
ConsoleMessage* consoleMessage = ConsoleMessage::create(SecurityMessageSource, ErrorMessageLevel, reportMessage);
m_policy->logToConsole(consoleMessage);
}
m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportEndpoints, m_header, ContentSecurityPolicy::EvalViolation);
}
bool CSPDirectiveList::checkEval(SourceListDirective* directive) const
{
return !directive || directive->allowEval();
}
bool CSPDirectiveList::checkInline(SourceListDirective* directive) const
{
return !directive || (directive->allowInline() && !directive->isHashOrNoncePresent());
}
bool CSPDirectiveList::isMatchingNoncePresent(SourceListDirective* directive, const String& nonce) const
{
return directive && directive->allowNonce(nonce);
}
bool CSPDirectiveList::checkHash(SourceListDirective* directive, const CSPHashValue& hashValue) const
{
return !directive || directive->allowHash(hashValue);
}
bool CSPDirectiveList::checkHashedAttributes(SourceListDirective* directive) const
{
return !directive || directive->allowHashedAttributes();
}
bool CSPDirectiveList::checkDynamic(SourceListDirective* directive) const
{
return !directive || directive->allowDynamic();
}
void CSPDirectiveList::reportMixedContent(const KURL& mixedURL, ResourceRequest::RedirectStatus redirectStatus) const
{
if (strictMixedContentChecking())
m_policy->reportViolation(ContentSecurityPolicy::BlockAllMixedContent, ContentSecurityPolicy::BlockAllMixedContent, String(), mixedURL, m_reportEndpoints, m_header, ContentSecurityPolicy::URLViolation, nullptr, redirectStatus);
}
bool CSPDirectiveList::checkSource(SourceListDirective* directive, const KURL& url, ResourceRequest::RedirectStatus redirectStatus) 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() ? m_policy->url() : url, redirectStatus);
}
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(KURL(), current->securityContext()->getSecurityOrigin()->toString());
if (!directive->allows(url, ResourceRequest::RedirectStatus::NoRedirect))
return false;
}
return true;
}
bool CSPDirectiveList::checkRequestWithoutIntegrity(WebURLRequest::RequestContext context) const
{
if (m_requireSRIFor == RequireSRIForToken::None)
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 ((m_requireSRIFor & RequireSRIForToken::Script)
&& (context == WebURLRequest::RequestContextScript
|| context == WebURLRequest::RequestContextImport
|| context == WebURLRequest::RequestContextServiceWorker
|| context == WebURLRequest::RequestContextSharedWorker
|| context == WebURLRequest::RequestContextWorker)) {
return false;
}
if ((m_requireSRIFor & RequireSRIForToken::Style) && context == WebURLRequest::RequestContextStyle)
return false;
return true;
}
bool CSPDirectiveList::checkRequestWithoutIntegrityAndReportViolation(WebURLRequest::RequestContext context, const KURL& url, ResourceRequest::RedirectStatus redirectStatus) const
{
if (checkRequestWithoutIntegrity(context))
return true;
String resourceType;
switch (context) {
case WebURLRequest::RequestContextScript:
case WebURLRequest::RequestContextImport:
resourceType = "script";
break;
case WebURLRequest::RequestContextStyle:
resourceType = "stylesheet";
break;
case WebURLRequest::RequestContextServiceWorker:
resourceType = "service worker";
break;
case WebURLRequest::RequestContextSharedWorker:
resourceType = "shared worker";
break;
case WebURLRequest::RequestContextWorker:
resourceType = "worker";
break;
default:
break;
}
reportViolation(ContentSecurityPolicy::RequireSRIFor, ContentSecurityPolicy::RequireSRIFor, "Refused to load the " + resourceType + " '" + url.elidedString() + "' because 'require-sri-for' directive requires integrity attribute be present for all " + resourceType + "s.", url, redirectStatus);
return denyIfEnforcingPolicy();
}
bool CSPDirectiveList::allowRequestWithoutIntegrity(WebURLRequest::RequestContext context, const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (reportingStatus == ContentSecurityPolicy::SendReport)
return checkRequestWithoutIntegrityAndReportViolation(context, url, redirectStatus);
return denyIfEnforcingPolicy() || checkRequestWithoutIntegrity(context);
}
bool CSPDirectiveList::checkMediaType(MediaListDirective* directive, const String& type, const String& typeAttribute) const
{
if (!directive)
return true;
if (typeAttribute.isEmpty() || typeAttribute.stripWhiteSpace() != type)
return false;
return directive->allows(type);
}
SourceListDirective* CSPDirectiveList::operativeDirective(SourceListDirective* directive) const
{
return directive ? directive : m_defaultSrc.get();
}
SourceListDirective* CSPDirectiveList::operativeDirective(SourceListDirective* directive, SourceListDirective* override) const
{
return directive ? directive : override;
}
bool CSPDirectiveList::checkEvalAndReportViolation(SourceListDirective* directive, const String& consoleMessage, ScriptState* scriptState, ContentSecurityPolicy::ExceptionStatus exceptionStatus) const
{
if (checkEval(directive))
return true;
String suffix = String();
if (directive == m_defaultSrc)
suffix = " Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback.";
reportViolationWithState(directive->text(), ContentSecurityPolicy::ScriptSrc, consoleMessage + "\"" + directive->text() + "\"." + suffix + "\n", KURL(), scriptState, exceptionStatus);
if (!m_reportOnly) {
m_policy->reportBlockedScriptExecutionToInspector(directive->text());
return false;
}
return true;
}
bool CSPDirectiveList::checkMediaTypeAndReportViolation(MediaListDirective* directive, const String& type, const String& typeAttribute, const String& consoleMessage) const
{
if (checkMediaType(directive, type, typeAttribute))
return true;
String message = consoleMessage + "\'" + directive->text() + "\'.";
if (typeAttribute.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->text(), ContentSecurityPolicy::PluginTypes, message + "\n", KURL(), ResourceRequest::RedirectStatus::NoRedirect);
return denyIfEnforcingPolicy();
}
bool CSPDirectiveList::checkInlineAndReportViolation(SourceListDirective* directive, const String& consoleMessage, const String& contextURL, const WTF::OrdinalNumber& contextLine, bool isScript, const String& hashValue) const
{
if (checkInline(directive))
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 ('" + hashValue + "'), or a nonce ('nonce-...') is required to enable inline execution.";
if (directive == m_defaultSrc)
suffix = suffix + " Note also that '" + String(isScript ? "script" : "style") + "-src' was not explicitly set, so 'default-src' is used as a fallback.";
}
reportViolationWithLocation(directive->text(), isScript ? ContentSecurityPolicy::ScriptSrc : ContentSecurityPolicy::StyleSrc, consoleMessage + "\"" + directive->text() + "\"." + suffix + "\n", KURL(), contextURL, contextLine);
if (!m_reportOnly) {
if (isScript)
m_policy->reportBlockedScriptExecutionToInspector(directive->text());
return false;
}
return true;
}
bool CSPDirectiveList::checkSourceAndReportViolation(SourceListDirective* directive, const KURL& url, const String& effectiveDirective, ResourceRequest::RedirectStatus redirectStatus) const
{
if (!directive)
return true;
// We ignore URL-based whitelists if we're allowing dynamic script injection.
if (checkSource(directive, url, redirectStatus) && !checkDynamic(directive))
return true;
String prefix;
if (ContentSecurityPolicy::BaseURI == effectiveDirective)
prefix = "Refused to set the document's base URI to '";
else if (ContentSecurityPolicy::ChildSrc == effectiveDirective)
prefix = "Refused to create a child context containing '";
else if (ContentSecurityPolicy::ConnectSrc == effectiveDirective)
prefix = "Refused to connect to '";
else if (ContentSecurityPolicy::FontSrc == effectiveDirective)
prefix = "Refused to load the font '";
else if (ContentSecurityPolicy::FormAction == effectiveDirective)
prefix = "Refused to send form data to '";
else if (ContentSecurityPolicy::FrameSrc == effectiveDirective)
prefix = "Refused to frame '";
else if (ContentSecurityPolicy::ImgSrc == effectiveDirective)
prefix = "Refused to load the image '";
else if (ContentSecurityPolicy::MediaSrc == effectiveDirective)
prefix = "Refused to load media from '";
else if (ContentSecurityPolicy::ManifestSrc == effectiveDirective)
prefix = "Refused to load manifest from '";
else if (ContentSecurityPolicy::ObjectSrc == effectiveDirective)
prefix = "Refused to load plugin data from '";
else if (ContentSecurityPolicy::ScriptSrc == effectiveDirective)
prefix = "Refused to load the script '";
else if (ContentSecurityPolicy::StyleSrc == effectiveDirective)
prefix = "Refused to load the stylesheet '";
String suffix = String();
if (checkDynamic(directive))
suffix = " 'strict-dynamic' is present, so host-based whitelisting is disabled.";
if (directive == m_defaultSrc)
suffix = suffix + " Note that '" + effectiveDirective + "' was not explicitly set, so 'default-src' is used as a fallback.";
reportViolation(directive->text(), effectiveDirective, prefix + url.elidedString() + "' because it violates the following Content Security Policy directive: \"" + directive->text() + "\"." + suffix + "\n", url, redirectStatus);
return denyIfEnforcingPolicy();
}
bool CSPDirectiveList::checkAncestorsAndReportViolation(SourceListDirective* directive, LocalFrame* frame, const KURL& url) const
{
if (checkAncestors(directive, frame))
return true;
reportViolationWithFrame(directive->text(), "frame-ancestors", "Refused to display '" + url.elidedString() + "' in a frame because an ancestor violates the following Content Security Policy directive: \"" + directive->text() + "\".", url, frame);
return denyIfEnforcingPolicy();
}
bool CSPDirectiveList::allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (reportingStatus == ContentSecurityPolicy::SendReport) {
return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), "Refused to execute JavaScript URL because it violates the following Content Security Policy directive: ", contextURL, contextLine, true, "sha256-...");
}
return checkInline(operativeDirective(m_scriptSrc.get()));
}
bool CSPDirectiveList::allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (reportingStatus == ContentSecurityPolicy::SendReport) {
return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), "Refused to execute inline event handler because it violates the following Content Security Policy directive: ", contextURL, contextLine, true, "sha256-...");
}
return checkInline(operativeDirective(m_scriptSrc.get()));
}
bool CSPDirectiveList::allowInlineScript(const String& contextURL, const String& nonce, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus, const String& content) const
{
if (isMatchingNoncePresent(operativeDirective(m_scriptSrc.get()), nonce))
return true;
if (reportingStatus == ContentSecurityPolicy::SendReport)
return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), "Refused to execute inline script because it violates the following Content Security Policy directive: ", contextURL, contextLine, true, getSha256String(content));
return checkInline(operativeDirective(m_scriptSrc.get()));
}
bool CSPDirectiveList::allowInlineStyle(const String& contextURL, const String& nonce, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus, const String& content) const
{
if (isMatchingNoncePresent(operativeDirective(m_styleSrc.get()), nonce))
return true;
if (reportingStatus == ContentSecurityPolicy::SendReport)
return checkInlineAndReportViolation(operativeDirective(m_styleSrc.get()), "Refused to apply inline style because it violates the following Content Security Policy directive: ", contextURL, contextLine, false, getSha256String(content));
return checkInline(operativeDirective(m_styleSrc.get()));
}
bool CSPDirectiveList::allowEval(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus, ContentSecurityPolicy::ExceptionStatus exceptionStatus) const
{
if (reportingStatus == ContentSecurityPolicy::SendReport) {
return checkEvalAndReportViolation(operativeDirective(m_scriptSrc.get()), "Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: ", scriptState, exceptionStatus);
}
return checkEval(operativeDirective(m_scriptSrc.get()));
}
bool CSPDirectiveList::allowPluginType(const String& type, const String& typeAttribute, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ?
checkMediaTypeAndReportViolation(m_pluginTypes.get(), type, typeAttribute, "Refused to load '" + url.elidedString() + "' (MIME type '" + typeAttribute + "') because it violates the following Content Security Policy Directive: ") :
checkMediaType(m_pluginTypes.get(), type, typeAttribute);
}
bool CSPDirectiveList::allowScriptFromSource(const KURL& url, const String& nonce, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (isMatchingNoncePresent(operativeDirective(m_scriptSrc.get()), nonce))
return true;
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_scriptSrc.get()), url, ContentSecurityPolicy::ScriptSrc, redirectStatus) : checkSource(operativeDirective(m_scriptSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowObjectFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (url.protocolIsAbout())
return true;
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_objectSrc.get()), url, ContentSecurityPolicy::ObjectSrc, redirectStatus) : checkSource(operativeDirective(m_objectSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowChildFrameFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (url.protocolIsAbout())
return true;
// 'frame-src' is the only directive which overrides something other than the default sources.
// It 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* whichDirective = operativeDirective(m_frameSrc.get(), operativeDirective(m_childSrc.get()));
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(whichDirective, url, ContentSecurityPolicy::FrameSrc, redirectStatus) : checkSource(whichDirective, url, redirectStatus);
}
bool CSPDirectiveList::allowImageFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_imgSrc.get()), url, ContentSecurityPolicy::ImgSrc, redirectStatus) : checkSource(operativeDirective(m_imgSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowStyleFromSource(const KURL& url, const String& nonce, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
if (isMatchingNoncePresent(operativeDirective(m_styleSrc.get()), nonce))
return true;
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_styleSrc.get()), url, ContentSecurityPolicy::StyleSrc, redirectStatus) : checkSource(operativeDirective(m_styleSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowFontFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_fontSrc.get()), url, ContentSecurityPolicy::FontSrc, redirectStatus) : checkSource(operativeDirective(m_fontSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowMediaFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_mediaSrc.get()), url, ContentSecurityPolicy::MediaSrc, redirectStatus) : checkSource(operativeDirective(m_mediaSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowManifestFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_manifestSrc.get()), url, ContentSecurityPolicy::ManifestSrc, redirectStatus) : checkSource(operativeDirective(m_manifestSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowConnectToSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_connectSrc.get()), url, ContentSecurityPolicy::ConnectSrc, redirectStatus) : checkSource(operativeDirective(m_connectSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowFormAction(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(m_formAction.get(), url, ContentSecurityPolicy::FormAction, redirectStatus) : checkSource(m_formAction.get(), url, redirectStatus);
}
bool CSPDirectiveList::allowBaseURI(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(m_baseURI.get(), url, ContentSecurityPolicy::BaseURI, redirectStatus) : checkSource(m_baseURI.get(), url, redirectStatus);
}
bool CSPDirectiveList::allowChildContextFromSource(const KURL& url, ResourceRequest::RedirectStatus redirectStatus, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkSourceAndReportViolation(operativeDirective(m_childSrc.get()), url, ContentSecurityPolicy::ChildSrc, redirectStatus) : checkSource(operativeDirective(m_childSrc.get()), url, redirectStatus);
}
bool CSPDirectiveList::allowAncestors(LocalFrame* frame, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const
{
return reportingStatus == ContentSecurityPolicy::SendReport ? checkAncestorsAndReportViolation(m_frameAncestors.get(), frame, url) : checkAncestors(m_frameAncestors.get(), frame);
}
bool CSPDirectiveList::allowScriptHash(const CSPHashValue& hashValue, ContentSecurityPolicy::InlineType type) const
{
if (type == ContentSecurityPolicy::InlineType::Attribute) {
if (!m_policy->experimentalFeaturesEnabled())
return false;
if (!checkHashedAttributes(operativeDirective(m_scriptSrc.get())))
return false;
}
return checkHash(operativeDirective(m_scriptSrc.get()), hashValue);
}
bool CSPDirectiveList::allowStyleHash(const CSPHashValue& hashValue, ContentSecurityPolicy::InlineType type) const
{
if (type != ContentSecurityPolicy::InlineType::Block)
return false;
return checkHash(operativeDirective(m_styleSrc.get()), hashValue);
}
bool CSPDirectiveList::allowDynamic() const
{
return checkDynamic(operativeDirective(m_scriptSrc.get()));
}
const String& CSPDirectiveList::pluginTypesText() const
{
ASSERT(hasPluginTypes());
return m_pluginTypes->text();
}
bool CSPDirectiveList::shouldSendCSPHeader(Resource::Type 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)
{
m_header = String(begin, end - begin);
if (begin == end)
return;
const UChar* position = begin;
while (position < end) {
const UChar* directiveBegin = position;
skipUntil<UChar>(position, end, ';');
String name, value;
if (parseDirective(directiveBegin, position, name, value)) {
ASSERT(!name.isEmpty());
addDirective(name, value);
}
ASSERT(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)
{
ASSERT(name.isEmpty());
ASSERT(value.isEmpty());
const UChar* position = begin;
skipWhile<UChar, isASCIISpace>(position, end);
// Empty directive (e.g. ";;;"). Exit early.
if (position == end)
return false;
const UChar* nameBegin = position;
skipWhile<UChar, isCSPDirectiveNameCharacter>(position, end);
// The directive-name must be non-empty.
if (nameBegin == position) {
skipWhile<UChar, isNotASCIISpace>(position, end);
m_policy->reportUnsupportedDirective(String(nameBegin, position - nameBegin));
return false;
}
name = String(nameBegin, position - nameBegin);
if (position == end)
return true;
if (!skipExactly<UChar, isASCIISpace>(position, end)) {
skipWhile<UChar, isNotASCIISpace>(position, end);
m_policy->reportUnsupportedDirective(String(nameBegin, position - nameBegin));
return false;
}
skipWhile<UChar, isASCIISpace>(position, end);
const UChar* valueBegin = position;
skipWhile<UChar, isCSPDirectiveValueCharacter>(position, end);
if (position != end) {
m_policy->reportInvalidDirectiveValueCharacter(name, String(valueBegin, end - valueBegin));
return false;
}
// The directive-value may be empty.
if (valueBegin == position)
return true;
value = String(valueBegin, position - valueBegin);
return true;
}
void CSPDirectiveList::parseRequireSRIFor(const String& name, const String& value)
{
if (m_requireSRIFor != 0) {
m_policy->reportDuplicateDirective(name);
return;
}
StringBuilder tokenErrors;
unsigned numberOfTokenErrors = 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* tokenBegin = position;
skipWhile<UChar, isNotASCIISpace>(position, end);
if (tokenBegin < position) {
String token = String(tokenBegin, position - tokenBegin);
if (equalIgnoringCase(token, "script")) {
m_requireSRIFor |= RequireSRIForToken::Script;
} else if (equalIgnoringCase(token, "style")) {
m_requireSRIFor |= RequireSRIForToken::Style;
} else {
if (numberOfTokenErrors)
tokenErrors.append(", \'");
else
tokenErrors.append('\'');
tokenErrors.append(token);
tokenErrors.append('\'');
numberOfTokenErrors++;
}
}
}
if (numberOfTokenErrors == 0)
return;
String invalidTokensErrorMessage;
if (numberOfTokenErrors > 1)
tokenErrors.append(" are invalid 'require-sri-for' tokens.");
else
tokenErrors.append(" is an invalid 'require-sri-for' token.");
invalidTokensErrorMessage = tokenErrors.toString();
DCHECK(!invalidTokensErrorMessage.isEmpty());
m_policy->reportInvalidRequireSRIForTokens(invalidTokensErrorMessage);
}
void CSPDirectiveList::parseReportURI(const String& name, const String& value)
{
if (!m_reportEndpoints.isEmpty()) {
m_policy->reportDuplicateDirective(name);
return;
}
// Remove report-uri in meta policies, per https://www.w3.org/TR/CSP2/#delivery-html-meta-element.
if (m_headerSource == ContentSecurityPolicyHeaderSourceMeta) {
UseCounter::count(m_policy->document(), UseCounter::InvalidReportUriDirectiveInMetaCSP);
m_policy->reportInvalidDirectiveInMeta(name);
return;
}
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* urlBegin = position;
skipWhile<UChar, isNotASCIISpace>(position, end);
if (urlBegin < position) {
String url = String(urlBegin, position - urlBegin);
m_reportEndpoints.append(url);
}
}
}
template<class CSPDirectiveType>
void CSPDirectiveList::setCSPDirective(const String& name, const String& value, Member<CSPDirectiveType>& directive)
{
if (directive) {
m_policy->reportDuplicateDirective(name);
return;
}
// Remove frame-ancestors directives in meta policies, per https://www.w3.org/TR/CSP2/#delivery-html-meta-element.
if (m_headerSource == ContentSecurityPolicyHeaderSourceMeta && name == ContentSecurityPolicy::FrameAncestors) {
UseCounter::count(m_policy->document(), UseCounter::InvalidFrameAncestorsDirectiveInMetaCSP);
m_policy->reportInvalidDirectiveInMeta(name);
return;
}
directive = new CSPDirectiveType(name, value, m_policy);
}
void CSPDirectiveList::applySandboxPolicy(const String& name, const String& sandboxPolicy)
{
// Remove sandbox directives in meta policies, per https://www.w3.org/TR/CSP2/#delivery-html-meta-element.
if (m_headerSource == ContentSecurityPolicyHeaderSourceMeta) {
UseCounter::count(m_policy->document(), UseCounter::InvalidSandboxDirectiveInMetaCSP);
m_policy->reportInvalidDirectiveInMeta(name);
return;
}
if (m_reportOnly) {
m_policy->reportInvalidInReportOnly(name);
return;
}
if (m_hasSandboxPolicy) {
m_policy->reportDuplicateDirective(name);
return;
}
m_hasSandboxPolicy = true;
String invalidTokens;
SpaceSplitString policyTokens(AtomicString(sandboxPolicy), SpaceSplitString::ShouldNotFoldCase);
m_policy->enforceSandboxFlags(parseSandboxPolicy(policyTokens, invalidTokens));
if (!invalidTokens.isNull())
m_policy->reportInvalidSandboxFlags(invalidTokens);
}
void CSPDirectiveList::treatAsPublicAddress(const String& name, const String& value)
{
if (m_reportOnly) {
m_policy->reportInvalidInReportOnly(name);
return;
}
if (m_treatAsPublicAddress) {
m_policy->reportDuplicateDirective(name);
return;
}
m_treatAsPublicAddress = true;
m_policy->treatAsPublicAddress();
if (!value.isEmpty())
m_policy->reportValueForEmptyDirective(name, value);
}
void CSPDirectiveList::enforceStrictMixedContentChecking(const String& name, const String& value)
{
if (m_strictMixedContentCheckingEnforced) {
m_policy->reportDuplicateDirective(name);
return;
}
if (!value.isEmpty())
m_policy->reportValueForEmptyDirective(name, value);
m_strictMixedContentCheckingEnforced = true;
if (!m_reportOnly)
m_policy->enforceStrictMixedContentChecking();
}
void CSPDirectiveList::enableInsecureRequestsUpgrade(const String& name, const String& value)
{
if (m_reportOnly) {
m_policy->reportInvalidInReportOnly(name);
return;
}
if (m_upgradeInsecureRequests) {
m_policy->reportDuplicateDirective(name);
return;
}
m_upgradeInsecureRequests = true;
m_policy->upgradeInsecureRequests();
if (!value.isEmpty())
m_policy->reportValueForEmptyDirective(name, value);
}
void CSPDirectiveList::parseReflectedXSS(const String& name, const String& value)
{
if (m_reflectedXSSDisposition != ReflectedXSSUnset) {
m_policy->reportDuplicateDirective(name);
m_reflectedXSSDisposition = ReflectedXSSInvalid;
return;
}
if (value.isEmpty()) {
m_reflectedXSSDisposition = ReflectedXSSInvalid;
m_policy->reportInvalidReflectedXSS(value);
return;
}
Vector<UChar> characters;
value.appendTo(characters);
const UChar* position = characters.data();
const UChar* end = position + characters.size();
skipWhile<UChar, isASCIISpace>(position, end);
const UChar* begin = position;
skipWhile<UChar, isNotASCIISpace>(position, end);
// value1
// ^
if (equalIgnoringCase("allow", begin, position - begin)) {
m_reflectedXSSDisposition = AllowReflectedXSS;
} else if (equalIgnoringCase("filter", begin, position - begin)) {
m_reflectedXSSDisposition = FilterReflectedXSS;
} else if (equalIgnoringCase("block", begin, position - begin)) {
m_reflectedXSSDisposition = BlockReflectedXSS;
} else {
m_reflectedXSSDisposition = ReflectedXSSInvalid;
m_policy->reportInvalidReflectedXSS(value);
return;
}
skipWhile<UChar, isASCIISpace>(position, end);
if (position == end && m_reflectedXSSDisposition != ReflectedXSSUnset)
return;
// value1 value2
// ^
m_reflectedXSSDisposition = ReflectedXSSInvalid;
m_policy->reportInvalidReflectedXSS(value);
}
void CSPDirectiveList::parseReferrer(const String& name, const String& value)
{
m_didSetReferrerPolicy = true;
UseCounter::count(m_policy->document(), UseCounter::CSPReferrerDirective);
if (value.isEmpty()) {
m_policy->reportInvalidReferrer(value);
m_referrerPolicy = ReferrerPolicyNever;
return;
}
Vector<UChar> characters;
value.appendTo(characters);
const UChar* position = characters.data();
const UChar* end = position + characters.size();
skipWhile<UChar, isASCIISpace>(position, end);
const UChar* begin = position;
skipWhile<UChar, isNotASCIISpace>(position, end);
// value1
// ^
if (equalIgnoringCase("unsafe-url", begin, position - begin)) {
m_referrerPolicy = ReferrerPolicyAlways;
} else if (equalIgnoringCase("no-referrer", begin, position - begin)) {
m_referrerPolicy = ReferrerPolicyNever;
} else if (equalIgnoringCase("no-referrer-when-downgrade", begin, position - begin)) {
m_referrerPolicy = ReferrerPolicyDefault;
} else if (equalIgnoringCase("origin", begin, position - begin)) {
m_referrerPolicy = ReferrerPolicyOrigin;
} else if (equalIgnoringCase("origin-when-cross-origin", begin, position - begin) || equalIgnoringCase("origin-when-crossorigin", begin, position - begin)) {
m_referrerPolicy = ReferrerPolicyOriginWhenCrossOrigin;
} else {
m_policy->reportInvalidReferrer(value);
return;
}
skipWhile<UChar, isASCIISpace>(position, end);
if (position == end)
return;
// value1 value2
// ^
m_policy->reportInvalidReferrer(value);
}
void CSPDirectiveList::addDirective(const String& name, const String& value)
{
ASSERT(!name.isEmpty());
if (equalIgnoringCase(name, ContentSecurityPolicy::DefaultSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_defaultSrc);
// TODO(mkwst) It seems unlikely that developers would use different
// algorithms for scripts and styles. We may want to combine the
// usesScriptHashAlgorithms() and usesStyleHashAlgorithms.
m_policy->usesScriptHashAlgorithms(m_defaultSrc->hashAlgorithmsUsed());
m_policy->usesStyleHashAlgorithms(m_defaultSrc->hashAlgorithmsUsed());
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ScriptSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_scriptSrc);
m_policy->usesScriptHashAlgorithms(m_scriptSrc->hashAlgorithmsUsed());
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ObjectSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_objectSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::FrameAncestors)) {
setCSPDirective<SourceListDirective>(name, value, m_frameAncestors);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::FrameSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_frameSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ImgSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_imgSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::StyleSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_styleSrc);
m_policy->usesStyleHashAlgorithms(m_styleSrc->hashAlgorithmsUsed());
} else if (equalIgnoringCase(name, ContentSecurityPolicy::FontSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_fontSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::MediaSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_mediaSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ConnectSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_connectSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::Sandbox)) {
applySandboxPolicy(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ReportURI)) {
parseReportURI(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::BaseURI)) {
setCSPDirective<SourceListDirective>(name, value, m_baseURI);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ChildSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_childSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::FormAction)) {
setCSPDirective<SourceListDirective>(name, value, m_formAction);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::PluginTypes)) {
setCSPDirective<MediaListDirective>(name, value, m_pluginTypes);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ReflectedXSS)) {
parseReflectedXSS(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::Referrer)) {
parseReferrer(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::UpgradeInsecureRequests)) {
enableInsecureRequestsUpgrade(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::BlockAllMixedContent)) {
enforceStrictMixedContentChecking(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::ManifestSrc)) {
setCSPDirective<SourceListDirective>(name, value, m_manifestSrc);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::TreatAsPublicAddress)) {
treatAsPublicAddress(name, value);
} else if (equalIgnoringCase(name, ContentSecurityPolicy::RequireSRIFor) && m_policy->experimentalFeaturesEnabled()) {
parseRequireSRIFor(name, value);
} else {
m_policy->reportUnsupportedDirective(name);
}
}
DEFINE_TRACE(CSPDirectiveList)
{
visitor->trace(m_policy);
visitor->trace(m_pluginTypes);
visitor->trace(m_baseURI);
visitor->trace(m_childSrc);
visitor->trace(m_connectSrc);
visitor->trace(m_defaultSrc);
visitor->trace(m_fontSrc);
visitor->trace(m_formAction);
visitor->trace(m_frameAncestors);
visitor->trace(m_frameSrc);
visitor->trace(m_imgSrc);
visitor->trace(m_mediaSrc);
visitor->trace(m_manifestSrc);
visitor->trace(m_objectSrc);
visitor->trace(m_scriptSrc);
visitor->trace(m_styleSrc);
}
} // namespace blink