| // 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/CSPSource.h" |
| |
| #include "core/frame/UseCounter.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 blink { |
| |
| CSPSource::CSPSource(ContentSecurityPolicy* policy, |
| const String& scheme, |
| const String& host, |
| int port, |
| const String& path, |
| WildcardDisposition hostWildcard, |
| WildcardDisposition portWildcard) |
| : m_policy(policy), |
| m_scheme(scheme.lower()), |
| m_host(host), |
| m_port(port), |
| m_path(path), |
| m_hostWildcard(hostWildcard), |
| m_portWildcard(portWildcard) {} |
| |
| bool CSPSource::matches(const KURL& url, |
| ResourceRequest::RedirectStatus redirectStatus) const { |
| bool schemesMatch = m_scheme.isEmpty() ? m_policy->protocolMatchesSelf(url) |
| : schemeMatches(url.protocol()); |
| if (!schemesMatch) |
| return false; |
| if (isSchemeOnly()) |
| return true; |
| bool pathsMatch = (redirectStatus == RedirectStatus::FollowedRedirect) || |
| pathMatches(url.path()); |
| return hostMatches(url.host()) && portMatches(url.port(), url.protocol()) && |
| pathsMatch; |
| } |
| |
| bool CSPSource::schemeMatches(const String& protocol) const { |
| DCHECK_EQ(protocol, protocol.lower()); |
| if (m_scheme == "http") |
| return protocol == "http" || protocol == "https"; |
| if (m_scheme == "ws") |
| return protocol == "ws" || protocol == "wss"; |
| return protocol == m_scheme; |
| } |
| |
| bool CSPSource::hostMatches(const String& host) const { |
| Document* document = m_policy->document(); |
| bool match; |
| |
| bool equalHosts = m_host == host; |
| if (m_hostWildcard == HasWildcard) { |
| match = host.endsWith(String("." + m_host), TextCaseUnicodeInsensitive); |
| |
| // Chrome used to, incorrectly, match *.x.y to x.y. This was fixed, but |
| // the following count measures when a match fails that would have |
| // passed the old, incorrect style, in case a lot of sites were |
| // relying on that behavior. |
| if (document && equalHosts) |
| UseCounter::count(*document, |
| UseCounter::CSPSourceWildcardWouldMatchExactHost); |
| } else { |
| match = equalHosts; |
| } |
| |
| return match; |
| } |
| |
| bool CSPSource::pathMatches(const String& urlPath) const { |
| if (m_path.isEmpty()) |
| return true; |
| |
| String path = decodeURLEscapeSequences(urlPath); |
| |
| if (m_path.endsWith("/")) |
| return path.startsWith(m_path); |
| |
| return path == m_path; |
| } |
| |
| bool CSPSource::portMatches(int port, const String& protocol) const { |
| if (m_portWildcard == HasWildcard) |
| return true; |
| |
| if (port == m_port) |
| return true; |
| |
| if (m_port == 80 && |
| (port == 443 || (port == 0 && defaultPortForProtocol(protocol) == 443))) |
| return true; |
| |
| if (!port) |
| return isDefaultPortForProtocol(m_port, protocol); |
| |
| if (!m_port) |
| return isDefaultPortForProtocol(port, protocol); |
| |
| return false; |
| } |
| |
| bool CSPSource::subsumes(CSPSource* other) const { |
| if (!schemeMatches(other->m_scheme)) |
| return false; |
| |
| if (other->isSchemeOnly() || isSchemeOnly()) |
| return isSchemeOnly(); |
| |
| if ((m_hostWildcard == NoWildcard && other->m_hostWildcard == HasWildcard) || |
| (m_portWildcard == NoWildcard && other->m_portWildcard == HasWildcard)) { |
| return false; |
| } |
| |
| bool hostSubsumes = (m_host == other->m_host || hostMatches(other->m_host)); |
| bool portSubsumes = (m_portWildcard == HasWildcard) || |
| portMatches(other->m_port, other->m_scheme); |
| bool pathSubsumes = pathMatches(other->m_path); |
| return hostSubsumes && portSubsumes && pathSubsumes; |
| } |
| |
| bool CSPSource::isSimilar(CSPSource* other) const { |
| bool schemesMatch = |
| schemeMatches(other->m_scheme) || other->schemeMatches(m_scheme); |
| if (!schemesMatch || isSchemeOnly() || other->isSchemeOnly()) |
| return schemesMatch; |
| bool hostsMatch = (m_host == other->m_host) || hostMatches(other->m_host) || |
| other->hostMatches(m_host); |
| bool portsMatch = (other->m_portWildcard == HasWildcard) || |
| portMatches(other->m_port, other->m_scheme) || |
| other->portMatches(m_port, m_scheme); |
| bool pathsMatch = pathMatches(other->m_path) || other->pathMatches(m_path); |
| if (hostsMatch && portsMatch && pathsMatch) |
| return true; |
| |
| return false; |
| } |
| |
| CSPSource* CSPSource::intersect(CSPSource* other) const { |
| if (!isSimilar(other)) |
| return nullptr; |
| |
| String scheme = other->schemeMatches(m_scheme) ? m_scheme : other->m_scheme; |
| if (isSchemeOnly() || other->isSchemeOnly()) { |
| const CSPSource* stricter = isSchemeOnly() ? other : this; |
| return new CSPSource(m_policy, scheme, stricter->m_host, stricter->m_port, |
| stricter->m_path, stricter->m_hostWildcard, |
| stricter->m_portWildcard); |
| } |
| |
| String host = m_hostWildcard == NoWildcard ? m_host : other->m_host; |
| String path = other->pathMatches(m_path) ? m_path : other->m_path; |
| // Choose this port if the other port is empty, has wildcard or is a port for |
| // a less secure scheme such as "http" whereas scheme of this is "https", in |
| // which case the lengths would differ. |
| int port = (other->m_portWildcard == HasWildcard || !other->m_port || |
| m_scheme.length() > other->m_scheme.length()) |
| ? m_port |
| : other->m_port; |
| WildcardDisposition hostWildcard = |
| (m_hostWildcard == HasWildcard) ? other->m_hostWildcard : m_hostWildcard; |
| WildcardDisposition portWildcard = |
| (m_portWildcard == HasWildcard) ? other->m_portWildcard : m_portWildcard; |
| return new CSPSource(m_policy, scheme, host, port, path, hostWildcard, |
| portWildcard); |
| } |
| |
| bool CSPSource::isSchemeOnly() const { |
| return m_host.isEmpty(); |
| } |
| |
| bool CSPSource::firstSubsumesSecond( |
| const HeapVector<Member<CSPSource>>& listA, |
| const HeapVector<Member<CSPSource>>& listB) { |
| // Empty vector of CSPSources has an effect of 'none'. |
| if (!listA.size() || !listB.size()) |
| return !listB.size(); |
| |
| // Walk through all the items in |listB|, ensuring that each is subsumed by at |
| // least one item in |listA|. If any item in |listB| is not subsumed, return |
| // false. |
| for (const auto& sourceB : listB) { |
| bool foundMatch = false; |
| for (const auto& sourceA : listA) { |
| if ((foundMatch = sourceA->subsumes(sourceB))) |
| break; |
| } |
| if (!foundMatch) |
| return false; |
| } |
| return true; |
| } |
| |
| DEFINE_TRACE(CSPSource) { |
| visitor->trace(m_policy); |
| } |
| |
| } // namespace blink |