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)
39 , m_allowInline(false)
41 , m_hashAlgorithmsUsed(0)
45 bool CSPSourceList::matches(const KURL& url) const
50 KURL effectiveURL = SecurityOrigin::shouldUseInnerURL(url) ? SecurityOrigin::extractInnerURL(url) : url;
52 for (size_t i = 0; i < m_list.size(); ++i) {
53 if (m_list[i].matches(effectiveURL))
60 bool CSPSourceList::allowInline() const
65 bool CSPSourceList::allowEval() const
70 bool CSPSourceList::allowNonce(const String& nonce) const
72 return !nonce.isNull() && m_nonces.contains(nonce);
75 bool CSPSourceList::allowHash(const CSPHashValue& hashValue) const
77 return m_hashes.contains(hashValue);
80 uint8_t CSPSourceList::hashAlgorithmsUsed() const
82 return m_hashAlgorithmsUsed;
85 bool CSPSourceList::isHashOrNoncePresent() const
87 return !m_nonces.isEmpty() || m_hashAlgorithmsUsed != ContentSecurityPolicyHashAlgorithmNone;
90 // source-list = *WSP [ source *( 1*WSP source ) *WSP ]
91 // / *WSP "'none'" *WSP
93 void CSPSourceList::parse(const UChar* begin, const UChar* end)
95 // We represent 'none' as an empty m_list.
96 if (isSourceListNone(begin, end))
99 const UChar* position = begin;
100 while (position < end) {
101 skipWhile<UChar, isASCIISpace>(position, end);
105 const UChar* beginSource = position;
106 skipWhile<UChar, isSourceCharacter>(position, end);
108 String scheme, host, path;
110 bool hostHasWildcard = false;
111 bool portHasWildcard = false;
113 if (parseSource(beginSource, position, scheme, host, port, path, hostHasWildcard, portHasWildcard)) {
114 // Wildcard hosts and keyword sources ('self', 'unsafe-inline',
115 // etc.) aren't stored in m_list, but as attributes on the source
117 if (scheme.isEmpty() && host.isEmpty())
119 if (m_policy->isDirectiveName(host))
120 m_policy->reportDirectiveAsSourceExpression(m_directiveName, host);
121 m_list.append(CSPSource(m_policy, scheme, host, port, path, hostHasWildcard, portHasWildcard));
123 m_policy->reportInvalidSourceExpression(m_directiveName, String(beginSource, position - beginSource));
126 ASSERT(position == end || isASCIISpace(*position));
130 // source = scheme ":"
131 // / ( [ scheme "://" ] host [ port ] [ path ] )
133 bool CSPSourceList::parseSource(const UChar* begin, const UChar* end, String& scheme, String& host, int& port, String& path, bool& hostHasWildcard, bool& portHasWildcard)
138 if (equalIgnoringCase("'none'", begin, end - begin))
141 if (end - begin == 1 && *begin == '*') {
146 if (equalIgnoringCase("'self'", begin, end - begin)) {
151 if (equalIgnoringCase("'unsafe-inline'", begin, end - begin)) {
152 addSourceUnsafeInline();
156 if (equalIgnoringCase("'unsafe-eval'", begin, end - begin)) {
157 addSourceUnsafeEval();
162 if (!parseNonce(begin, end, nonce))
165 if (!nonce.isNull()) {
166 addSourceNonce(nonce);
171 ContentSecurityPolicyHashAlgorithm algorithm = ContentSecurityPolicyHashAlgorithmNone;
172 if (!parseHash(begin, end, hash, algorithm))
175 if (hash.size() > 0) {
176 addSourceHash(algorithm, hash);
180 const UChar* position = begin;
181 const UChar* beginHost = begin;
182 const UChar* beginPath = end;
183 const UChar* beginPort = 0;
185 skipWhile<UChar, isNotColonOrSlash>(position, end);
187 if (position == end) {
190 return parseHost(beginHost, position, host, hostHasWildcard);
193 if (position < end && *position == '/') {
194 // host/path || host/ || /
196 return parseHost(beginHost, position, host, hostHasWildcard) && parsePath(position, end, path);
199 if (position < end && *position == ':') {
200 if (end - position == 1) {
203 return parseScheme(begin, position, scheme);
206 if (position[1] == '/') {
207 // scheme://host || scheme://
209 if (!parseScheme(begin, position, scheme)
210 || !skipExactly<UChar>(position, end, ':')
211 || !skipExactly<UChar>(position, end, '/')
212 || !skipExactly<UChar>(position, end, '/'))
216 beginHost = position;
217 skipWhile<UChar, isNotColonOrSlash>(position, end);
220 if (position < end && *position == ':') {
221 // host:port || scheme://host:port
223 beginPort = position;
224 skipUntil<UChar>(position, end, '/');
228 if (position < end && *position == '/') {
229 // scheme://host/path || scheme://host:port/path
231 if (position == beginHost)
233 beginPath = position;
236 if (!parseHost(beginHost, beginPort ? beginPort : beginPath, host, hostHasWildcard))
240 if (!parsePort(beginPort, beginPath, port, portHasWildcard))
246 if (beginPath != end) {
247 if (!parsePath(beginPath, end, path))
254 // nonce-source = "'nonce-" nonce-value "'"
255 // nonce-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
257 bool CSPSourceList::parseNonce(const UChar* begin, const UChar* end, String& nonce)
259 DEFINE_STATIC_LOCAL(const String, noncePrefix, ("'nonce-"));
261 if (!equalIgnoringCase(noncePrefix.characters8(), begin, noncePrefix.length()))
264 const UChar* position = begin + noncePrefix.length();
265 const UChar* nonceBegin = position;
267 skipWhile<UChar, isNonceCharacter>(position, end);
268 ASSERT(nonceBegin <= position);
270 if ((position + 1) != end || *position != '\'' || !(position - nonceBegin))
273 nonce = String(nonceBegin, position - nonceBegin);
277 // hash-source = "'" hash-algorithm "-" hash-value "'"
278 // hash-algorithm = "sha1" / "sha256" / "sha384" / "sha512"
279 // hash-value = 1*( ALPHA / DIGIT / "+" / "/" / "=" )
281 bool CSPSourceList::parseHash(const UChar* begin, const UChar* end, DigestValue& hash, ContentSecurityPolicyHashAlgorithm& hashAlgorithm)
283 // Any additions or subtractions from this struct should also modify the
284 // respective entries in the kAlgorithmMap array in checkDigest().
285 static const struct {
287 ContentSecurityPolicyHashAlgorithm algorithm;
288 } kSupportedPrefixes[] = {
289 { "'sha1-", ContentSecurityPolicyHashAlgorithmSha1 },
290 { "'sha256-", ContentSecurityPolicyHashAlgorithmSha256 },
291 { "'sha384-", ContentSecurityPolicyHashAlgorithmSha384 },
292 { "'sha512-", ContentSecurityPolicyHashAlgorithmSha512 }
296 hashAlgorithm = ContentSecurityPolicyHashAlgorithmNone;
298 // Instead of this sizeof() calculation to get the length of this array,
299 // it would be preferable to use WTF_ARRAY_LENGTH for simplicity and to
300 // guarantee a compile time calculation. Unfortunately, on some
301 // compliers, the call to WTF_ARRAY_LENGTH fails on arrays of anonymous
302 // stucts, so, for now, it is necessary to resort to this sizeof
304 for (size_t i = 0; i < (sizeof(kSupportedPrefixes) / sizeof(kSupportedPrefixes[0])); i++) {
305 if (equalIgnoringCase(kSupportedPrefixes[i].prefix, begin, strlen(kSupportedPrefixes[i].prefix))) {
306 prefix = kSupportedPrefixes[i].prefix;
307 hashAlgorithm = kSupportedPrefixes[i].algorithm;
312 if (hashAlgorithm == ContentSecurityPolicyHashAlgorithmNone)
315 const UChar* position = begin + prefix.length();
316 const UChar* hashBegin = position;
318 skipWhile<UChar, isBase64EncodedCharacter>(position, end);
319 ASSERT(hashBegin <= position);
321 // Base64 encodings may end with exactly one or two '=' characters
322 skipExactly<UChar>(position, position + 1, '=');
323 skipExactly<UChar>(position, position + 1, '=');
325 if ((position + 1) != end || *position != '\'' || !(position - hashBegin))
328 Vector<char> hashVector;
329 base64Decode(hashBegin, position - hashBegin, hashVector);
330 if (hashVector.size() > kMaxDigestSize)
332 hash.append(reinterpret_cast<uint8_t*>(hashVector.data()), hashVector.size());
336 // ; <scheme> production from RFC 3986
337 // scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." )
339 bool CSPSourceList::parseScheme(const UChar* begin, const UChar* end, String& scheme)
341 ASSERT(begin <= end);
342 ASSERT(scheme.isEmpty());
347 const UChar* position = begin;
349 if (!skipExactly<UChar, isASCIIAlpha>(position, end))
352 skipWhile<UChar, isSchemeContinuationCharacter>(position, end);
357 scheme = String(begin, end - begin);
361 // host = [ "*." ] 1*host-char *( "." 1*host-char )
363 // host-char = ALPHA / DIGIT / "-"
365 bool CSPSourceList::parseHost(const UChar* begin, const UChar* end, String& host, bool& hostHasWildcard)
367 ASSERT(begin <= end);
368 ASSERT(host.isEmpty());
369 ASSERT(!hostHasWildcard);
374 const UChar* position = begin;
376 if (skipExactly<UChar>(position, end, '*')) {
377 hostHasWildcard = true;
382 if (!skipExactly<UChar>(position, end, '.'))
386 const UChar* hostBegin = position;
388 while (position < end) {
389 if (!skipExactly<UChar, isHostCharacter>(position, end))
392 skipWhile<UChar, isHostCharacter>(position, end);
394 if (position < end && !skipExactly<UChar>(position, end, '.'))
398 ASSERT(position == end);
399 host = String(hostBegin, end - hostBegin);
403 bool CSPSourceList::parsePath(const UChar* begin, const UChar* end, String& path)
405 ASSERT(begin <= end);
406 ASSERT(path.isEmpty());
408 const UChar* position = begin;
409 skipWhile<UChar, isPathComponentCharacter>(position, end);
410 // path/to/file.js?query=string || path/to/file.js#anchor
413 m_policy->reportInvalidPathCharacter(m_directiveName, String(begin, end - begin), *position);
415 path = decodeURLEscapeSequences(String(begin, position - begin));
417 ASSERT(position <= end);
418 ASSERT(position == end || (*position == '#' || *position == '?'));
422 // port = ":" ( 1*DIGIT / "*" )
424 bool CSPSourceList::parsePort(const UChar* begin, const UChar* end, int& port, bool& portHasWildcard)
426 ASSERT(begin <= end);
428 ASSERT(!portHasWildcard);
430 if (!skipExactly<UChar>(begin, end, ':'))
431 ASSERT_NOT_REACHED();
436 if (end - begin == 1 && *begin == '*') {
438 portHasWildcard = true;
442 const UChar* position = begin;
443 skipWhile<UChar, isASCIIDigit>(position, end);
449 port = charactersToIntStrict(begin, end - begin, &ok);
453 void CSPSourceList::addSourceSelf()
455 m_list.append(CSPSource(m_policy, m_policy->securityOrigin()->protocol(), m_policy->securityOrigin()->host(), m_policy->securityOrigin()->port(), String(), false, false));
458 void CSPSourceList::addSourceStar()
463 void CSPSourceList::addSourceUnsafeInline()
465 m_allowInline = true;
468 void CSPSourceList::addSourceUnsafeEval()
473 void CSPSourceList::addSourceNonce(const String& nonce)
478 void CSPSourceList::addSourceHash(const ContentSecurityPolicyHashAlgorithm& algorithm, const DigestValue& hash)
480 m_hashes.add(CSPHashValue(algorithm, hash));
481 m_hashAlgorithmsUsed |= algorithm;
485 } // namespace WebCore