1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
6 #include "core/frame/csp/CSPSourceList.h"
8 #include "core/frame/csp/CSPSource.h"
9 #include "core/frame/csp/ContentSecurityPolicy.h"
10 #include "platform/ParsingUtilities.h"
11 #include "platform/weborigin/KURL.h"
12 #include "platform/weborigin/SecurityOrigin.h"
13 #include "wtf/HashSet.h"
14 #include "wtf/text/Base64.h"
15 #include "wtf/text/WTFString.h"
19 static bool isSourceListNone(const UChar* begin, const UChar* end)
21 skipWhile<UChar, isASCIISpace>(begin, end);
23 const UChar* position = begin;
24 skipWhile<UChar, isSourceCharacter>(position, end);
25 if (!equalIgnoringCase("'none'", begin, position - begin))
28 skipWhile<UChar, isASCIISpace>(position, end);
35 CSPSourceList::CSPSourceList(ContentSecurityPolicy* policy, const String& directiveName)
37 , m_directiveName(directiveName)
40 , m_allowInline(false)
42 , m_hashAlgorithmsUsed(0)
46 bool CSPSourceList::matches(const KURL& url) const
51 KURL effectiveURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url;
53 if (m_allowSelf && m_policy->urlMatchesSelf(effectiveURL))
56 for (size_t i = 0; i < m_list.size(); ++i) {
57 if (m_list[i].matches(effectiveURL))
64 bool CSPSourceList::allowInline() const
69 bool CSPSourceList::allowEval() const
74 bool CSPSourceList::allowNonce(const String& nonce) const
76 return !nonce.isNull() && m_nonces.contains(nonce);
79 bool CSPSourceList::allowHash(const CSPHashValue& hashValue) const
81 return m_hashes.contains(hashValue);
84 uint8_t CSPSourceList::hashAlgorithmsUsed() const
86 return m_hashAlgorithmsUsed;
89 bool CSPSourceList::isHashOrNoncePresent() const
91 return !m_nonces.isEmpty() || m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone;
94 // source-list = *WSP [ source *( 1*WSP source ) *WSP ]
95 // / *WSP "'none'" *WSP
97 void CSPSourceList::parse(const UChar* begin, const UChar* end)
99 // We represent 'none' as an empty m_list.
100 if (isSourceListNone(begin, end))
103 const UChar* position = begin;
104 while (position < end) {
105 skipWhile<UChar, isASCIISpace>(position, end);
109 const UChar* beginSource = position;
110 skipWhile<UChar, isSourceCharacter>(position, end);
112 String scheme, host, path;
114 CSPSource::WildcardDisposition hostWildcard = CSPSource::NoWildcard;
115 CSPSource::WildcardDisposition portWildcard = CSPSource::NoWildcard;
117 if (parseSource(beginSource, position, scheme, host, port, path, hostWildcard, portWildcard)) {
118 // Wildcard hosts and keyword sources ('self', 'unsafe-inline',
119 // etc.) aren't stored in m_list, but as attributes on the source
121 if (scheme.isEmpty() && host.isEmpty())
123 if (m_policy->isDirectiveName(host))
124 m_policy->reportDirectiveAsSourceExpression(m_directiveName, host);
125 m_list.append(CSPSource(m_policy, scheme, host, port, path, hostWildcard, portWildcard));
127 m_policy->reportInvalidSourceExpression(m_directiveName, String(beginSource, position - beginSource));
130 ASSERT(position == end || isASCIISpace(*position));
134 // source = scheme ":"
135 // / ( [ scheme "://" ] host [ port ] [ path ] )
137 bool CSPSourceList::parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, CSPSource::WildcardDisposition& hostWildcard, CSPSource::WildcardDisposition& portWildcard)
142 if (equalIgnoringCase("'none'", begin, end - begin))
145 if (end - begin == 1 && *begin == '*') {
150 if (equalIgnoringCase("'self'", begin, end - begin)) {
155 if (equalIgnoringCase("'unsafe-inline'", begin, end - begin)) {
156 addSourceUnsafeInline();
160 if (equalIgnoringCase("'unsafe-eval'", begin, end - begin)) {
161 addSourceUnsafeEval();
166 if (!parseNonce(begin, end, nonce))
169 if (!nonce.isNull()) {
170 addSourceNonce(nonce);
175 ContentSecurityPolicyHashAlgorithm algorithm = ContentSecurityPolicyHashAlgorithmNone;
176 if (!parseHash(begin, end, hash, algorithm))
179 if (hash.size() > 0) {
180 addSourceHash(algorithm, hash);
184 const UChar* position = begin;
185 const UChar* beginHost = begin;
186 const UChar* beginPath = end;
187 const UChar* beginPort = 0;
189 skipWhile<UChar, isNotColonOrSlash>(position, end);
191 if (position == end) {
194 return parseHost(beginHost, position, host, hostWildcard);
197 if (position < end && *position == '/') {
198 // host/path || host/ || /
200 return parseHost(beginHost, position, host, hostWildcard) && parsePath(position, end, path);
203 if (position < end && *position == ':') {
204 if (end - position == 1) {
207 return parseScheme(begin, position, scheme);
210 if (position[1] == '/') {
211 // scheme://host || scheme://
213 if (!parseScheme(begin, position, scheme)
214 || !skipExactly<UChar>(position, end, ':')
215 || !skipExactly<UChar>(position, end, '/')
216 || !skipExactly<UChar>(position, end, '/'))
220 beginHost = position;
221 skipWhile<UChar, isNotColonOrSlash>(position, end);
224 if (position < end && *position == ':') {
225 // host:port || scheme://host:port
227 beginPort = position;
228 skipUntil<UChar>(position, end, '/');
232 if (position < end && *position == '/') {
233 // scheme://host/path || scheme://host:port/path
235 if (position == beginHost)
237 beginPath = position;
240 if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, hostWildcard))
244 if (!parsePort(beginPort, beginPath, port, portWildcard))
250 if (beginPath != end) {
251 if (!parsePath(beginPath, end, path))
258 // nonce-source = "'nonce-" nonce-value "'"
259 // nonce-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
261 bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& nonce)
263 size_t nonceLength = end - begin;
264 const char* prefix = "'nonce-";
266 if (nonceLength <= strlen(prefix) || !equalIgnoringCase(prefix, begin, strlen(prefix)))
269 const UChar* position = begin + strlen(prefix);
270 const UChar* nonceBegin = position;
272 ASSERT(position < end);
273 skipWhile<UChar, isNonceCharacter>(position, end);
274 ASSERT(nonceBegin <= position);
276 if (position + 1 != end || *position != '\'' || position == nonceBegin)
279 nonce = String(nonceBegin, position - nonceBegin);
283 // hash-source = "'" hash-algorithm "-" hash-value "'"
284 // hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512"
285 // hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
287 bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm)
289 // Any additions or subtractions from this struct should also modify the
290 // respective entries in the kAlgorithmMap array in checkDigest().
291 static const struct {
293 ContentSecurityPolicyHashAlgorithm type;
294 } kSupportedPrefixes[] = {
295 { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 },
296 { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 },
297 { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 },
298 { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 }
302 hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone;
303 size_t hashLength = end - begin;
305 for (const auto& algorithm : kSupportedPrefixes) {
306 if (hashLength > strlen(algorithm.prefix) && equalIgnoringCase(algorithm.prefix, begin, strlen(algorithm.prefix))) {
307 prefix = algorithm.prefix;
308 hashAlgorithm = algorithm.type;
313 if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone)
316 const UChar* position = begin + prefix.length();
317 const UChar* hashBegin = position;
319 ASSERT(position < end);
320 skipWhile<UChar, isBase64EncodedCharacter>(position, end);
321 ASSERT(hashBegin <= position);
323 // Base64 encodings may end with exactly one or two '=' characters
325 skipExactly<UChar>(position, position + 1, '=');
327 skipExactly<UChar>(position, position + 1, '=');
329 if (position + 1 != end || *position != '\'' || position == hashBegin)
332 Vector<char> hashVector;
333 base64Decode(hashBegin, position - hashBegin, hashVector);
334 if (hashVector.size() > kMaxDigestSize)
336 hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size());
340 // ; <scheme> production from RFC 3986
341 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
343 bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
345 ASSERT(begin <= end);
346 ASSERT(scheme.isEmpty());
351 const UChar* position = begin;
353 if (!skipExactly<UChar, isASCIIAlpha>(position, end))
356 skipWhile<UChar, isSchemeContinuationCharacter>(position, end);
361 scheme = String(begin, end - begin);
365 // host = [ "*." ] 1*host-char *( "." 1*host-char )
367 // host-char = ALPHA / DIGIT / "-"
369 bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, CSPSource::WildcardDisposition& hostWildcard)
371 ASSERT(begin <= end);
372 ASSERT(host.isEmpty());
373 ASSERT(hostWildcard == CSPSource::NoWildcard);
378 const UChar* position = begin;
380 if (skipExactly<UChar>(position, end, '*')) {
381 hostWildcard = CSPSource::HasWildcard;
386 if (!skipExactly<UChar>(position, end, '.'))
390 const UChar* hostBegin = position;
392 while (position < end) {
393 if (!skipExactly<UChar, isHostCharacter>(position, end))
396 skipWhile<UChar, isHostCharacter>(position, end);
398 if (position < end && !skipExactly<UChar>(position, end, '.'))
402 ASSERT(position == end);
403 host = String(hostBegin, end - hostBegin);
407 bool CSPSourceList::parsePath(const UChar* begin, const UChar* end, String& path)
409 ASSERT(begin <= end);
410 ASSERT(path.isEmpty());
412 const UChar* position = begin;
413 skipWhile<UChar, isPathComponentCharacter>(position, end);
414 // path/to/file.js?query=string || path/to/file.js#anchor
417 m_policy->reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position);
419 path = decodeURLEscapeSequences(String(begin, position - begin));
421 ASSERT(position <= end);
422 ASSERT(position == end || (*position == '#' || *position == '?'));
426 // port = ":" ( 1*DIGIT / "*" )
428 bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, CSPSource::WildcardDisposition& portWildcard)
430 ASSERT(begin <= end);
432 ASSERT(portWildcard == CSPSource::NoWildcard);
434 if (!skipExactly<UChar>(begin, end, ':'))
435 ASSERT_NOT_REACHED();
440 if (end - begin == 1 && *begin == '*') {
442 portWildcard = CSPSource::HasWildcard;
446 const UChar* position = begin;
447 skipWhile<UChar, isASCIIDigit>(position, end);
453 port = charactersToIntStrict(begin, end - begin, &ok);
457 void CSPSourceList::addSourceSelf()
462 void CSPSourceList::addSourceStar()
467 void CSPSourceList::addSourceUnsafeInline()
469 m_allowInline = true;
472 void CSPSourceList::addSourceUnsafeEval()
477 void CSPSourceList::addSourceNonce(const String& nonce)
482 void CSPSourceList::addSourceHash(const ContentSecurityPolicyHashAlgorithm& algorithm, const DigestValue& hash)
484 m_hashes.add(CSPHashValue(algorithm, hash));
485 m_hashAlgorithmsUsed |= algorithm;