diff options
Diffstat (limited to 'Source/WebCore/page/csp')
19 files changed, 3191 insertions, 0 deletions
diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.cpp b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp new file mode 100644 index 000000000..475087f0e --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.cpp @@ -0,0 +1,881 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2013-2017 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 "ContentSecurityPolicyDirective.h" +#include "ContentSecurityPolicyDirectiveList.h" +#include "ContentSecurityPolicyDirectiveNames.h" +#include "ContentSecurityPolicyHash.h" +#include "ContentSecurityPolicySource.h" +#include "ContentSecurityPolicySourceList.h" +#include "DOMStringList.h" +#include "Document.h" +#include "DocumentLoader.h" +#include "EventNames.h" +#include "FormData.h" +#include "FormDataList.h" +#include "Frame.h" +#include "HTMLParserIdioms.h" +#include "InspectorInstrumentation.h" +#include "JSDOMWindowShell.h" +#include "JSMainThreadExecState.h" +#include "ParsingUtilities.h" +#include "PingLoader.h" +#include "ResourceRequest.h" +#include "RuntimeEnabledFeatures.h" +#include "SchemeRegistry.h" +#include "SecurityOrigin.h" +#include "SecurityPolicyViolationEvent.h" +#include "Settings.h" +#include "TextEncoding.h" +#include <inspector/InspectorValues.h> +#include <inspector/ScriptCallStack.h> +#include <inspector/ScriptCallStackFactory.h> +#include <pal/crypto/CryptoDigest.h> +#include <wtf/SetForScope.h> +#include <wtf/text/StringBuilder.h> +#include <wtf/text/TextPosition.h> + +using namespace Inspector; + +namespace WebCore { + +static String consoleMessageForViolation(const char* effectiveViolatedDirective, const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const char* prefix, const char* subject = "it") +{ + StringBuilder result; + if (violatedDirective.directiveList().isReportOnly()) + result.appendLiteral("[Report Only] "); + result.append(prefix); + if (!blockedURL.isEmpty()) { + result.append(' '); + result.append(blockedURL.stringCenterEllipsizedToLength()); + } + result.appendLiteral(" because "); + result.append(subject); + if (violatedDirective.isDefaultSrc()) { + result.appendLiteral(" appears in neither the "); + result.append(effectiveViolatedDirective); + result.appendLiteral(" directive nor the default-src directive of the Content Security Policy."); + } else { + result.appendLiteral(" does not appear in the "); + result.append(effectiveViolatedDirective); + result.appendLiteral(" directive of the Content Security Policy."); + } + return result.toString(); +} + +ContentSecurityPolicy::ContentSecurityPolicy(ScriptExecutionContext& scriptExecutionContext) + : m_scriptExecutionContext(&scriptExecutionContext) + , m_sandboxFlags(SandboxNone) +{ + ASSERT(scriptExecutionContext.securityOrigin()); + updateSourceSelf(*scriptExecutionContext.securityOrigin()); +} + +ContentSecurityPolicy::ContentSecurityPolicy(const SecurityOrigin& securityOrigin, const Frame* frame) + : m_frame(frame) + , m_sandboxFlags(SandboxNone) +{ + updateSourceSelf(securityOrigin); +} + +ContentSecurityPolicy::~ContentSecurityPolicy() +{ +} + +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); +} + +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; + bool isReportOnly = false; + for (auto& policy : m_policies) { + if (!policy->hasBlockAllMixedContentDirective()) + continue; + + isReportOnly = policy->isReportOnly(); + + StringBuilder consoleMessage; + if (isReportOnly) + consoleMessage.appendLiteral("[Report Only] "); + consoleMessage.append("Blocked mixed content "); + consoleMessage.append(url.stringCenterEllipsizedToLength()); + consoleMessage.appendLiteral(" because "); + consoleMessage.append("'block-all-mixed-content' appears in the Content Security Policy."); + reportViolation(ContentSecurityPolicyDirectiveNames::blockAllMixedContent, ContentSecurityPolicyDirectiveNames::blockAllMixedContent, *policy, url, consoleMessage.toString()); + + if (!isReportOnly) + allow = false; + } + return allow; +} + +void ContentSecurityPolicy::didCreateWindowShell(JSDOMWindowShell& windowShell) const +{ + JSDOMWindow* window = windowShell.window(); + ASSERT(window); + ASSERT(window->scriptExecutionContext()); + ASSERT(window->scriptExecutionContext()->contentSecurityPolicy() == this); + if (!windowShell.world().isNormal()) { + window->setEvalEnabled(true); + return; + } + window->setEvalEnabled(m_lastPolicyEvalDisabledErrorMessage.isNull(), m_lastPolicyEvalDisabledErrorMessage); +} + +ContentSecurityPolicyResponseHeaders ContentSecurityPolicy::responseHeaders() const +{ + ContentSecurityPolicyResponseHeaders result; + result.m_headers.reserveInitialCapacity(m_policies.size()); + for (auto& policy : m_policies) + result.m_headers.uncheckedAppend({ policy->header(), policy->headerType() }); + return result; +} + +void ContentSecurityPolicy::didReceiveHeaders(const ContentSecurityPolicyResponseHeaders& headers, ReportParsingErrors reportParsingErrors) +{ + SetForScope<bool> isReportingEnabled(m_isReportingEnabled, reportParsingErrors == ReportParsingErrors::Yes); + for (auto& header : headers.m_headers) + didReceiveHeader(header.first, header.second, ContentSecurityPolicy::PolicyFrom::HTTPHeader); +} + +void ContentSecurityPolicy::didReceiveHeader(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicy::PolicyFrom policyFrom) +{ + if (m_hasAPIPolicy) + return; + + if (policyFrom == PolicyFrom::API) { + ASSERT(m_policies.isEmpty()); + m_hasAPIPolicy = true; + } + + // 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. + auto characters = StringView(header).upconvertedCharacters(); + const UChar* begin = characters; + const UChar* position = begin; + const UChar* end = begin + header.length(); + while (position < end) { + skipUntil<UChar>(position, end, ','); + + // header1,header2 OR header1 + // ^ ^ + m_policies.append(ContentSecurityPolicyDirectiveList::create(*this, String(begin, position - begin), type, policyFrom)); + + // Skip the comma, and begin the next header from the current position. + ASSERT(position == end || *position == ','); + skipExactly<UChar>(position, end, ','); + begin = position; + } + + if (m_scriptExecutionContext) + applyPolicyToScriptExecutionContext(); +} + +void ContentSecurityPolicy::updateSourceSelf(const SecurityOrigin& securityOrigin) +{ + m_selfSourceProtocol = securityOrigin.protocol(); + m_selfSource = std::make_unique<ContentSecurityPolicySource>(*this, m_selfSourceProtocol, securityOrigin.host(), securityOrigin.port(), emptyString(), false, false); +} + +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(); + if (policy->hasBlockAllMixedContentDirective() && !policy->isReportOnly()) + enableStrictMixedContentMode = true; + } + + if (!m_lastPolicyEvalDisabledErrorMessage.isNull()) + m_scriptExecutionContext->disableEval(m_lastPolicyEvalDisabledErrorMessage); + if (m_sandboxFlags != SandboxNone && is<Document>(m_scriptExecutionContext)) + m_scriptExecutionContext->enforceSandboxFlags(m_sandboxFlags); + if (enableStrictMixedContentMode) + m_scriptExecutionContext->setStrictMixedContentMode(true); +} + +void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value) +{ + m_overrideInlineStyleAllowed = value; +} + +bool ContentSecurityPolicy::urlMatchesSelf(const URL& url) const +{ + return m_selfSource->matches(url); +} + +bool ContentSecurityPolicy::allowContentSecurityPolicySourceStarToMatchAnyProtocol() const +{ + if (is<Document>(m_scriptExecutionContext)) + return downcast<Document>(*m_scriptExecutionContext).settings().allowContentSecurityPolicySourceStarToMatchAnyProtocol(); + return false; +} + +bool ContentSecurityPolicy::protocolMatchesSelf(const URL& url) const +{ + if (equalLettersIgnoringASCIICase(m_selfSourceProtocol, "http")) + return url.protocolIsInHTTPFamily(); + return equalIgnoringASCIICase(url.protocol(), m_selfSourceProtocol); +} + +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 PAL::CryptoDigest::Algorithm toCryptoDigestAlgorithm(ContentSecurityPolicyHashAlgorithm algorithm) +{ + switch (algorithm) { + case ContentSecurityPolicyHashAlgorithm::SHA_256: + return PAL::CryptoDigest::Algorithm::SHA_256; + case ContentSecurityPolicyHashAlgorithm::SHA_384: + return PAL::CryptoDigest::Algorithm::SHA_384; + case ContentSecurityPolicyHashAlgorithm::SHA_512: + return PAL::CryptoDigest::Algorithm::SHA_512; + } + ASSERT_NOT_REACHED(); + return PAL::CryptoDigest::Algorithm::SHA_512; +} + +template<typename Predicate> +ContentSecurityPolicy::HashInEnforcedAndReportOnlyPoliciesPair ContentSecurityPolicy::findHashOfContentInPolicies(Predicate&& predicate, const String& content, OptionSet<ContentSecurityPolicyHashAlgorithm> algorithms) const +{ + if (algorithms.isEmpty() || content.isEmpty()) + return { false, false }; + + // FIXME: We should compute the document encoding once and cache it instead of computing it on each invocation. + TextEncoding documentEncoding; + if (is<Document>(m_scriptExecutionContext)) + documentEncoding = downcast<Document>(*m_scriptExecutionContext).textEncoding(); + const TextEncoding& encodingToUse = documentEncoding.isValid() ? documentEncoding : UTF8Encoding(); + + // FIXME: Compute the digest with respect to the raw bytes received from the page. + // See <https://bugs.webkit.org/show_bug.cgi?id=155184>. + CString contentCString = encodingToUse.encode(content, EntitiesForUnencodables); + bool foundHashInEnforcedPolicies = false; + bool foundHashInReportOnlyPolicies = false; + for (auto algorithm : algorithms) { + auto cryptoDigest = PAL::CryptoDigest::create(toCryptoDigestAlgorithm(algorithm)); + cryptoDigest->addBytes(contentCString.data(), contentCString.length()); + ContentSecurityPolicyHash hash = { algorithm, cryptoDigest->computeHash() }; + if (!foundHashInEnforcedPolicies && allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, std::forward<Predicate>(predicate), hash)) + foundHashInEnforcedPolicies = true; + if (!foundHashInReportOnlyPolicies && allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::ReportOnly, std::forward<Predicate>(predicate), hash)) + foundHashInReportOnlyPolicies = true; + if (foundHashInEnforcedPolicies && foundHashInReportOnlyPolicies) + break; + } + return { foundHashInEnforcedPolicies, foundHashInReportOnlyPolicies }; +} + +bool ContentSecurityPolicy::allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script", "its hash, its nonce, or 'unsafe-inline'"); + reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber())); + if (!violatedDirective.directiveList().isReportOnly()) + reportBlockedScriptExecutionToInspector(violatedDirective.text()); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript); +} + +bool ContentSecurityPolicy::allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script for an inline event handler", "'unsafe-inline'"); + reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber())); + if (!violatedDirective.directiveList().isReportOnly()) + reportBlockedScriptExecutionToInspector(violatedDirective.text()); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript); +} + +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::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& scriptContent, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + bool foundHashInEnforcedPolicies; + bool foundHashInReportOnlyPolicies; + std::tie(foundHashInEnforcedPolicies, foundHashInReportOnlyPolicies) = findHashOfContentInPolicies(&ContentSecurityPolicyDirectiveList::violatedDirectiveForScriptHash, scriptContent, m_hashAlgorithmsForInlineScripts); + if (foundHashInEnforcedPolicies && foundHashInReportOnlyPolicies) + return true; + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script", "its hash, its nonce, or 'unsafe-inline'"); + reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber())); + if (!violatedDirective.directiveList().isReportOnly()) + reportBlockedScriptExecutionToInspector(violatedDirective.text()); + }; + // FIXME: We should not report that the inline script violated a policy when its hash matched a source + // expression in the policy and the page has more than one policy. See <https://bugs.webkit.org/show_bug.cgi?id=159832>. + if (!foundHashInReportOnlyPolicies) + allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::ReportOnly, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript); + return foundHashInEnforcedPolicies || allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript); +} + +bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& styleContent, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + if (m_overrideInlineStyleAllowed) + return true; + bool foundHashInEnforcedPolicies; + bool foundHashInReportOnlyPolicies; + std::tie(foundHashInEnforcedPolicies, foundHashInReportOnlyPolicies) = findHashOfContentInPolicies(&ContentSecurityPolicyDirectiveList::violatedDirectiveForStyleHash, styleContent, m_hashAlgorithmsForInlineStylesheets); + if (foundHashInEnforcedPolicies && foundHashInReportOnlyPolicies) + return true; + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::styleSrc, violatedDirective, URL(), "Refused to apply a stylesheet", "its hash, its nonce, or 'unsafe-inline'"); + reportViolation(ContentSecurityPolicyDirectiveNames::styleSrc, violatedDirective, URL(), consoleMessage, contextURL, TextPosition(contextLine, WTF::OrdinalNumber())); + }; + // FIXME: We should not report that the inline stylesheet violated a policy when its hash matched a source + // expression in the policy and the page has more than one policy. See <https://bugs.webkit.org/show_bug.cgi?id=159832>. + if (!foundHashInReportOnlyPolicies) + allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::ReportOnly, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyle); + return foundHashInEnforcedPolicies || allPoliciesWithDispositionAllow(ContentSecurityPolicy::Disposition::Enforce, handleViolatedDirective, &ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyle); +} + +bool ContentSecurityPolicy::allowEval(JSC::ExecState* state, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), "Refused to execute a script", "'unsafe-eval'"); + reportViolation(ContentSecurityPolicyDirectiveNames::scriptSrc, violatedDirective, URL(), consoleMessage, state); + if (!violatedDirective.directiveList().isReportOnly()) + reportBlockedScriptExecutionToInspector(violatedDirective.text()); + }; + 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(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::frameAncestors, violatedDirective, url, "Refused to load"); + reportViolation(ContentSecurityPolicyDirectiveNames::frameAncestors, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrameAncestor, frame); +} + +bool ContentSecurityPolicy::allowPluginType(const String& type, const String& typeAttribute, const URL& url, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + String sourceURL; + TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::pluginTypes, violatedDirective, url, "Refused to load", "its MIME type"); + reportViolation(ContentSecurityPolicyDirectiveNames::pluginTypes, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForPluginType, type, typeAttribute); +} + +bool ContentSecurityPolicy::allowObjectFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying())) + 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(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::objectSrc, violatedDirective, url, "Refused to load"); + reportViolation(ContentSecurityPolicyDirectiveNames::objectSrc, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForObjectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone::Yes); +} + +bool ContentSecurityPolicy::allowChildFrameFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying())) + return true; + String sourceURL; + TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + const char* effectiveViolatedDirective = violatedDirective.name() == ContentSecurityPolicyDirectiveNames::frameSrc ? ContentSecurityPolicyDirectiveNames::frameSrc : ContentSecurityPolicyDirectiveNames::childSrc; + String consoleMessage = consoleMessageForViolation(effectiveViolatedDirective, violatedDirective, url, "Refused to load"); + reportViolation(effectiveViolatedDirective, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForFrame, url, redirectResponseReceived == RedirectResponseReceived::Yes); +} + +bool ContentSecurityPolicy::allowResourceFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived, const char* name, ResourcePredicate resourcePredicate) const +{ + if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying())) + return true; + String sourceURL; + TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(name, violatedDirective, url, "Refused to load"); + reportViolation(name, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), resourcePredicate, url, redirectResponseReceived == RedirectResponseReceived::Yes); +} + +bool ContentSecurityPolicy::allowChildContextFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::childSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForChildContext); +} + +bool ContentSecurityPolicy::allowScriptFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::scriptSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForScript); +} + +bool ContentSecurityPolicy::allowImageFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::imgSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForImage); +} + +bool ContentSecurityPolicy::allowStyleFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::styleSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForStyle); +} + +bool ContentSecurityPolicy::allowFontFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::fontSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForFont); +} + +bool ContentSecurityPolicy::allowMediaFromSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::mediaSrc, &ContentSecurityPolicyDirectiveList::violatedDirectiveForMedia); +} + +bool ContentSecurityPolicy::allowConnectToSource(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying())) + return true; + String sourceURL; + TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::connectSrc, violatedDirective, url, "Refused to connect to"); + reportViolation(ContentSecurityPolicyDirectiveNames::connectSrc, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForConnectSource, url, redirectResponseReceived == RedirectResponseReceived::Yes); +} + +bool ContentSecurityPolicy::allowFormAction(const URL& url, RedirectResponseReceived redirectResponseReceived) const +{ + return allowResourceFromSource(url, redirectResponseReceived, ContentSecurityPolicyDirectiveNames::formAction, &ContentSecurityPolicyDirectiveList::violatedDirectiveForFormAction); +} + +bool ContentSecurityPolicy::allowBaseURI(const URL& url, bool overrideContentSecurityPolicy) const +{ + if (overrideContentSecurityPolicy) + return true; + if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol().toStringWithoutCopying())) + return true; + String sourceURL; + TextPosition sourcePosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber()); + auto handleViolatedDirective = [&] (const ContentSecurityPolicyDirective& violatedDirective) { + String consoleMessage = consoleMessageForViolation(ContentSecurityPolicyDirectiveNames::baseURI, violatedDirective, url, "Refused to change the document base URL to"); + reportViolation(ContentSecurityPolicyDirectiveNames::baseURI, violatedDirective, url, consoleMessage, sourceURL, sourcePosition); + }; + return allPoliciesAllow(WTFMove(handleViolatedDirective), &ContentSecurityPolicyDirectiveList::violatedDirectiveForBaseURI, url); +} + +static String stripURLForUseInReport(Document& document, const URL& url) +{ + if (!url.isValid()) + return String(); + if (!url.isHierarchical() || url.protocolIs("file")) + return url.protocol().toString(); + return document.securityOrigin().canRequest(url) ? url.strippedForUseAsReferrer() : SecurityOrigin::create(url).get().toString(); +} + +void ContentSecurityPolicy::reportViolation(const String& violatedDirective, const ContentSecurityPolicyDirective& effectiveViolatedDirective, const URL& blockedURL, const String& consoleMessage, JSC::ExecState* state) const +{ + // FIXME: Extract source file and source position from JSC::ExecState. + return reportViolation(violatedDirective, effectiveViolatedDirective.text(), effectiveViolatedDirective.directiveList(), blockedURL, consoleMessage, String(), TextPosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber::beforeFirst()), state); +} + +void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const URL& blockedURL, const String& consoleMessage, JSC::ExecState* state) const +{ + // FIXME: Extract source file and source position from JSC::ExecState. + return reportViolation(effectiveViolatedDirective, violatedDirective, violatedDirectiveList, blockedURL, consoleMessage, String(), TextPosition(WTF::OrdinalNumber::beforeFirst(), WTF::OrdinalNumber::beforeFirst()), state); +} + +void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const String& consoleMessage, const String& sourceURL, const TextPosition& sourcePosition, JSC::ExecState* state) const +{ + return reportViolation(effectiveViolatedDirective, violatedDirective.text(), violatedDirective.directiveList(), blockedURL, consoleMessage, sourceURL, sourcePosition, state); +} + +void ContentSecurityPolicy::reportViolation(const String& effectiveViolatedDirective, const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const URL& blockedURL, const String& consoleMessage, const String& sourceURL, const TextPosition& sourcePosition, JSC::ExecState* state) const +{ + logToConsole(consoleMessage, sourceURL, sourcePosition.m_line, state); + + if (!m_isReportingEnabled) + return; + + // FIXME: Support sending reports from worker. + if (!is<Document>(m_scriptExecutionContext) && !m_frame) + return; + + ASSERT(!m_frame || effectiveViolatedDirective == ContentSecurityPolicyDirectiveNames::frameAncestors); + + auto& document = is<Document>(m_scriptExecutionContext) ? downcast<Document>(*m_scriptExecutionContext) : *m_frame->document(); + auto* frame = document.frame(); + ASSERT(!m_frame || m_frame == frame); + if (!frame) + return; + + String documentURI; + String blockedURI; + if (is<Document>(m_scriptExecutionContext)) { + documentURI = document.url().strippedForUseAsReferrer(); + blockedURI = stripURLForUseInReport(document, blockedURL); + } else { + // The URL of |document| may not have been initialized (say, when reporting a frame-ancestors violation). + // So, we use the URL of the blocked document for the protected document URL. + documentURI = blockedURL; + blockedURI = blockedURL; + } + String violatedDirectiveText = violatedDirective; + String originalPolicy = violatedDirectiveList.header(); + String referrer = document.referrer(); + ASSERT(document.loader()); + // FIXME: Is it policy to not use the status code for HTTPS, or is that a bug? + unsigned short statusCode = document.url().protocolIs("http") && document.loader() ? document.loader()->response().httpStatusCode() : 0; + + String sourceFile; + int lineNumber = 0; + int columnNumber = 0; + auto stack = createScriptCallStack(JSMainThreadExecState::currentState(), 2); + auto* callFrame = stack->firstNonNativeCallFrame(); + if (callFrame && callFrame->lineNumber()) { + sourceFile = stripURLForUseInReport(document, URL(URL(), callFrame->sourceURL())); + lineNumber = callFrame->lineNumber(); + columnNumber = callFrame->columnNumber(); + } + + // 1. Dispatch violation event. + bool canBubble = false; + bool cancelable = false; + document.enqueueDocumentEvent(SecurityPolicyViolationEvent::create(eventNames().securitypolicyviolationEvent, canBubble, cancelable, documentURI, referrer, blockedURI, violatedDirectiveText, effectiveViolatedDirective, originalPolicy, sourceFile, statusCode, lineNumber, columnNumber)); + + // 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 = InspectorObject::create(); + cspReport->setString(ASCIILiteral("document-uri"), documentURI); + cspReport->setString(ASCIILiteral("referrer"), referrer); + cspReport->setString(ASCIILiteral("violated-directive"), violatedDirectiveText); + cspReport->setString(ASCIILiteral("effective-directive"), effectiveViolatedDirective); + cspReport->setString(ASCIILiteral("original-policy"), originalPolicy); + cspReport->setString(ASCIILiteral("blocked-uri"), blockedURI); + cspReport->setInteger(ASCIILiteral("status-code"), statusCode); + if (!sourceFile.isNull()) { + cspReport->setString(ASCIILiteral("source-file"), sourceFile); + cspReport->setInteger(ASCIILiteral("line-number"), lineNumber); + cspReport->setInteger(ASCIILiteral("column-number"), columnNumber); + } + + auto reportObject = InspectorObject::create(); + reportObject->setObject(ASCIILiteral("csp-report"), WTFMove(cspReport)); + + auto report = FormData::create(reportObject->toJSONString().utf8()); + for (const auto& url : reportURIs) + PingLoader::sendViolationReport(*frame, is<Document>(m_scriptExecutionContext) ? document.completeURL(url) : document.completeURL(url, blockedURL), report.copyRef(), ViolationReportType::ContentSecurityPolicy); +} + +void ContentSecurityPolicy::reportUnsupportedDirective(const String& name) const +{ + String message; + if (equalLettersIgnoringASCIICase(name, "allow")) + message = ASCIILiteral("The 'allow' directive has been replaced with 'default-src'. Please use that directive instead, as 'allow' has no effect."); + else if (equalLettersIgnoringASCIICase(name, "options")) + message = ASCIILiteral("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."); + else if (equalLettersIgnoringASCIICase(name, "policy-uri")) + message = ASCIILiteral("The 'policy-uri' directive has been removed from the specification. Please specify a complete policy via the Content-Security-Policy header."); + 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, const String& 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"; + 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 +{ + String message = 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."); + logToConsole(message); +} + +void ContentSecurityPolicy::reportInvalidPathCharacter(const String& directiveName, const String& value, const char invalidChar) const +{ + ASSERT(invalidChar == '#' || invalidChar == '?'); + + String ignoring; + if (invalidChar == '?') + ignoring = "The query component, including the '?', will be ignored."; + else + ignoring = "The fragment identifier, including the '#', will be ignored."; + + String message = makeString("The source list for Content Security Policy directive '", directiveName, "' contains a source with an invalid path: '", value, "'. ", ignoring); + logToConsole(message); +} + +void ContentSecurityPolicy::reportInvalidSourceExpression(const String& directiveName, const String& source) const +{ + String message = makeString("The source list for Content Security Policy directive '", directiveName, "' contains an invalid source: '", source, "'. It will be ignored."); + if (equalLettersIgnoringASCIICase(source, "'none'")) + message = makeString(message, " Note that 'none' has no effect unless it is the only expression in the source list."); + logToConsole(message); +} + +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 WTF::OrdinalNumber& contextLine, JSC::ExecState* state) const +{ + if (!m_isReportingEnabled) + return; + + // FIXME: <http://webkit.org/b/114317> ContentSecurityPolicy::logToConsole should include a column number + if (m_scriptExecutionContext) + m_scriptExecutionContext->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, contextURL, contextLine.oneBasedInt(), 0, state); + else if (m_frame && m_frame->document()) + static_cast<ScriptExecutionContext*>(m_frame->document())->addConsoleMessage(MessageSource::Security, MessageLevel::Error, message, contextURL, contextLine.oneBasedInt(), 0, 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") && !url.protocolIs("ws")) + return; + + bool upgradeRequest = m_insecureNavigationRequestsToUpgrade.contains(SecurityOrigin::create(url)); + if (requestType == InsecureRequestType::Load || requestType == InsecureRequestType::FormSubmission) + upgradeRequest |= m_upgradeInsecureRequests; + + if (!upgradeRequest) + return; + + if (url.protocolIs("http")) + url.setProtocol("https"); + else if (url.protocolIs("ws")) + url.setProtocol("wss"); + else + return; + + 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")) + upgradeURL.setProtocol("http"); + else if (upgradeURL.protocolIs("wss")) + upgradeURL.setProtocol("ws"); + + m_insecureNavigationRequestsToUpgrade.add(SecurityOrigin::create(upgradeURL)); +} + +void ContentSecurityPolicy::inheritInsecureNavigationRequestsToUpgradeFromOpener(const ContentSecurityPolicy& other) +{ + m_insecureNavigationRequestsToUpgrade.add(other.m_insecureNavigationRequestsToUpgrade.begin(), other.m_insecureNavigationRequestsToUpgrade.end()); +} + +HashSet<RefPtr<SecurityOrigin>>&& ContentSecurityPolicy::takeNavigationRequestsToUpgrade() +{ + return WTFMove(m_insecureNavigationRequestsToUpgrade); +} + +void ContentSecurityPolicy::setInsecureNavigationRequestsToUpgrade(HashSet<RefPtr<SecurityOrigin>>&& insecureNavigationRequests) +{ + m_insecureNavigationRequestsToUpgrade = WTFMove(insecureNavigationRequests); +} + +} diff --git a/Source/WebCore/page/csp/ContentSecurityPolicy.h b/Source/WebCore/page/csp/ContentSecurityPolicy.h new file mode 100644 index 000000000..5286a86a6 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicy.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "ContentSecurityPolicyResponseHeaders.h" +#include "SecurityOrigin.h" +#include "SecurityOriginHash.h" +#include <wtf/HashSet.h> +#include <wtf/OptionSet.h> +#include <wtf/Vector.h> +#include <wtf/text/TextPosition.h> + +namespace JSC { +class ExecState; +} + +namespace WTF { +class OrdinalNumber; +} + +namespace WebCore { + +class ContentSecurityPolicyDirective; +class ContentSecurityPolicyDirectiveList; +class ContentSecurityPolicySource; +class DOMStringList; +class Frame; +class JSDOMWindowShell; +class ResourceRequest; +class ScriptExecutionContext; +class SecurityOrigin; +class TextEncoding; +class URL; + +enum class ContentSecurityPolicyHashAlgorithm; + +typedef Vector<std::unique_ptr<ContentSecurityPolicyDirectiveList>> CSPDirectiveListVector; +typedef int SandboxFlags; + +class ContentSecurityPolicy { + WTF_MAKE_FAST_ALLOCATED; +public: + explicit ContentSecurityPolicy(ScriptExecutionContext&); + explicit ContentSecurityPolicy(const SecurityOrigin&, const Frame* = nullptr); + ~ContentSecurityPolicy(); + + void copyStateFrom(const ContentSecurityPolicy*); + void copyUpgradeInsecureRequestStateFrom(const ContentSecurityPolicy&); + + void didCreateWindowShell(JSDOMWindowShell&) const; + + enum class PolicyFrom { + API, + HTTPEquivMeta, + HTTPHeader, + Inherited, + }; + ContentSecurityPolicyResponseHeaders responseHeaders() const; + enum ReportParsingErrors { No, Yes }; + void didReceiveHeaders(const ContentSecurityPolicyResponseHeaders&, ReportParsingErrors = ReportParsingErrors::Yes); + void didReceiveHeader(const String&, ContentSecurityPolicyHeaderType, ContentSecurityPolicy::PolicyFrom); + + bool allowScriptWithNonce(const String& nonce, bool overrideContentSecurityPolicy = false) const; + bool allowStyleWithNonce(const String& nonce, bool overrideContentSecurityPolicy = false) const; + + bool allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy = false) const; + bool allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, bool overrideContentSecurityPolicy = false) const; + bool allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& scriptContent, bool overrideContentSecurityPolicy = false) const; + bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, const String& styleContent, bool overrideContentSecurityPolicy = false) const; + + bool allowEval(JSC::ExecState*, bool overrideContentSecurityPolicy = false) const; + + bool allowPluginType(const String& type, const String& typeAttribute, const URL&, bool overrideContentSecurityPolicy = false) const; + + bool allowFrameAncestors(const Frame&, const URL&, bool overrideContentSecurityPolicy = false) const; + + enum class RedirectResponseReceived { No, Yes }; + bool allowScriptFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowImageFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowStyleFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowFontFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowMediaFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + + bool allowChildFrameFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowChildContextFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowConnectToSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowFormAction(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + + bool allowObjectFromSource(const URL&, RedirectResponseReceived = RedirectResponseReceived::No) const; + bool allowBaseURI(const URL&, bool overrideContentSecurityPolicy = false) const; + + void setOverrideAllowInlineStyle(bool); + + void gatherReportURIs(DOMStringList&) const; + + bool allowRunningOrDisplayingInsecureContent(const URL&); + + // The following functions are used by internal data structures to call back into this object when parsing, validating, + // and applying a Content Security Policy. + // FIXME: We should make the various directives serve only as state stores for the parsed policy and remove these functions. + // This class should traverse the directives, validating the policy, and applying it to the script execution context. + + // Used by ContentSecurityPolicyMediaListDirective + void reportInvalidPluginTypes(const String&) const; + + // Used by ContentSecurityPolicySourceList + void reportDirectiveAsSourceExpression(const String& directiveName, const String& sourceExpression) const; + void reportInvalidPathCharacter(const String& directiveName, const String& value, const char) const; + void reportInvalidSourceExpression(const String& directiveName, const String& source) const; + bool urlMatchesSelf(const URL&) const; + bool allowContentSecurityPolicySourceStarToMatchAnyProtocol() const; + + // Used by ContentSecurityPolicyDirectiveList + void reportDuplicateDirective(const String&) const; + void reportInvalidDirectiveValueCharacter(const String& directiveName, const String& value) const; + void reportInvalidSandboxFlags(const String&) const; + void reportInvalidDirectiveInReportOnlyMode(const String&) const; + void reportInvalidDirectiveInHTTPEquivMeta(const String&) const; + void reportMissingReportURI(const String&) const; + void reportUnsupportedDirective(const String&) const; + void enforceSandboxFlags(SandboxFlags sandboxFlags) { m_sandboxFlags |= sandboxFlags; } + void addHashAlgorithmsForInlineScripts(OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsForInlineScripts) + { + m_hashAlgorithmsForInlineScripts |= hashAlgorithmsForInlineScripts; + } + void addHashAlgorithmsForInlineStylesheets(OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsForInlineStylesheets) + { + m_hashAlgorithmsForInlineStylesheets |= hashAlgorithmsForInlineStylesheets; + } + + // Used by ContentSecurityPolicySource + bool protocolMatchesSelf(const URL&) const; + + void setUpgradeInsecureRequests(bool); + bool upgradeInsecureRequests() const { return m_upgradeInsecureRequests; } + enum class InsecureRequestType { Load, FormSubmission, Navigation }; + void upgradeInsecureRequestIfNeeded(ResourceRequest&, InsecureRequestType) const; + void upgradeInsecureRequestIfNeeded(URL&, InsecureRequestType) const; + + HashSet<RefPtr<SecurityOrigin>>&& takeNavigationRequestsToUpgrade(); + void inheritInsecureNavigationRequestsToUpgradeFromOpener(const ContentSecurityPolicy&); + void setInsecureNavigationRequestsToUpgrade(HashSet<RefPtr<SecurityOrigin>>&&); + +private: + void logToConsole(const String& message, const String& contextURL = String(), const WTF::OrdinalNumber& contextLine = WTF::OrdinalNumber::beforeFirst(), JSC::ExecState* = nullptr) const; + void updateSourceSelf(const SecurityOrigin&); + void applyPolicyToScriptExecutionContext(); + + const TextEncoding& documentEncoding() const; + + enum class Disposition { + Enforce, + ReportOnly, + }; + + using ViolatedDirectiveCallback = std::function<void (const ContentSecurityPolicyDirective&)>; + + template<typename Predicate, typename... Args> + typename std::enable_if<!std::is_convertible<Predicate, ViolatedDirectiveCallback>::value, bool>::type allPoliciesWithDispositionAllow(Disposition, Predicate&&, Args&&...) const; + + template<typename Predicate, typename... Args> + bool allPoliciesWithDispositionAllow(Disposition, ViolatedDirectiveCallback&&, Predicate&&, Args&&...) const; + + template<typename Predicate, typename... Args> + bool allPoliciesAllow(ViolatedDirectiveCallback&&, Predicate&&, Args&&...) const WARN_UNUSED_RETURN; + + using ResourcePredicate = const ContentSecurityPolicyDirective *(ContentSecurityPolicyDirectiveList::*)(const URL &, bool) const; + bool allowResourceFromSource(const URL&, RedirectResponseReceived, const char*, ResourcePredicate) const; + + using HashInEnforcedAndReportOnlyPoliciesPair = std::pair<bool, bool>; + template<typename Predicate> HashInEnforcedAndReportOnlyPoliciesPair findHashOfContentInPolicies(Predicate&&, const String& content, OptionSet<ContentSecurityPolicyHashAlgorithm>) const WARN_UNUSED_RETURN; + + void reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const String& consoleMessage, JSC::ExecState*) const; + void reportViolation(const String& effectiveViolatedDirective, const String& violatedDirective, const ContentSecurityPolicyDirectiveList&, const URL& blockedURL, const String& consoleMessage, JSC::ExecState* = nullptr) const; + void reportViolation(const String& effectiveViolatedDirective, const ContentSecurityPolicyDirective& violatedDirective, const URL& blockedURL, const String& consoleMessage, const String& sourceURL, const TextPosition& sourcePosition, JSC::ExecState* = nullptr) const; + void reportViolation(const String& effectiveViolatedDirective, const String& violatedDirective, const ContentSecurityPolicyDirectiveList& violatedDirectiveList, const URL& blockedURL, const String& consoleMessage, const String& sourceURL, const TextPosition& sourcePosition, JSC::ExecState*) const; + void reportBlockedScriptExecutionToInspector(const String& directiveText) const; + + // We can never have both a script execution context and a frame. + ScriptExecutionContext* m_scriptExecutionContext { nullptr }; + const Frame* m_frame { nullptr }; + std::unique_ptr<ContentSecurityPolicySource> m_selfSource; + String m_selfSourceProtocol; + CSPDirectiveListVector m_policies; + String m_lastPolicyEvalDisabledErrorMessage; + SandboxFlags m_sandboxFlags; + bool m_overrideInlineStyleAllowed { false }; + bool m_isReportingEnabled { true }; + bool m_upgradeInsecureRequests { false }; + bool m_hasAPIPolicy { false }; + OptionSet<ContentSecurityPolicyHashAlgorithm> m_hashAlgorithmsForInlineScripts; + OptionSet<ContentSecurityPolicyHashAlgorithm> m_hashAlgorithmsForInlineStylesheets; + HashSet<RefPtr<SecurityOrigin>> m_insecureNavigationRequestsToUpgrade; +}; + +} diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirective.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyDirective.cpp new file mode 100644 index 000000000..980c72fd4 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirective.cpp @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 ITS 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 "ContentSecurityPolicyDirective.h" + +#include "ContentSecurityPolicyDirectiveList.h" + +namespace WebCore { + +bool ContentSecurityPolicyDirective::isDefaultSrc() const +{ + return this == m_directiveList.defaultSrc(); +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirective.h b/Source/WebCore/page/csp/ContentSecurityPolicyDirective.h new file mode 100644 index 000000000..a8f1d4f0b --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirective.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class ContentSecurityPolicyDirectiveList; + +class ContentSecurityPolicyDirective { +public: + ContentSecurityPolicyDirective(const ContentSecurityPolicyDirectiveList& directiveList, const String& name, const String& value) + : m_name(name) + , m_text(name + ' ' + value) + , m_directiveList(directiveList) + { + } + + const String& name() const { return m_name; } + const String& text() const { return m_text; } + + const ContentSecurityPolicyDirectiveList& directiveList() const { return m_directiveList; } + + bool isDefaultSrc() const; + +private: + String m_name; + String m_text; + const ContentSecurityPolicyDirectiveList& m_directiveList; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp new file mode 100644 index 000000000..4bdc6f1d2 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.cpp @@ -0,0 +1,511 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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 "ContentSecurityPolicyDirectiveList.h" + +#include "ContentSecurityPolicyDirectiveNames.h" +#include "Document.h" +#include "Frame.h" +#include "ParsingUtilities.h" +#include "SecurityContext.h" + +namespace WebCore { + +static bool isDirectiveNameCharacter(UChar c) +{ + return isASCIIAlphanumeric(c) || c == '-'; +} + +static bool isDirectiveValueCharacter(UChar c) +{ + return isASCIISpace(c) || (c >= 0x21 && c <= 0x7e); // Whitespace + VCHAR +} + +static bool isNotASCIISpace(UChar c) +{ + return !isASCIISpace(c); +} + +static inline bool checkEval(ContentSecurityPolicySourceListDirective* directive) +{ + return !directive || directive->allowEval(); +} + +static inline bool checkInline(ContentSecurityPolicySourceListDirective* directive) +{ + return !directive || directive->allowInline(); +} + +static inline bool checkSource(ContentSecurityPolicySourceListDirective* directive, const URL& url, bool didReceiveRedirectResponse = false, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone shouldAllowEmptyURLIfSourceListEmpty = ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone::No) +{ + return !directive || directive->allows(url, didReceiveRedirectResponse, shouldAllowEmptyURLIfSourceListEmpty); +} + +static inline bool checkHash(ContentSecurityPolicySourceListDirective* directive, const ContentSecurityPolicyHash& hash) +{ + return !directive || directive->allows(hash); +} + +static inline bool checkNonce(ContentSecurityPolicySourceListDirective* directive, const String& nonce) +{ + return !directive || directive->allows(nonce); +} + +static inline bool checkFrameAncestors(ContentSecurityPolicySourceListDirective* directive, const Frame& frame) +{ + if (!directive) + return true; + bool didReceiveRedirectResponse = false; + for (Frame* current = frame.tree().parent(); current; current = current->tree().parent()) { + if (!directive->allows(current->document()->url(), didReceiveRedirectResponse, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone::No)) + return false; + } + return true; +} + +static inline bool checkMediaType(ContentSecurityPolicyMediaListDirective* directive, const String& type, const String& typeAttribute) +{ + if (!directive) + return true; + if (typeAttribute.isEmpty() || typeAttribute.stripWhiteSpace() != type) + return false; + return directive->allows(type); +} + +ContentSecurityPolicyDirectiveList::ContentSecurityPolicyDirectiveList(ContentSecurityPolicy& policy, ContentSecurityPolicyHeaderType type) + : m_policy(policy) + , m_headerType(type) +{ + m_reportOnly = (type == ContentSecurityPolicyHeaderType::Report || type == ContentSecurityPolicyHeaderType::PrefixedReport); +} + +std::unique_ptr<ContentSecurityPolicyDirectiveList> ContentSecurityPolicyDirectiveList::create(ContentSecurityPolicy& policy, const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicy::PolicyFrom from) +{ + auto directives = std::make_unique<ContentSecurityPolicyDirectiveList>(policy, type); + directives->parse(header, from); + + if (!checkEval(directives->operativeDirective(directives->m_scriptSrc.get()))) { + String message = makeString("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() && directives->reportURIs().isEmpty()) + policy.reportMissingReportURI(header); + + return directives; +} + +ContentSecurityPolicySourceListDirective* ContentSecurityPolicyDirectiveList::operativeDirective(ContentSecurityPolicySourceListDirective* directive) const +{ + return directive ? directive : m_defaultSrc.get(); +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeEval() const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_scriptSrc.get()); + if (checkEval(operativeDirective)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineScript() const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_scriptSrc.get()); + if (checkInline(operativeDirective)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForUnsafeInlineStyle() const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_styleSrc.get()); + if (checkInline(operativeDirective)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForScriptHash(const ContentSecurityPolicyHash& hash) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_scriptSrc.get()); + if (checkHash(operativeDirective, hash)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForStyleHash(const ContentSecurityPolicyHash& hash) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_styleSrc.get()); + if (checkHash(operativeDirective, hash)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForScriptNonce(const String& nonce) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_scriptSrc.get()); + if (checkNonce(operativeDirective, nonce)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForStyleNonce(const String& nonce) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_styleSrc.get()); + if (checkNonce(operativeDirective, nonce)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForBaseURI(const URL& url) const +{ + if (checkSource(m_baseURI.get(), url)) + return nullptr; + return m_baseURI.get(); +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForChildContext(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_childSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForConnectSource(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_connectSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForFont(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_fontSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForFormAction(const URL& url, bool didReceiveRedirectResponse) const +{ + if (checkSource(m_formAction.get(), url, didReceiveRedirectResponse)) + return nullptr; + return m_formAction.get(); +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForFrame(const URL& url, bool didReceiveRedirectResponse) const +{ + if (url.isBlankURL()) + return nullptr; + + // We must enforce the frame-src directive (if specified) before enforcing the child-src directive for a nested browsing + // context by <https://w3c.github.io/webappsec-csp/2/#directive-child-src-nested> (29 August 2015). + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_frameSrc ? m_frameSrc.get() : m_childSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForFrameAncestor(const Frame& frame) const +{ + if (checkFrameAncestors(m_frameAncestors.get(), frame)) + return nullptr; + return m_frameAncestors.get(); +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForImage(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_imgSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForMedia(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_mediaSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForObjectSource(const URL& url, bool didReceiveRedirectResponse, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone shouldAllowEmptyURLIfSourceListEmpty) const +{ + if (url.isBlankURL()) + return nullptr; + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_objectSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse, shouldAllowEmptyURLIfSourceListEmpty)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForPluginType(const String& type, const String& typeAttribute) const +{ + if (checkMediaType(m_pluginTypes.get(), type, typeAttribute)) + return nullptr; + return m_pluginTypes.get(); +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForScript(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_scriptSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +const ContentSecurityPolicyDirective* ContentSecurityPolicyDirectiveList::violatedDirectiveForStyle(const URL& url, bool didReceiveRedirectResponse) const +{ + ContentSecurityPolicySourceListDirective* operativeDirective = this->operativeDirective(m_styleSrc.get()); + if (checkSource(operativeDirective, url, didReceiveRedirectResponse)) + return nullptr; + return operativeDirective; +} + +// policy = directive-list +// directive-list = [ directive *( ";" [ directive ] ) ] +// +void ContentSecurityPolicyDirectiveList::parse(const String& policy, ContentSecurityPolicy::PolicyFrom policyFrom) +{ + m_header = policy; + if (policy.isEmpty()) + return; + + auto characters = StringView(policy).upconvertedCharacters(); + const UChar* position = characters; + const UChar* end = position + policy.length(); + + while (position < end) { + const UChar* directiveBegin = position; + skipUntil<UChar>(position, end, ';'); + + String name, value; + if (parseDirective(directiveBegin, position, name, value)) { + ASSERT(!name.isEmpty()); + if (policyFrom == ContentSecurityPolicy::PolicyFrom::Inherited) { + if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::upgradeInsecureRequests)) + continue; + } else if (policyFrom == ContentSecurityPolicy::PolicyFrom::HTTPEquivMeta) { + if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::sandbox) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::reportURI) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::frameAncestors)) { + m_policy.reportInvalidDirectiveInHTTPEquivMeta(name); + continue; + } + } + 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 ContentSecurityPolicyDirectiveList::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, isDirectiveNameCharacter>(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, isDirectiveValueCharacter>(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 ContentSecurityPolicyDirectiveList::parseReportURI(const String& name, const String& value) +{ + if (!m_reportURIs.isEmpty()) { + m_policy.reportDuplicateDirective(name); + return; + } + + auto characters = StringView(value).upconvertedCharacters(); + const UChar* position = characters; + const UChar* end = position + value.length(); + + while (position < end) { + skipWhile<UChar, isASCIISpace>(position, end); + + const UChar* urlBegin = position; + skipWhile<UChar, isNotASCIISpace>(position, end); + + if (urlBegin < position) + m_reportURIs.append(value.substring(urlBegin - characters, position - urlBegin)); + } +} + + +template<class CSPDirectiveType> +void ContentSecurityPolicyDirectiveList::setCSPDirective(const String& name, const String& value, std::unique_ptr<CSPDirectiveType>& directive) +{ + if (directive) { + m_policy.reportDuplicateDirective(name); + return; + } + directive = std::make_unique<CSPDirectiveType>(*this, name, value); +} + +void ContentSecurityPolicyDirectiveList::applySandboxPolicy(const String& name, const String& sandboxPolicy) +{ + if (m_reportOnly) { + m_policy.reportInvalidDirectiveInReportOnlyMode(name); + return; + } + if (m_haveSandboxPolicy) { + m_policy.reportDuplicateDirective(name); + return; + } + m_haveSandboxPolicy = true; + String invalidTokens; + m_policy.enforceSandboxFlags(SecurityContext::parseSandboxPolicy(sandboxPolicy, invalidTokens)); + if (!invalidTokens.isNull()) + m_policy.reportInvalidSandboxFlags(invalidTokens); +} + +void ContentSecurityPolicyDirectiveList::setUpgradeInsecureRequests(const String& name) +{ + if (m_reportOnly) { + m_policy.reportInvalidDirectiveInReportOnlyMode(name); + return; + } + if (m_upgradeInsecureRequests) { + m_policy.reportDuplicateDirective(name); + return; + } + m_upgradeInsecureRequests = true; + m_policy.setUpgradeInsecureRequests(true); +} + +void ContentSecurityPolicyDirectiveList::setBlockAllMixedContentEnabled(const String& name) +{ + if (m_hasBlockAllMixedContentDirective) { + m_policy.reportDuplicateDirective(name); + return; + } + m_hasBlockAllMixedContentDirective = true; +} + +void ContentSecurityPolicyDirectiveList::addDirective(const String& name, const String& value) +{ + ASSERT(!name.isEmpty()); + + if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::defaultSrc)) { + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_defaultSrc); + m_policy.addHashAlgorithmsForInlineScripts(m_defaultSrc->hashAlgorithmsUsed()); + m_policy.addHashAlgorithmsForInlineStylesheets(m_defaultSrc->hashAlgorithmsUsed()); + } else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::scriptSrc)) { + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_scriptSrc); + m_policy.addHashAlgorithmsForInlineScripts(m_scriptSrc->hashAlgorithmsUsed()); + } else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::styleSrc)) { + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_styleSrc); + m_policy.addHashAlgorithmsForInlineStylesheets(m_styleSrc->hashAlgorithmsUsed()); + } else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::objectSrc)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_objectSrc); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::frameSrc)) { + // FIXME: Log to console "The frame-src directive is deprecated. Use the child-src directive instead." + // See <https://bugs.webkit.org/show_bug.cgi?id=155773>. + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_frameSrc); + } else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::imgSrc)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_imgSrc); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::fontSrc)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_fontSrc); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::mediaSrc)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_mediaSrc); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::connectSrc)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_connectSrc); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::childSrc)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_childSrc); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::formAction)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_formAction); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::baseURI)) + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_baseURI); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::frameAncestors)) { + if (m_reportOnly) { + m_policy.reportInvalidDirectiveInReportOnlyMode(name); + return; + } + setCSPDirective<ContentSecurityPolicySourceListDirective>(name, value, m_frameAncestors); + } else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::pluginTypes)) + setCSPDirective<ContentSecurityPolicyMediaListDirective>(name, value, m_pluginTypes); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::sandbox)) + applySandboxPolicy(name, value); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::reportURI)) + parseReportURI(name, value); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::upgradeInsecureRequests)) + setUpgradeInsecureRequests(name); + else if (equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::blockAllMixedContent)) + setBlockAllMixedContentEnabled(name); + else + m_policy.reportUnsupportedDirective(name); +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h new file mode 100644 index 000000000..3adb22fe8 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveList.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "ContentSecurityPolicy.h" +#include "ContentSecurityPolicyHash.h" +#include "ContentSecurityPolicyMediaListDirective.h" +#include "ContentSecurityPolicySourceListDirective.h" +#include "URL.h" + +namespace WebCore { + +class Frame; + +class ContentSecurityPolicyDirectiveList { + WTF_MAKE_FAST_ALLOCATED; +public: + static std::unique_ptr<ContentSecurityPolicyDirectiveList> create(ContentSecurityPolicy&, const String&, ContentSecurityPolicyHeaderType, ContentSecurityPolicy::PolicyFrom); + ContentSecurityPolicyDirectiveList(ContentSecurityPolicy&, ContentSecurityPolicyHeaderType); + + const String& header() const { return m_header; } + ContentSecurityPolicyHeaderType headerType() const { return m_headerType; } + + const ContentSecurityPolicyDirective* violatedDirectiveForUnsafeEval() const; + const ContentSecurityPolicyDirective* violatedDirectiveForUnsafeInlineScript() const; + const ContentSecurityPolicyDirective* violatedDirectiveForUnsafeInlineStyle() const; + + const ContentSecurityPolicyDirective* violatedDirectiveForScriptHash(const ContentSecurityPolicyHash&) const; + const ContentSecurityPolicyDirective* violatedDirectiveForStyleHash(const ContentSecurityPolicyHash&) const; + + const ContentSecurityPolicyDirective* violatedDirectiveForScriptNonce(const String&) const; + const ContentSecurityPolicyDirective* violatedDirectiveForStyleNonce(const String&) const; + + const ContentSecurityPolicyDirective* violatedDirectiveForBaseURI(const URL&) const; + const ContentSecurityPolicyDirective* violatedDirectiveForChildContext(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForConnectSource(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForFont(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForFormAction(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForFrame(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForFrameAncestor(const Frame&) const; + const ContentSecurityPolicyDirective* violatedDirectiveForImage(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForMedia(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForObjectSource(const URL&, bool didReceiveRedirectResponse, ContentSecurityPolicySourceListDirective::ShouldAllowEmptyURLIfSourceListIsNotNone) const; + const ContentSecurityPolicyDirective* violatedDirectiveForPluginType(const String& type, const String& typeAttribute) const; + const ContentSecurityPolicyDirective* violatedDirectiveForScript(const URL&, bool didReceiveRedirectResponse) const; + const ContentSecurityPolicyDirective* violatedDirectiveForStyle(const URL&, bool didReceiveRedirectResponse) const; + + const ContentSecurityPolicyDirective* defaultSrc() const { return m_defaultSrc.get(); } + + bool hasBlockAllMixedContentDirective() const { return m_hasBlockAllMixedContentDirective; } + + const String& evalDisabledErrorMessage() const { return m_evalDisabledErrorMessage; } + bool isReportOnly() const { return m_reportOnly; } + const Vector<String>& reportURIs() const { return m_reportURIs; } + + // FIXME: Remove this once we teach ContentSecurityPolicyDirectiveList how to log an arbitrary console message. + const ContentSecurityPolicy& policy() const { return m_policy; } + +private: + void parse(const String&, ContentSecurityPolicy::PolicyFrom); + + bool parseDirective(const UChar* begin, const UChar* end, String& name, String& value); + void parseReportURI(const String& name, const String& value); + void parsePluginTypes(const String& name, const String& value); + void addDirective(const String& name, const String& value); + void applySandboxPolicy(const String& name, const String& sandboxPolicy); + void setUpgradeInsecureRequests(const String& name); + void setBlockAllMixedContentEnabled(const String& name); + + template <class CSPDirectiveType> + void setCSPDirective(const String& name, const String& value, std::unique_ptr<CSPDirectiveType>&); + + ContentSecurityPolicySourceListDirective* operativeDirective(ContentSecurityPolicySourceListDirective*) const; + + void setEvalDisabledErrorMessage(const String& errorMessage) { m_evalDisabledErrorMessage = errorMessage; } + + // FIXME: Make this a const reference once we teach applySandboxPolicy() to store its policy as opposed to applying it directly onto ContentSecurityPolicy. + ContentSecurityPolicy& m_policy; + + String m_header; + ContentSecurityPolicyHeaderType m_headerType; + + bool m_reportOnly { false }; + bool m_haveSandboxPolicy { false }; + bool m_upgradeInsecureRequests { false }; + bool m_hasBlockAllMixedContentDirective { false }; + + std::unique_ptr<ContentSecurityPolicyMediaListDirective> m_pluginTypes; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_baseURI; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_connectSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_childSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_defaultSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_fontSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_formAction; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_frameAncestors; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_frameSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_imgSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_mediaSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_objectSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_scriptSrc; + std::unique_ptr<ContentSecurityPolicySourceListDirective> m_styleSrc; + + Vector<String> m_reportURIs; + + String m_evalDisabledErrorMessage; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveNames.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveNames.cpp new file mode 100644 index 000000000..261f741f6 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveNames.cpp @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 ITS 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 "ContentSecurityPolicyDirectiveNames.h" + +namespace WebCore { + +namespace ContentSecurityPolicyDirectiveNames { + +const char* const baseURI = "base-uri"; +const char* const childSrc = "child-src"; +const char* const connectSrc = "connect-src"; +const char* const defaultSrc = "default-src"; +const char* const fontSrc = "font-src"; +const char* const formAction = "form-action"; +const char* const frameAncestors = "frame-ancestors"; +const char* const frameSrc = "frame-src"; +const char* const imgSrc = "img-src"; +const char* const mediaSrc = "media-src"; +const char* const objectSrc = "object-src"; +const char* const pluginTypes = "plugin-types"; +const char* const reportURI = "report-uri"; +const char* const sandbox = "sandbox"; +const char* const scriptSrc = "script-src"; +const char* const styleSrc = "style-src"; +const char* const upgradeInsecureRequests = "upgrade-insecure-requests"; +const char* const blockAllMixedContent = "block-all-mixed-content"; + +} // namespace ContentSecurityPolicyDirectiveNames + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveNames.h b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveNames.h new file mode 100644 index 000000000..f13f04aca --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyDirectiveNames.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 ITS 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. + */ + +#pragma once + +namespace WebCore { + +namespace ContentSecurityPolicyDirectiveNames { + +extern const char* const baseURI; +extern const char* const childSrc; +extern const char* const connectSrc; +extern const char* const defaultSrc; +extern const char* const fontSrc; +extern const char* const formAction; +extern const char* const frameAncestors; +extern const char* const frameSrc; +extern const char* const imgSrc; +extern const char* const mediaSrc; +extern const char* const objectSrc; +extern const char* const pluginTypes; +extern const char* const reportURI; +extern const char* const sandbox; +extern const char* const scriptSrc; +extern const char* const styleSrc; +extern const char* const upgradeInsecureRequests; +extern const char* const blockAllMixedContent; + +} // namespace ContentSecurityPolicyDirectiveNames + +} // namespace WebCore + diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyHash.h b/Source/WebCore/page/csp/ContentSecurityPolicyHash.h new file mode 100644 index 000000000..83c1ec22e --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyHash.h @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 ITS 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. + */ + +#pragma once + +#include <wtf/HashTraits.h> +#include <wtf/Hasher.h> +#include <wtf/Vector.h> + +namespace WebCore { + +// Keep this synchronized with the constant maximumContentSecurityPolicyDigestLength below. +enum class ContentSecurityPolicyHashAlgorithm { + SHA_256 = 1 << 0, + SHA_384 = 1 << 1, + SHA_512 = 1 << 2, +}; + +const size_t maximumContentSecurityPolicyDigestLength = 64; // bytes to hold SHA-512 digest + +typedef Vector<uint8_t> ContentSecurityPolicyDigest; +typedef std::pair<ContentSecurityPolicyHashAlgorithm, ContentSecurityPolicyDigest> ContentSecurityPolicyHash; + +} // namespace WebCore + +namespace WTF { + +template<> struct DefaultHash<WebCore::ContentSecurityPolicyHashAlgorithm> { typedef IntHash<WebCore::ContentSecurityPolicyHashAlgorithm> Hash; }; +template<> struct HashTraits<WebCore::ContentSecurityPolicyHashAlgorithm> : StrongEnumHashTraits<WebCore::ContentSecurityPolicyHashAlgorithm> { }; +template<> struct DefaultHash<WebCore::ContentSecurityPolicyDigest> { + struct Hash { + static unsigned hash(const WebCore::ContentSecurityPolicyDigest& digest) + { + return StringHasher::computeHashAndMaskTop8Bits(digest.data(), digest.size()); + } + static bool equal(const WebCore::ContentSecurityPolicyDigest& a, const WebCore::ContentSecurityPolicyDigest& b) + { + return a == b; + } + static const bool safeToCompareToEmptyOrDeleted = true; + }; +}; + +} // namespace WTF diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyMediaListDirective.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyMediaListDirective.cpp new file mode 100644 index 000000000..3d3ed4d0d --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyMediaListDirective.cpp @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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 "ContentSecurityPolicyMediaListDirective.h" + +#include "ContentSecurityPolicy.h" +#include "ContentSecurityPolicyDirectiveList.h" +#include "ParsingUtilities.h" +#include <wtf/text/StringHash.h> + +namespace WebCore { + +static bool isMediaTypeCharacter(UChar c) +{ + return !isASCIISpace(c) && c != '/'; +} + +static bool isNotASCIISpace(UChar c) +{ + return !isASCIISpace(c); +} + +ContentSecurityPolicyMediaListDirective::ContentSecurityPolicyMediaListDirective(const ContentSecurityPolicyDirectiveList& directiveList, const String& name, const String& value) + : ContentSecurityPolicyDirective(directiveList, name, value) +{ + parse(value); +} + +bool ContentSecurityPolicyMediaListDirective::allows(const String& type) const +{ + return m_pluginTypes.contains(type); +} + +void ContentSecurityPolicyMediaListDirective::parse(const String& value) +{ + auto characters = StringView(value).upconvertedCharacters(); + const UChar* begin = characters; + const UChar* position = begin; + const UChar* end = begin + value.length(); + + // 'plugin-types ____;' OR 'plugin-types;' + if (value.isEmpty()) { + directiveList().policy().reportInvalidPluginTypes(value); + return; + } + + while (position < end) { + // _____ OR _____mime1/mime1 + // ^ ^ + skipWhile<UChar, isASCIISpace>(position, end); + if (position == end) + return; + + // mime1/mime1 mime2/mime2 + // ^ + begin = position; + if (!skipExactly<UChar, isMediaTypeCharacter>(position, end)) { + skipWhile<UChar, isNotASCIISpace>(position, end); + directiveList().policy().reportInvalidPluginTypes(String(begin, position - begin)); + continue; + } + skipWhile<UChar, isMediaTypeCharacter>(position, end); + + // mime1/mime1 mime2/mime2 + // ^ + if (!skipExactly<UChar>(position, end, '/')) { + skipWhile<UChar, isNotASCIISpace>(position, end); + directiveList().policy().reportInvalidPluginTypes(String(begin, position - begin)); + continue; + } + + // mime1/mime1 mime2/mime2 + // ^ + if (!skipExactly<UChar, isMediaTypeCharacter>(position, end)) { + skipWhile<UChar, isNotASCIISpace>(position, end); + directiveList().policy().reportInvalidPluginTypes(String(begin, position - begin)); + continue; + } + skipWhile<UChar, isMediaTypeCharacter>(position, end); + + // mime1/mime1 mime2/mime2 OR mime1/mime1 OR mime1/mime1/error + // ^ ^ ^ + if (position < end && isNotASCIISpace(*position)) { + skipWhile<UChar, isNotASCIISpace>(position, end); + directiveList().policy().reportInvalidPluginTypes(String(begin, position - begin)); + continue; + } + m_pluginTypes.add(String(begin, position - begin)); + + ASSERT(position == end || isASCIISpace(*position)); + } +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyMediaListDirective.h b/Source/WebCore/page/csp/ContentSecurityPolicyMediaListDirective.h new file mode 100644 index 000000000..d991481fa --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyMediaListDirective.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "ContentSecurityPolicyDirective.h" +#include <wtf/HashSet.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class ContentSecurityPolicyDirectiveList; + +class ContentSecurityPolicyMediaListDirective : public ContentSecurityPolicyDirective { +public: + ContentSecurityPolicyMediaListDirective(const ContentSecurityPolicyDirectiveList&, const String& name, const String& value); + + bool allows(const String& type) const; + +private: + void parse(const String&); + + HashSet<String> m_pluginTypes; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.cpp b/Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.cpp new file mode 100644 index 000000000..0b67b91d4 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 ITS 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 "ContentSecurityPolicyResponseHeaders.h" + +#include "HTTPHeaderNames.h" +#include "ResourceResponse.h" + +namespace WebCore { + +ContentSecurityPolicyResponseHeaders::ContentSecurityPolicyResponseHeaders(const ResourceResponse& response) +{ + String policyValue = response.httpHeaderField(HTTPHeaderName::ContentSecurityPolicy); + if (!policyValue.isEmpty()) + m_headers.append({ policyValue, ContentSecurityPolicyHeaderType::Enforce }); + + policyValue = response.httpHeaderField(HTTPHeaderName::ContentSecurityPolicyReportOnly); + if (!policyValue.isEmpty()) + m_headers.append({ policyValue, ContentSecurityPolicyHeaderType::Report }); + + policyValue = response.httpHeaderField(HTTPHeaderName::XWebKitCSP); + if (!policyValue.isEmpty()) + m_headers.append({ policyValue, ContentSecurityPolicyHeaderType::PrefixedEnforce }); + + policyValue = response.httpHeaderField(HTTPHeaderName::XWebKitCSPReportOnly); + if (!policyValue.isEmpty()) + m_headers.append({ policyValue, ContentSecurityPolicyHeaderType::PrefixedReport }); +} + +ContentSecurityPolicyResponseHeaders ContentSecurityPolicyResponseHeaders::isolatedCopy() const +{ + ContentSecurityPolicyResponseHeaders isolatedCopy; + isolatedCopy.m_headers.reserveInitialCapacity(m_headers.size()); + for (auto& header : m_headers) + isolatedCopy.m_headers.uncheckedAppend({ header.first.isolatedCopy(), header.second }); + return isolatedCopy; +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.h b/Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.h new file mode 100644 index 000000000..b154613c3 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicyResponseHeaders.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2016 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 APPLE INC. AND ITS CONTRIBUTORS ``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 ITS 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. + */ + +#pragma once + +#include <wtf/Vector.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class ContentSecurityPolicy; +class ResourceResponse; + +enum class ContentSecurityPolicyHeaderType { + Report, + Enforce, + PrefixedReport, + PrefixedEnforce, +}; + +class ContentSecurityPolicyResponseHeaders { +public: + explicit ContentSecurityPolicyResponseHeaders(const ResourceResponse&); + + ContentSecurityPolicyResponseHeaders isolatedCopy() const; + +private: + friend class ContentSecurityPolicy; + + ContentSecurityPolicyResponseHeaders() = default; + + Vector<std::pair<String, ContentSecurityPolicyHeaderType>> m_headers; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySource.cpp b/Source/WebCore/page/csp/ContentSecurityPolicySource.cpp new file mode 100644 index 000000000..59604c67a --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicySource.cpp @@ -0,0 +1,113 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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 "ContentSecurityPolicySource.h" + +#include "ContentSecurityPolicy.h" +#include "URL.h" + +namespace WebCore { + +ContentSecurityPolicySource::ContentSecurityPolicySource(const ContentSecurityPolicy& policy, const String& scheme, const String& host, std::optional<uint16_t> port, const String& path, bool hostHasWildcard, bool portHasWildcard) + : m_policy(policy) + , m_scheme(scheme) + , m_host(host) + , m_port(port) + , m_path(path) + , m_hostHasWildcard(hostHasWildcard) + , m_portHasWildcard(portHasWildcard) +{ +} + +bool ContentSecurityPolicySource::matches(const URL& url, bool didReceiveRedirectResponse) const +{ + if (!schemeMatches(url)) + return false; + if (isSchemeOnly()) + return true; + return hostMatches(url) && portMatches(url) && (didReceiveRedirectResponse || pathMatches(url)); +} + +bool ContentSecurityPolicySource::schemeMatches(const URL& url) const +{ + if (m_scheme.isEmpty()) + return m_policy.protocolMatchesSelf(url); + if (equalLettersIgnoringASCIICase(m_scheme, "http")) + return url.protocolIsInHTTPFamily(); + return equalIgnoringASCIICase(url.protocol(), m_scheme); +} + +bool ContentSecurityPolicySource::hostMatches(const URL& url) const +{ + const String& host = url.host(); + if (equalIgnoringASCIICase(host, m_host)) + return true; + return m_hostHasWildcard && host.endsWith("." + m_host, false); + +} + +bool ContentSecurityPolicySource::pathMatches(const URL& url) const +{ + if (m_path.isEmpty()) + return true; + + String path = decodeURLEscapeSequences(url.path()); + + if (m_path.endsWith("/")) + return path.startsWith(m_path); + + return path == m_path; +} + +bool ContentSecurityPolicySource::portMatches(const URL& url) const +{ + if (m_portHasWildcard) + return true; + + std::optional<uint16_t> port = url.port(); + + if (port == m_port) + return true; + + if (isDefaultPortForProtocol(m_port.value(), "http") && ((!port && url.protocolIs("https")) || isDefaultPortForProtocol(port.value(), "https"))) + return true; + + if (!port) + return isDefaultPortForProtocol(m_port.value(), url.protocol()); + + if (!m_port) + return isDefaultPortForProtocol(port.value(), url.protocol()); + + return false; +} + +bool ContentSecurityPolicySource::isSchemeOnly() const +{ + return m_host.isEmpty(); +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySource.h b/Source/WebCore/page/csp/ContentSecurityPolicySource.h new file mode 100644 index 000000000..28a1a6830 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicySource.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class ContentSecurityPolicy; +class URL; + +class ContentSecurityPolicySource { + WTF_MAKE_FAST_ALLOCATED; +public: + ContentSecurityPolicySource(const ContentSecurityPolicy&, const String& scheme, const String& host, std::optional<uint16_t> port, const String& path, bool hostHasWildcard, bool portHasWildcard); + + bool matches(const URL&, bool didReceiveRedirectResponse = false) const; + +private: + bool schemeMatches(const URL&) const; + bool hostMatches(const URL&) const; + bool pathMatches(const URL&) const; + bool portMatches(const URL&) const; + bool isSchemeOnly() const; + + const ContentSecurityPolicy& m_policy; + String m_scheme; + String m_host; + std::optional<uint16_t> m_port; + String m_path; + + bool m_hostHasWildcard; + bool m_portHasWildcard; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp new file mode 100644 index 000000000..b9e8c1141 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.cpp @@ -0,0 +1,520 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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 "ContentSecurityPolicySourceList.h" + +#include "ContentSecurityPolicy.h" +#include "ContentSecurityPolicyDirectiveNames.h" +#include "ParsingUtilities.h" +#include "URL.h" +#include <wtf/ASCIICType.h> +#include <wtf/NeverDestroyed.h> +#include <wtf/text/Base64.h> + +namespace WebCore { + +static bool isCSPDirectiveName(const String& name) +{ + return equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::baseURI) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::connectSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::defaultSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::fontSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::formAction) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::frameSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::imgSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::mediaSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::objectSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::pluginTypes) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::reportURI) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::sandbox) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::scriptSrc) + || equalIgnoringASCIICase(name, ContentSecurityPolicyDirectiveNames::styleSrc); +} + +static bool isSourceCharacter(UChar c) +{ + return !isASCIISpace(c); +} + +static bool isHostCharacter(UChar c) +{ + return isASCIIAlphanumeric(c) || c == '-'; +} + +static bool isPathComponentCharacter(UChar c) +{ + return c != '?' && c != '#'; +} + +static bool isSchemeContinuationCharacter(UChar c) +{ + return isASCIIAlphanumeric(c) || c == '+' || c == '-' || c == '.'; +} + +static bool isNotColonOrSlash(UChar c) +{ + return c != ':' && c != '/'; +} + +static bool isSourceListNone(const String& value) +{ + auto characters = StringView(value).upconvertedCharacters(); + const UChar* begin = characters; + const UChar* end = characters + value.length(); + skipWhile<UChar, isASCIISpace>(begin, end); + + const UChar* position = begin; + skipWhile<UChar, isSourceCharacter>(position, end); + if (!equalLettersIgnoringASCIICase(begin, position - begin, "'none'")) + return false; + + skipWhile<UChar, isASCIISpace>(position, end); + if (position != end) + return false; + + return true; +} + +ContentSecurityPolicySourceList::ContentSecurityPolicySourceList(const ContentSecurityPolicy& policy, const String& directiveName) + : m_policy(policy) + , m_directiveName(directiveName) +{ +} + +void ContentSecurityPolicySourceList::parse(const String& value) +{ + if (isSourceListNone(value)) { + m_isNone = true; + return; + } + auto characters = StringView(value).upconvertedCharacters(); + parse(characters, characters + value.length()); +} + +bool ContentSecurityPolicySourceList::isProtocolAllowedByStar(const URL& url) const +{ + if (m_policy.allowContentSecurityPolicySourceStarToMatchAnyProtocol()) + return true; + + // Although not allowed by the Content Security Policy Level 3 spec., we allow a data URL to match + // "img-src *" and either a data URL or blob URL to match "media-src *" for web compatibility. + bool isAllowed = url.protocolIsInHTTPFamily() || url.protocolIs("ws") || url.protocolIs("wss") || m_policy.protocolMatchesSelf(url); + if (equalIgnoringASCIICase(m_directiveName, ContentSecurityPolicyDirectiveNames::imgSrc)) + isAllowed |= url.protocolIsData(); + else if (equalIgnoringASCIICase(m_directiveName, ContentSecurityPolicyDirectiveNames::mediaSrc)) + isAllowed |= url.protocolIsData() || url.protocolIsBlob(); + return isAllowed; +} + +bool ContentSecurityPolicySourceList::matches(const URL& url, bool didReceiveRedirectResponse) +{ + if (m_allowStar && isProtocolAllowedByStar(url)) + return true; + + if (m_allowSelf && m_policy.urlMatchesSelf(url)) + return true; + + for (auto& entry : m_list) { + if (entry.matches(url, didReceiveRedirectResponse)) + return true; + } + + return false; +} + +bool ContentSecurityPolicySourceList::matches(const ContentSecurityPolicyHash& hash) const +{ + return m_hashes.contains(hash); +} + +bool ContentSecurityPolicySourceList::matches(const String& nonce) const +{ + return m_nonces.contains(nonce); +} + +// source-list = *WSP [ source *( 1*WSP source ) *WSP ] +// / *WSP "'none'" *WSP +// +void ContentSecurityPolicySourceList::parse(const UChar* begin, const UChar* end) +{ + const UChar* position = begin; + + while (position < end) { + skipWhile<UChar, isASCIISpace>(position, end); + if (position == end) + return; + + const UChar* beginSource = position; + skipWhile<UChar, isSourceCharacter>(position, end); + + String scheme, host, path; + std::optional<uint16_t> port; + bool hostHasWildcard = false; + bool portHasWildcard = false; + + if (parseNonceSource(beginSource, position)) + continue; + + if (parseHashSource(beginSource, position)) + continue; + + if (parseSource(beginSource, position, scheme, host, port, path, hostHasWildcard, portHasWildcard)) { + // Wildcard hosts and keyword sources ('self', 'unsafe-inline', + // etc.) aren't stored in m_list, but as attributes on the source + // list itself. + if (scheme.isEmpty() && host.isEmpty()) + continue; + if (isCSPDirectiveName(host)) + m_policy.reportDirectiveAsSourceExpression(m_directiveName, host); + m_list.append(ContentSecurityPolicySource(m_policy, scheme, host, port, path, hostHasWildcard, portHasWildcard)); + } else + m_policy.reportInvalidSourceExpression(m_directiveName, String(beginSource, position - beginSource)); + + ASSERT(position == end || isASCIISpace(*position)); + } +} + +// source = scheme ":" +// / ( [ scheme "://" ] host [ port ] [ path ] ) +// / "'self'" +// +bool ContentSecurityPolicySourceList::parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, std::optional<uint16_t>& port, String& path, bool& hostHasWildcard, bool& portHasWildcard) +{ + if (begin == end) + return false; + + if (equalLettersIgnoringASCIICase(begin, end - begin, "'none'")) + return false; + + if (end - begin == 1 && *begin == '*') { + m_allowStar = true; + return true; + } + + if (equalLettersIgnoringASCIICase(begin, end - begin, "'self'")) { + m_allowSelf = true; + return true; + } + + if (equalLettersIgnoringASCIICase(begin, end - begin, "'unsafe-inline'")) { + m_allowInline = true; + return true; + } + + if (equalLettersIgnoringASCIICase(begin, end - begin, "'unsafe-eval'")) { + m_allowEval = true; + return true; + } + + const UChar* position = begin; + const UChar* beginHost = begin; + const UChar* beginPath = end; + const UChar* beginPort = nullptr; + + skipWhile<UChar, isNotColonOrSlash>(position, end); + + if (position == end) { + // host + // ^ + return parseHost(beginHost, position, host, hostHasWildcard); + } + + if (position < end && *position == '/') { + // host/path || host/ || / + // ^ ^ ^ + return parseHost(beginHost, position, host, hostHasWildcard) && parsePath(position, end, path); + } + + if (position < end && *position == ':') { + if (end - position == 1) { + // scheme: + // ^ + return parseScheme(begin, position, scheme); + } + + if (position[1] == '/') { + // scheme://host || scheme:// + // ^ ^ + if (!parseScheme(begin, position, scheme) + || !skipExactly<UChar>(position, end, ':') + || !skipExactly<UChar>(position, end, '/') + || !skipExactly<UChar>(position, end, '/')) + return false; + if (position == end) + return false; + beginHost = position; + skipWhile<UChar, isNotColonOrSlash>(position, end); + } + + if (position < end && *position == ':') { + // host:port || scheme://host:port + // ^ ^ + beginPort = position; + skipUntil<UChar>(position, end, '/'); + } + } + + if (position < end && *position == '/') { + // scheme://host/path || scheme://host:port/path + // ^ ^ + if (position == beginHost) + return false; + + beginPath = position; + } + + if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, hostHasWildcard)) + return false; + + if (!beginPort) + port = std::nullopt; + else { + if (!parsePort(beginPort, beginPath, port, portHasWildcard)) + return false; + } + + if (beginPath != end) { + if (!parsePath(beginPath, end, path)) + return false; + } + + return true; +} + +// ; <scheme> production from RFC 3986 +// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +// +bool ContentSecurityPolicySourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme) +{ + ASSERT(begin <= end); + ASSERT(scheme.isEmpty()); + + if (begin == end) + return false; + + const UChar* position = begin; + + if (!skipExactly<UChar, isASCIIAlpha>(position, end)) + return false; + + skipWhile<UChar, isSchemeContinuationCharacter>(position, end); + + if (position != end) + return false; + + scheme = String(begin, end - begin); + return true; +} + +// host = [ "*." ] 1*host-char *( "." 1*host-char ) +// / "*" +// host-char = ALPHA / DIGIT / "-" +// +bool ContentSecurityPolicySourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard) +{ + ASSERT(begin <= end); + ASSERT(host.isEmpty()); + ASSERT(!hostHasWildcard); + + if (begin == end) + return false; + + const UChar* position = begin; + + if (skipExactly<UChar>(position, end, '*')) { + hostHasWildcard = true; + + if (position == end) + return true; + + if (!skipExactly<UChar>(position, end, '.')) + return false; + } + + const UChar* hostBegin = position; + + while (position < end) { + if (!skipExactly<UChar, isHostCharacter>(position, end)) + return false; + + skipWhile<UChar, isHostCharacter>(position, end); + + if (position < end && !skipExactly<UChar>(position, end, '.')) + return false; + } + + ASSERT(position == end); + host = String(hostBegin, end - hostBegin); + return true; +} + +bool ContentSecurityPolicySourceList::parsePath(const UChar* begin, const UChar* end, String& path) +{ + ASSERT(begin <= end); + ASSERT(path.isEmpty()); + + const UChar* position = begin; + skipWhile<UChar, isPathComponentCharacter>(position, end); + // path/to/file.js?query=string || path/to/file.js#anchor + // ^ ^ + if (position < end) + m_policy.reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position); + + path = decodeURLEscapeSequences(String(begin, position - begin)); + + ASSERT(position <= end); + ASSERT(position == end || (*position == '#' || *position == '?')); + return true; +} + +// port = ":" ( 1*DIGIT / "*" ) +// +bool ContentSecurityPolicySourceList::parsePort(const UChar* begin, const UChar* end, std::optional<uint16_t>& port, bool& portHasWildcard) +{ + ASSERT(begin <= end); + ASSERT(!port); + ASSERT(!portHasWildcard); + + if (!skipExactly<UChar>(begin, end, ':')) + ASSERT_NOT_REACHED(); + + if (begin == end) + return false; + + if (end - begin == 1 && *begin == '*') { + port = std::nullopt; + portHasWildcard = true; + return true; + } + + const UChar* position = begin; + skipWhile<UChar, isASCIIDigit>(position, end); + + if (position != end) + return false; + + bool ok; + int portInt = charactersToIntStrict(begin, end - begin, &ok); + if (portInt < 0 || portInt > std::numeric_limits<uint16_t>::max()) + return false; + port = portInt; + return ok; +} + +static bool isBase64Character(UChar c) +{ + return isASCIIAlphanumeric(c) || c == '+' || c == '/' || c == '-' || c == '_'; +} + +// Match Blink's behavior of allowing an equal sign to appear anywhere in the value of the nonce +// even though this does not match the behavior of Content Security Policy Level 3 spec., +// <https://w3c.github.io/webappsec-csp/> (29 February 2016). +static bool isNonceCharacter(UChar c) +{ + return isBase64Character(c) || c == '='; +} + +// nonce-source = "'nonce-" nonce-value "'" +// nonce-value = base64-value +bool ContentSecurityPolicySourceList::parseNonceSource(const UChar* begin, const UChar* end) +{ + static NeverDestroyed<String> noncePrefix("'nonce-", String::ConstructFromLiteral); + if (!StringView(begin, end - begin).startsWithIgnoringASCIICase(noncePrefix.get())) + return false; + const UChar* position = begin + noncePrefix.get().length(); + const UChar* beginNonceValue = position; + skipWhile<UChar, isNonceCharacter>(position, end); + if (position >= end || position == beginNonceValue || *position != '\'') + return false; + m_nonces.add(String(beginNonceValue, position - beginNonceValue)); + return true; +} + +static bool parseHashAlgorithmAdvancingPosition(const UChar*& position, size_t length, ContentSecurityPolicyHashAlgorithm& algorithm) +{ + static struct { + NeverDestroyed<String> label; + ContentSecurityPolicyHashAlgorithm algorithm; + } labelToHashAlgorithmTable[] { + { ASCIILiteral("sha256"), ContentSecurityPolicyHashAlgorithm::SHA_256 }, + { ASCIILiteral("sha384"), ContentSecurityPolicyHashAlgorithm::SHA_384 }, + { ASCIILiteral("sha512"), ContentSecurityPolicyHashAlgorithm::SHA_512 }, + }; + + StringView stringView(position, length); + for (auto& entry : labelToHashAlgorithmTable) { + String& label = entry.label.get(); + if (!stringView.startsWithIgnoringASCIICase(label)) + continue; + position += label.length(); + algorithm = entry.algorithm; + return true; + } + return false; +} + +// hash-source = "'" hash-algorithm "-" base64-value "'" +// hash-algorithm = "sha256" / "sha384" / "sha512" +// base64-value = 1*( ALPHA / DIGIT / "+" / "/" / "-" / "_" )*2( "=" ) +bool ContentSecurityPolicySourceList::parseHashSource(const UChar* begin, const UChar* end) +{ + if (begin == end) + return false; + + const UChar* position = begin; + if (!skipExactly<UChar>(position, end, '\'')) + return false; + + ContentSecurityPolicyHashAlgorithm algorithm; + if (!parseHashAlgorithmAdvancingPosition(position, end - position, algorithm)) + return false; + + if (!skipExactly<UChar>(position, end, '-')) + return false; + + const UChar* beginHashValue = position; + skipWhile<UChar, isBase64Character>(position, end); + skipExactly<UChar>(position, end, '='); + skipExactly<UChar>(position, end, '='); + if (position >= end || position == beginHashValue || *position != '\'') + return false; + Vector<uint8_t> digest; + StringView hashValue(beginHashValue, position - beginHashValue); // base64url or base64 encoded + // FIXME: Normalize Base64URL to Base64 instead of decoding twice. See <https://bugs.webkit.org/show_bug.cgi?id=155186>. + if (!base64Decode(hashValue.toStringWithoutCopying(), digest, Base64ValidatePadding)) { + if (!base64URLDecode(hashValue.toStringWithoutCopying(), digest)) + return false; + } + if (digest.size() > maximumContentSecurityPolicyDigestLength) + return false; + + m_hashes.add(std::make_pair(algorithm, digest)); + m_hashAlgorithmsUsed |= algorithm; + return true; +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h new file mode 100644 index 000000000..04dc29c33 --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceList.h @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "ContentSecurityPolicyHash.h" +#include "ContentSecurityPolicySource.h" +#include <wtf/HashSet.h> +#include <wtf/OptionSet.h> +#include <wtf/text/StringHash.h> +#include <wtf/text/WTFString.h> + +namespace WebCore { + +class ContentSecurityPolicy; +class URL; + +class ContentSecurityPolicySourceList { +public: + ContentSecurityPolicySourceList(const ContentSecurityPolicy&, const String& directiveName); + + void parse(const String&); + + bool matches(const URL&, bool didReceiveRedirectResponse); + bool matches(const ContentSecurityPolicyHash&) const; + bool matches(const String& nonce) const; + + OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsUsed() const { return m_hashAlgorithmsUsed; } + + bool allowInline() const { return m_allowInline && m_hashes.isEmpty() && m_nonces.isEmpty(); } + bool allowEval() const { return m_allowEval; } + bool allowSelf() const { return m_allowSelf; } + bool isNone() const { return m_isNone; } + +private: + void parse(const UChar* begin, const UChar* end); + + bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, std::optional<uint16_t>& port, String& path, bool& hostHasWildcard, bool& portHasWildcard); + bool parseScheme(const UChar* begin, const UChar* end, String& scheme); + bool parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard); + bool parsePort(const UChar* begin, const UChar* end, std::optional<uint16_t>& port, bool& portHasWildcard); + bool parsePath(const UChar* begin, const UChar* end, String& path); + + bool parseNonceSource(const UChar* begin, const UChar* end); + + bool isProtocolAllowedByStar(const URL&) const; + + bool parseHashSource(const UChar* begin, const UChar* end); + + const ContentSecurityPolicy& m_policy; + Vector<ContentSecurityPolicySource> m_list; + HashSet<String> m_nonces; + HashSet<ContentSecurityPolicyHash> m_hashes; + OptionSet<ContentSecurityPolicyHashAlgorithm> m_hashAlgorithmsUsed; + String m_directiveName; + bool m_allowSelf { false }; + bool m_allowStar { false }; + bool m_allowInline { false }; + bool m_allowEval { false }; + bool m_isNone { false }; +}; + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp new file mode 100644 index 000000000..c2bf1d45a --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.cpp @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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 "ContentSecurityPolicySourceListDirective.h" + +#include "ContentSecurityPolicy.h" +#include "ContentSecurityPolicyDirectiveList.h" +#include "URL.h" + +namespace WebCore { + +ContentSecurityPolicySourceListDirective::ContentSecurityPolicySourceListDirective(const ContentSecurityPolicyDirectiveList& directiveList, const String& name, const String& value) + : ContentSecurityPolicyDirective(directiveList, name, value) + , m_sourceList(directiveList.policy(), name) +{ + m_sourceList.parse(value); +} + +bool ContentSecurityPolicySourceListDirective::allows(const URL& url, bool didReceiveRedirectResponse, ShouldAllowEmptyURLIfSourceListIsNotNone shouldAllowEmptyURLIfSourceListEmpty) +{ + if (url.isEmpty()) + return shouldAllowEmptyURLIfSourceListEmpty == ShouldAllowEmptyURLIfSourceListIsNotNone::Yes && !m_sourceList.isNone(); + return m_sourceList.matches(url, didReceiveRedirectResponse); +} + +bool ContentSecurityPolicySourceListDirective::allows(const String& nonce) const +{ + return m_sourceList.matches(nonce); +} + +bool ContentSecurityPolicySourceListDirective::allows(const ContentSecurityPolicyHash& hash) const +{ + return m_sourceList.matches(hash); +} + +} // namespace WebCore diff --git a/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h new file mode 100644 index 000000000..afb2ed44f --- /dev/null +++ b/Source/WebCore/page/csp/ContentSecurityPolicySourceListDirective.h @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2011 Google, Inc. All rights reserved. + * Copyright (C) 2016 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. + */ + +#pragma once + +#include "ContentSecurityPolicyDirective.h" +#include "ContentSecurityPolicySourceList.h" + +namespace WebCore { + +class ContentSecurityPolicyDirectiveList; + +class ContentSecurityPolicySourceListDirective : public ContentSecurityPolicyDirective { +public: + ContentSecurityPolicySourceListDirective(const ContentSecurityPolicyDirectiveList&, const String& name, const String& value); + + enum class ShouldAllowEmptyURLIfSourceListIsNotNone { No, Yes }; + bool allows(const URL&, bool didReceiveRedirectResponse, ShouldAllowEmptyURLIfSourceListIsNotNone); + bool allows(const ContentSecurityPolicyHash&) const; + bool allows(const String& nonce) const; + bool allowInline() const { return m_sourceList.allowInline(); } + bool allowEval() const { return m_sourceList.allowEval(); } + + OptionSet<ContentSecurityPolicyHashAlgorithm> hashAlgorithmsUsed() const { return m_sourceList.hashAlgorithmsUsed(); } + +private: + ContentSecurityPolicySourceList m_sourceList; +}; + +} // namespace WebCore |