OLD | NEW |
1 // Copyright 2014 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "core/frame/csp/CSPSource.h" | 5 #include "core/frame/csp/CSPSource.h" |
6 | 6 |
7 #include "core/frame/UseCounter.h" | 7 #include "core/frame/UseCounter.h" |
8 #include "core/frame/csp/ContentSecurityPolicy.h" | 8 #include "core/frame/csp/ContentSecurityPolicy.h" |
9 #include "platform/weborigin/KURL.h" | 9 #include "platform/weborigin/KURL.h" |
10 #include "platform/weborigin/KnownPorts.h" | 10 #include "platform/weborigin/KnownPorts.h" |
(...skipping 24 matching lines...) Expand all Loading... |
35 if (isSchemeOnly()) | 35 if (isSchemeOnly()) |
36 return true; | 36 return true; |
37 bool pathsMatch = | 37 bool pathsMatch = |
38 (redirectStatus == RedirectStatus::FollowedRedirect) || pathMatches(url); | 38 (redirectStatus == RedirectStatus::FollowedRedirect) || pathMatches(url); |
39 return hostMatches(url) && portMatches(url) && pathsMatch; | 39 return hostMatches(url) && portMatches(url) && pathsMatch; |
40 } | 40 } |
41 | 41 |
42 bool CSPSource::schemeMatches(const KURL& url) const { | 42 bool CSPSource::schemeMatches(const KURL& url) const { |
43 if (m_scheme.isEmpty()) | 43 if (m_scheme.isEmpty()) |
44 return m_policy->protocolMatchesSelf(url); | 44 return m_policy->protocolMatchesSelf(url); |
45 if (equalIgnoringCase(m_scheme, "http")) | 45 return schemeMatches(url.protocol()); |
46 return equalIgnoringCase(url.protocol(), "http") || | 46 } |
47 equalIgnoringCase(url.protocol(), "https"); | 47 |
48 if (equalIgnoringCase(m_scheme, "ws")) | 48 bool CSPSource::schemeMatches(const String& protocol) const { |
49 return equalIgnoringCase(url.protocol(), "ws") || | 49 // TODO(amalika): DCHECK schemes are lower case & remove calls to |
50 equalIgnoringCase(url.protocol(), "wss"); | 50 // equalIgnoringCase |
51 return equalIgnoringCase(url.protocol(), m_scheme); | 51 if (equalIgnoringCase(m_scheme, "http")) { |
| 52 return equalIgnoringCase(protocol, "http") || |
| 53 equalIgnoringCase(protocol, "https"); |
| 54 } |
| 55 if (equalIgnoringCase(m_scheme, "ws")) { |
| 56 return equalIgnoringCase(protocol, "ws") || |
| 57 equalIgnoringCase(protocol, "wss"); |
| 58 } |
| 59 return equalIgnoringCase(protocol, m_scheme); |
52 } | 60 } |
53 | 61 |
54 bool CSPSource::hostMatches(const KURL& url) const { | 62 bool CSPSource::hostMatches(const KURL& url) const { |
55 const String& host = url.host(); | 63 return hostMatches(url.host()); |
| 64 } |
| 65 |
| 66 bool CSPSource::hostMatches(const String& host) const { |
56 Document* document = m_policy->document(); | 67 Document* document = m_policy->document(); |
57 bool match; | 68 bool match; |
58 | 69 |
59 bool equalHosts = equalIgnoringCase(host, m_host); | 70 bool equalHosts = equalIgnoringCase(host, m_host); |
60 if (m_hostWildcard == HasWildcard) { | 71 if (m_hostWildcard == HasWildcard) { |
61 match = host.endsWith(String("." + m_host), TextCaseInsensitive); | 72 match = host.endsWith(String("." + m_host), TextCaseInsensitive); |
62 | 73 |
63 // Chrome used to, incorrectly, match *.x.y to x.y. This was fixed, but | 74 // Chrome used to, incorrectly, match *.x.y to x.y. This was fixed, but |
64 // the following count measures when a match fails that would have | 75 // the following count measures when a match fails that would have |
65 // passed the old, incorrect style, in case a lot of sites were | 76 // passed the old, incorrect style, in case a lot of sites were |
66 // relying on that behavior. | 77 // relying on that behavior. |
67 if (document && equalHosts) | 78 if (document && equalHosts) |
68 UseCounter::count(*document, | 79 UseCounter::count(*document, |
69 UseCounter::CSPSourceWildcardWouldMatchExactHost); | 80 UseCounter::CSPSourceWildcardWouldMatchExactHost); |
70 } else { | 81 } else { |
71 match = equalHosts; | 82 match = equalHosts; |
72 } | 83 } |
73 | 84 |
74 return match; | 85 return match; |
75 } | 86 } |
76 | 87 |
77 bool CSPSource::pathMatches(const KURL& url) const { | 88 bool CSPSource::pathMatches(const KURL& url) const { |
| 89 return pathMatches(url.path()); |
| 90 } |
| 91 |
| 92 bool CSPSource::pathMatches(const String& urlPath) const { |
78 if (m_path.isEmpty()) | 93 if (m_path.isEmpty()) |
79 return true; | 94 return true; |
80 | 95 |
81 String path = decodeURLEscapeSequences(url.path()); | 96 String path = decodeURLEscapeSequences(urlPath); |
82 | 97 |
83 if (m_path.endsWith("/")) | 98 if (m_path.endsWith("/")) |
84 return path.startsWith(m_path); | 99 return path.startsWith(m_path); |
85 | 100 |
86 return path == m_path; | 101 return path == m_path; |
87 } | 102 } |
88 | 103 |
89 bool CSPSource::portMatches(const KURL& url) const { | 104 bool CSPSource::portMatches(const KURL& url) const { |
| 105 return portMatches(url.port(), url.protocol()); |
| 106 } |
| 107 |
| 108 bool CSPSource::portMatches(int port, const String& protocol) const { |
90 if (m_portWildcard == HasWildcard) | 109 if (m_portWildcard == HasWildcard) |
91 return true; | 110 return true; |
92 | 111 |
93 int port = url.port(); | |
94 | |
95 if (port == m_port) | 112 if (port == m_port) |
96 return true; | 113 return true; |
97 | 114 |
98 if (m_port == 80 && | 115 if (m_port == 80 && |
99 (port == 443 || | 116 (port == 443 || (port == 0 && defaultPortForProtocol(protocol) == 443))) |
100 (port == 0 && defaultPortForProtocol(url.protocol()) == 443))) | |
101 return true; | 117 return true; |
102 | 118 |
103 if (!port) | 119 if (!port) |
104 return isDefaultPortForProtocol(m_port, url.protocol()); | 120 return isDefaultPortForProtocol(m_port, protocol); |
105 | 121 |
106 if (!m_port) | 122 if (!m_port) |
107 return isDefaultPortForProtocol(port, url.protocol()); | 123 return isDefaultPortForProtocol(port, protocol); |
108 | 124 |
109 return false; | 125 return false; |
110 } | 126 } |
111 | 127 |
| 128 CSPSource* CSPSource::getPreferredCSPSource(CSPSource* other) { |
| 129 // This function assumes that schemes, hosts, ports and paths match between |
| 130 // the two gives sources. CSPSource that has stronger protocol(https/wss) or |
| 131 // more information (path or port specified) will be returned. |
| 132 // If nullptr is returned, that means there is no preference between 'this' |
| 133 // and 'other' CSPSources. If new CSPSource is returned then that means we |
| 134 // found a match between 'this' and 'other' but neither one is strongly |
| 135 // stricter than the other so the combination of two has to be created. |
| 136 bool preferABasedOnScheme = false, preferBBasedOnScheme = false; |
| 137 // If the schemes match but their lengths are not equal, that means one of the |
| 138 // schemes is 'https' or 'wss' |
| 139 if (m_scheme.length() != other->m_scheme.length()) { |
| 140 String aScheme = m_scheme.lower(); |
| 141 preferABasedOnScheme = |
| 142 aScheme.length() > 3 ? (aScheme == "https") : (aScheme == "wss"); |
| 143 preferBBasedOnScheme = !preferABasedOnScheme; |
| 144 } |
| 145 |
| 146 bool preferABasedOnPort = (m_port && !other->m_port), |
| 147 preferBBasedOnPort = (!m_port && other->m_port); |
| 148 bool preferABasedOnPath = (!(m_path.isEmpty() || m_path == "/") && |
| 149 (other->m_path.isEmpty() || other->m_path == "/")), |
| 150 preferBBasedOnPath = |
| 151 ((m_path.isEmpty() || m_path == "/") && |
| 152 !(other->m_path.isEmpty() || other->m_path == "/")); |
| 153 |
| 154 bool preferA = |
| 155 preferABasedOnPort || preferABasedOnPath || preferABasedOnScheme; |
| 156 bool preferB = |
| 157 preferBBasedOnPort || preferBBasedOnPath || preferBBasedOnScheme; |
| 158 |
| 159 if (preferA & preferB) { |
| 160 // If we have conflicting preferences, we have to create a new sources. |
| 161 String scheme = preferBBasedOnScheme ? other->m_path : m_path; |
| 162 String host = (m_hostWildcard == HasWildcard) ? other->m_host : m_host; |
| 163 String path = preferBBasedOnPath ? other->m_path : m_path; |
| 164 int port = preferBBasedOnPort ? other->m_port : m_port; |
| 165 WildcardDisposition hostWildcard = (m_hostWildcard == HasWildcard) |
| 166 ? other->m_hostWildcard |
| 167 : m_hostWildcard; |
| 168 WildcardDisposition portWildcard = (m_portWildcard == HasWildcard) |
| 169 ? other->m_portWildcard |
| 170 : m_portWildcard; |
| 171 return new CSPSource(m_policy, scheme, host, port, path, hostWildcard, |
| 172 portWildcard); |
| 173 } |
| 174 |
| 175 if (preferA || preferB) |
| 176 return preferA ? this : other; |
| 177 |
| 178 // nullptr signifies that there is not preference between 'this' or 'other'. |
| 179 return nullptr; |
| 180 } |
| 181 |
| 182 CSPSource* CSPSource::getPreferredCSPSourceBasedOnWildcards( |
| 183 CSPSource* other, |
| 184 CSPSource* preferredCSPSource) { |
| 185 if (preferredCSPSource != other) |
| 186 return this; |
| 187 // We mostly want preferredCSPSource except for wildcards. |
| 188 return new CSPSource(m_policy, preferredCSPSource->m_scheme, |
| 189 preferredCSPSource->m_host, preferredCSPSource->m_port, |
| 190 preferredCSPSource->m_path, m_hostWildcard, |
| 191 m_portWildcard); |
| 192 } |
| 193 |
| 194 CSPSource* CSPSource::getPreferredCSPSourceBasedOnEmptySchemes( |
| 195 CSPSource* other, |
| 196 CSPSource* preferredCSPSource) { |
| 197 if (preferredCSPSource == other && !isSchemeOnly()) { |
| 198 return other->getPreferredCSPSourceBasedOnEmptySchemes(this, |
| 199 preferredCSPSource); |
| 200 } |
| 201 if (preferredCSPSource == this && !other->isSchemeOnly()) { |
| 202 // Case when scheme of 'this' is simply 'https://' and 'other' is |
| 203 // 'http://example.com' |
| 204 // So that we need to upgrade scheme of 'other'. |
| 205 return new CSPSource(other->m_policy, m_scheme, other->m_host, |
| 206 other->m_port, other->m_path, other->m_hostWildcard, |
| 207 other->m_portWildcard); |
| 208 } |
| 209 if (preferredCSPSource) |
| 210 return preferredCSPSource; |
| 211 |
| 212 return isSchemeOnly() && !other->isSchemeOnly() ? other : this; |
| 213 } |
| 214 |
| 215 CSPSource* CSPSource::getNormalized(CSPSource* other) { |
| 216 // Preference always goes to returning 'this' if it is at least as strict as |
| 217 // 'other'. |
| 218 // If 'other' is returned, that means 'other' is definitely stricter than |
| 219 // 'this'. |
| 220 // If nullptr is returned, that means there is no similarity between 'this' |
| 221 // and 'other' (unmatching hosts). |
| 222 // If new CSPSource is returned then that means we found a match between |
| 223 // 'this' and 'other' but neither one is strongly stricter than the other so |
| 224 // the combination of two has to be created. |
| 225 bool schemesMatch = schemeMatches(other->m_scheme) || |
| 226 (equalIgnoringCase(m_scheme, "https") && |
| 227 equalIgnoringCase(other->m_scheme, "http")) || |
| 228 (equalIgnoringCase(m_scheme, "wss") && |
| 229 equalIgnoringCase(other->m_scheme, "ws")); |
| 230 bool schemesOnly = isSchemeOnly() || other->isSchemeOnly(); |
| 231 bool hostsMatch = |
| 232 schemesOnly || equalIgnoringCase(m_host, other->m_host) || |
| 233 (m_hostWildcard == HasWildcard && hostMatches(other->m_host)) || |
| 234 (other->m_hostWildcard == HasWildcard && other->hostMatches(m_host)); |
| 235 bool portsMatch = schemesOnly || m_portWildcard == HasWildcard || |
| 236 other->m_portWildcard == HasWildcard || |
| 237 portMatches(other->m_port, other->m_scheme); |
| 238 if (!schemesMatch || !hostsMatch || !portsMatch || |
| 239 !(other->m_path.isEmpty() || other->m_path == "/" || |
| 240 pathMatches(other->m_path))) { |
| 241 return nullptr; |
| 242 } |
| 243 |
| 244 CSPSource* preferredCSPSource = getPreferredCSPSource(other); |
| 245 if (schemesOnly) |
| 246 return getPreferredCSPSourceBasedOnEmptySchemes(other, preferredCSPSource); |
| 247 |
| 248 // If preferredCSPSource is already a new CSPSource, we might as well |
| 249 // immediately return it since we consider wildcards when we create a new |
| 250 // CSPSource. |
| 251 if (preferredCSPSource && preferredCSPSource != this && |
| 252 preferredCSPSource != other) { |
| 253 return preferredCSPSource; |
| 254 } |
| 255 |
| 256 int totalNumberOfWildcards = (m_hostWildcard == HasWildcard) + |
| 257 (m_portWildcard == HasWildcard) + |
| 258 (other->m_hostWildcard == HasWildcard) + |
| 259 (other->m_portWildcard == HasWildcard); |
| 260 |
| 261 if (totalNumberOfWildcards == 0 || totalNumberOfWildcards == 4) |
| 262 return preferredCSPSource ? preferredCSPSource : this; |
| 263 |
| 264 if (totalNumberOfWildcards == 1) { |
| 265 if (m_hostWildcard == HasWildcard || m_portWildcard == HasWildcard) { |
| 266 return other->getPreferredCSPSourceBasedOnWildcards(this, |
| 267 preferredCSPSource); |
| 268 } |
| 269 return getPreferredCSPSourceBasedOnWildcards(other, preferredCSPSource); |
| 270 } |
| 271 |
| 272 if (totalNumberOfWildcards == 2) { |
| 273 // There is one wildcard in a and in b. |
| 274 if (m_hostWildcard == other->m_hostWildcard && |
| 275 m_portWildcard == other->m_portWildcard) { |
| 276 return preferredCSPSource ? preferredCSPSource : this; |
| 277 } |
| 278 if (m_portWildcard == other->m_hostWildcard && |
| 279 m_hostWildcard == other->m_portWildcard) { |
| 280 // Since neither one is not stricter than the other, we have to create |
| 281 // a new CSPSource with no wildcards. |
| 282 if (!preferredCSPSource) |
| 283 preferredCSPSource = this; |
| 284 return new CSPSource(m_policy, preferredCSPSource->m_scheme, |
| 285 preferredCSPSource->m_host, |
| 286 preferredCSPSource->m_port, |
| 287 preferredCSPSource->m_path, NoWildcard, NoWildcard); |
| 288 } |
| 289 |
| 290 // Both wildcards are either in 'this' or both in 'other. |
| 291 return (m_portWildcard == HasWildcard) |
| 292 ? other->getPreferredCSPSourceBasedOnWildcards( |
| 293 this, preferredCSPSource) |
| 294 : getPreferredCSPSourceBasedOnWildcards(other, |
| 295 preferredCSPSource); |
| 296 } |
| 297 |
| 298 // There are total of 3 wildcards. |
| 299 if (m_hostWildcard == HasWildcard && m_portWildcard == HasWildcard) { |
| 300 return other->getPreferredCSPSourceBasedOnWildcards(this, |
| 301 preferredCSPSource); |
| 302 } |
| 303 |
| 304 return getPreferredCSPSourceBasedOnWildcards(other, preferredCSPSource); |
| 305 } |
| 306 |
112 bool CSPSource::isSchemeOnly() const { | 307 bool CSPSource::isSchemeOnly() const { |
113 return m_host.isEmpty(); | 308 return m_host.isEmpty(); |
114 } | 309 } |
115 | 310 |
116 DEFINE_TRACE(CSPSource) { | 311 DEFINE_TRACE(CSPSource) { |
117 visitor->trace(m_policy); | 312 visitor->trace(m_policy); |
118 } | 313 } |
119 | 314 |
120 } // namespace blink | 315 } // namespace blink |
OLD | NEW |