| // 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 "core/frame/csp/SourceListDirective.h" |
| |
| #include "core/frame/csp/CSPSource.h" |
| #include "core/frame/csp/ContentSecurityPolicy.h" |
| #include "platform/network/ContentSecurityPolicyParsers.h" |
| #include "platform/weborigin/KURL.h" |
| #include "platform/weborigin/SecurityOrigin.h" |
| #include "platform/wtf/HashSet.h" |
| #include "platform/wtf/text/Base64.h" |
| #include "platform/wtf/text/ParsingUtilities.h" |
| #include "platform/wtf/text/StringToNumber.h" |
| #include "platform/wtf/text/WTFString.h" |
| |
| namespace blink { |
| |
| SourceListDirective::SourceListDirective(const String& name, |
| const String& value, |
| ContentSecurityPolicy* policy) |
| : CSPDirective(name, value, policy), |
| policy_(policy), |
| directive_name_(name), |
| allow_self_(false), |
| allow_star_(false), |
| allow_inline_(false), |
| allow_eval_(false), |
| allow_dynamic_(false), |
| allow_hashed_attributes_(false), |
| report_sample_(false), |
| hash_algorithms_used_(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 (!EqualIgnoringASCIICase("'none'", StringView(begin, position - begin))) |
| return false; |
| |
| skipWhile<UChar, IsASCIISpace>(position, end); |
| if (position != end) |
| return false; |
| |
| return true; |
| } |
| |
| bool SourceListDirective::Allows( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| // 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 (allow_star_) { |
| if (url.ProtocolIsInHTTPFamily() || url.ProtocolIs("ftp") || |
| url.ProtocolIs("ws") || url.ProtocolIs("wss") || |
| policy_->ProtocolEqualsSelf(url.Protocol())) |
| return true; |
| |
| return HasSourceMatchInList(url, redirect_status); |
| } |
| |
| if (allow_self_ && policy_->UrlMatchesSelf(url)) |
| return true; |
| |
| return HasSourceMatchInList(url, redirect_status); |
| } |
| |
| bool SourceListDirective::AllowInline() const { |
| return allow_inline_; |
| } |
| |
| bool SourceListDirective::AllowEval() const { |
| return allow_eval_; |
| } |
| |
| bool SourceListDirective::AllowDynamic() const { |
| return allow_dynamic_; |
| } |
| |
| bool SourceListDirective::AllowNonce(const String& nonce) const { |
| String nonce_stripped = nonce.StripWhiteSpace(); |
| return !nonce_stripped.IsNull() && nonces_.Contains(nonce_stripped); |
| } |
| |
| bool SourceListDirective::AllowHash(const CSPHashValue& hash_value) const { |
| return hashes_.Contains(hash_value); |
| } |
| |
| bool SourceListDirective::AllowHashedAttributes() const { |
| return allow_hashed_attributes_; |
| } |
| |
| bool SourceListDirective::AllowReportSample() const { |
| return report_sample_; |
| } |
| |
| bool SourceListDirective::IsNone() const { |
| return !list_.size() && !allow_self_ && !allow_star_ && !allow_inline_ && |
| !allow_hashed_attributes_ && !allow_eval_ && !allow_dynamic_ && |
| !nonces_.size() && !hashes_.size(); |
| } |
| |
| uint8_t SourceListDirective::HashAlgorithmsUsed() const { |
| return hash_algorithms_used_; |
| } |
| |
| bool SourceListDirective::IsHashOrNoncePresent() const { |
| return !nonces_.IsEmpty() || |
| hash_algorithms_used_ != kContentSecurityPolicyHashAlgorithmNone; |
| } |
| |
| // 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* begin_source = position; |
| skipWhile<UChar, IsSourceCharacter>(position, end); |
| |
| String scheme, host, path; |
| int port = 0; |
| CSPSource::WildcardDisposition host_wildcard = CSPSource::kNoWildcard; |
| CSPSource::WildcardDisposition port_wildcard = CSPSource::kNoWildcard; |
| |
| if (ParseSource(begin_source, position, scheme, host, port, path, |
| host_wildcard, port_wildcard)) { |
| // 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 (ContentSecurityPolicy::GetDirectiveType(host) != |
| ContentSecurityPolicy::DirectiveType::kUndefined) |
| policy_->ReportDirectiveAsSourceExpression(directive_name_, host); |
| list_.push_back(new CSPSource(policy_, scheme, host, port, path, |
| host_wildcard, port_wildcard)); |
| } else { |
| policy_->ReportInvalidSourceExpression( |
| directive_name_, String(begin_source, position - begin_source)); |
| } |
| |
| 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& host_wildcard, |
| CSPSource::WildcardDisposition& port_wildcard) { |
| if (begin == end) |
| return false; |
| |
| StringView token(begin, end - begin); |
| |
| if (EqualIgnoringASCIICase("'none'", token)) |
| return false; |
| |
| if (end - begin == 1 && *begin == '*') { |
| AddSourceStar(); |
| return true; |
| } |
| |
| if (EqualIgnoringASCIICase("'self'", token)) { |
| AddSourceSelf(); |
| return true; |
| } |
| |
| if (EqualIgnoringASCIICase("'unsafe-inline'", token)) { |
| AddSourceUnsafeInline(); |
| return true; |
| } |
| |
| if (EqualIgnoringASCIICase("'unsafe-eval'", token)) { |
| AddSourceUnsafeEval(); |
| return true; |
| } |
| |
| if (EqualIgnoringASCIICase("'strict-dynamic'", token)) { |
| AddSourceStrictDynamic(); |
| return true; |
| } |
| |
| if (EqualIgnoringASCIICase("'unsafe-hashed-attributes'", token)) { |
| AddSourceUnsafeHashedAttributes(); |
| return true; |
| } |
| |
| if (EqualIgnoringASCIICase("'report-sample'", token)) { |
| AddReportSample(); |
| return true; |
| } |
| |
| String nonce; |
| if (!ParseNonce(begin, end, nonce)) |
| return false; |
| |
| if (!nonce.IsNull()) { |
| AddSourceNonce(nonce); |
| return true; |
| } |
| |
| DigestValue hash; |
| ContentSecurityPolicyHashAlgorithm algorithm = |
| kContentSecurityPolicyHashAlgorithmNone; |
| if (!ParseHash(begin, end, hash, algorithm)) |
| return false; |
| |
| if (hash.size() > 0) { |
| AddSourceHash(algorithm, hash); |
| return true; |
| } |
| |
| const UChar* position = begin; |
| const UChar* begin_host = begin; |
| const UChar* begin_path = end; |
| const UChar* begin_port = 0; |
| |
| skipWhile<UChar, IsNotColonOrSlash>(position, end); |
| |
| if (position == end) { |
| // host |
| // ^ |
| return ParseHost(begin_host, position, host, host_wildcard); |
| } |
| |
| if (position < end && *position == '/') { |
| // host/path || host/ || / |
| // ^ ^ ^ |
| return ParseHost(begin_host, position, host, host_wildcard) && |
| 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; |
| begin_host = position; |
| skipWhile<UChar, IsNotColonOrSlash>(position, end); |
| } |
| |
| if (position < end && *position == ':') { |
| // host:port || scheme://host:port |
| // ^ ^ |
| begin_port = position; |
| skipUntil<UChar>(position, end, '/'); |
| } |
| } |
| |
| if (position < end && *position == '/') { |
| // scheme://host/path || scheme://host:port/path |
| // ^ ^ |
| if (position == begin_host) |
| return false; |
| begin_path = position; |
| } |
| |
| if (!ParseHost(begin_host, begin_port ? begin_port : begin_path, host, |
| host_wildcard)) |
| return false; |
| |
| if (begin_port) { |
| if (!ParsePort(begin_port, begin_path, port, port_wildcard)) |
| return false; |
| } else { |
| port = 0; |
| } |
| |
| if (begin_path != end) { |
| if (!ParsePath(begin_path, 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 nonce_length = end - begin; |
| StringView prefix("'nonce-"); |
| |
| // TODO(esprehn): Should be StringView(begin, nonceLength).startsWith(prefix). |
| if (nonce_length <= prefix.length() || |
| !EqualIgnoringASCIICase(prefix, StringView(begin, prefix.length()))) |
| return true; |
| |
| const UChar* position = begin + prefix.length(); |
| const UChar* nonce_begin = position; |
| |
| DCHECK(position < end); |
| skipWhile<UChar, IsNonceCharacter>(position, end); |
| DCHECK(nonce_begin <= position); |
| |
| if (position + 1 != end || *position != '\'' || position == nonce_begin) |
| return false; |
| |
| nonce = String(nonce_begin, position - nonce_begin); |
| 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& hash_algorithm) { |
| // 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-", kContentSecurityPolicyHashAlgorithmSha1}, |
| {"'sha256-", kContentSecurityPolicyHashAlgorithmSha256}, |
| {"'sha384-", kContentSecurityPolicyHashAlgorithmSha384}, |
| {"'sha512-", kContentSecurityPolicyHashAlgorithmSha512}, |
| {"'sha-256-", kContentSecurityPolicyHashAlgorithmSha256}, |
| {"'sha-384-", kContentSecurityPolicyHashAlgorithmSha384}, |
| {"'sha-512-", kContentSecurityPolicyHashAlgorithmSha512}}; |
| |
| StringView prefix; |
| hash_algorithm = kContentSecurityPolicyHashAlgorithmNone; |
| size_t hash_length = end - begin; |
| |
| for (const auto& algorithm : kSupportedPrefixes) { |
| prefix = algorithm.prefix; |
| // TODO(esprehn): Should be StringView(begin, end - |
| // begin).startsWith(prefix). |
| if (hash_length > prefix.length() && |
| EqualIgnoringASCIICase(prefix, StringView(begin, prefix.length()))) { |
| hash_algorithm = algorithm.type; |
| break; |
| } |
| } |
| |
| if (hash_algorithm == kContentSecurityPolicyHashAlgorithmNone) |
| return true; |
| |
| const UChar* position = begin + prefix.length(); |
| const UChar* hash_begin = position; |
| |
| DCHECK(position < end); |
| skipWhile<UChar, IsBase64EncodedCharacter>(position, end); |
| DCHECK(hash_begin <= 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 == hash_begin) |
| return false; |
| |
| Vector<char> hash_vector; |
| // We accept base64url-encoded data here by normalizing it to base64. |
| Base64Decode(NormalizeToBase64(String(hash_begin, position - hash_begin)), |
| hash_vector); |
| if (hash_vector.size() > kMaxDigestSize) |
| return false; |
| hash.Append(reinterpret_cast<uint8_t*>(hash_vector.data()), |
| hash_vector.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 / "-" |
| // |
| // static |
| bool SourceListDirective::ParseHost( |
| const UChar* begin, |
| const UChar* end, |
| String& host, |
| CSPSource::WildcardDisposition& host_wildcard) { |
| DCHECK(begin <= end); |
| DCHECK(host.IsEmpty()); |
| DCHECK(host_wildcard == CSPSource::kNoWildcard); |
| |
| if (begin == end) |
| return false; |
| |
| const UChar* position = begin; |
| |
| // Parse "*" or [ "*." ]. |
| if (skipExactly<UChar>(position, end, '*')) { |
| host_wildcard = CSPSource::kHasWildcard; |
| |
| if (position == end) { |
| // "*" |
| return true; |
| } |
| |
| if (!skipExactly<UChar>(position, end, '.')) |
| return false; |
| } |
| const UChar* host_begin = position; |
| |
| // Parse 1*host-hcar. |
| if (!skipExactly<UChar, IsHostCharacter>(position, end)) |
| return false; |
| skipWhile<UChar, IsHostCharacter>(position, end); |
| |
| // Parse *( "." 1*host-char ). |
| while (position < end) { |
| if (!skipExactly<UChar>(position, end, '.')) |
| return false; |
| if (!skipExactly<UChar, IsHostCharacter>(position, end)) |
| return false; |
| skipWhile<UChar, IsHostCharacter>(position, end); |
| } |
| |
| host = String(host_begin, end - host_begin); |
| 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) { |
| policy_->ReportInvalidPathCharacter(directive_name_, |
| 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& port_wildcard) { |
| DCHECK(begin <= end); |
| DCHECK(!port); |
| DCHECK(port_wildcard == CSPSource::kNoWildcard); |
| |
| if (!skipExactly<UChar>(begin, end, ':')) |
| NOTREACHED(); |
| |
| if (begin == end) |
| return false; |
| |
| if (end - begin == 1 && *begin == '*') { |
| port = 0; |
| port_wildcard = CSPSource::kHasWildcard; |
| 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() { |
| allow_self_ = true; |
| } |
| |
| void SourceListDirective::AddSourceStar() { |
| allow_star_ = true; |
| } |
| |
| void SourceListDirective::AddSourceUnsafeInline() { |
| allow_inline_ = true; |
| } |
| |
| void SourceListDirective::AddSourceUnsafeEval() { |
| allow_eval_ = true; |
| } |
| |
| void SourceListDirective::AddSourceStrictDynamic() { |
| allow_dynamic_ = true; |
| } |
| |
| void SourceListDirective::AddSourceUnsafeHashedAttributes() { |
| allow_hashed_attributes_ = true; |
| } |
| |
| void SourceListDirective::AddReportSample() { |
| report_sample_ = true; |
| } |
| |
| void SourceListDirective::AddSourceNonce(const String& nonce) { |
| nonces_.insert(nonce); |
| } |
| |
| void SourceListDirective::AddSourceHash( |
| const ContentSecurityPolicyHashAlgorithm& algorithm, |
| const DigestValue& hash) { |
| hashes_.insert(CSPHashValue(algorithm, hash)); |
| hash_algorithms_used_ |= algorithm; |
| } |
| |
| void SourceListDirective::AddSourceToMap( |
| HeapHashMap<String, Member<CSPSource>>& hash_map, |
| CSPSource* source) { |
| hash_map.insert(source->GetScheme(), source); |
| if (source->GetScheme() == "http") |
| hash_map.insert("https", source); |
| else if (source->GetScheme() == "ws") |
| hash_map.insert("wss", source); |
| } |
| |
| bool SourceListDirective::HasSourceMatchInList( |
| const KURL& url, |
| ResourceRequest::RedirectStatus redirect_status) const { |
| for (size_t i = 0; i < list_.size(); ++i) { |
| if (list_[i]->Matches(url, redirect_status)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool SourceListDirective::AllowAllInline() const { |
| const ContentSecurityPolicy::DirectiveType& type = |
| ContentSecurityPolicy::GetDirectiveType(directive_name_); |
| if (type != ContentSecurityPolicy::DirectiveType::kDefaultSrc && |
| type != ContentSecurityPolicy::DirectiveType::kStyleSrc && |
| type != ContentSecurityPolicy::DirectiveType::kScriptSrc) { |
| return false; |
| } |
| return allow_inline_ && !IsHashOrNoncePresent() && |
| (type != ContentSecurityPolicy::DirectiveType::kScriptSrc || |
| !allow_dynamic_); |
| } |
| |
| HeapVector<Member<CSPSource>> SourceListDirective::GetSources( |
| Member<CSPSource> self) const { |
| HeapVector<Member<CSPSource>> sources = list_; |
| if (allow_star_) { |
| sources.push_back(new CSPSource(policy_, "ftp", String(), 0, String(), |
| CSPSource::kNoWildcard, |
| CSPSource::kNoWildcard)); |
| sources.push_back(new CSPSource(policy_, "ws", String(), 0, String(), |
| CSPSource::kNoWildcard, |
| CSPSource::kNoWildcard)); |
| sources.push_back(new CSPSource(policy_, "http", String(), 0, String(), |
| CSPSource::kNoWildcard, |
| CSPSource::kNoWildcard)); |
| if (self) { |
| sources.push_back(new CSPSource(policy_, self->GetScheme(), String(), 0, |
| String(), CSPSource::kNoWildcard, |
| CSPSource::kNoWildcard)); |
| } |
| } else if (allow_self_ && self) { |
| sources.push_back(self); |
| } |
| |
| return sources; |
| } |
| |
| bool SourceListDirective::Subsumes( |
| const HeapVector<Member<SourceListDirective>>& other) const { |
| if (!other.size() || other[0]->IsNone()) |
| return other.size(); |
| |
| bool allow_inline_other = other[0]->allow_inline_; |
| bool allow_eval_other = other[0]->allow_eval_; |
| bool allow_dynamic_other = other[0]->allow_dynamic_; |
| bool allow_hashed_attributes_other = other[0]->allow_hashed_attributes_; |
| bool is_hash_or_nonce_present_other = other[0]->IsHashOrNoncePresent(); |
| HashSet<String> nonces_b = other[0]->nonces_; |
| HashSet<CSPHashValue> hashes_b = other[0]->hashes_; |
| |
| HeapVector<Member<CSPSource>> normalized_b = |
| other[0]->GetSources(other[0]->policy_->GetSelfSource()); |
| for (size_t i = 1; i < other.size(); i++) { |
| allow_inline_other = allow_inline_other && other[i]->allow_inline_; |
| allow_eval_other = allow_eval_other && other[i]->allow_eval_; |
| allow_dynamic_other = allow_dynamic_other && other[i]->allow_dynamic_; |
| allow_hashed_attributes_other = |
| allow_hashed_attributes_other && other[i]->allow_hashed_attributes_; |
| is_hash_or_nonce_present_other = |
| is_hash_or_nonce_present_other && other[i]->IsHashOrNoncePresent(); |
| nonces_b = other[i]->GetIntersectNonces(nonces_b); |
| hashes_b = other[i]->GetIntersectHashes(hashes_b); |
| normalized_b = other[i]->GetIntersectCSPSources(normalized_b); |
| } |
| |
| if (!SubsumesNoncesAndHashes(nonces_b, hashes_b)) |
| return false; |
| |
| const ContentSecurityPolicy::DirectiveType type = |
| ContentSecurityPolicy::GetDirectiveType(directive_name_); |
| if (type == ContentSecurityPolicy::DirectiveType::kScriptSrc || |
| type == ContentSecurityPolicy::DirectiveType::kStyleSrc) { |
| if (!allow_eval_ && allow_eval_other) |
| return false; |
| if (!allow_hashed_attributes_ && allow_hashed_attributes_other) |
| return false; |
| bool allow_all_inline_other = |
| allow_inline_other && !is_hash_or_nonce_present_other && |
| (type != ContentSecurityPolicy::DirectiveType::kScriptSrc || |
| !allow_dynamic_other); |
| if (!AllowAllInline() && allow_all_inline_other) |
| return false; |
| } |
| |
| if (type == ContentSecurityPolicy::DirectiveType::kScriptSrc && |
| (allow_dynamic_ || allow_dynamic_other)) { |
| // If `this` does not allow `strict-dynamic`, then it must be that `other` |
| // does allow, so the result is `false`. |
| if (!allow_dynamic_) |
| return false; |
| // All keyword source expressions have been considered so only CSPSource |
| // subsumption is left. However, `strict-dynamic` ignores all CSPSources so |
| // for subsumption to be true either `other` must allow `strict-dynamic` or |
| // have no allowed CSPSources. |
| return allow_dynamic_other || !normalized_b.size(); |
| } |
| |
| // If embedding CSP specifies `self`, `self` refers to the embedee's origin. |
| HeapVector<Member<CSPSource>> normalized_a = |
| GetSources(other[0]->policy_->GetSelfSource()); |
| return CSPSource::FirstSubsumesSecond(normalized_a, normalized_b); |
| } |
| |
| WebContentSecurityPolicySourceList |
| SourceListDirective::ExposeForNavigationalChecks() const { |
| WebContentSecurityPolicySourceList source_list; |
| source_list.allow_self = allow_self_; |
| source_list.allow_star = allow_star_; |
| WebVector<WebContentSecurityPolicySourceExpression> list(list_.size()); |
| for (size_t i = 0; i < list_.size(); ++i) |
| list[i] = list_[i]->ExposeForNavigationalChecks(); |
| source_list.sources.Swap(list); |
| return source_list; |
| } |
| |
| bool SourceListDirective::SubsumesNoncesAndHashes( |
| const HashSet<String>& nonces, |
| const HashSet<CSPHashValue> hashes) const { |
| if (!nonces.IsEmpty() && nonces_.IsEmpty()) |
| return false; |
| |
| for (const auto& hash : hashes) { |
| if (!hashes_.Contains(hash)) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| HashSet<String> SourceListDirective::GetIntersectNonces( |
| const HashSet<String>& other) const { |
| if (!nonces_.size() || !other.size()) |
| return !nonces_.size() ? nonces_ : other; |
| |
| HashSet<String> normalized; |
| for (const auto& nonce : nonces_) { |
| if (other.Contains(nonce)) |
| normalized.insert(nonce); |
| } |
| |
| return normalized; |
| } |
| |
| HashSet<CSPHashValue> SourceListDirective::GetIntersectHashes( |
| const HashSet<CSPHashValue>& other) const { |
| if (!hashes_.size() || !other.size()) |
| return !hashes_.size() ? hashes_ : other; |
| |
| HashSet<CSPHashValue> normalized; |
| for (const auto& hash : hashes_) { |
| if (other.Contains(hash)) |
| normalized.insert(hash); |
| } |
| |
| return normalized; |
| } |
| |
| HeapHashMap<String, Member<CSPSource>> |
| SourceListDirective::GetIntersectSchemesOnly( |
| const HeapVector<Member<CSPSource>>& other) const { |
| HeapHashMap<String, Member<CSPSource>> schemes_a; |
| for (const auto& source_a : list_) { |
| if (source_a->IsSchemeOnly()) |
| AddSourceToMap(schemes_a, source_a); |
| } |
| // Add schemes only sources if they are present in both `this` and `other`, |
| // allowing upgrading `http` to `https` and `ws` to `wss`. |
| HeapHashMap<String, Member<CSPSource>> intersect; |
| for (const auto& source_b : other) { |
| if (source_b->IsSchemeOnly()) { |
| if (schemes_a.Contains(source_b->GetScheme())) |
| AddSourceToMap(intersect, source_b); |
| else if (source_b->GetScheme() == "http" && schemes_a.Contains("https")) |
| intersect.insert("https", schemes_a.at("https")); |
| else if (source_b->GetScheme() == "ws" && schemes_a.Contains("wss")) |
| intersect.insert("wss", schemes_a.at("wss")); |
| } |
| } |
| |
| return intersect; |
| } |
| |
| HeapVector<Member<CSPSource>> SourceListDirective::GetIntersectCSPSources( |
| const HeapVector<Member<CSPSource>>& other) const { |
| auto schemes_map = GetIntersectSchemesOnly(other); |
| HeapVector<Member<CSPSource>> normalized; |
| // Add all normalized scheme source expressions. |
| for (const auto& it : schemes_map) { |
| // We do not add secure versions if insecure schemes are present. |
| if ((it.key != "https" || !schemes_map.Contains("http")) && |
| (it.key != "wss" || !schemes_map.Contains("ws"))) { |
| normalized.push_back(it.value); |
| } |
| } |
| |
| HeapVector<Member<CSPSource>> this_vector = |
| GetSources(policy_->GetSelfSource()); |
| for (const auto& source_a : this_vector) { |
| if (schemes_map.Contains(source_a->GetScheme())) |
| continue; |
| |
| CSPSource* match(nullptr); |
| for (const auto& source_b : other) { |
| // No need to add a host source expression if it is subsumed by the |
| // matching scheme source expression. |
| if (schemes_map.Contains(source_b->GetScheme())) |
| continue; |
| // If sourceA is scheme only but there was no intersection for it in the |
| // `other` list, we add all the sourceB with that scheme. |
| if (source_a->IsSchemeOnly()) { |
| if (CSPSource* local_match = source_b->Intersect(source_a)) |
| normalized.push_back(local_match); |
| continue; |
| } |
| if (source_b->Subsumes(source_a)) { |
| match = source_a; |
| break; |
| } |
| if (CSPSource* local_match = source_b->Intersect(source_a)) |
| match = local_match; |
| } |
| if (match) |
| normalized.push_back(match); |
| } |
| return normalized; |
| } |
| |
| DEFINE_TRACE(SourceListDirective) { |
| visitor->Trace(policy_); |
| visitor->Trace(list_); |
| CSPDirective::Trace(visitor); |
| } |
| |
| } // namespace blink |