diff options
author | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-08 14:30:41 +0200 |
---|---|---|
committer | Jocelyn Turcotte <jocelyn.turcotte@digia.com> | 2014-08-12 13:49:54 +0200 |
commit | ab0a50979b9eb4dfa3320eff7e187e41efedf7a9 (patch) | |
tree | 498dfb8a97ff3361a9f7486863a52bb4e26bb898 /chromium/third_party/WebKit/Source/core/frame/csp | |
parent | 4ce69f7403811819800e7c5ae1318b2647e778d1 (diff) |
Update Chromium to beta version 37.0.2062.68
Change-Id: I188e3b5aff1bec75566014291b654eb19f5bc8ca
Reviewed-by: Andras Becsi <andras.becsi@digia.com>
Diffstat (limited to 'chromium/third_party/WebKit/Source/core/frame/csp')
13 files changed, 2746 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirective.h b/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirective.h new file mode 100644 index 00000000000..e870ccb9c94 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirective.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CSPDirective_h +#define CSPDirective_h + +#include "wtf/text/WTFString.h" + +namespace WebCore { + +class ContentSecurityPolicy; +class KURL; + +class CSPDirective { + WTF_MAKE_NONCOPYABLE(CSPDirective); +public: + CSPDirective(const String& name, const String& value, ContentSecurityPolicy* policy) + : m_name(name) + , m_text(name + ' ' + value) + , m_policy(policy) + { + } + + const String& text() const { return m_text; } + +protected: + const ContentSecurityPolicy* policy() const { return m_policy; } + +private: + String m_name; + String m_text; + ContentSecurityPolicy* m_policy; +}; + +} // namespace WebCore + +#endif diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp b/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp new file mode 100644 index 00000000000..a453dfdeb6c --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.cpp @@ -0,0 +1,679 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "core/frame/csp/CSPDirectiveList.h" + +#include "core/frame/LocalFrame.h" +#include "platform/ParsingUtilities.h" +#include "platform/weborigin/KURL.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +CSPDirectiveList::CSPDirectiveList(ContentSecurityPolicy* policy, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source) + : m_policy(policy) + , m_headerType(type) + , m_headerSource(source) + , m_reportOnly(false) + , m_haveSandboxPolicy(false) + , m_reflectedXSSDisposition(ReflectedXSSUnset) + , m_didSetReferrerPolicy(false) + , m_referrerPolicy(ReferrerPolicyDefault) +{ + m_reportOnly = type == ContentSecurityPolicyHeaderTypeReport; +} + +PassOwnPtr<CSPDirectiveList> CSPDirectiveList::create(ContentSecurityPolicy* policy, const UChar* begin, const UChar* end, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source) +{ + OwnPtr<CSPDirectiveList> directives = adoptPtr(new CSPDirectiveList(policy, type, source)); + directives->parse(begin, end); + + if (!directives->checkEval(directives->operativeDirective(directives->m_scriptSrc.get()))) { + String message = "Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: \"" + directives->operativeDirective(directives->m_scriptSrc.get())->text() + "\".\n"; + directives->setEvalDisabledErrorMessage(message); + } + + if (directives->isReportOnly() && directives->reportURIs().isEmpty()) + policy->reportMissingReportURI(String(begin, end - begin)); + + return directives.release(); +} + +void CSPDirectiveList::reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL) const +{ + String message = m_reportOnly ? "[Report Only] " + consoleMessage : consoleMessage; + m_policy->executionContext()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); + m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportURIs, m_header); +} + +void CSPDirectiveList::reportViolationWithLocation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, const String& contextURL, const WTF::OrdinalNumber& contextLine) const +{ + String message = m_reportOnly ? "[Report Only] " + consoleMessage : consoleMessage; + m_policy->executionContext()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message, contextURL, contextLine.oneBasedInt()); + m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportURIs, m_header); +} + +void CSPDirectiveList::reportViolationWithState(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, ScriptState* scriptState) const +{ + String message = m_reportOnly ? "[Report Only] " + consoleMessage : consoleMessage; + m_policy->executionContext()->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message, scriptState); + m_policy->reportViolation(directiveText, effectiveDirective, message, blockedURL, m_reportURIs, m_header); +} + +bool CSPDirectiveList::checkEval(SourceListDirective* directive) const +{ + return !directive || directive->allowEval(); +} + +bool CSPDirectiveList::checkInline(SourceListDirective* directive) const +{ + return !directive || (directive->allowInline() && !directive->isHashOrNoncePresent()); +} + +bool CSPDirectiveList::checkNonce(SourceListDirective* directive, const String& nonce) const +{ + return !directive || directive->allowNonce(nonce); +} + +bool CSPDirectiveList::checkHash(SourceListDirective* directive, const CSPHashValue& hashValue) const +{ + return !directive || directive->allowHash(hashValue); +} + +bool CSPDirectiveList::checkSource(SourceListDirective* directive, const KURL& url) const +{ + return !directive || directive->allows(url); +} + +bool CSPDirectiveList::checkAncestors(SourceListDirective* directive, LocalFrame* frame) const +{ + if (!frame || !directive) + return true; + + for (Frame* current = frame->tree().parent(); current; current = current->tree().parent()) { + // FIXME: To make this work for out-of-process iframes, we need to propagate URL information of ancestor frames across processes. + if (!current->isLocalFrame() || !directive->allows(toLocalFrame(current)->document()->url())) + return false; + } + return true; +} + +bool CSPDirectiveList::checkMediaType(MediaListDirective* directive, const String& type, const String& typeAttribute) const +{ + if (!directive) + return true; + if (typeAttribute.isEmpty() || typeAttribute.stripWhiteSpace() != type) + return false; + return directive->allows(type); +} + +SourceListDirective* CSPDirectiveList::operativeDirective(SourceListDirective* directive) const +{ + return directive ? directive : m_defaultSrc.get(); +} + +SourceListDirective* CSPDirectiveList::operativeDirective(SourceListDirective* directive, SourceListDirective* override) const +{ + return directive ? directive : override; +} + +bool CSPDirectiveList::checkEvalAndReportViolation(SourceListDirective* directive, const String& consoleMessage, ScriptState* scriptState) const +{ + if (checkEval(directive)) + return true; + + String suffix = String(); + if (directive == m_defaultSrc) + suffix = " Note that 'script-src' was not explicitly set, so 'default-src' is used as a fallback."; + + reportViolationWithState(directive->text(), ContentSecurityPolicy::ScriptSrc, consoleMessage + "\"" + directive->text() + "\"." + suffix + "\n", KURL(), scriptState); + if (!m_reportOnly) { + m_policy->reportBlockedScriptExecutionToInspector(directive->text()); + return false; + } + return true; +} + +bool CSPDirectiveList::checkMediaTypeAndReportViolation(MediaListDirective* directive, const String& type, const String& typeAttribute, const String& consoleMessage) const +{ + if (checkMediaType(directive, type, typeAttribute)) + return true; + + String message = consoleMessage + "\'" + directive->text() + "\'."; + if (typeAttribute.isEmpty()) + message = message + " When enforcing the 'plugin-types' directive, the plugin's media type must be explicitly declared with a 'type' attribute on the containing element (e.g. '<object type=\"[TYPE GOES HERE]\" ...>')."; + + reportViolation(directive->text(), ContentSecurityPolicy::PluginTypes, message + "\n", KURL()); + return denyIfEnforcingPolicy(); +} + +bool CSPDirectiveList::checkInlineAndReportViolation(SourceListDirective* directive, const String& consoleMessage, const String& contextURL, const WTF::OrdinalNumber& contextLine, bool isScript) const +{ + if (checkInline(directive)) + return true; + + String suffix = String(); + if (directive->allowInline() && directive->isHashOrNoncePresent()) { + // If inline is allowed, but a hash or nonce is present, we ignore 'unsafe-inline'. Throw a reasonable error. + suffix = " Note that 'unsafe-inline' is ignored if either a hash or nonce value is present in the source list."; + } else { + suffix = " Either the 'unsafe-inline' keyword, a hash ('sha256-...'), or a nonce ('nonce-...') is required to enable inline execution."; + if (directive == m_defaultSrc) + suffix = suffix + " Note also that '" + String(isScript ? "script" : "style") + "-src' was not explicitly set, so 'default-src' is used as a fallback."; + } + + reportViolationWithLocation(directive->text(), isScript ? ContentSecurityPolicy::ScriptSrc : ContentSecurityPolicy::StyleSrc, consoleMessage + "\"" + directive->text() + "\"." + suffix + "\n", KURL(), contextURL, contextLine); + + if (!m_reportOnly) { + if (isScript) + m_policy->reportBlockedScriptExecutionToInspector(directive->text()); + return false; + } + return true; +} + +bool CSPDirectiveList::checkSourceAndReportViolation(SourceListDirective* directive, const KURL& url, const String& effectiveDirective) const +{ + if (checkSource(directive, url)) + return true; + + String prefix; + if (ContentSecurityPolicy::BaseURI == effectiveDirective) + prefix = "Refused to set the document's base URI to '"; + else if (ContentSecurityPolicy::ChildSrc == effectiveDirective) + prefix = "Refused to create a child context containing '"; + else if (ContentSecurityPolicy::ConnectSrc == effectiveDirective) + prefix = "Refused to connect to '"; + else if (ContentSecurityPolicy::FontSrc == effectiveDirective) + prefix = "Refused to load the font '"; + else if (ContentSecurityPolicy::FormAction == effectiveDirective) + prefix = "Refused to send form data to '"; + else if (ContentSecurityPolicy::FrameSrc == effectiveDirective) + prefix = "Refused to frame '"; + else if (ContentSecurityPolicy::ImgSrc == effectiveDirective) + prefix = "Refused to load the image '"; + else if (ContentSecurityPolicy::MediaSrc == effectiveDirective) + prefix = "Refused to load media from '"; + else if (ContentSecurityPolicy::ObjectSrc == effectiveDirective) + prefix = "Refused to load plugin data from '"; + else if (ContentSecurityPolicy::ScriptSrc == effectiveDirective) + prefix = "Refused to load the script '"; + else if (ContentSecurityPolicy::StyleSrc == effectiveDirective) + prefix = "Refused to load the stylesheet '"; + + String suffix = String(); + if (directive == m_defaultSrc) + suffix = " Note that '" + effectiveDirective + "' was not explicitly set, so 'default-src' is used as a fallback."; + + reportViolation(directive->text(), effectiveDirective, prefix + url.elidedString() + "' because it violates the following Content Security Policy directive: \"" + directive->text() + "\"." + suffix + "\n", url); + return denyIfEnforcingPolicy(); +} + +bool CSPDirectiveList::checkAncestorsAndReportViolation(SourceListDirective* directive, LocalFrame* frame) const +{ + if (checkAncestors(directive, frame)) + return true; + + reportViolation(directive->text(), "frame-ancestors", "Refused to display '" + frame->document()->url().elidedString() + " in a frame because an ancestor violates the following Content Security Policy directive: \"" + directive->text() + "\".", frame->document()->url()); + return denyIfEnforcingPolicy(); +} + +bool CSPDirectiveList::allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute JavaScript URL because it violates the following Content Security Policy directive: ")); + if (reportingStatus == ContentSecurityPolicy::SendReport) + return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine, true); + + return checkInline(operativeDirective(m_scriptSrc.get())); +} + +bool CSPDirectiveList::allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute inline event handler because it violates the following Content Security Policy directive: ")); + if (reportingStatus == ContentSecurityPolicy::SendReport) + return checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine, true); + return checkInline(operativeDirective(m_scriptSrc.get())); +} + +bool CSPDirectiveList::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to execute inline script because it violates the following Content Security Policy directive: ")); + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkInlineAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, contextURL, contextLine, true) : + checkInline(operativeDirective(m_scriptSrc.get())); +} + +bool CSPDirectiveList::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to apply inline style because it violates the following Content Security Policy directive: ")); + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkInlineAndReportViolation(operativeDirective(m_styleSrc.get()), consoleMessage, contextURL, contextLine, false) : + checkInline(operativeDirective(m_styleSrc.get())); +} + +bool CSPDirectiveList::allowEval(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + DEFINE_STATIC_LOCAL(String, consoleMessage, ("Refused to evaluate a string as JavaScript because 'unsafe-eval' is not an allowed source of script in the following Content Security Policy directive: ")); + + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkEvalAndReportViolation(operativeDirective(m_scriptSrc.get()), consoleMessage, scriptState) : + checkEval(operativeDirective(m_scriptSrc.get())); +} + +bool CSPDirectiveList::allowPluginType(const String& type, const String& typeAttribute, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkMediaTypeAndReportViolation(m_pluginTypes.get(), type, typeAttribute, "Refused to load '" + url.elidedString() + "' (MIME type '" + typeAttribute + "') because it violates the following Content Security Policy Directive: ") : + checkMediaType(m_pluginTypes.get(), type, typeAttribute); +} + +bool CSPDirectiveList::allowScriptFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_scriptSrc.get()), url, ContentSecurityPolicy::ScriptSrc) : + checkSource(operativeDirective(m_scriptSrc.get()), url); +} + +bool CSPDirectiveList::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + if (url.protocolIsAbout()) + return true; + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_objectSrc.get()), url, ContentSecurityPolicy::ObjectSrc) : + checkSource(operativeDirective(m_objectSrc.get()), url); +} + +bool CSPDirectiveList::allowChildFrameFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + if (url.protocolIsAbout()) + return true; + + // 'frame-src' is the only directive which overrides something other than the default sources. + // It overrides 'child-src', which overrides the default sources. So, we do this nested set + // of calls to 'operativeDirective()' to grab 'frame-src' if it exists, 'child-src' if it + // doesn't, and 'defaut-src' if neither are available. + // + // All of this only applies, of course, if we're in CSP 1.1. In CSP 1.0, 'frame-src' + // overrides 'default-src' directly. + SourceListDirective* whichDirective = m_policy->experimentalFeaturesEnabled() ? + operativeDirective(m_frameSrc.get(), operativeDirective(m_childSrc.get())) : + operativeDirective(m_frameSrc.get()); + + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(whichDirective, url, ContentSecurityPolicy::FrameSrc) : + checkSource(whichDirective, url); +} + +bool CSPDirectiveList::allowImageFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_imgSrc.get()), url, ContentSecurityPolicy::ImgSrc) : + checkSource(operativeDirective(m_imgSrc.get()), url); +} + +bool CSPDirectiveList::allowStyleFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_styleSrc.get()), url, ContentSecurityPolicy::StyleSrc) : + checkSource(operativeDirective(m_styleSrc.get()), url); +} + +bool CSPDirectiveList::allowFontFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_fontSrc.get()), url, ContentSecurityPolicy::FontSrc) : + checkSource(operativeDirective(m_fontSrc.get()), url); +} + +bool CSPDirectiveList::allowMediaFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_mediaSrc.get()), url, ContentSecurityPolicy::MediaSrc) : + checkSource(operativeDirective(m_mediaSrc.get()), url); +} + +bool CSPDirectiveList::allowConnectToSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_connectSrc.get()), url, ContentSecurityPolicy::ConnectSrc) : + checkSource(operativeDirective(m_connectSrc.get()), url); +} + +bool CSPDirectiveList::allowFormAction(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(m_formAction.get(), url, ContentSecurityPolicy::FormAction) : + checkSource(m_formAction.get(), url); +} + +bool CSPDirectiveList::allowBaseURI(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(m_baseURI.get(), url, ContentSecurityPolicy::BaseURI) : + checkSource(m_baseURI.get(), url); +} + +bool CSPDirectiveList::allowAncestors(LocalFrame* frame, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkAncestorsAndReportViolation(m_frameAncestors.get(), frame) : + checkAncestors(m_frameAncestors.get(), frame); +} + +bool CSPDirectiveList::allowChildContextFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return reportingStatus == ContentSecurityPolicy::SendReport ? + checkSourceAndReportViolation(operativeDirective(m_childSrc.get()), url, ContentSecurityPolicy::ChildSrc) : + checkSource(operativeDirective(m_childSrc.get()), url); +} + +bool CSPDirectiveList::allowScriptNonce(const String& nonce) const +{ + return checkNonce(operativeDirective(m_scriptSrc.get()), nonce); +} + +bool CSPDirectiveList::allowStyleNonce(const String& nonce) const +{ + return checkNonce(operativeDirective(m_styleSrc.get()), nonce); +} + +bool CSPDirectiveList::allowScriptHash(const CSPHashValue& hashValue) const +{ + return checkHash(operativeDirective(m_scriptSrc.get()), hashValue); +} + +bool CSPDirectiveList::allowStyleHash(const CSPHashValue& hashValue) const +{ + return checkHash(operativeDirective(m_styleSrc.get()), hashValue); +} + +// policy = directive-list +// directive-list = [ directive *( ";" [ directive ] ) ] +// +void CSPDirectiveList::parse(const UChar* begin, const UChar* end) +{ + m_header = String(begin, end - begin); + + if (begin == end) + return; + + const UChar* position = begin; + while (position < end) { + const UChar* directiveBegin = position; + skipUntil<UChar>(position, end, ';'); + + String name, value; + if (parseDirective(directiveBegin, position, name, value)) { + ASSERT(!name.isEmpty()); + addDirective(name, value); + } + + ASSERT(position == end || *position == ';'); + skipExactly<UChar>(position, end, ';'); + } +} + +// directive = *WSP [ directive-name [ WSP directive-value ] ] +// directive-name = 1*( ALPHA / DIGIT / "-" ) +// directive-value = *( WSP / <VCHAR except ";"> ) +// +bool CSPDirectiveList::parseDirective(const UChar* begin, const UChar* end, String& name, String& value) +{ + ASSERT(name.isEmpty()); + ASSERT(value.isEmpty()); + + const UChar* position = begin; + skipWhile<UChar, isASCIISpace>(position, end); + + // Empty directive (e.g. ";;;"). Exit early. + if (position == end) + return false; + + const UChar* nameBegin = position; + skipWhile<UChar, isCSPDirectiveNameCharacter>(position, end); + + // The directive-name must be non-empty. + if (nameBegin == position) { + skipWhile<UChar, isNotASCIISpace>(position, end); + m_policy->reportUnsupportedDirective(String(nameBegin, position - nameBegin)); + return false; + } + + name = String(nameBegin, position - nameBegin); + + if (position == end) + return true; + + if (!skipExactly<UChar, isASCIISpace>(position, end)) { + skipWhile<UChar, isNotASCIISpace>(position, end); + m_policy->reportUnsupportedDirective(String(nameBegin, position - nameBegin)); + return false; + } + + skipWhile<UChar, isASCIISpace>(position, end); + + const UChar* valueBegin = position; + skipWhile<UChar, isCSPDirectiveValueCharacter>(position, end); + + if (position != end) { + m_policy->reportInvalidDirectiveValueCharacter(name, String(valueBegin, end - valueBegin)); + return false; + } + + // The directive-value may be empty. + if (valueBegin == position) + return true; + + value = String(valueBegin, position - valueBegin); + return true; +} + +void CSPDirectiveList::parseReportURI(const String& name, const String& value) +{ + if (!m_reportURIs.isEmpty()) { + m_policy->reportDuplicateDirective(name); + return; + } + + Vector<UChar> characters; + value.appendTo(characters); + + const UChar* position = characters.data(); + const UChar* end = position + characters.size(); + + while (position < end) { + skipWhile<UChar, isASCIISpace>(position, end); + + const UChar* urlBegin = position; + skipWhile<UChar, isNotASCIISpace>(position, end); + + if (urlBegin < position) { + String url = String(urlBegin, position - urlBegin); + m_reportURIs.append(m_policy->completeURL(url)); + } + } +} + + +template<class CSPDirectiveType> +void CSPDirectiveList::setCSPDirective(const String& name, const String& value, OwnPtr<CSPDirectiveType>& directive) +{ + if (directive) { + m_policy->reportDuplicateDirective(name); + return; + } + directive = adoptPtr(new CSPDirectiveType(name, value, m_policy)); +} + +void CSPDirectiveList::applySandboxPolicy(const String& name, const String& sandboxPolicy) +{ + if (m_reportOnly) { + m_policy->reportInvalidInReportOnly(name); + return; + } + if (m_haveSandboxPolicy) { + m_policy->reportDuplicateDirective(name); + return; + } + m_haveSandboxPolicy = true; + String invalidTokens; + m_policy->enforceSandboxFlags(parseSandboxPolicy(sandboxPolicy, invalidTokens)); + if (!invalidTokens.isNull()) + m_policy->reportInvalidSandboxFlags(invalidTokens); +} + +void CSPDirectiveList::parseReflectedXSS(const String& name, const String& value) +{ + if (m_reflectedXSSDisposition != ReflectedXSSUnset) { + m_policy->reportDuplicateDirective(name); + m_reflectedXSSDisposition = ReflectedXSSInvalid; + return; + } + + if (value.isEmpty()) { + m_reflectedXSSDisposition = ReflectedXSSInvalid; + m_policy->reportInvalidReflectedXSS(value); + return; + } + + Vector<UChar> characters; + value.appendTo(characters); + + const UChar* position = characters.data(); + const UChar* end = position + characters.size(); + + skipWhile<UChar, isASCIISpace>(position, end); + const UChar* begin = position; + skipWhile<UChar, isNotASCIISpace>(position, end); + + // value1 + // ^ + if (equalIgnoringCase("allow", begin, position - begin)) { + m_reflectedXSSDisposition = AllowReflectedXSS; + } else if (equalIgnoringCase("filter", begin, position - begin)) { + m_reflectedXSSDisposition = FilterReflectedXSS; + } else if (equalIgnoringCase("block", begin, position - begin)) { + m_reflectedXSSDisposition = BlockReflectedXSS; + } else { + m_reflectedXSSDisposition = ReflectedXSSInvalid; + m_policy->reportInvalidReflectedXSS(value); + return; + } + + skipWhile<UChar, isASCIISpace>(position, end); + if (position == end && m_reflectedXSSDisposition != ReflectedXSSUnset) + return; + + // value1 value2 + // ^ + m_reflectedXSSDisposition = ReflectedXSSInvalid; + m_policy->reportInvalidReflectedXSS(value); +} + +void CSPDirectiveList::parseReferrer(const String& name, const String& value) +{ + if (m_didSetReferrerPolicy) { + m_policy->reportDuplicateDirective(name); + m_referrerPolicy = ReferrerPolicyNever; + return; + } + + m_didSetReferrerPolicy = true; + + if (value.isEmpty()) { + m_policy->reportInvalidReferrer(value); + m_referrerPolicy = ReferrerPolicyNever; + return; + } + + Vector<UChar> characters; + value.appendTo(characters); + + const UChar* position = characters.data(); + const UChar* end = position + characters.size(); + + skipWhile<UChar, isASCIISpace>(position, end); + const UChar* begin = position; + skipWhile<UChar, isNotASCIISpace>(position, end); + + // value1 + // ^ + if (equalIgnoringCase("always", begin, position - begin)) { + m_referrerPolicy = ReferrerPolicyAlways; + } else if (equalIgnoringCase("default", begin, position - begin)) { + m_referrerPolicy = ReferrerPolicyDefault; + } else if (equalIgnoringCase("never", begin, position - begin)) { + m_referrerPolicy = ReferrerPolicyNever; + } else if (equalIgnoringCase("origin", begin, position - begin)) { + m_referrerPolicy = ReferrerPolicyOrigin; + } else { + m_referrerPolicy = ReferrerPolicyNever; + m_policy->reportInvalidReferrer(value); + return; + } + + skipWhile<UChar, isASCIISpace>(position, end); + if (position == end) + return; + + // value1 value2 + // ^ + m_referrerPolicy = ReferrerPolicyNever; + m_policy->reportInvalidReferrer(value); + +} + +void CSPDirectiveList::addDirective(const String& name, const String& value) +{ + ASSERT(!name.isEmpty()); + + if (equalIgnoringCase(name, ContentSecurityPolicy::DefaultSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_defaultSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::ScriptSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_scriptSrc); + m_policy->usesScriptHashAlgorithms(m_scriptSrc->hashAlgorithmsUsed()); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::ObjectSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_objectSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::FrameAncestors)) { + setCSPDirective<SourceListDirective>(name, value, m_frameAncestors); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::FrameSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_frameSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::ImgSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_imgSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::StyleSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_styleSrc); + m_policy->usesStyleHashAlgorithms(m_styleSrc->hashAlgorithmsUsed()); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::FontSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_fontSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::MediaSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_mediaSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::ConnectSrc)) { + setCSPDirective<SourceListDirective>(name, value, m_connectSrc); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::Sandbox)) { + applySandboxPolicy(name, value); + } else if (equalIgnoringCase(name, ContentSecurityPolicy::ReportURI)) { + parseReportURI(name, value); + } else if (m_policy->experimentalFeaturesEnabled()) { + if (equalIgnoringCase(name, ContentSecurityPolicy::BaseURI)) + setCSPDirective<SourceListDirective>(name, value, m_baseURI); + else if (equalIgnoringCase(name, ContentSecurityPolicy::ChildSrc)) + setCSPDirective<SourceListDirective>(name, value, m_childSrc); + else if (equalIgnoringCase(name, ContentSecurityPolicy::FormAction)) + setCSPDirective<SourceListDirective>(name, value, m_formAction); + else if (equalIgnoringCase(name, ContentSecurityPolicy::PluginTypes)) + setCSPDirective<MediaListDirective>(name, value, m_pluginTypes); + else if (equalIgnoringCase(name, ContentSecurityPolicy::ReflectedXSS)) + parseReflectedXSS(name, value); + else if (equalIgnoringCase(name, ContentSecurityPolicy::Referrer)) + parseReferrer(name, value); + else + m_policy->reportUnsupportedDirective(name); + } else { + m_policy->reportUnsupportedDirective(name); + } +} + + +} // namespace WebCore diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h b/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h new file mode 100644 index 00000000000..b33b18693ad --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPDirectiveList.h @@ -0,0 +1,141 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CSPDirectiveList_h +#define CSPDirectiveList_h + +#include "core/frame/csp/ContentSecurityPolicy.h" +#include "core/frame/csp/MediaListDirective.h" +#include "core/frame/csp/SourceListDirective.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "platform/network/HTTPParsers.h" +#include "platform/weborigin/KURL.h" +#include "platform/weborigin/ReferrerPolicy.h" +#include "wtf/OwnPtr.h" +#include "wtf/Vector.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +class ContentSecurityPolicy; + +class CSPDirectiveList { + WTF_MAKE_FAST_ALLOCATED; + WTF_MAKE_NONCOPYABLE(CSPDirectiveList); +public: + static PassOwnPtr<CSPDirectiveList> create(ContentSecurityPolicy*, const UChar* begin, const UChar* end, ContentSecurityPolicyHeaderType, ContentSecurityPolicyHeaderSource); + + void parse(const UChar* begin, const UChar* end); + + const String& header() const { return m_header; } + ContentSecurityPolicyHeaderType headerType() const { return m_headerType; } + ContentSecurityPolicyHeaderSource headerSource() const { return m_headerSource; } + + bool allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; + bool allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; + bool allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; + bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus) const; + bool allowEval(ScriptState*, ContentSecurityPolicy::ReportingStatus) const; + bool allowPluginType(const String& type, const String& typeAttribute, const KURL&, ContentSecurityPolicy::ReportingStatus) const; + + bool allowScriptFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowObjectFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowChildFrameFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowImageFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowStyleFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowFontFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowMediaFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowConnectToSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowFormAction(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowBaseURI(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowAncestors(LocalFrame*, ContentSecurityPolicy::ReportingStatus) const; + bool allowChildContextFromSource(const KURL&, ContentSecurityPolicy::ReportingStatus) const; + bool allowScriptNonce(const String&) const; + bool allowStyleNonce(const String&) const; + bool allowScriptHash(const CSPHashValue&) const; + bool allowStyleHash(const CSPHashValue&) const; + + const String& evalDisabledErrorMessage() const { return m_evalDisabledErrorMessage; } + ReflectedXSSDisposition reflectedXSSDisposition() const { return m_reflectedXSSDisposition; } + ReferrerPolicy referrerPolicy() const { return m_referrerPolicy; } + bool didSetReferrerPolicy() const { return m_didSetReferrerPolicy; } + bool isReportOnly() const { return m_reportOnly; } + const Vector<KURL>& reportURIs() const { return m_reportURIs; } + +private: + CSPDirectiveList(ContentSecurityPolicy*, ContentSecurityPolicyHeaderType, ContentSecurityPolicyHeaderSource); + + 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 parseReflectedXSS(const String& name, const String& value); + void parseReferrer(const String& name, const String& value); + void addDirective(const String& name, const String& value); + void applySandboxPolicy(const String& name, const String& sandboxPolicy); + + template <class CSPDirectiveType> + void setCSPDirective(const String& name, const String& value, OwnPtr<CSPDirectiveType>&); + + SourceListDirective* operativeDirective(SourceListDirective*) const; + SourceListDirective* operativeDirective(SourceListDirective*, SourceListDirective* override) const; + void reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL) const; + void reportViolationWithLocation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, const String& contextURL, const WTF::OrdinalNumber& contextLine) const; + void reportViolationWithState(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, ScriptState*) const; + + bool checkEval(SourceListDirective*) const; + bool checkInline(SourceListDirective*) const; + bool checkNonce(SourceListDirective*, const String&) const; + bool checkHash(SourceListDirective*, const CSPHashValue&) const; + bool checkSource(SourceListDirective*, const KURL&) const; + bool checkMediaType(MediaListDirective*, const String& type, const String& typeAttribute) const; + bool checkAncestors(SourceListDirective*, LocalFrame*) const; + + void setEvalDisabledErrorMessage(const String& errorMessage) { m_evalDisabledErrorMessage = errorMessage; } + + bool checkEvalAndReportViolation(SourceListDirective*, const String& consoleMessage, ScriptState*) const; + bool checkInlineAndReportViolation(SourceListDirective*, const String& consoleMessage, const String& contextURL, const WTF::OrdinalNumber& contextLine, bool isScript) const; + + bool checkSourceAndReportViolation(SourceListDirective*, const KURL&, const String& effectiveDirective) const; + bool checkMediaTypeAndReportViolation(MediaListDirective*, const String& type, const String& typeAttribute, const String& consoleMessage) const; + bool checkAncestorsAndReportViolation(SourceListDirective*, LocalFrame*) const; + + bool denyIfEnforcingPolicy() const { return m_reportOnly; } + + ContentSecurityPolicy* m_policy; + + String m_header; + ContentSecurityPolicyHeaderType m_headerType; + ContentSecurityPolicyHeaderSource m_headerSource; + + bool m_reportOnly; + bool m_haveSandboxPolicy; + ReflectedXSSDisposition m_reflectedXSSDisposition; + + bool m_didSetReferrerPolicy; + ReferrerPolicy m_referrerPolicy; + + OwnPtr<MediaListDirective> m_pluginTypes; + OwnPtr<SourceListDirective> m_baseURI; + OwnPtr<SourceListDirective> m_childSrc; + OwnPtr<SourceListDirective> m_connectSrc; + OwnPtr<SourceListDirective> m_defaultSrc; + OwnPtr<SourceListDirective> m_fontSrc; + OwnPtr<SourceListDirective> m_formAction; + OwnPtr<SourceListDirective> m_frameAncestors; + OwnPtr<SourceListDirective> m_frameSrc; + OwnPtr<SourceListDirective> m_imgSrc; + OwnPtr<SourceListDirective> m_mediaSrc; + OwnPtr<SourceListDirective> m_objectSrc; + OwnPtr<SourceListDirective> m_scriptSrc; + OwnPtr<SourceListDirective> m_styleSrc; + + Vector<KURL> m_reportURIs; + + String m_evalDisabledErrorMessage; +}; + + +} // namespace + +#endif diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPSource.cpp b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSource.cpp new file mode 100644 index 00000000000..863addc09ce --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSource.cpp @@ -0,0 +1,93 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "core/frame/csp/CSPSource.h" + +#include "core/frame/csp/ContentSecurityPolicy.h" +#include "platform/weborigin/KURL.h" +#include "platform/weborigin/KnownPorts.h" +#include "platform/weborigin/SecurityOrigin.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +CSPSource::CSPSource(ContentSecurityPolicy* policy, const String& scheme, const String& host, int 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 CSPSource::matches(const KURL& url) const +{ + if (!schemeMatches(url)) + return false; + if (isSchemeOnly()) + return true; + return hostMatches(url) && portMatches(url) && pathMatches(url); +} + +bool CSPSource::schemeMatches(const KURL& url) const +{ + if (m_scheme.isEmpty()) { + String protectedResourceScheme(m_policy->securityOrigin()->protocol()); + if (equalIgnoringCase("http", protectedResourceScheme)) + return url.protocolIs("http") || url.protocolIs("https"); + return equalIgnoringCase(url.protocol(), protectedResourceScheme); + } + return equalIgnoringCase(url.protocol(), m_scheme); +} + +bool CSPSource::hostMatches(const KURL& url) const +{ + const String& host = url.host(); + if (equalIgnoringCase(host, m_host)) + return true; + return m_hostHasWildcard && host.endsWith("." + m_host, false); + +} + +bool CSPSource::pathMatches(const KURL& url) const +{ + if (m_path.isEmpty()) + return true; + + String path = decodeURLEscapeSequences(url.path()); + + if (m_path.endsWith("/")) + return path.startsWith(m_path, false); + + return path == m_path; +} + +bool CSPSource::portMatches(const KURL& url) const +{ + if (m_portHasWildcard) + return true; + + int port = url.port(); + + if (port == m_port) + return true; + + if (!port) + return isDefaultPortForProtocol(m_port, url.protocol()); + + if (!m_port) + return isDefaultPortForProtocol(port, url.protocol()); + + return false; +} + +bool CSPSource::isSchemeOnly() const +{ + return m_host.isEmpty(); +} + +} // namespace diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPSource.h b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSource.h new file mode 100644 index 00000000000..9088b196b3f --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSource.h @@ -0,0 +1,39 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CSPSource_h +#define CSPSource_h + +#include "wtf/text/WTFString.h" + +namespace WebCore { + +class ContentSecurityPolicy; +class KURL; + +class CSPSource { +public: + CSPSource(ContentSecurityPolicy*, const String& scheme, const String& host, int port, const String& path, bool hostHasWildcard, bool portHasWildcard); + bool matches(const KURL&) const; + +private: + bool schemeMatches(const KURL&) const; + bool hostMatches(const KURL&) const; + bool pathMatches(const KURL&) const; + bool portMatches(const KURL&) const; + bool isSchemeOnly() const; + + ContentSecurityPolicy* m_policy; + String m_scheme; + String m_host; + int m_port; + String m_path; + + bool m_hostHasWildcard; + bool m_portHasWildcard; +}; + +} // namespace + +#endif diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp new file mode 100644 index 00000000000..bff7146dab0 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSourceList.cpp @@ -0,0 +1,485 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "core/frame/csp/CSPSourceList.h" + +#include "core/frame/csp/CSPSource.h" +#include "core/frame/csp/ContentSecurityPolicy.h" +#include "platform/ParsingUtilities.h" +#include "platform/weborigin/KURL.h" +#include "platform/weborigin/SecurityOrigin.h" +#include "wtf/HashSet.h" +#include "wtf/text/Base64.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +static bool isSourceListNone(const UChar* begin, const UChar* end) +{ + skipWhile<UChar, isASCIISpace>(begin, end); + + const UChar* position = begin; + skipWhile<UChar, isSourceCharacter>(position, end); + if (!equalIgnoringCase("'none'", begin, position - begin)) + return false; + + skipWhile<UChar, isASCIISpace>(position, end); + if (position != end) + return false; + + return true; +} + +CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName) + : m_policy(policy) + , m_directiveName(directiveName) + , m_allowStar(false) + , m_allowInline(false) + , m_allowEval(false) + , m_hashAlgorithmsUsed(0) +{ +} + +bool CSPSourceList::matches(const KURL& url) const +{ + if (m_allowStar) + return true; + + KURL effectiveURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url; + + for (size_t i = 0; i < m_list.size(); ++i) { + if (m_list[i].matches(effectiveURL)) + return true; + } + + return false; +} + +bool CSPSourceList::allowInline() const +{ + return m_allowInline; +} + +bool CSPSourceList::allowEval() const +{ + return m_allowEval; +} + +bool CSPSourceList::allowNonce(const String& nonce) const +{ + return !nonce.isNull() && m_nonces.contains(nonce); +} + +bool CSPSourceList::allowHash(const CSPHashValue& hashValue) const +{ + return m_hashes.contains(hashValue); +} + +uint8_t CSPSourceList::hashAlgorithmsUsed() const +{ + return m_hashAlgorithmsUsed; +} + +bool CSPSourceList::isHashOrNoncePresent() const +{ + return !m_nonces.isEmpty() || m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone; +} + +// source-list = *WSP [ source *( 1*WSP source ) *WSP ] +// / *WSP "'none'" *WSP +// +void CSPSourceList::parse(const UChar* begin, const UChar* end) +{ + // We represent 'none' as an empty m_list. + if (isSourceListNone(begin, end)) + return; + + 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; + int port = 0; + bool hostHasWildcard = false; + bool portHasWildcard = false; + + 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 (m_policy->isDirectiveName(host)) + m_policy->reportDirectiveAsSourceExpression(m_directiveName, host); + m_list.append(CSPSource(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 CSPSourceList::parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, bool& hostHasWildcard, bool& portHasWildcard) +{ + if (begin == end) + return false; + + if (equalIgnoringCase("'none'", begin, end - begin)) + return false; + + if (end - begin == 1 && *begin == '*') { + addSourceStar(); + return true; + } + + if (equalIgnoringCase("'self'", begin, end - begin)) { + addSourceSelf(); + return true; + } + + if (equalIgnoringCase("'unsafe-inline'", begin, end - begin)) { + addSourceUnsafeInline(); + return true; + } + + if (equalIgnoringCase("'unsafe-eval'", begin, end - begin)) { + addSourceUnsafeEval(); + return true; + } + + String nonce; + if (!parseNonce(begin, end, nonce)) + return false; + + if (!nonce.isNull()) { + addSourceNonce(nonce); + return true; + } + + DigestValue hash; + ContentSecurityPolicyHashAlgorithm algorithm = ContentSecurityPolicyHashAlgorithmNone; + if (!parseHash(begin, end, hash, algorithm)) + return false; + + if (hash.size() > 0) { + addSourceHash(algorithm, hash); + return true; + } + + const UChar* position = begin; + const UChar* beginHost = begin; + const UChar* beginPath = end; + const UChar* beginPort = 0; + + 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 true; + 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) { + if (!parsePort(beginPort, beginPath, port, portHasWildcard)) + return false; + } else { + port = 0; + } + + if (beginPath != end) { + if (!parsePath(beginPath, end, path)) + return false; + } + + return true; +} + +// nonce-source = "'nonce-" nonce-value "'" +// nonce-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) +// +bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& nonce) +{ + DEFINE_STATIC_LOCAL(const String, noncePrefix, ("'nonce-")); + + if (!equalIgnoringCase(noncePrefix.characters8(), begin, noncePrefix.length())) + return true; + + const UChar* position = begin + noncePrefix.length(); + const UChar* nonceBegin = position; + + skipWhile<UChar, isNonceCharacter>(position, end); + ASSERT(nonceBegin <= position); + + if ((position + 1) != end || *position != '\'' || !(position - nonceBegin)) + return false; + + nonce = String(nonceBegin, position - nonceBegin); + return true; +} + +// hash-source = "'" hash-algorithm "-" hash-value "'" +// hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512" +// hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" ) +// +bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm) +{ + // Any additions or subtractions from this struct should also modify the + // respective entries in the kAlgorithmMap array in checkDigest(). + static const struct { + const char* prefix; + ContentSecurityPolicyHashAlgorithm algorithm; + } kSupportedPrefixes[] = { + { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 }, + { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 }, + { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 }, + { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 } + }; + + String prefix; + hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone; + + // Instead of this sizeof() calculation to get the length of this array, + // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to + // guarantee a compile time calculation. Unfortunately, on some + // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous + // stucts, so, for now, it is necessary to resort to this sizeof + // calculation. + for (size_t i = 0; i < (sizeof(kSupportedPrefixes) / sizeof(kSupportedPrefixes[0])); i++) { + if (equalIgnoringCase(kSupportedPrefixes[i].prefix, begin, strlen(kSupportedPrefixes[i].prefix))) { + prefix = kSupportedPrefixes[i].prefix; + hashAlgorithm = kSupportedPrefixes[i].algorithm; + break; + } + } + + if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone) + return true; + + const UChar* position = begin + prefix.length(); + const UChar* hashBegin = position; + + skipWhile<UChar, isBase64EncodedCharacter>(position, end); + ASSERT(hashBegin <= position); + + // Base64 encodings may end with exactly one or two '=' characters + skipExactly<UChar>(position, position + 1, '='); + skipExactly<UChar>(position, position + 1, '='); + + if ((position + 1) != end || *position != '\'' || !(position - hashBegin)) + return false; + + Vector<char> hashVector; + base64Decode(hashBegin, position - hashBegin, hashVector); + if (hashVector.size() > kMaxDigestSize) + return false; + hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size()); + return true; +} + +// ; <scheme> production from RFC 3986 +// scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) +// +bool CSPSourceList::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 CSPSourceList::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 CSPSourceList::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 CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& 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 = 0; + portHasWildcard = true; + return true; + } + + const UChar* position = begin; + skipWhile<UChar, isASCIIDigit>(position, end); + + if (position != end) + return false; + + bool ok; + port = charactersToIntStrict(begin, end - begin, &ok); + return ok; +} + +void CSPSourceList::addSourceSelf() +{ + m_list.append(CSPSource(m_policy, m_policy->securityOrigin()->protocol(), m_policy->securityOrigin()->host(), m_policy->securityOrigin()->port(), String(), false, false)); +} + +void CSPSourceList::addSourceStar() +{ + m_allowStar = true; +} + +void CSPSourceList::addSourceUnsafeInline() +{ + m_allowInline = true; +} + +void CSPSourceList::addSourceUnsafeEval() +{ + m_allowEval = true; +} + +void CSPSourceList::addSourceNonce(const String& nonce) +{ + m_nonces.add(nonce); +} + +void CSPSourceList::addSourceHash(const ContentSecurityPolicyHashAlgorithm& algorithm, const DigestValue& hash) +{ + m_hashes.add(CSPHashValue(algorithm, hash)); + m_hashAlgorithmsUsed |= algorithm; +} + + +} // namespace WebCore diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/CSPSourceList.h b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSourceList.h new file mode 100644 index 00000000000..c982fe68ac7 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/CSPSourceList.h @@ -0,0 +1,65 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef CSPSourceList_h +#define CSPSourceList_h + +#include "core/frame/csp/CSPSource.h" +#include "platform/Crypto.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "wtf/HashSet.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +class ContentSecurityPolicy; +class KURL; + +class CSPSourceList { + WTF_MAKE_NONCOPYABLE(CSPSourceList); +public: + CSPSourceList(ContentSecurityPolicy*, const String& directiveName); + + void parse(const UChar* begin, const UChar* end); + + bool matches(const KURL&) const; + bool allowInline() const; + bool allowEval() const; + bool allowNonce(const String&) const; + bool allowHash(const CSPHashValue&) const; + uint8_t hashAlgorithmsUsed() const; + + bool isHashOrNoncePresent() const; + +private: + bool parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& 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, int& port, bool& portHasWildcard); + bool parsePath(const UChar* begin, const UChar* end, String& path); + bool parseNonce(const UChar* begin, const UChar* end, String& nonce); + bool parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm&); + + void addSourceSelf(); + void addSourceStar(); + void addSourceUnsafeInline(); + void addSourceUnsafeEval(); + void addSourceNonce(const String& nonce); + void addSourceHash(const ContentSecurityPolicyHashAlgorithm&, const DigestValue& hash); + + ContentSecurityPolicy* m_policy; + Vector<CSPSource> m_list; + String m_directiveName; + bool m_allowStar; + bool m_allowInline; + bool m_allowEval; + HashSet<String> m_nonces; + HashSet<CSPHashValue> m_hashes; + uint8_t m_hashAlgorithmsUsed; +}; + + +} // namespace WebCore + +#endif diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp b/chromium/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp new file mode 100644 index 00000000000..cdd5d063a27 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.cpp @@ -0,0 +1,786 @@ +/* + * Copyright (C) 2011 Google, 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 COMPUTER, 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 "core/frame/csp/ContentSecurityPolicy.h" + +#include "bindings/v8/ScriptCallStackFactory.h" +#include "bindings/v8/ScriptController.h" +#include "core/dom/DOMStringList.h" +#include "core/dom/Document.h" +#include "core/events/SecurityPolicyViolationEvent.h" +#include "core/frame/LocalDOMWindow.h" +#include "core/frame/LocalFrame.h" +#include "core/frame/UseCounter.h" +#include "core/frame/csp/CSPDirectiveList.h" +#include "core/frame/csp/CSPSource.h" +#include "core/frame/csp/CSPSourceList.h" +#include "core/frame/csp/MediaListDirective.h" +#include "core/frame/csp/SourceListDirective.h" +#include "core/inspector/InspectorInstrumentation.h" +#include "core/inspector/ScriptCallStack.h" +#include "core/loader/DocumentLoader.h" +#include "core/loader/PingLoader.h" +#include "platform/Crypto.h" +#include "platform/JSONValues.h" +#include "platform/NotImplemented.h" +#include "platform/ParsingUtilities.h" +#include "platform/RuntimeEnabledFeatures.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "platform/network/ContentSecurityPolicyResponseHeaders.h" +#include "platform/network/FormData.h" +#include "platform/network/ResourceResponse.h" +#include "platform/weborigin/KURL.h" +#include "platform/weborigin/KnownPorts.h" +#include "platform/weborigin/SchemeRegistry.h" +#include "platform/weborigin/SecurityOrigin.h" +#include "public/platform/Platform.h" +#include "public/platform/WebArrayBuffer.h" +#include "public/platform/WebCrypto.h" +#include "public/platform/WebCryptoAlgorithm.h" +#include "wtf/StringHasher.h" +#include "wtf/text/StringBuilder.h" +#include "wtf/text/StringUTF8Adaptor.h" + +namespace WebCore { + +// CSP 1.0 Directives +const char ContentSecurityPolicy::ConnectSrc[] = "connect-src"; +const char ContentSecurityPolicy::DefaultSrc[] = "default-src"; +const char ContentSecurityPolicy::FontSrc[] = "font-src"; +const char ContentSecurityPolicy::FrameSrc[] = "frame-src"; +const char ContentSecurityPolicy::ImgSrc[] = "img-src"; +const char ContentSecurityPolicy::MediaSrc[] = "media-src"; +const char ContentSecurityPolicy::ObjectSrc[] = "object-src"; +const char ContentSecurityPolicy::ReportURI[] = "report-uri"; +const char ContentSecurityPolicy::Sandbox[] = "sandbox"; +const char ContentSecurityPolicy::ScriptSrc[] = "script-src"; +const char ContentSecurityPolicy::StyleSrc[] = "style-src"; + +// CSP 1.1 Directives +const char ContentSecurityPolicy::BaseURI[] = "base-uri"; +const char ContentSecurityPolicy::ChildSrc[] = "child-src"; +const char ContentSecurityPolicy::FormAction[] = "form-action"; +const char ContentSecurityPolicy::FrameAncestors[] = "frame-ancestors"; +const char ContentSecurityPolicy::PluginTypes[] = "plugin-types"; +const char ContentSecurityPolicy::ReflectedXSS[] = "reflected-xss"; +const char ContentSecurityPolicy::Referrer[] = "referrer"; + +bool ContentSecurityPolicy::isDirectiveName(const String& name) +{ + return (equalIgnoringCase(name, ConnectSrc) + || equalIgnoringCase(name, DefaultSrc) + || equalIgnoringCase(name, FontSrc) + || equalIgnoringCase(name, FrameSrc) + || equalIgnoringCase(name, ImgSrc) + || equalIgnoringCase(name, MediaSrc) + || equalIgnoringCase(name, ObjectSrc) + || equalIgnoringCase(name, ReportURI) + || equalIgnoringCase(name, Sandbox) + || equalIgnoringCase(name, ScriptSrc) + || equalIgnoringCase(name, StyleSrc) + || equalIgnoringCase(name, BaseURI) + || equalIgnoringCase(name, ChildSrc) + || equalIgnoringCase(name, FormAction) + || equalIgnoringCase(name, FrameAncestors) + || equalIgnoringCase(name, PluginTypes) + || equalIgnoringCase(name, ReflectedXSS) + || equalIgnoringCase(name, Referrer) + ); +} + +static UseCounter::Feature getUseCounterType(ContentSecurityPolicyHeaderType type) +{ + switch (type) { + case ContentSecurityPolicyHeaderTypeEnforce: + return UseCounter::ContentSecurityPolicy; + case ContentSecurityPolicyHeaderTypeReport: + return UseCounter::ContentSecurityPolicyReportOnly; + } + ASSERT_NOT_REACHED(); + return UseCounter::NumberOfFeatures; +} + +static ReferrerPolicy mergeReferrerPolicies(ReferrerPolicy a, ReferrerPolicy b) +{ + if (a != b) + return ReferrerPolicyNever; + return a; +} + +ContentSecurityPolicy::ContentSecurityPolicy(ExecutionContext* executionContext) + : m_executionContext(executionContext) + , m_overrideInlineStyleAllowed(false) + , m_scriptHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone) + , m_styleHashAlgorithmsUsed(ContentSecurityPolicyHashAlgorithmNone) +{ +} + +ContentSecurityPolicy::~ContentSecurityPolicy() +{ +} + +void ContentSecurityPolicy::copyStateFrom(const ContentSecurityPolicy* other) +{ + ASSERT(m_policies.isEmpty()); + for (CSPDirectiveListVector::const_iterator iter = other->m_policies.begin(); iter != other->m_policies.end(); ++iter) + addPolicyFromHeaderValue((*iter)->header(), (*iter)->headerType(), (*iter)->headerSource()); +} + +void ContentSecurityPolicy::didReceiveHeaders(const ContentSecurityPolicyResponseHeaders& headers) +{ + if (!headers.contentSecurityPolicy().isEmpty()) + didReceiveHeader(headers.contentSecurityPolicy(), ContentSecurityPolicyHeaderTypeEnforce, ContentSecurityPolicyHeaderSourceHTTP); + if (!headers.contentSecurityPolicyReportOnly().isEmpty()) + didReceiveHeader(headers.contentSecurityPolicyReportOnly(), ContentSecurityPolicyHeaderTypeReport, ContentSecurityPolicyHeaderSourceHTTP); +} + +void ContentSecurityPolicy::didReceiveHeader(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source) +{ + addPolicyFromHeaderValue(header, type, source); +} + +void ContentSecurityPolicy::addPolicyFromHeaderValue(const String& header, ContentSecurityPolicyHeaderType type, ContentSecurityPolicyHeaderSource source) +{ + Document* document = this->document(); + if (document) { + UseCounter::count(*document, getUseCounterType(type)); + + // CSP 1.1 defines report-only in a <meta> element as invalid. Measure for now, disable in experimental mode. + if (source == ContentSecurityPolicyHeaderSourceMeta && type == ContentSecurityPolicyHeaderTypeReport) { + UseCounter::count(*document, UseCounter::ContentSecurityPolicyReportOnlyInMeta); + if (experimentalFeaturesEnabled()) { + reportReportOnlyInMeta(header); + return; + } + } + } + + + Vector<UChar> characters; + header.appendTo(characters); + + const UChar* begin = characters.data(); + const UChar* end = begin + characters.size(); + + // 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. + const UChar* position = begin; + while (position < end) { + skipUntil<UChar>(position, end, ','); + + // header1,header2 OR header1 + // ^ ^ + OwnPtr<CSPDirectiveList> policy = CSPDirectiveList::create(this, begin, position, type, source); + + // We disable 'eval()' even in the case of report-only policies, and rely on the check in the V8Initializer::codeGenerationCheckCallbackInMainThread callback to determine whether the call should execute or not. + if (!policy->allowEval(0, SuppressReport)) + m_executionContext->disableEval(policy->evalDisabledErrorMessage()); + + m_policies.append(policy.release()); + + // Skip the comma, and begin the next header from the current position. + ASSERT(position == end || *position == ','); + skipExactly<UChar>(position, end, ','); + begin = position; + } + + if (document && type != ContentSecurityPolicyHeaderTypeReport && didSetReferrerPolicy()) + document->setReferrerPolicy(referrerPolicy()); +} + +void ContentSecurityPolicy::setOverrideAllowInlineStyle(bool value) +{ + m_overrideInlineStyleAllowed = value; +} + +const String& ContentSecurityPolicy::deprecatedHeader() const +{ + return m_policies.isEmpty() ? emptyString() : m_policies[0]->header(); +} + +ContentSecurityPolicyHeaderType ContentSecurityPolicy::deprecatedHeaderType() const +{ + return m_policies.isEmpty() ? ContentSecurityPolicyHeaderTypeEnforce : m_policies[0]->headerType(); +} + +template<bool (CSPDirectiveList::*allowed)(ContentSecurityPolicy::ReportingStatus) const> +bool isAllowedByAll(const CSPDirectiveListVector& policies, ContentSecurityPolicy::ReportingStatus reportingStatus) +{ + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowed)(reportingStatus)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowed)(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus) const> +bool isAllowedByAllWithState(const CSPDirectiveListVector& policies, ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus) +{ + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowed)(scriptState, reportingStatus)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowed)(const String&, const WTF::OrdinalNumber&, ContentSecurityPolicy::ReportingStatus) const> +bool isAllowedByAllWithContext(const CSPDirectiveListVector& policies, const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) +{ + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowed)(contextURL, contextLine, reportingStatus)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowed)(const String&) const> +bool isAllowedByAllWithNonce(const CSPDirectiveListVector& policies, const String& nonce) +{ + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowed)(nonce)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowed)(const CSPHashValue&) const> +bool isAllowedByAllWithHash(const CSPDirectiveListVector& policies, const CSPHashValue& hashValue) +{ + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowed)(hashValue)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowFromURL)(const KURL&, ContentSecurityPolicy::ReportingStatus) const> +bool isAllowedByAllWithURL(const CSPDirectiveListVector& policies, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) +{ + if (SchemeRegistry::schemeShouldBypassContentSecurityPolicy(url.protocol())) + return true; + + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowFromURL)(url, reportingStatus)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowed)(LocalFrame*, ContentSecurityPolicy::ReportingStatus) const> +bool isAllowedByAllWithFrame(const CSPDirectiveListVector& policies, LocalFrame* frame, ContentSecurityPolicy::ReportingStatus reportingStatus) +{ + for (size_t i = 0; i < policies.size(); ++i) { + if (!(policies[i].get()->*allowed)(frame, reportingStatus)) + return false; + } + return true; +} + +template<bool (CSPDirectiveList::*allowed)(const CSPHashValue&) const> +bool checkDigest(const String& source, uint8_t hashAlgorithmsUsed, const CSPDirectiveListVector& policies) +{ + // Any additions or subtractions from this struct should also modify the + // respective entries in the kSupportedPrefixes array in + // CSPSourceList::parseHash(). + static const struct { + ContentSecurityPolicyHashAlgorithm cspHashAlgorithm; + HashAlgorithm algorithm; + } kAlgorithmMap[] = { + { ContentSecurityPolicyHashAlgorithmSha1, HashAlgorithmSha1 }, + { ContentSecurityPolicyHashAlgorithmSha256, HashAlgorithmSha256 }, + { ContentSecurityPolicyHashAlgorithmSha384, HashAlgorithmSha384 }, + { ContentSecurityPolicyHashAlgorithmSha512, HashAlgorithmSha512 } + }; + + // Only bother normalizing the source/computing digests if there are any checks to be done. + if (hashAlgorithmsUsed == ContentSecurityPolicyHashAlgorithmNone) + return false; + + StringUTF8Adaptor normalizedSource(source, StringUTF8Adaptor::Normalize, WTF::EntitiesForUnencodables); + + // See comment in CSPSourceList::parseHash about why we are using this sizeof + // calculation instead of WTF_ARRAY_LENGTH. + for (size_t i = 0; i < (sizeof(kAlgorithmMap) / sizeof(kAlgorithmMap[0])); i++) { + DigestValue digest; + if (kAlgorithmMap[i].cspHashAlgorithm & hashAlgorithmsUsed) { + bool digestSuccess = computeDigest(kAlgorithmMap[i].algorithm, normalizedSource.data(), normalizedSource.length(), digest); + if (digestSuccess && isAllowedByAllWithHash<allowed>(policies, CSPHashValue(kAlgorithmMap[i].cspHashAlgorithm, digest))) + return true; + } + } + + return false; +} + +bool ContentSecurityPolicy::allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithContext<&CSPDirectiveList::allowJavaScriptURLs>(m_policies, contextURL, contextLine, reportingStatus); +} + +bool ContentSecurityPolicy::allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithContext<&CSPDirectiveList::allowInlineEventHandlers>(m_policies, contextURL, contextLine, reportingStatus); +} + +bool ContentSecurityPolicy::allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithContext<&CSPDirectiveList::allowInlineScript>(m_policies, contextURL, contextLine, reportingStatus); +} + +bool ContentSecurityPolicy::allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + if (m_overrideInlineStyleAllowed) + return true; + return isAllowedByAllWithContext<&CSPDirectiveList::allowInlineStyle>(m_policies, contextURL, contextLine, reportingStatus); +} + +bool ContentSecurityPolicy::allowEval(ScriptState* scriptState, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithState<&CSPDirectiveList::allowEval>(m_policies, scriptState, reportingStatus); +} + +String ContentSecurityPolicy::evalDisabledErrorMessage() const +{ + for (size_t i = 0; i < m_policies.size(); ++i) { + if (!m_policies[i]->allowEval(0, SuppressReport)) + return m_policies[i]->evalDisabledErrorMessage(); + } + return String(); +} + +bool ContentSecurityPolicy::allowPluginType(const String& type, const String& typeAttribute, const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + for (size_t i = 0; i < m_policies.size(); ++i) { + if (!m_policies[i]->allowPluginType(type, typeAttribute, url, reportingStatus)) + return false; + } + return true; +} + +bool ContentSecurityPolicy::allowScriptFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowScriptNonce(const String& nonce) const +{ + return isAllowedByAllWithNonce<&CSPDirectiveList::allowScriptNonce>(m_policies, nonce); +} + +bool ContentSecurityPolicy::allowStyleNonce(const String& nonce) const +{ + return isAllowedByAllWithNonce<&CSPDirectiveList::allowStyleNonce>(m_policies, nonce); +} + +bool ContentSecurityPolicy::allowScriptHash(const String& source) const +{ + return checkDigest<&CSPDirectiveList::allowScriptHash>(source, m_scriptHashAlgorithmsUsed, m_policies); +} + +bool ContentSecurityPolicy::allowStyleHash(const String& source) const +{ + return checkDigest<&CSPDirectiveList::allowStyleHash>(source, m_styleHashAlgorithmsUsed, m_policies); +} + +void ContentSecurityPolicy::usesScriptHashAlgorithms(uint8_t algorithms) +{ + m_scriptHashAlgorithmsUsed |= algorithms; +} + +void ContentSecurityPolicy::usesStyleHashAlgorithms(uint8_t algorithms) +{ + m_styleHashAlgorithmsUsed |= algorithms; +} + +bool ContentSecurityPolicy::allowObjectFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowObjectFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowChildFrameFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowChildFrameFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowImageFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowImageFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowStyleFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowStyleFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowFontFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowFontFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowMediaFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowMediaFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowConnectToSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowConnectToSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowFormAction(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowFormAction>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowBaseURI(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowBaseURI>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowAncestors(LocalFrame* frame, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithFrame<&CSPDirectiveList::allowAncestors>(m_policies, frame, reportingStatus); +} + +bool ContentSecurityPolicy::allowChildContextFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + return isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::allowWorkerContextFromSource(const KURL& url, ContentSecurityPolicy::ReportingStatus reportingStatus) const +{ + // CSP 1.1 moves workers from 'script-src' to the new 'child-src'. Measure the impact of this backwards-incompatible change. + if (m_executionContext->isDocument()) { + Document* document = static_cast<Document*>(m_executionContext); + UseCounter::count(*document, UseCounter::WorkerSubjectToCSP); + if (isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, SuppressReport) && !isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, SuppressReport)) + UseCounter::count(*document, UseCounter::WorkerAllowedByChildBlockedByScript); + } + + return experimentalFeaturesEnabled() ? + isAllowedByAllWithURL<&CSPDirectiveList::allowChildContextFromSource>(m_policies, url, reportingStatus) : + isAllowedByAllWithURL<&CSPDirectiveList::allowScriptFromSource>(m_policies, url, reportingStatus); +} + +bool ContentSecurityPolicy::isActive() const +{ + return !m_policies.isEmpty(); +} + +ReflectedXSSDisposition ContentSecurityPolicy::reflectedXSSDisposition() const +{ + ReflectedXSSDisposition disposition = ReflectedXSSUnset; + for (size_t i = 0; i < m_policies.size(); ++i) { + if (m_policies[i]->reflectedXSSDisposition() > disposition) + disposition = std::max(disposition, m_policies[i]->reflectedXSSDisposition()); + } + return disposition; +} + +ReferrerPolicy ContentSecurityPolicy::referrerPolicy() const +{ + ReferrerPolicy policy = ReferrerPolicyDefault; + bool first = true; + for (size_t i = 0; i < m_policies.size(); ++i) { + if (m_policies[i]->didSetReferrerPolicy()) { + if (first) + policy = m_policies[i]->referrerPolicy(); + else + policy = mergeReferrerPolicies(policy, m_policies[i]->referrerPolicy()); + } + } + return policy; +} + +bool ContentSecurityPolicy::didSetReferrerPolicy() const +{ + for (size_t i = 0; i < m_policies.size(); ++i) { + if (m_policies[i]->didSetReferrerPolicy()) + return true; + } + return false; +} + +SecurityOrigin* ContentSecurityPolicy::securityOrigin() const +{ + return m_executionContext->securityContext().securityOrigin(); +} + +const KURL ContentSecurityPolicy::url() const +{ + return m_executionContext->contextURL(); +} + +KURL ContentSecurityPolicy::completeURL(const String& url) const +{ + return m_executionContext->contextCompleteURL(url); +} + +void ContentSecurityPolicy::enforceSandboxFlags(SandboxFlags mask) const +{ + if (Document* document = this->document()) + document->enforceSandboxFlags(mask); +} + +static String stripURLForUseInReport(Document* document, const KURL& url) +{ + if (!url.isValid()) + return String(); + if (!url.isHierarchical() || url.protocolIs("file")) + return url.protocol(); + return document->securityOrigin()->canRequest(url) ? url.strippedForUseAsReferrer() : SecurityOrigin::create(url)->toString(); +} + +static void gatherSecurityPolicyViolationEventData(SecurityPolicyViolationEventInit& init, Document* document, const String& directiveText, const String& effectiveDirective, const KURL& blockedURL, const String& header) +{ + init.documentURI = document->url().string(); + init.referrer = document->referrer(); + init.blockedURI = stripURLForUseInReport(document, blockedURL); + init.violatedDirective = directiveText; + init.effectiveDirective = effectiveDirective; + init.originalPolicy = header; + init.sourceFile = String(); + init.lineNumber = 0; + init.columnNumber = 0; + init.statusCode = 0; + + if (!SecurityOrigin::isSecure(document->url()) && document->loader()) + init.statusCode = document->loader()->response().httpStatusCode(); + + RefPtrWillBeRawPtr<ScriptCallStack> stack = createScriptCallStack(1, false); + if (!stack) + return; + + const ScriptCallFrame& callFrame = stack->at(0); + + if (callFrame.lineNumber()) { + KURL source = KURL(ParsedURLString, callFrame.sourceURL()); + init.sourceFile = stripURLForUseInReport(document, source); + init.lineNumber = callFrame.lineNumber(); + init.columnNumber = callFrame.columnNumber(); + } +} + +void ContentSecurityPolicy::reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, const Vector<KURL>& reportURIs, const String& header) +{ + // FIXME: Support sending reports from worker. + if (!m_executionContext->isDocument()) + return; + + Document* document = this->document(); + LocalFrame* frame = document->frame(); + if (!frame) + return; + + SecurityPolicyViolationEventInit violationData; + gatherSecurityPolicyViolationEventData(violationData, document, directiveText, effectiveDirective, blockedURL, header); + + if (experimentalFeaturesEnabled()) + frame->domWindow()->enqueueDocumentEvent(SecurityPolicyViolationEvent::create(EventTypeNames::securitypolicyviolation, violationData)); + + 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 supress 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. + + RefPtr<JSONObject> cspReport = JSONObject::create(); + cspReport->setString("document-uri", violationData.documentURI); + cspReport->setString("referrer", violationData.referrer); + cspReport->setString("violated-directive", violationData.violatedDirective); + if (experimentalFeaturesEnabled()) + cspReport->setString("effective-directive", violationData.effectiveDirective); + cspReport->setString("original-policy", violationData.originalPolicy); + cspReport->setString("blocked-uri", violationData.blockedURI); + if (!violationData.sourceFile.isEmpty() && violationData.lineNumber) { + cspReport->setString("source-file", violationData.sourceFile); + cspReport->setNumber("line-number", violationData.lineNumber); + cspReport->setNumber("column-number", violationData.columnNumber); + } + cspReport->setNumber("status-code", violationData.statusCode); + + RefPtr<JSONObject> reportObject = JSONObject::create(); + reportObject->setObject("csp-report", cspReport.release()); + String stringifiedReport = reportObject->toJSONString(); + + if (!shouldSendViolationReport(stringifiedReport)) + return; + + RefPtr<FormData> report = FormData::create(stringifiedReport.utf8()); + + for (size_t i = 0; i < reportURIs.size(); ++i) + PingLoader::sendViolationReport(frame, reportURIs[i], report, PingLoader::ContentSecurityPolicyViolationReport); + + didSendViolationReport(stringifiedReport); +} + +void ContentSecurityPolicy::reportInvalidReferrer(const String& invalidValue) const +{ + logToConsole("The 'referrer' Content Security Policy directive has the invalid value \"" + invalidValue + "\". Valid values are \"always\", \"default\", \"never\", and \"origin\"."); +} + +void ContentSecurityPolicy::reportReportOnlyInMeta(const String& header) const +{ + logToConsole("The report-only Content Security Policy '" + header + "' was delivered via a <meta> element, which is disallowed. The policy has been ignored."); +} + +void ContentSecurityPolicy::reportMetaOutsideHead(const String& header) const +{ + logToConsole("The Content Security Policy '" + header + "' was delivered via a <meta> element outside the document's <head>, which is disallowed. The policy has been ignored."); +} + +void ContentSecurityPolicy::reportInvalidInReportOnly(const String& name) const +{ + logToConsole("The Content Security Policy directive '" + name + "' is ignored when delivered in a report-only policy."); +} + +void ContentSecurityPolicy::reportUnsupportedDirective(const String& name) const +{ + DEFINE_STATIC_LOCAL(String, allow, ("allow")); + DEFINE_STATIC_LOCAL(String, options, ("options")); + DEFINE_STATIC_LOCAL(String, policyURI, ("policy-uri")); + DEFINE_STATIC_LOCAL(String, allowMessage, ("The 'allow' directive has been replaced with 'default-src'. Please use that directive instead, as 'allow' has no effect.")); + DEFINE_STATIC_LOCAL(String, optionsMessage, ("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.")); + DEFINE_STATIC_LOCAL(String, policyURIMessage, ("The 'policy-uri' directive has been removed from the specification. Please specify a complete policy via the Content-Security-Policy header.")); + + String message = "Unrecognized Content-Security-Policy directive '" + name + "'.\n"; + if (equalIgnoringCase(name, allow)) + message = allowMessage; + else if (equalIgnoringCase(name, options)) + message = optionsMessage; + else if (equalIgnoringCase(name, policyURI)) + message = policyURIMessage; + + logToConsole(message); +} + +void ContentSecurityPolicy::reportDirectiveAsSourceExpression(const String& directiveName, const String& sourceExpression) const +{ + String message = "The Content Security Policy directive '" + directiveName + "' contains '" + sourceExpression + "' as a source expression. Did you mean '" + directiveName + " ...; " + sourceExpression + "...' (note the semicolon)?"; + logToConsole(message); +} + +void ContentSecurityPolicy::reportDuplicateDirective(const String& name) const +{ + String message = "Ignoring duplicate Content-Security-Policy directive '" + name + "'.\n"; + logToConsole(message); +} + +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 = "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::reportInvalidReflectedXSS(const String& invalidValue) const +{ + logToConsole("The 'reflected-xss' Content Security Policy directive has the invalid value \"" + invalidValue + "\". Valid values are \"allow\", \"filter\", and \"block\"."); +} + +void ContentSecurityPolicy::reportInvalidDirectiveValueCharacter(const String& directiveName, const String& value) const +{ + String message = "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 = "The fragment identifier, including the '#', will be ignored."; + if (invalidChar == '?') + ignoring = "The query component, including the '?', will be ignored."; + String message = "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 = "The source list for Content Security Policy directive '" + directiveName + "' contains an invalid source: '" + source + "'. It will be ignored."; + if (equalIgnoringCase(source, "'none'")) + message = 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 +{ + m_executionContext->addConsoleMessage(SecurityMessageSource, ErrorMessageLevel, message); +} + +void ContentSecurityPolicy::reportBlockedScriptExecutionToInspector(const String& directiveText) const +{ + m_executionContext->reportBlockedScriptExecutionToInspector(directiveText); +} + +bool ContentSecurityPolicy::experimentalFeaturesEnabled() const +{ + return RuntimeEnabledFeatures::experimentalContentSecurityPolicyFeaturesEnabled(); +} + +bool ContentSecurityPolicy::shouldBypassMainWorld(ExecutionContext* context) +{ + if (context && context->isDocument()) { + Document* document = toDocument(context); + if (document->frame()) + return document->frame()->script().shouldBypassMainWorldContentSecurityPolicy(); + } + return false; +} + +bool ContentSecurityPolicy::shouldSendViolationReport(const String& report) const +{ + // Collisions have no security impact, so we can save space by storing only the string's hash rather than the whole report. + return !m_violationReportsSent.contains(report.impl()->hash()); +} + +void ContentSecurityPolicy::didSendViolationReport(const String& report) +{ + m_violationReportsSent.add(report.impl()->hash()); +} + +} // namespace WebCore diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h b/chromium/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h new file mode 100644 index 00000000000..5d07921bfef --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/ContentSecurityPolicy.h @@ -0,0 +1,203 @@ +/* + * Copyright (C) 2011 Google, 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 COMPUTER, 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. + */ + +#ifndef ContentSecurityPolicy_h +#define ContentSecurityPolicy_h + +#include "bindings/v8/ScriptState.h" +#include "core/dom/Document.h" +#include "core/dom/ExecutionContext.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "platform/network/HTTPParsers.h" +#include "platform/weborigin/ReferrerPolicy.h" +#include "wtf/HashSet.h" +#include "wtf/PassOwnPtr.h" +#include "wtf/PassRefPtr.h" +#include "wtf/RefCounted.h" +#include "wtf/Vector.h" +#include "wtf/text/StringHash.h" +#include "wtf/text/TextPosition.h" +#include "wtf/text/WTFString.h" + +namespace WTF { +class OrdinalNumber; +} + +namespace WebCore { + +class ContentSecurityPolicyResponseHeaders; +class CSPDirectiveList; +class DOMStringList; +class JSONObject; +class KURL; +class SecurityOrigin; + +typedef int SandboxFlags; +typedef Vector<OwnPtr<CSPDirectiveList> > CSPDirectiveListVector; + +class ContentSecurityPolicy : public RefCounted<ContentSecurityPolicy> { + WTF_MAKE_FAST_ALLOCATED; +public: + // CSP 1.0 Directives + static const char ConnectSrc[]; + static const char DefaultSrc[]; + static const char FontSrc[]; + static const char FrameSrc[]; + static const char ImgSrc[]; + static const char MediaSrc[]; + static const char ObjectSrc[]; + static const char ReportURI[]; + static const char Sandbox[]; + static const char ScriptSrc[]; + static const char StyleSrc[]; + + // CSP 1.1 Directives + static const char BaseURI[]; + static const char ChildSrc[]; + static const char FormAction[]; + static const char FrameAncestors[]; + static const char PluginTypes[]; + static const char ReflectedXSS[]; + static const char Referrer[]; + + static PassRefPtr<ContentSecurityPolicy> create(ExecutionContext* executionContext) + { + return adoptRef(new ContentSecurityPolicy(executionContext)); + } + ~ContentSecurityPolicy(); + + void copyStateFrom(const ContentSecurityPolicy*); + + enum ReportingStatus { + SendReport, + SuppressReport + }; + + void didReceiveHeaders(const ContentSecurityPolicyResponseHeaders&); + void didReceiveHeader(const String&, ContentSecurityPolicyHeaderType, ContentSecurityPolicyHeaderSource); + + // These functions are wrong because they assume that there is only one header. + // FIXME: Replace them with functions that return vectors. + const String& deprecatedHeader() const; + ContentSecurityPolicyHeaderType deprecatedHeaderType() const; + + bool allowJavaScriptURLs(const String& contextURL, const WTF::OrdinalNumber& contextLine, ReportingStatus = SendReport) const; + bool allowInlineEventHandlers(const String& contextURL, const WTF::OrdinalNumber& contextLine, ReportingStatus = SendReport) const; + bool allowInlineScript(const String& contextURL, const WTF::OrdinalNumber& contextLine, ReportingStatus = SendReport) const; + bool allowInlineStyle(const String& contextURL, const WTF::OrdinalNumber& contextLine, ReportingStatus = SendReport) const; + bool allowEval(ScriptState* = 0, ReportingStatus = SendReport) const; + bool allowPluginType(const String& type, const String& typeAttribute, const KURL&, ReportingStatus = SendReport) const; + + bool allowScriptFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowObjectFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowChildFrameFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowImageFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowStyleFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowFontFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowMediaFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowConnectToSource(const KURL&, ReportingStatus = SendReport) const; + bool allowFormAction(const KURL&, ReportingStatus = SendReport) const; + bool allowBaseURI(const KURL&, ReportingStatus = SendReport) const; + bool allowAncestors(LocalFrame*, ReportingStatus = SendReport) const; + bool allowChildContextFromSource(const KURL&, ReportingStatus = SendReport) const; + bool allowWorkerContextFromSource(const KURL&, ReportingStatus = SendReport) const; + + // The nonce and hash allow functions are guaranteed to not have any side + // effects, including reporting. + bool allowScriptNonce(const String& nonce) const; + bool allowStyleNonce(const String& nonce) const; + bool allowScriptHash(const String& source) const; + bool allowStyleHash(const String& source) const; + + void usesScriptHashAlgorithms(uint8_t ContentSecurityPolicyHashAlgorithm); + void usesStyleHashAlgorithms(uint8_t ContentSecurityPolicyHashAlgorithm); + + ReflectedXSSDisposition reflectedXSSDisposition() const; + + ReferrerPolicy referrerPolicy() const; + bool didSetReferrerPolicy() const; + + void setOverrideAllowInlineStyle(bool); + + bool isActive() const; + + void reportDirectiveAsSourceExpression(const String& directiveName, const String& sourceExpression) const; + void reportDuplicateDirective(const String&) const; + void reportInvalidDirectiveValueCharacter(const String& directiveName, const String& value) const; + void reportInvalidPathCharacter(const String& directiveName, const String& value, const char) const; + void reportInvalidPluginTypes(const String&) const; + void reportInvalidSandboxFlags(const String&) const; + void reportInvalidSourceExpression(const String& directiveName, const String& source) const; + void reportInvalidReflectedXSS(const String&) const; + void reportMissingReportURI(const String&) const; + void reportUnsupportedDirective(const String&) const; + void reportInvalidInReportOnly(const String&) const; + void reportInvalidReferrer(const String&) const; + void reportReportOnlyInMeta(const String&) const; + void reportMetaOutsideHead(const String&) const; + void reportViolation(const String& directiveText, const String& effectiveDirective, const String& consoleMessage, const KURL& blockedURL, const Vector<KURL>& reportURIs, const String& header); + + void reportBlockedScriptExecutionToInspector(const String& directiveText) const; + + const KURL url() const; + KURL completeURL(const String&) const; + SecurityOrigin* securityOrigin() const; + void enforceSandboxFlags(SandboxFlags) const; + String evalDisabledErrorMessage() const; + + bool experimentalFeaturesEnabled() const; + + static bool shouldBypassMainWorld(ExecutionContext*); + + static bool isDirectiveName(const String&); + + ExecutionContext* executionContext() const { return m_executionContext; } + Document* document() const { return m_executionContext->isDocument() ? toDocument(m_executionContext) : 0; } + +private: + explicit ContentSecurityPolicy(ExecutionContext*); + + void logToConsole(const String& message) const; + void addPolicyFromHeaderValue(const String&, ContentSecurityPolicyHeaderType, ContentSecurityPolicyHeaderSource); + + bool shouldSendViolationReport(const String&) const; + void didSendViolationReport(const String&); + + ExecutionContext* m_executionContext; + bool m_overrideInlineStyleAllowed; + CSPDirectiveListVector m_policies; + + HashSet<unsigned, AlreadyHashed> m_violationReportsSent; + + // We put the hash functions used on the policy object so that we only need + // to calculate a hash once and then distribute it to all of the directives + // for validation. + uint8_t m_scriptHashAlgorithmsUsed; + uint8_t m_styleHashAlgorithmsUsed; +}; + +} + +#endif diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/MediaListDirective.cpp b/chromium/third_party/WebKit/Source/core/frame/csp/MediaListDirective.cpp new file mode 100644 index 00000000000..434c779dfd8 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/MediaListDirective.cpp @@ -0,0 +1,86 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "core/frame/csp/MediaListDirective.h" + +#include "core/frame/csp/ContentSecurityPolicy.h" +#include "platform/ParsingUtilities.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "wtf/HashSet.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +MediaListDirective::MediaListDirective(const String& name, const String& value, ContentSecurityPolicy* policy) + : CSPDirective(name, value, policy) +{ + Vector<UChar> characters; + value.appendTo(characters); + parse(characters.data(), characters.data() + characters.size()); +} + +bool MediaListDirective::allows(const String& type) +{ + return m_pluginTypes.contains(type); +} + +void MediaListDirective::parse(const UChar* begin, const UChar* end) +{ + const UChar* position = begin; + + // 'plugin-types ____;' OR 'plugin-types;' + if (position == end) { + policy()->reportInvalidPluginTypes(String()); + 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); + 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); + policy()->reportInvalidPluginTypes(String(begin, position - begin)); + continue; + } + + // mime1/mime1 mime2/mime2 + // ^ + if (!skipExactly<UChar, isMediaTypeCharacter>(position, end)) { + skipWhile<UChar, isNotASCIISpace>(position, end); + 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); + policy()->reportInvalidPluginTypes(String(begin, position - begin)); + continue; + } + m_pluginTypes.add(String(begin, position - begin)); + + ASSERT(position == end || isASCIISpace(*position)); + } +} + +} // namespace WebCore diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/MediaListDirective.h b/chromium/third_party/WebKit/Source/core/frame/csp/MediaListDirective.h new file mode 100644 index 00000000000..9f230ce987e --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/MediaListDirective.h @@ -0,0 +1,31 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef MediaListDirective_h +#define MediaListDirective_h + +#include "core/frame/csp/CSPDirective.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "wtf/HashSet.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +class ContentSecurityPolicy; + +class MediaListDirective FINAL : public CSPDirective { + WTF_MAKE_NONCOPYABLE(MediaListDirective); +public: + MediaListDirective(const String& name, const String& value, ContentSecurityPolicy*); + bool allows(const String& type); + +private: + void parse(const UChar* begin, const UChar* end); + + HashSet<String> m_pluginTypes; +}; + +} // namespace WebCore + +#endif diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp b/chromium/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp new file mode 100644 index 00000000000..ccf8c37d792 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp @@ -0,0 +1,62 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "config.h" +#include "core/frame/csp/SourceListDirective.h" + +#include "core/frame/csp/CSPSourceList.h" +#include "core/frame/csp/ContentSecurityPolicy.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "platform/weborigin/KURL.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +SourceListDirective::SourceListDirective(const String& name, const String& value, ContentSecurityPolicy* policy) + : CSPDirective(name, value, policy) + , m_sourceList(policy, name) +{ + Vector<UChar> characters; + value.appendTo(characters); + + m_sourceList.parse(characters.data(), characters.data() + characters.size()); +} + +bool SourceListDirective::allows(const KURL& url) const +{ + return m_sourceList.matches(url.isEmpty() ? policy()->url() : url); +} + +bool SourceListDirective::allowInline() const +{ + return m_sourceList.allowInline(); +} + +bool SourceListDirective::allowEval() const +{ + return m_sourceList.allowEval(); +} + +bool SourceListDirective::allowNonce(const String& nonce) const +{ + return m_sourceList.allowNonce(nonce.stripWhiteSpace()); +} + +bool SourceListDirective::allowHash(const CSPHashValue& hashValue) const +{ + return m_sourceList.allowHash(hashValue); +} + +bool SourceListDirective::isHashOrNoncePresent() const +{ + return m_sourceList.isHashOrNoncePresent(); +} + +uint8_t SourceListDirective::hashAlgorithmsUsed() const +{ + return m_sourceList.hashAlgorithmsUsed(); +} + +} // namespace WebCore + diff --git a/chromium/third_party/WebKit/Source/core/frame/csp/SourceListDirective.h b/chromium/third_party/WebKit/Source/core/frame/csp/SourceListDirective.h new file mode 100644 index 00000000000..38005d13833 --- /dev/null +++ b/chromium/third_party/WebKit/Source/core/frame/csp/SourceListDirective.h @@ -0,0 +1,38 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SourceListDirective_h +#define SourceListDirective_h + +#include "core/frame/csp/CSPDirective.h" +#include "core/frame/csp/CSPSourceList.h" +#include "platform/network/ContentSecurityPolicyParsers.h" +#include "wtf/HashSet.h" +#include "wtf/text/WTFString.h" + +namespace WebCore { + +class ContentSecurityPolicy; +class KURL; + +class SourceListDirective FINAL : public CSPDirective { + WTF_MAKE_NONCOPYABLE(SourceListDirective); +public: + SourceListDirective(const String& name, const String& value, ContentSecurityPolicy*); + + bool allows(const KURL&) const; + bool allowInline() const; + bool allowEval() const; + bool allowNonce(const String& nonce) const; + bool allowHash(const CSPHashValue&) const; + bool isHashOrNoncePresent() const; + uint8_t hashAlgorithmsUsed() const; + +private: + CSPSourceList m_sourceList; +}; + +} // namespace WebCore + +#endif |