1 // Copyright 2019 The Chromium Authors
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "net/cookies/site_for_cookies.h"
6
7 #include <utility>
8
9 #include "base/strings/strcat.h"
10 #include "base/strings/string_util.h"
11 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
12 #include "net/cookies/cookie_util.h"
13
14 namespace net {
15
16 SiteForCookies::SiteForCookies() = default;
17
SiteForCookies(const SchemefulSite & site)18 SiteForCookies::SiteForCookies(const SchemefulSite& site)
19 : site_(site), schemefully_same_(!site.opaque()) {
20 site_.ConvertWebSocketToHttp();
21 }
22
23 SiteForCookies::SiteForCookies(const SiteForCookies& other) = default;
24 SiteForCookies::SiteForCookies(SiteForCookies&& other) = default;
25
26 SiteForCookies::~SiteForCookies() = default;
27
28 SiteForCookies& SiteForCookies::operator=(const SiteForCookies& other) =
29 default;
30 SiteForCookies& SiteForCookies::operator=(SiteForCookies&& site_for_cookies) =
31 default;
32
33 // static
FromWire(const SchemefulSite & site,bool schemefully_same,SiteForCookies * out)34 bool SiteForCookies::FromWire(const SchemefulSite& site,
35 bool schemefully_same,
36 SiteForCookies* out) {
37 SiteForCookies candidate(site);
38 if (site != candidate.site_)
39 return false;
40
41 candidate.schemefully_same_ = schemefully_same;
42
43 *out = std::move(candidate);
44 return true;
45 }
46
47 // static
FromOrigin(const url::Origin & origin)48 SiteForCookies SiteForCookies::FromOrigin(const url::Origin& origin) {
49 return SiteForCookies(SchemefulSite(origin));
50 }
51
52 // static
FromUrl(const GURL & url)53 SiteForCookies SiteForCookies::FromUrl(const GURL& url) {
54 return SiteForCookies::FromOrigin(url::Origin::Create(url));
55 }
56
ToDebugString() const57 std::string SiteForCookies::ToDebugString() const {
58 std::string same_scheme_string = schemefully_same_ ? "true" : "false";
59 return base::StrCat({"SiteForCookies: {site=", site_.Serialize(),
60 "; schemefully_same=", same_scheme_string, "}"});
61 }
62
IsFirstParty(const GURL & url) const63 bool SiteForCookies::IsFirstParty(const GURL& url) const {
64 return IsFirstPartyWithSchemefulMode(
65 url, cookie_util::IsSchemefulSameSiteEnabled());
66 }
67
IsFirstPartyWithSchemefulMode(const GURL & url,bool compute_schemefully) const68 bool SiteForCookies::IsFirstPartyWithSchemefulMode(
69 const GURL& url,
70 bool compute_schemefully) const {
71 if (compute_schemefully)
72 return IsSchemefullyFirstParty(url);
73
74 return IsSchemelesslyFirstParty(url);
75 }
76
IsEquivalent(const SiteForCookies & other) const77 bool SiteForCookies::IsEquivalent(const SiteForCookies& other) const {
78 if (IsNull() || other.IsNull()) {
79 // We need to check if `other.IsNull()` explicitly in order to catch if
80 // `other.schemefully_same_` is false when "Schemeful Same-Site" is enabled.
81 return IsNull() && other.IsNull();
82 }
83
84 // In the case where the site has no registrable domain or host, the scheme
85 // cannot be ws(s) or http(s), so equality of sites implies actual equality of
86 // schemes (not just modulo ws-http and wss-https compatibility).
87 if (cookie_util::IsSchemefulSameSiteEnabled() ||
88 !site_.has_registrable_domain_or_host()) {
89 return site_ == other.site_;
90 }
91
92 return site_.SchemelesslyEqual(other.site_);
93 }
94
CompareWithFrameTreeSiteAndRevise(const SchemefulSite & other)95 bool SiteForCookies::CompareWithFrameTreeSiteAndRevise(
96 const SchemefulSite& other) {
97 // Two opaque SFC are considered equivalent.
98 if (site_.opaque() && other.opaque())
99 return true;
100
101 // But if only one is opaque we should return false.
102 if (site_.opaque())
103 return false;
104
105 // Nullify `this` if the `other` is opaque
106 if (other.opaque()) {
107 site_ = SchemefulSite();
108 return false;
109 }
110
111 bool nullify = site_.has_registrable_domain_or_host()
112 ? !site_.SchemelesslyEqual(other)
113 : site_ != other;
114
115 if (nullify) {
116 // We should only nullify this SFC if the registrable domains (or the entire
117 // site for cases without an RD) don't match. We *should not* nullify if
118 // only the schemes mismatch (unless there is no RD) because cookies may be
119 // processed with LEGACY semantics which only use the RDs. Eventually, when
120 // schemeful same-site can no longer be disabled, we can revisit this.
121 site_ = SchemefulSite();
122 return false;
123 }
124
125 MarkIfCrossScheme(other);
126
127 return true;
128 }
129
CompareWithFrameTreeOriginAndRevise(const url::Origin & other)130 bool SiteForCookies::CompareWithFrameTreeOriginAndRevise(
131 const url::Origin& other) {
132 return CompareWithFrameTreeSiteAndRevise(SchemefulSite(other));
133 }
134
RepresentativeUrl() const135 GURL SiteForCookies::RepresentativeUrl() const {
136 if (IsNull())
137 return GURL();
138 // Cannot use url::Origin::GetURL() because it loses the hostname for file:
139 // scheme origins.
140 GURL result(base::StrCat({scheme(), "://", registrable_domain(), "/"}));
141 DCHECK(result.is_valid());
142 return result;
143 }
144
IsNull() const145 bool SiteForCookies::IsNull() const {
146 if (cookie_util::IsSchemefulSameSiteEnabled())
147 return site_.opaque() || !schemefully_same_;
148
149 return site_.opaque();
150 }
151
IsSchemefullyFirstParty(const GURL & url) const152 bool SiteForCookies::IsSchemefullyFirstParty(const GURL& url) const {
153 // Can't use IsNull() as we want the same behavior regardless of
154 // SchemefulSameSite feature status.
155 if (site_.opaque() || !schemefully_same_ || !url.is_valid())
156 return false;
157
158 SchemefulSite other_site(url);
159 other_site.ConvertWebSocketToHttp();
160 return site_ == other_site;
161 }
162
IsSchemelesslyFirstParty(const GURL & url) const163 bool SiteForCookies::IsSchemelesslyFirstParty(const GURL& url) const {
164 // Can't use IsNull() as we want the same behavior regardless of
165 // SchemefulSameSite feature status.
166 if (site_.opaque() || !url.is_valid())
167 return false;
168
169 // We don't need to bother changing WebSocket schemes to http, because if
170 // there is no registrable domain or host, the scheme cannot be ws(s) or
171 // http(s), and the latter comparison is schemeless anyway.
172 SchemefulSite other_site(url);
173 if (!site_.has_registrable_domain_or_host())
174 return site_ == other_site;
175
176 return site_.SchemelesslyEqual(other_site);
177 }
178
MarkIfCrossScheme(const SchemefulSite & other)179 void SiteForCookies::MarkIfCrossScheme(const SchemefulSite& other) {
180 // If `this` is IsNull() then `this` doesn't match anything which means that
181 // the scheme check is pointless. Also exit early if schemefully_same_ is
182 // already false.
183 if (IsNull() || !schemefully_same_)
184 return;
185
186 // Mark if `other` is opaque. Opaque origins shouldn't match.
187 if (other.opaque()) {
188 schemefully_same_ = false;
189 return;
190 }
191
192 // Conversion to http/https should have occurred during construction.
193 DCHECK_NE(url::kWsScheme, scheme());
194 DCHECK_NE(url::kWssScheme, scheme());
195
196 // If the schemes are equal, modulo ws-http and wss-https, don't mark.
197 if (scheme() == other.site_as_origin_.scheme() ||
198 (scheme() == url::kHttpsScheme &&
199 other.site_as_origin_.scheme() == url::kWssScheme) ||
200 (scheme() == url::kHttpScheme &&
201 other.site_as_origin_.scheme() == url::kWsScheme)) {
202 return;
203 }
204
205 // Mark that the two are cross-scheme to each other.
206 schemefully_same_ = false;
207 }
208
operator <(const SiteForCookies & lhs,const SiteForCookies & rhs)209 bool operator<(const SiteForCookies& lhs, const SiteForCookies& rhs) {
210 // Similar to IsEquivalent(), if they're both null then they're equivalent
211 // and therefore `lhs` is not < `rhs`.
212 if (lhs.IsNull() && rhs.IsNull())
213 return false;
214
215 // If only `lhs` is null then it's always < `rhs`.
216 if (lhs.IsNull())
217 return true;
218
219 // If only `rhs` is null then `lhs` is not < `rhs`.
220 if (rhs.IsNull())
221 return false;
222
223 // Otherwise neither are null and we need to compare the `site_`s.
224 return lhs.site_ < rhs.site_;
225 }
226
227 } // namespace net
228