blob: 882c974316404ffb3f35640922c7b032a60f8152 [file] [log] [blame]
/*
* Copyright (C) 2011 Google, Inc. All rights reserved.
* Copyright (C) 2013-2018 Apple Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY GOOGLE INC. ``AS IS'' AND ANY
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "ContentSecurityPolicy.h"
#include "BlobURL.h"
#include "ContentSecurityPolicyClient.h"
#include "ContentSecurityPolicyDirective.h"
#include "ContentSecurityPolicyDirectiveList.h"
#include "ContentSecurityPolicyDirectiveNames.h"
#include "ContentSecurityPolicyHash.h"
#include "ContentSecurityPolicySource.h"
#include "ContentSecurityPolicySourceList.h"
#include "DOMStringList.h"
#include "DocumentInlines.h"
#include "DocumentLoader.h"
#include "EventNames.h"
#include "FormData.h"
#include "Frame.h"
#include "HTMLParserIdioms.h"
#include "InspectorInstrumentation.h"
#include "JSExecState.h"
#include "JSWindowProxy.h"
#include "LegacySchemeRegistry.h"
#include "ParsingUtilities.h"
#include "PingLoader.h"
#include "ResourceRequest.h"
#include "SecurityOrigin.h"
#include "SecurityPolicyViolationEvent.h"
#include "Settings.h"
#include "SubresourceIntegrity.h"
#include <JavaScriptCore/ScriptCallStack.h>
#include <JavaScriptCore/ScriptCallStackFactory.h>
#include <pal/crypto/CryptoDigest.h>
#include <pal/text/TextEncoding.h>
#include <wtf/JSONValues.h>
#include <wtf/SetForScope.h>
#include <wtf/text/StringBuilder.h>
#include <wtf/text/StringParsingBuffer.h>
#include <wtf/text/TextPosition.h>
namespace WebCore {
using namespace Inspector;
static String consoleMessageForViolation(const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const char* prefix, const char* subject = "it")
{
bool isDefaultSrc = violatedDirective.isDefaultSrc();
String name = violatedDirective.nameForReporting();
if (violatedDirective.nameForReporting().startsWith(StringView { ContentSecurityPolicyDirectiveNames::scriptSrc }))
name = ContentSecurityPolicyDirectiveNames::scriptSrc;
else if (violatedDirective.nameForReporting().startsWith(StringView { ContentSecurityPolicyDirectiveNames::styleSrc }))
name = ContentSecurityPolicyDirectiveNames::styleSrc;
return makeString(violatedDirective.directiveList().isReportOnly() ? "[Report Only] " : "",
prefix, blockedURL.isEmpty() ? "" : " ", blockedURL.stringCenterEllipsizedToLength(), " because ", subject,
isDefaultSrc ? " appears in neither the " : " does not appear in the ",
name,
isDefaultSrc ? " directive nor the default-src directive of the Content Security Policy." : " directive of the Content Security Policy.");
}
ContentSecurityPolicy::ContentSecurityPolicy(URL&& protectedURL, ContentSecurityPolicyClient* client)
: m_client { client }
, m_protectedURL { WTFMove(protectedURL) }
{
updateSourceSelf(SecurityOrigin::create(m_protectedURL).get());
}
ContentSecurityPolicy::ContentSecurityPolicy(URL&& protectedURL, ScriptExecutionContext& scriptExecutionContext)
: m_scriptExecutionContext(&scriptExecutionContext)
, m_protectedURL { WTFMove(protectedURL) }
{
ASSERT(scriptExecutionContext.securityOrigin());
updateSourceSelf(*scriptExecutionContext.securityOrigin());
// FIXME: handle the non-document case.
if (is<Document>(m_scriptExecutionContext)) {
if (auto* page = downcast<Document>(*m_scriptExecutionContext).page())
m_contentSecurityPolicyModeForExtension = page->contentSecurityPolicyModeForExtension();
}
}
ContentSecurityPolicy::~ContentSecurityPolicy() = default;
void ContentSecurityPolicy::copyStateFrom(const ContentSecurityPolicy* other)
{
if (m_hasAPIPolicy)
return;
ASSERT(m_policies.isEmpty());
for (auto& policy : other->m_policies)
didReceiveHeader(policy->header(), policy->headerType(), ContentSecurityPolicy::PolicyFrom::Inherited, String { });
m_referrer = other->m_referrer;
m_httpStatusCode = other->m_httpStatusCode;
}
void ContentSecurityPolicy::createPolicyForPluginDocumentFrom(const ContentSecurityPolicy& other)
{
if (m_hasAPIPolicy)
return;
ASSERT(m_policies.isEmpty());
for (auto& policy : other.m_policies)
didReceiveHeader(policy->header(), policy->headerType(), ContentSecurityPolicy::PolicyFrom::InheritedForPluginDocument, String { });
m_referrer = other.m_referrer;
m_httpStatusCode = other.m_httpStatusCode;
}
void ContentSecurityPolicy::copyUpgradeInsecureRequestStateFrom(const ContentSecurityPolicy& other)
{
m_upgradeInsecureRequests = other.m_upgradeInsecureRequests;
m_insecureNavigationRequestsToUpgrade.add(other.m_insecureNavigationRequestsToUpgrade.begin(), other.m_insecureNavigationRequestsToUpgrade.end());
}
bool ContentSecurityPolicy::allowRunningOrDisplayingInsecureContent(const URL& url)
{
bool allow = true;
for (auto& policy : m_policies) {
if (!policy->hasBlockAllMixedContentDirective())
continue;
bool isReportOnly = policy->isReportOnly();
auto message = makeString(isReportOnly ? "[Report Only] " : "", "Blocked mixed content ",
url.stringCenterEllipsizedToLength(), " because 'block-all-mixed-content' appears in the Content Security Policy.");
reportViolation(ContentSecurityPolicyDirectiveNames::blockAllMixedContent, *policy, url.string(), message);
if (!isReportOnly)
allow = false;
}
return allow;
}
void ContentSecurityPolicy::didCreateWindowProxy(JSWindowProxy& windowProxy) const
{
auto* window = windowProxy.window();
ASSERT(window);
ASSERT(window->scriptExecutionContext());
ASSERT(window->scriptExecutionContext()->contentSecurityPolicy() == this);
if (!windowProxy.world().isNormal()) {
window->setEvalEnabled(true);
return;
}
window->setEvalEnabled(m_lastPolicyEvalDisabledErrorMessage.isNull(), m_lastPolicyEvalDisabledErrorMessage);
window->setWebAssemblyEnabled(m_lastPolicyWebAssemblyDisabledErrorMessage.isNull(), m_lastPolicyWebAssemblyDisabledErrorMessage);
}
ContentSecurityPolicyResponseHeaders ContentSecurityPolicy::responseHeaders() const
{
if (!m_cachedResponseHeaders) {
ContentSecurityPolicyResponseHeaders result;
result.m_headers = m_policies.map([](auto& policy) {
return std::pair { policy->header(), policy->headerType() };
});
result.m_httpStatusCode = m_httpStatusCode;
m_cachedResponseHeaders = WTFMove(result);
}
return *m_cachedResponseHeaders;
}
void ContentSecurityPolicy::didReceiveHeaders(const ContentSecurityPolicyResponseHeaders& headers, String&& referrer, ReportParsingErrors reportParsingErrors)
{
SetForScope isReportingEnabled(m_isReportingEnabled, reportParsingErrors == ReportParsingErrors::Yes);
for (auto& header : headers.m_headers)
didReceiveHeader(header.first, header.second, ContentSecurityPolicy::PolicyFrom::HTTPHeader, String { });
m_referrer = WTFMove(referrer);
m_httpStatusCode = headers.m_httpStatusCode;
}
void ContentSecurityPolicy::didReceiveHeaders(const ContentSecurityPolicy& other, ReportParsingErrors reportParsingErrors)
{
SetForScope isReportingEnabled(m_isReportingEnabled, reportParsingErrors == ReportParsingErrors::Yes);
for (auto& policy : other.m_policies)
didReceiveHeader(policy->header(), policy->headerType(), ContentSecurityPolicy::PolicyFrom::HTTPHeader, String { });
m_referrer = other.m_referrer;
m_httpStatusCode = other.m_httpStatusCode;
m_upgradeInsecureRequests = other.m_upgradeInsecureRequests;
m_insecureNavigationRequestsToUpgrade.add(other.m_insecureNavigationRequestsToUpgrade.begin(), other.m_insecureNavigationRequestsToUpgrade.end());
}
void ContentSecurityPolicy::didReceiveHeader(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicy::PolicyFrom policyFrom, String&& referrer, int httpStatusCode)
{
if (m_hasAPIPolicy)
return;
m_referrer = WTFMove(referrer);
m_httpStatusCode = httpStatusCode;
if (policyFrom == PolicyFrom::API) {
ASSERT(m_policies.isEmpty());
m_hasAPIPolicy = true;
} else if (policyFrom == PolicyFrom::HTTPHeader)
m_isHeaderDelivered = true;
m_cachedResponseHeaders = std::nullopt;
// RFC2616, section 4.2 specifies that headers appearing multiple times can
// be combined with a comma. Walk the header string, and parse each comma
// separated chunk as a separate header.
readCharactersForParsing(header, [&](auto buffer) {
skipWhile<isASCIISpace>(buffer);
auto begin = buffer.position();
while (buffer.hasCharactersRemaining()) {
skipUntil(buffer, ',');
// header1,header2 OR header1
// ^ ^
m_policies.append(ContentSecurityPolicyDirectiveList::create(*this, String(begin, buffer.position() - begin), type, policyFrom));
// Skip the comma, and begin the next header from the current position.
ASSERT(buffer.atEnd() || *buffer == ',');
skipExactly(buffer, ',');
begin = buffer.position();
}
});
if (m_scriptExecutionContext)
applyPolicyToScriptExecutionContext();
}
void ContentSecurityPolicy::updateSourceSelf(const SecurityOrigin& securityOrigin)
{
m_selfSourceProtocol = securityOrigin.protocol().convertToASCIILowercase();
m_selfSource = makeUnique<ContentSecurityPolicySource>(*this, m_selfSourceProtocol, securityOrigin.host(), securityOrigin.port(), emptyString(), false, false, IsSelfSource::Yes);
}
void ContentSecurityPolicy::applyPolicyToScriptExecutionContext()
{
ASSERT(m_scriptExecutionContext);
// Update source self as the security origin may have changed between the time we were created and now.
// For instance, we may have been initially created for an about:blank iframe that later inherited the
// security origin of its owner document.
ASSERT(m_scriptExecutionContext->securityOrigin());
updateSourceSelf(*m_scriptExecutionContext->securityOrigin());
bool enableStrictMixedContentMode = false;
for (auto& policy : m_policies) {
const ContentSecurityPolicyDirective* violatedDirective = policy->violatedDirectiveForUnsafeEval();
if (violatedDirective && !violatedDirective->directiveList().isReportOnly()) {
m_lastPolicyEvalDisabledErrorMessage = policy->evalDisabledErrorMessage();
m_lastPolicyWebAssemblyDisabledErrorMessage = policy->webAssemblyDisabledErrorMessage();
}
if (policy->hasBlockAllMixedContentDirective() && !policy->isReportOnly())
enableStrictMixedContentMode = true;
}
if (!m_lastPolicyEvalDisabledErrorMessage.isNull())
m_scriptExecutionContext->disableEval(m_lastPolicyEvalDisabledErrorMessage);
if (!m_lastPolicyWebAssemblyDisabledErrorMessage.isNull())
m_scriptExecutionContext->disableWebAssembly(m_lastPolicyWebAssemblyDisabledErrorMessage);
if (m_sandboxFlags != SandboxNone && is<Document>(m_scriptExecutionContext))
m_scriptExecutionContext->enforceSandboxFlags(m_sandboxFlags, SecurityContext::SandboxFlagsSource::CSP);
if (enableStrictMixedContentMode)
m_scriptExecutionContext->setStrictMixedContentMode(true);
}
void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value)
{
m_overrideInlineStyleAllowed = value;
}
bool ContentSecurityPolicy::urlMatchesSelf(const URL& url, bool forFrameSrc) const
{
// As per https://w3c.github.io/webappsec-csp/#match-url-to-source-expression, we compare the URL origin with the policy origin.
// We get origin using https://url.spec.whatwg.org/#concept-url-origin which has specific blob URLs treatment as follow.
if (forFrameSrc && url.protocolIsBlob())
return m_selfSource->matches(BlobURL::getOriginURL(url));
return m_selfSource->matches(url);
}
bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtocol() const
{
if (is<Document>(m_scriptExecutionContext))
return downcast<Document>(*m_scriptExecutionContext).settings().allowContentSecurityPolicySourceStarToMatchAnyProtocol();
return false;
}
template<typename Predicate, typename... Args>
typename std::enable_if<!std::is_convertible<Predicate, ContentSecurityPolicy::ViolatedDirectiveCallback>::value, bool>::type ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, Predicate&& predicate, Args&&... args) const
{
bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly;
for (auto& policy : m_policies) {
if (policy->isReportOnly() != isReportOnly)
continue;
if ((policy.get()->*predicate)(std::forward<Args>(args)...))
return false;
}
return true;
}
template<typename Predicate, typename... Args>
bool ContentSecurityPolicy::allPoliciesWithDispositionAllow(Disposition disposition, ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const
{
bool isReportOnly = disposition == ContentSecurityPolicy::Disposition::ReportOnly;
bool isAllowed = true;
for (auto& policy : m_policies) {
if (policy->isReportOnly() != isReportOnly)
continue;
if (const ContentSecurityPolicyDirective* violatedDirective = (policy.get()->*predicate)(std::forward<Args>(args)...)) {
isAllowed = false;
callback(*violatedDirective);
}
}
return isAllowed;
}
template<typename Predicate, typename... Args>
bool ContentSecurityPolicy::allPoliciesAllow(ViolatedDirectiveCallback&& callback, Predicate&& predicate, Args&&... args) const
{
bool isAllowed = true;
for (auto& policy : m_policies) {
if (const ContentSecurityPolicyDirective* violatedDirective = (policy.get()->*predicate)(std::forward<Args>(args)...)) {
if (!violatedDirective->directiveList().isReportOnly())
isAllowed = false;
callback(*violatedDirective);
}
}
return isAllowed;
}
static Vector<ResourceCryptographicDigest> parseSubResourceIntegrityIntoDigests(const String& subResourceIntegrity)
{
auto encodedDigests = parseIntegrityMetadata(subResourceIntegrity);
if (!encodedDigests)
return { };
return WTF::compactMap(*encodedDigests, [](auto& encodedDigest) {
return decodeEncodedResourceCryptographicDigest(encodedDigest);
});
}
static Vector<ContentSecurityPolicyHash> generateHashesForContent(const StringView content, OptionSet<ContentSecurityPolicyHashAlgorithm> algorithms)
{
CString utf8Content = content.utf8(StrictConversionReplacingUnpairedSurrogatesWithFFFD);
Vector<ContentSecurityPolicyHash> hashes;
for (auto algorithm : algorithms) {
auto hash = cryptographicDigestForBytes(algorithm, utf8Content.data(), utf8Content.length());
hashes.append(hash);
}
return hashes;
}
bool ContentSecurityPolicy::allowJavaScriptURLs(const String& contextURL, const OrdinalNumber& contextLine, const String& source, Element* element) const
{
bool didNotifyInspector = false;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, URL(), "Refused to execute a script", "its hash or 'unsafe-inline'");
reportViolation(violatedDirective, "inline"_s, consoleMessage, contextURL, source, TextPosition(contextLine, OrdinalNumber()), URL(), nullptr, element);
if (!didNotifyInspector && violatedDirective.directiveList().isReportOnly()) {
reportBlockedScriptExecutionToInspector(violatedDirective.text());
didNotifyInspector = true;
}
};
auto contentHashes = generateHashesForContent(source, m_hashAlgorithmsForInlineScripts);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForInlineJavascriptURL, contentHashes);
}
bool ContentSecurityPolicy::allowInlineEventHandlers(const String& contextURL, const OrdinalNumber& contextLine, const String& source, Element* element, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
bool didNotifyInspector = false;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, URL(), "Refused to execute a script for an inline event handler", "'unsafe-inline'");
reportViolation(violatedDirective, "inline"_s, consoleMessage, contextURL, source, TextPosition(contextLine, OrdinalNumber()), URL(), nullptr, element);
if (!didNotifyInspector && !violatedDirective.directiveList().isReportOnly()) {
reportBlockedScriptExecutionToInspector(violatedDirective.text());
didNotifyInspector = true;
}
};
auto contentHashes = generateHashesForContent(source, m_hashAlgorithmsForInlineScripts);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForInlineEventHandlers, contentHashes);
}
bool ContentSecurityPolicy::allowScriptWithNonce(const String& nonce, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
if (strippedNonce.isEmpty())
return false;
// FIXME: We need to report violations in report-only policies. See <https://bugs.webkit.org/show_bug.cgi?id=159830>.
return allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, &ContentSecurityPolicyDirectiveList::violatedDirectiveForScriptNonce, strippedNonce);
}
bool ContentSecurityPolicy::allowStyleWithNonce(const String& nonce, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
if (strippedNonce.isEmpty())
return false;
// FIXME: We need to report violations in report-only policies. See <https://bugs.webkit.org/show_bug.cgi?id=159830>.
return allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, &ContentSecurityPolicyDirectiveList::violatedDirectiveForStyleNonce, strippedNonce);
}
bool ContentSecurityPolicy::shouldPerformEarlyCSPCheck() const
{
// We perform checks early if strict-dynamic is included in the CSP policy because
// we have access to necessary information about the script that we do not have later on.
for (auto& policy : m_policies) {
if (policy.get()->strictDynamicIncluded())
return true;
}
return false;
}
bool ContentSecurityPolicy::allowNonParserInsertedScripts(const URL& sourceURL, const URL& contextURL, const OrdinalNumber& contextLine, const String& nonce, const StringView& scriptContent, ParserInserted parserInserted) const
{
if (!shouldPerformEarlyCSPCheck())
return true;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
TextPosition sourcePosition(contextLine, OrdinalNumber());
const char* message = sourceURL.isEmpty() ? "Refused to execute a script" : "Refused to load";
String consoleMessage = consoleMessageForViolation(violatedDirective, sourceURL, message);
reportViolation(violatedDirective, sourceURL.isEmpty() ? "inline"_s : sourceURL.string(), consoleMessage, contextURL.string(), scriptContent, sourcePosition);
};
auto contentHashes = generateHashesForContent(scriptContent, m_hashAlgorithmsForInlineScripts);
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForNonParserInsertedScripts, strippedNonce, contentHashes, sourceURL, parserInserted);
}
bool ContentSecurityPolicy::allowInlineScript(const String& contextURL, const OrdinalNumber& contextLine, StringView scriptContent, Element& element, const String& nonce, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy || shouldPerformEarlyCSPCheck())
return true;
bool didNotifyInspector = false;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, URL(), "Refused to execute a script", "its hash, its nonce, or 'unsafe-inline'");
reportViolation(violatedDirective, "inline"_s, consoleMessage, contextURL, scriptContent, TextPosition(contextLine, OrdinalNumber()), URL(), nullptr, &element);
if (!didNotifyInspector && !violatedDirective.directiveList().isReportOnly()) {
reportBlockedScriptExecutionToInspector(violatedDirective.text());
didNotifyInspector = true;
}
};
auto contentHashes = generateHashesForContent(scriptContent, m_hashAlgorithmsForInlineScripts);
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScriptElement, strippedNonce, contentHashes);
}
bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const OrdinalNumber& contextLine, StringView styleContent, CheckUnsafeHashes shouldCheckUnsafeHashes, Element& element, const String& nonce, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
if (m_overrideInlineStyleAllowed)
return true;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, URL(), "Refused to apply a stylesheet", "its hash, its nonce, or 'unsafe-inline'");
reportViolation(violatedDirective, "inline"_s, consoleMessage, contextURL, styleContent, TextPosition(contextLine, OrdinalNumber()), URL(), nullptr, &element);
};
auto contentHashes = generateHashesForContent(styleContent, m_hashAlgorithmsForInlineStylesheets);
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
if (shouldCheckUnsafeHashes == CheckUnsafeHashes::Yes)
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyleAttribute, strippedNonce, contentHashes);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyleElement, strippedNonce, contentHashes);
}
bool ContentSecurityPolicy::allowEval(JSC::JSGlobalObject* state, LogToConsole shouldLogToConsole, StringView codeContent, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
bool didNotifyInspector = false;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = shouldLogToConsole == LogToConsole::Yes ? consoleMessageForViolation(violatedDirective, URL(), "Refused to execute a script", "'unsafe-eval'") : String();
reportViolation(violatedDirective, "eval"_s, consoleMessage, state, codeContent);
if (!didNotifyInspector && !violatedDirective.directiveList().isReportOnly()) {
reportBlockedScriptExecutionToInspector(violatedDirective.text());
didNotifyInspector = true;
}
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeEval);
}
bool ContentSecurityPolicy::allowFrameAncestors(const Frame& frame, const URL& url, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
Frame& topFrame = frame.tree().top();
if (&frame == &topFrame)
return true;
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, url.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrameAncestor, frame);
}
bool ContentSecurityPolicy::overridesXFrameOptions() const
{
// If a resource is delivered with an policy that includes a directive named frame-ancestors and whose disposition
// is "enforce", then the X-Frame-Options header MUST be ignored.
// https://www.w3.org/TR/CSP3/#frame-ancestors-and-frame-options
for (auto& policy : m_policies) {
if (!policy->isReportOnly() && policy->hasFrameAncestorsDirective())
return true;
}
return false;
}
bool ContentSecurityPolicy::allowFrameAncestors(const Vector<RefPtr<SecurityOrigin>>& ancestorOrigins, const URL& url, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
bool isTopLevelFrame = ancestorOrigins.isEmpty();
if (isTopLevelFrame)
return true;
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, url.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrameAncestorOrigins, ancestorOrigins);
}
bool ContentSecurityPolicy::allowPluginType(const String& type, const String& typeAttribute, const URL& url, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load", "its MIME type");
reportViolation(violatedDirective, url.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForPluginType, type, typeAttribute);
}
bool ContentSecurityPolicy::allowObjectFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
// As per section object-src of the Content Security Policy Level 3 spec., <http://w3c.github.io/webappsec-csp> (Editor's Draft, 29 February 2016),
// "If plugin content is loaded without an associated URL (perhaps an object element lacks a data attribute, but loads some default plugin based
// on the specified type), it MUST be blocked if object-src's value is 'none', but will otherwise be allowed".
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : url;
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, blockedURL.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForObjectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone::Yes);
}
bool ContentSecurityPolicy::allowChildFrameFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const
{
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, url.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrame, url, redirectResponseReceived == RedirectResponseReceived::Yes);
}
bool ContentSecurityPolicy::allowResourceFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, ResourcePredicate resourcePredicate, const URL& preRedirectURL) const
{
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : url;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, blockedURL.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), resourcePredicate, url, redirectResponseReceived == RedirectResponseReceived::Yes);
}
bool ContentSecurityPolicy::allowWorkerFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : url;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
auto consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, blockedURL.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForWorker, url, redirectResponseReceived == RedirectResponseReceived::Yes);
}
bool ContentSecurityPolicy::allowScriptFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL, const String& subResourceIntegrity, const String& nonce) const
{
if (shouldPerformEarlyCSPCheck())
return true;
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : url;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, blockedURL.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
auto subResourceIntegrityDigests = parseSubResourceIntegrityIntoDigests(subResourceIntegrity);
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForScript, url, redirectResponseReceived == RedirectResponseReceived::Yes, subResourceIntegrityDigests, strippedNonce);
}
bool ContentSecurityPolicy::allowImageFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
return allowResourceFromSource(url, redirectResponseReceived, &ContentSecurityPolicyDirectiveList::violatedDirectiveForImage, preRedirectURL);
}
bool ContentSecurityPolicy::allowStyleFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL, const String& nonce) const
{
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
const auto& blockedURL = !preRedirectURL.isNull() ? preRedirectURL : url;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to load");
reportViolation(violatedDirective, blockedURL.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
String strippedNonce = stripLeadingAndTrailingHTMLSpaces(nonce);
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForStyle, url, redirectResponseReceived == RedirectResponseReceived::Yes, strippedNonce);
}
bool ContentSecurityPolicy::allowFontFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
return allowResourceFromSource(url, redirectResponseReceived, &ContentSecurityPolicyDirectiveList::violatedDirectiveForFont, preRedirectURL);
}
#if ENABLE(APPLICATION_MANIFEST)
bool ContentSecurityPolicy::allowManifestFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
return allowResourceFromSource(url, redirectResponseReceived, &ContentSecurityPolicyDirectiveList::violatedDirectiveForManifest, preRedirectURL);
}
#endif // ENABLE(APPLICATION_MANIFEST)
bool ContentSecurityPolicy::allowMediaFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
return allowResourceFromSource(url, redirectResponseReceived, &ContentSecurityPolicyDirectiveList::violatedDirectiveForMedia, preRedirectURL);
}
bool ContentSecurityPolicy::allowConnectToSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to connect to");
reportViolation(violatedDirective, url.string(), consoleMessage, sourceURL, StringView(), sourcePosition, preRedirectURL);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForConnectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes);
}
bool ContentSecurityPolicy::allowFormAction(const URL& url, RedirectResponseReceived redirectResponseReceived, const URL& preRedirectURL) const
{
return allowResourceFromSource(url, redirectResponseReceived, &ContentSecurityPolicyDirectiveList::violatedDirectiveForFormAction, preRedirectURL);
}
bool ContentSecurityPolicy::allowBaseURI(const URL& url, bool overrideContentSecurityPolicy) const
{
if (overrideContentSecurityPolicy)
return true;
if (LegacySchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol()))
return true;
String sourceURL;
TextPosition sourcePosition(OrdinalNumber::beforeFirst(), OrdinalNumber());
auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) {
String consoleMessage = consoleMessageForViolation(violatedDirective, url, "Refused to change the document base URL to");
reportViolation(violatedDirective, url.string(), consoleMessage, sourceURL, StringView(), sourcePosition);
};
return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForBaseURI, url);
}
static bool shouldReportProtocolOnly(const URL& url)
{
return !url.isHierarchical() || url.protocolIs("file"_s);
}
String ContentSecurityPolicy::createURLForReporting(const URL& url, const String& violatedDirective) const
{
// This implements the deprecated CSP2 "strip uri for reporting" algorithm from https://www.w3.org/TR/CSP2/#violation-reports
// with the change that cross-origin is considered safe except on some directives.
// The frame-src, object-src, and block-all-mixed-content directives would allow a website to put another in a frame
// and listen to emitted securitypolicyviolations.
bool directiveIsSafe = violatedDirective != ContentSecurityPolicyDirectiveNames::frameSrc
&& violatedDirective != ContentSecurityPolicyDirectiveNames::objectSrc
&& violatedDirective != ContentSecurityPolicyDirectiveNames::blockAllMixedContent;
auto securityOrigin = static_cast<SecurityOriginData>(*m_selfSource).securityOrigin();
if (!url.isValid())
return { };
if (shouldReportProtocolOnly(url))
return url.protocol().toString();
if (securityOrigin->canRequest(url) || directiveIsSafe)
return url.strippedForUseAsReferrer();
return SecurityOrigin::create(url)->toString();
}
void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, JSC::JSGlobalObject* state, StringView sourceContent) const
{
// FIXME: Extract source file, and position from JSC::ExecState.
return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, String(), sourceContent, TextPosition(OrdinalNumber::beforeFirst(), OrdinalNumber::beforeFirst()), state);
}
void ContentSecurityPolicy::reportViolation(const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURL, const String& consoleMessage, JSC::JSGlobalObject* state) const
{
// FIXME: Extract source file, content, and position from JSC::ExecState.
return reportViolation(violatedDirective, violatedDirectiveList, blockedURL, consoleMessage, String(), StringView(), TextPosition(OrdinalNumber::beforeFirst(), OrdinalNumber::beforeFirst()), state);
}
void ContentSecurityPolicy::reportViolation(const ContentSecurityPolicyDirective& violatedDirective, const String& blockedURL, const String& consoleMessage, const String& sourceURL, const StringView& sourceContent, const TextPosition& sourcePosition, const URL& preRedirectURL, JSC::JSGlobalObject* state, Element* element) const
{
return reportViolation(violatedDirective.nameForReporting().convertToASCIILowercase(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourceContent, sourcePosition, state, preRedirectURL, element);
}
void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const String& blockedURLString, const String& consoleMessage, const String& sourceURL, const StringView& sourceContent, const TextPosition& sourcePosition, JSC::JSGlobalObject* state, const URL& preRedirectURL, Element* element) const
{
logToConsole(consoleMessage, sourceURL, sourcePosition.m_line, sourcePosition.m_column, state);
if (!m_isReportingEnabled)
return;
// FIXME: Support sending reports from worker.
CSPInfo info;
String blockedURI;
if (blockedURLString == "eval"_s || blockedURLString == "inline"_s)
blockedURI = blockedURLString;
else {
// If there is a redirect then we use the pre-redirect URL: https://www.w3.org/TR/CSP3/#security-violation-reports.
blockedURI = createURLForReporting(preRedirectURL.isNull() ? URL { blockedURLString } : preRedirectURL, effectiveViolatedDirective);
}
info.documentURI = m_documentURL ? m_documentURL.value().strippedForUseAsReferrer() : blockedURI;
info.lineNumber = sourcePosition.m_line.oneBasedInt();
info.columnNumber = sourcePosition.m_column.oneBasedInt();
info.sample = violatedDirectiveList.shouldReportSample(effectiveViolatedDirective) ? sourceContent.left(40).toString() : emptyString();
if (m_client)
m_client->willSendCSPViolationReport(info);
else {
if (!is<Document>(m_scriptExecutionContext))
return;
auto& document = downcast<Document>(*m_scriptExecutionContext);
auto* frame = document.frame();
if (!frame)
return;
info.documentURI = shouldReportProtocolOnly(document.url()) ? document.url().protocol().toString() : document.url().strippedForUseAsReferrer();
auto stack = createScriptCallStack(JSExecState::currentState(), 2);
auto* callFrame = stack->firstNonNativeCallFrame();
if (callFrame && callFrame->lineNumber()) {
info.sourceFile = createURLForReporting(URL { callFrame->sourceURL() }, effectiveViolatedDirective);
info.lineNumber = callFrame->lineNumber();
info.columnNumber = callFrame->columnNumber();
}
}
ASSERT(m_client || is<Document>(m_scriptExecutionContext));
// FIXME: Is it policy to not use the status code for HTTPS, or is that a bug?
unsigned short httpStatusCode = m_selfSourceProtocol == "http"_s ? m_httpStatusCode : 0;
// 1. Dispatch violation event.
SecurityPolicyViolationEventInit violationEventInit;
violationEventInit.documentURI = info.documentURI;
violationEventInit.referrer = m_referrer;
violationEventInit.blockedURI = blockedURI;
violationEventInit.violatedDirective = effectiveViolatedDirective; // Historical alias to effectiveDirective: https://www.w3.org/TR/CSP3/#violation-events.
violationEventInit.effectiveDirective = effectiveViolatedDirective;
violationEventInit.originalPolicy = violatedDirectiveList.header();
violationEventInit.sourceFile = info.sourceFile;
violationEventInit.disposition = violatedDirectiveList.isReportOnly() ? SecurityPolicyViolationEventDisposition::Report : SecurityPolicyViolationEventDisposition::Enforce;
violationEventInit.statusCode = httpStatusCode;
violationEventInit.lineNumber = info.lineNumber;
violationEventInit.columnNumber = info.columnNumber;
violationEventInit.sample = info.sample;
violationEventInit.bubbles = true;
violationEventInit.composed = true;
if (m_client)
m_client->enqueueSecurityPolicyViolationEvent(WTFMove(violationEventInit));
else {
auto& document = downcast<Document>(*m_scriptExecutionContext);
if (element && element->document() == document)
element->enqueueSecurityPolicyViolationEvent(WTFMove(violationEventInit));
else
document.enqueueSecurityPolicyViolationEvent(WTFMove(violationEventInit));
}
// 2. Send violation report (if applicable).
auto& reportURIs = violatedDirectiveList.reportURIs();
if (reportURIs.isEmpty())
return;
// We need to be careful here when deciding what information to send to the
// report-uri. Currently, we send only the current document's URL and the
// directive that was violated. The document's URL is safe to send because
// it's the document itself that's requesting that it be sent. You could
// make an argument that we shouldn't send HTTPS document URLs to HTTP
// report-uris (for the same reasons that we suppress the Referer in that
// case), but the Referer is sent implicitly whereas this request is only
// sent explicitly. As for which directive was violated, that's pretty
// harmless information.
auto cspReport = JSON::Object::create();
cspReport->setString("document-uri"_s, info.documentURI);
cspReport->setString("referrer"_s, m_referrer);
cspReport->setString("violated-directive"_s, effectiveViolatedDirective);
cspReport->setString("effective-directive"_s, effectiveViolatedDirective);
cspReport->setString("original-policy"_s, violatedDirectiveList.header());
cspReport->setString("blocked-uri"_s, blockedURI);
cspReport->setInteger("status-code"_s, httpStatusCode);
if (!info.sourceFile.isNull()) {
cspReport->setString("source-file"_s, info.sourceFile);
cspReport->setInteger("line-number"_s, info.lineNumber);
cspReport->setInteger("column-number"_s, info.columnNumber);
}
auto reportObject = JSON::Object::create();
reportObject->setObject("csp-report"_s, WTFMove(cspReport));
auto report = FormData::create(reportObject->toJSONString().utf8());
if (m_client) {
for (const auto& url : reportURIs)
m_client->sendCSPViolationReport(URL { m_protectedURL, url }, report.copyRef());
} else {
auto& document = downcast<Document>(*m_scriptExecutionContext);
for (const auto& url : reportURIs)
PingLoader::sendViolationReport(*document.frame(), URL { m_protectedURL, url }, report.copyRef(), ViolationReportType::ContentSecurityPolicy);
}
}
void ContentSecurityPolicy::reportUnsupportedDirective(const String& name) const
{
String message;
if (equalLettersIgnoringASCIICase(name, "allow"_s))
message = "The 'allow' directive has been replaced with 'default-src'. Please use that directive instead, as 'allow' has no effect."_s;
else if (equalLettersIgnoringASCIICase(name, "options"_s))
message = "The 'options' directive has been replaced with 'unsafe-inline' and 'unsafe-eval' source expressions for the 'script-src' and 'style-src' directives. Please use those directives instead, as 'options' has no effect."_s;
else if (equalLettersIgnoringASCIICase(name, "policy-uri"_s))
message = "The 'policy-uri' directive has been removed from the specification. Please specify a complete policy via the Content-Security-Policy header."_s;
else
message = makeString("Unrecognized Content-Security-Policy directive '", name, "'.\n"); // FIXME: Why does this include a newline?
logToConsole(message);
}
void ContentSecurityPolicy::reportDirectiveAsSourceExpression(const String& directiveName, StringView sourceExpression) const
{
logToConsole("The Content Security Policy directive '" + directiveName + "' contains '" + sourceExpression + "' as a source expression. Did you mean '" + directiveName + " ...; " + sourceExpression + "...' (note the semicolon)?");
}
void ContentSecurityPolicy::reportDuplicateDirective(const String& name) const
{
logToConsole(makeString("Ignoring duplicate Content-Security-Policy directive '", name, "'.\n"));
}
void ContentSecurityPolicy::reportInvalidPluginTypes(const String& pluginType) const
{
String message;
if (pluginType.isNull())
message = "'plugin-types' Content Security Policy directive is empty; all plugins will be blocked.\n"_s;
else
message = makeString("Invalid plugin type in 'plugin-types' Content Security Policy directive: '", pluginType, "'.\n");
logToConsole(message);
}
void ContentSecurityPolicy::reportInvalidSandboxFlags(const String& invalidFlags) const
{
logToConsole("Error while parsing the 'sandbox' Content Security Policy directive: " + invalidFlags);
}
void ContentSecurityPolicy::reportInvalidDirectiveInReportOnlyMode(const String& directiveName) const
{
logToConsole("The Content Security Policy directive '" + directiveName + "' is ignored when delivered in a report-only policy.");
}
void ContentSecurityPolicy::reportInvalidDirectiveInHTTPEquivMeta(const String& directiveName) const
{
logToConsole("The Content Security Policy directive '" + directiveName + "' is ignored when delivered via an HTML meta element.");
}
void ContentSecurityPolicy::reportInvalidDirectiveValueCharacter(const String& directiveName, const String& value) const
{
logToConsole(makeString("The value for Content Security Policy directive '", directiveName, "' contains an invalid character: '", value, "'. Non-whitespace characters outside ASCII 0x21-0x7E must be percent-encoded, as described in RFC 3986, section 2.1: http://tools.ietf.org/html/rfc3986#section-2.1."));
}
void ContentSecurityPolicy::reportInvalidPathCharacter(const String& directiveName, const String& value, const char invalidChar) const
{
ASSERT(invalidChar == '#' || invalidChar == '?');
const char* ignoring;
if (invalidChar == '?')
ignoring = "The query component, including the '?', will be ignored.";
else
ignoring = "The fragment identifier, including the '#', will be ignored.";
logToConsole(makeString("The source list for Content Security Policy directive '", directiveName, "' contains a source with an invalid path: '", value, "'. ", ignoring));
}
void ContentSecurityPolicy::reportInvalidSourceExpression(const String& directiveName, const String& source) const
{
logToConsole(makeString("The source list for Content Security Policy directive '", directiveName, "' contains an invalid source: '", source, "'. It will be ignored.",
equalLettersIgnoringASCIICase(source, "'none'"_s) ? " Note that 'none' has no effect unless it is the only expression in the source list." : ""));
}
void ContentSecurityPolicy::reportMissingReportURI(const String& policy) const
{
logToConsole("The Content Security Policy '" + policy + "' was delivered in report-only mode, but does not specify a 'report-uri'; the policy will have no effect. Please either add a 'report-uri' directive, or deliver the policy via the 'Content-Security-Policy' header.");
}
void ContentSecurityPolicy::logToConsole(const String& message, const String& contextURL, const OrdinalNumber& contextLine, const OrdinalNumber& contextColumn, JSC::JSGlobalObject* state) const
{
if (message.isEmpty())
return;
if (!m_isReportingEnabled)
return;
if (m_client)
m_client->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, 0);
else if (m_scriptExecutionContext)
m_scriptExecutionContext->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, contextURL, contextLine.oneBasedInt(), contextColumn.oneBasedInt(), state);
}
void ContentSecurityPolicy::reportBlockedScriptExecutionToInspector(const String& directiveText) const
{
if (m_scriptExecutionContext)
InspectorInstrumentation::scriptExecutionBlockedByCSP(m_scriptExecutionContext, directiveText);
}
void ContentSecurityPolicy::upgradeInsecureRequestIfNeeded(ResourceRequest& request, InsecureRequestType requestType) const
{
URL url = request.url();
upgradeInsecureRequestIfNeeded(url, requestType);
request.setURL(url);
}
void ContentSecurityPolicy::upgradeInsecureRequestIfNeeded(URL& url, InsecureRequestType requestType) const
{
if (!url.protocolIs("http"_s) && !url.protocolIs("ws"_s))
return;
bool upgradeRequest = m_insecureNavigationRequestsToUpgrade.contains(SecurityOriginData::fromURL(url));
if (requestType == InsecureRequestType::Load || requestType == InsecureRequestType::FormSubmission)
upgradeRequest |= m_upgradeInsecureRequests;
if (!upgradeRequest)
return;
if (url.protocolIs("http"_s))
url.setProtocol("https"_s);
else {
ASSERT(url.protocolIs("ws"_s));
url.setProtocol("wss"_s);
}
if (url.port() && url.port().value() == 80)
url.setPort(443);
}
void ContentSecurityPolicy::setUpgradeInsecureRequests(bool upgradeInsecureRequests)
{
m_upgradeInsecureRequests = upgradeInsecureRequests;
if (!m_upgradeInsecureRequests)
return;
if (!m_scriptExecutionContext)
return;
// Store the upgrade domain as an 'insecure' protocol so we can quickly identify
// origins we should upgrade.
URL upgradeURL = m_scriptExecutionContext->url();
if (upgradeURL.protocolIs("https"_s))
upgradeURL.setProtocol("http"_s);
else if (upgradeURL.protocolIs("wss"_s))
upgradeURL.setProtocol("ws"_s);
m_insecureNavigationRequestsToUpgrade.add(SecurityOriginData::fromURL(upgradeURL));
}
void ContentSecurityPolicy::inheritInsecureNavigationRequestsToUpgradeFromOpener(const ContentSecurityPolicy& other)
{
m_insecureNavigationRequestsToUpgrade.add(other.m_insecureNavigationRequestsToUpgrade.begin(), other.m_insecureNavigationRequestsToUpgrade.end());
}
HashSet<SecurityOriginData> ContentSecurityPolicy::takeNavigationRequestsToUpgrade()
{
return WTFMove(m_insecureNavigationRequestsToUpgrade);
}
void ContentSecurityPolicy::setInsecureNavigationRequestsToUpgrade(HashSet<SecurityOriginData>&& insecureNavigationRequests)
{
m_insecureNavigationRequestsToUpgrade = WTFMove(insecureNavigationRequests);
}
}