Index: third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp |
diff --git a/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp b/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp |
index 4d2cd4a8b7b26f4217594f9cc663c5dd2c914826..adf39a29dd9327244ebba2c0baa2d5ad010dc2ac 100644 |
--- a/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp |
+++ b/third_party/WebKit/Source/core/frame/csp/SourceListDirective.cpp |
@@ -4,10 +4,13 @@ |
#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 "platform/weborigin/SecurityOrigin.h" |
+#include "wtf/text/Base64.h" |
+#include "wtf/text/ParsingUtilities.h" |
+#include "wtf/text/StringToNumber.h" |
#include "wtf/text/WTFString.h" |
namespace blink { |
@@ -15,53 +18,558 @@ namespace blink { |
SourceListDirective::SourceListDirective(const String& name, |
const String& value, |
ContentSecurityPolicy* policy) |
- : CSPDirective(name, value, policy), m_sourceList(policy, name) { |
+ : CSPDirective(name, value, policy), |
+ m_policy(policy), |
+ m_directiveName(name), |
+ m_allowSelf(false), |
+ m_allowStar(false), |
+ m_allowInline(false), |
+ m_allowEval(false), |
+ m_allowDynamic(false), |
+ m_allowHashedAttributes(false), |
+ m_hashAlgorithmsUsed(0) { |
Vector<UChar> characters; |
value.appendTo(characters); |
+ parse(characters.data(), characters.data() + characters.size()); |
+} |
+ |
+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'", StringView(begin, position - begin))) |
+ return false; |
+ |
+ skipWhile<UChar, isASCIISpace>(position, end); |
+ if (position != end) |
+ return false; |
- m_sourceList.parse(characters.data(), characters.data() + characters.size()); |
+ return true; |
} |
bool SourceListDirective::allows( |
const KURL& url, |
ResourceRequest::RedirectStatus redirectStatus) const { |
- return m_sourceList.matches(url, redirectStatus); |
+ // Wildcards match network schemes ('http', 'https', 'ftp', 'ws', 'wss'), and |
+ // the scheme of the protected resource: |
+ // https://w3c.github.io/webappsec-csp/#match-url-to-source-expression. Other |
+ // schemes, including custom schemes, must be explicitly listed in a source |
+ // list. |
+ if (m_allowStar) { |
+ if (url.protocolIsInHTTPFamily() || url.protocolIs("ftp") || |
+ url.protocolIs("ws") || url.protocolIs("wss") || |
+ m_policy->protocolMatchesSelf(url)) |
+ return true; |
+ |
+ return hasSourceMatchInList(url, redirectStatus); |
+ } |
+ |
+ KURL effectiveURL = |
+ m_policy->selfMatchesInnerURL() && SecurityOrigin::shouldUseInnerURL(url) |
+ ? SecurityOrigin::extractInnerURL(url) |
+ : url; |
+ |
+ if (m_allowSelf && m_policy->urlMatchesSelf(effectiveURL)) |
+ return true; |
+ |
+ return hasSourceMatchInList(effectiveURL, redirectStatus); |
} |
bool SourceListDirective::allowInline() const { |
- return m_sourceList.allowInline(); |
+ return m_allowInline; |
} |
bool SourceListDirective::allowEval() const { |
- return m_sourceList.allowEval(); |
+ return m_allowEval; |
} |
bool SourceListDirective::allowDynamic() const { |
- return m_sourceList.allowDynamic(); |
+ return m_allowDynamic; |
} |
-bool SourceListDirective::allowNonce(const String& nonce) const { |
- return m_sourceList.allowNonce(nonce.stripWhiteSpace()); |
+bool SourceListDirective::allowNonce(String nonce) const { |
+ nonce = nonce.stripWhiteSpace(); |
+ return !nonce.isNull() && m_nonces.contains(nonce); |
} |
bool SourceListDirective::allowHash(const CSPHashValue& hashValue) const { |
- return m_sourceList.allowHash(hashValue); |
+ return m_hashes.contains(hashValue); |
} |
bool SourceListDirective::allowHashedAttributes() const { |
- return m_sourceList.allowHashedAttributes(); |
+ return m_allowHashedAttributes; |
+} |
+ |
+uint8_t SourceListDirective::hashAlgorithmsUsed() const { |
+ return m_hashAlgorithmsUsed; |
} |
bool SourceListDirective::isHashOrNoncePresent() const { |
- return m_sourceList.isHashOrNoncePresent(); |
+ return !m_nonces.isEmpty() || |
+ m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone; |
} |
-uint8_t SourceListDirective::hashAlgorithmsUsed() const { |
- return m_sourceList.hashAlgorithmsUsed(); |
+// source-list = *WSP [ source *( 1*WSP source ) *WSP ] |
+// / *WSP "'none'" *WSP |
+// |
+void SourceListDirective::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; |
+ CSPSource::WildcardDisposition hostWildcard = CSPSource::NoWildcard; |
+ CSPSource::WildcardDisposition portWildcard = CSPSource::NoWildcard; |
+ |
+ if (parseSource(beginSource, position, scheme, host, port, path, |
+ hostWildcard, portWildcard)) { |
+ // 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(new CSPSource(m_policy, scheme, host, port, path, |
+ hostWildcard, portWildcard)); |
+ } else { |
+ m_policy->reportInvalidSourceExpression( |
+ m_directiveName, String(beginSource, position - beginSource)); |
+ } |
+ |
+ DCHECK(position == end || isASCIISpace(*position)); |
+ } |
+} |
+ |
+// source = scheme ":" |
+// / ( [ scheme "://" ] host [ port ] [ path ] ) |
+// / "'self'" |
+bool SourceListDirective::parseSource( |
+ const UChar* begin, |
+ const UChar* end, |
+ String& scheme, |
+ String& host, |
+ int& port, |
+ String& path, |
+ CSPSource::WildcardDisposition& hostWildcard, |
+ CSPSource::WildcardDisposition& portWildcard) { |
+ if (begin == end) |
+ return false; |
+ |
+ StringView token(begin, end - begin); |
+ |
+ if (equalIgnoringCase("'none'", token)) |
+ return false; |
+ |
+ if (end - begin == 1 && *begin == '*') { |
+ addSourceStar(); |
+ return true; |
+ } |
+ |
+ if (equalIgnoringCase("'self'", token)) { |
+ addSourceSelf(); |
+ return true; |
+ } |
+ |
+ if (equalIgnoringCase("'unsafe-inline'", token)) { |
+ addSourceUnsafeInline(); |
+ return true; |
+ } |
+ |
+ if (equalIgnoringCase("'unsafe-eval'", token)) { |
+ addSourceUnsafeEval(); |
+ return true; |
+ } |
+ |
+ if (equalIgnoringCase("'strict-dynamic'", token)) { |
+ addSourceStrictDynamic(); |
+ return true; |
+ } |
+ |
+ if (equalIgnoringCase("'unsafe-hashed-attributes'", token)) { |
+ addSourceUnsafeHashedAttributes(); |
+ 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, hostWildcard); |
+ } |
+ |
+ if (position < end && *position == '/') { |
+ // host/path || host/ || / |
+ // ^ ^ ^ |
+ return parseHost(beginHost, position, host, hostWildcard) && |
+ parsePath(position, end, path); |
+ } |
+ |
+ if (position < end && *position == ':') { |
+ if (end - position == 1) { |
+ // scheme: |
+ // ^ |
+ return parseScheme(begin, position, scheme); |
+ } |
+ |
+ if (position[1] == '/') { |
+ // scheme://host || scheme:// |
+ // ^ ^ |
+ if (!parseScheme(begin, position, scheme) || |
+ !skipExactly<UChar>(position, end, ':') || |
+ !skipExactly<UChar>(position, end, '/') || |
+ !skipExactly<UChar>(position, end, '/')) |
+ return false; |
+ if (position == end) |
+ return false; |
+ beginHost = position; |
+ skipWhile<UChar, isNotColonOrSlash>(position, end); |
+ } |
+ |
+ if (position < end && *position == ':') { |
+ // host:port || scheme://host:port |
+ // ^ ^ |
+ beginPort = position; |
+ skipUntil<UChar>(position, end, '/'); |
+ } |
+ } |
+ |
+ if (position < end && *position == '/') { |
+ // scheme://host/path || scheme://host:port/path |
+ // ^ ^ |
+ if (position == beginHost) |
+ return false; |
+ beginPath = position; |
+ } |
+ |
+ if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, |
+ hostWildcard)) |
+ return false; |
+ |
+ if (beginPort) { |
+ if (!parsePort(beginPort, beginPath, port, portWildcard)) |
+ 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 SourceListDirective::parseNonce(const UChar* begin, |
+ const UChar* end, |
+ String& nonce) { |
+ size_t nonceLength = end - begin; |
+ StringView prefix("'nonce-"); |
+ |
+ // TODO(esprehn): Should be StringView(begin, nonceLength).startsWith(prefix). |
+ if (nonceLength <= prefix.length() || |
+ !equalIgnoringCase(prefix, StringView(begin, prefix.length()))) |
+ return true; |
+ |
+ const UChar* position = begin + prefix.length(); |
+ const UChar* nonceBegin = position; |
+ |
+ DCHECK(position < end); |
+ skipWhile<UChar, isNonceCharacter>(position, end); |
+ DCHECK(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 SourceListDirective::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 type; |
+ } kSupportedPrefixes[] = { |
+ // FIXME: Drop support for SHA-1. It's not in the spec. |
+ {"'sha1-", ContentSecurityPolicyHashAlgorithmSha1}, |
+ {"'sha256-", ContentSecurityPolicyHashAlgorithmSha256}, |
+ {"'sha384-", ContentSecurityPolicyHashAlgorithmSha384}, |
+ {"'sha512-", ContentSecurityPolicyHashAlgorithmSha512}, |
+ {"'sha-256-", ContentSecurityPolicyHashAlgorithmSha256}, |
+ {"'sha-384-", ContentSecurityPolicyHashAlgorithmSha384}, |
+ {"'sha-512-", ContentSecurityPolicyHashAlgorithmSha512}}; |
+ |
+ StringView prefix; |
+ hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone; |
+ size_t hashLength = end - begin; |
+ |
+ for (const auto& algorithm : kSupportedPrefixes) { |
+ prefix = algorithm.prefix; |
+ // TODO(esprehn): Should be StringView(begin, end - |
+ // begin).startsWith(prefix). |
+ if (hashLength > prefix.length() && |
+ equalIgnoringCase(prefix, StringView(begin, prefix.length()))) { |
+ hashAlgorithm = algorithm.type; |
+ break; |
+ } |
+ } |
+ |
+ if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone) |
+ return true; |
+ |
+ const UChar* position = begin + prefix.length(); |
+ const UChar* hashBegin = position; |
+ |
+ DCHECK(position < end); |
+ skipWhile<UChar, isBase64EncodedCharacter>(position, end); |
+ DCHECK(hashBegin <= position); |
+ |
+ // Base64 encodings may end with exactly one or two '=' characters |
+ if (position < end) |
+ skipExactly<UChar>(position, position + 1, '='); |
+ if (position < end) |
+ skipExactly<UChar>(position, position + 1, '='); |
+ |
+ if (position + 1 != end || *position != '\'' || position == hashBegin) |
+ return false; |
+ |
+ Vector<char> hashVector; |
+ // We accept base64url-encoded data here by normalizing it to base64. |
+ base64Decode(normalizeToBase64(String(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 SourceListDirective::parseScheme(const UChar* begin, |
+ const UChar* end, |
+ String& scheme) { |
+ DCHECK(begin <= end); |
+ DCHECK(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 SourceListDirective::parseHost( |
+ const UChar* begin, |
+ const UChar* end, |
+ String& host, |
+ CSPSource::WildcardDisposition& hostWildcard) { |
+ DCHECK(begin <= end); |
+ DCHECK(host.isEmpty()); |
+ DCHECK(hostWildcard == CSPSource::NoWildcard); |
+ |
+ if (begin == end) |
+ return false; |
+ |
+ const UChar* position = begin; |
+ |
+ if (skipExactly<UChar>(position, end, '*')) { |
+ hostWildcard = CSPSource::HasWildcard; |
+ |
+ 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; |
+ } |
+ |
+ DCHECK(position == end); |
+ host = String(hostBegin, end - hostBegin); |
+ return true; |
+} |
+ |
+bool SourceListDirective::parsePath(const UChar* begin, |
+ const UChar* end, |
+ String& path) { |
+ DCHECK(begin <= end); |
+ DCHECK(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)); |
+ |
+ DCHECK(position <= end); |
+ DCHECK(position == end || (*position == '#' || *position == '?')); |
+ return true; |
+} |
+ |
+// port = ":" ( 1*DIGIT / "*" ) |
+// |
+bool SourceListDirective::parsePort( |
+ const UChar* begin, |
+ const UChar* end, |
+ int& port, |
+ CSPSource::WildcardDisposition& portWildcard) { |
+ DCHECK(begin <= end); |
+ DCHECK(!port); |
+ DCHECK(portWildcard == CSPSource::NoWildcard); |
+ |
+ if (!skipExactly<UChar>(begin, end, ':')) |
+ NOTREACHED(); |
+ |
+ if (begin == end) |
+ return false; |
+ |
+ if (end - begin == 1 && *begin == '*') { |
+ port = 0; |
+ portWildcard = CSPSource::HasWildcard; |
+ 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 SourceListDirective::addSourceSelf() { |
+ m_allowSelf = true; |
+} |
+ |
+void SourceListDirective::addSourceStar() { |
+ m_allowStar = true; |
+} |
+ |
+void SourceListDirective::addSourceUnsafeInline() { |
+ m_allowInline = true; |
+} |
+ |
+void SourceListDirective::addSourceUnsafeEval() { |
+ m_allowEval = true; |
+} |
+ |
+void SourceListDirective::addSourceStrictDynamic() { |
+ m_allowDynamic = true; |
+} |
+ |
+void SourceListDirective::addSourceUnsafeHashedAttributes() { |
+ m_allowHashedAttributes = true; |
+} |
+ |
+void SourceListDirective::addSourceNonce(const String& nonce) { |
+ m_nonces.add(nonce); |
+} |
+ |
+void SourceListDirective::addSourceHash( |
+ const ContentSecurityPolicyHashAlgorithm& algorithm, |
+ const DigestValue& hash) { |
+ m_hashes.add(CSPHashValue(algorithm, hash)); |
+ m_hashAlgorithmsUsed |= algorithm; |
+} |
+ |
+bool SourceListDirective::hasSourceMatchInList( |
+ const KURL& url, |
+ ResourceRequest::RedirectStatus redirectStatus) const { |
+ for (size_t i = 0; i < m_list.size(); ++i) { |
+ if (m_list[i]->matches(url, redirectStatus)) |
+ return true; |
+ } |
+ |
+ return false; |
} |
DEFINE_TRACE(SourceListDirective) { |
- visitor->trace(m_sourceList); |
+ visitor->trace(m_policy); |
+ visitor->trace(m_list); |
CSPDirective::trace(visitor); |
} |