1 // Copyright 2012 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/cookie_util.h"
6
7 #include <cstdio>
8 #include <cstdlib>
9 #include <string>
10 #include <string_view>
11 #include <utility>
12
13 #include "base/check.h"
14 #include "base/feature_list.h"
15 #include "base/functional/bind.h"
16 #include "base/functional/callback.h"
17 #include "base/metrics/histogram_functions.h"
18 #include "base/metrics/histogram_macros.h"
19 #include "base/notreached.h"
20 #include "base/strings/strcat.h"
21 #include "base/strings/string_tokenizer.h"
22 #include "base/strings/string_util.h"
23 #include "base/types/optional_util.h"
24 #include "build/build_config.h"
25 #include "net/base/features.h"
26 #include "net/base/isolation_info.h"
27 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
28 #include "net/base/url_util.h"
29 #include "net/cookies/cookie_access_delegate.h"
30 #include "net/cookies/cookie_constants.h"
31 #include "net/cookies/cookie_inclusion_status.h"
32 #include "net/cookies/cookie_monster.h"
33 #include "net/cookies/cookie_options.h"
34 #include "net/first_party_sets/first_party_set_metadata.h"
35 #include "net/first_party_sets/first_party_sets_cache_filter.h"
36 #include "net/http/http_util.h"
37 #include "url/gurl.h"
38 #include "url/url_constants.h"
39
40 namespace net::cookie_util {
41
42 namespace {
43
44 using ContextType = CookieOptions::SameSiteCookieContext::ContextType;
45 using ContextMetadata = CookieOptions::SameSiteCookieContext::ContextMetadata;
46
MinNonNullTime()47 base::Time MinNonNullTime() {
48 return base::Time::FromInternalValue(1);
49 }
50
51 // Tries to assemble a base::Time given a base::Time::Exploded representing a
52 // UTC calendar date.
53 //
54 // If the date falls outside of the range supported internally by
55 // FromUTCExploded() on the current platform, then the result is:
56 //
57 // * Time(1) if it's below the range FromUTCExploded() supports.
58 // * Time::Max() if it's above the range FromUTCExploded() supports.
SaturatedTimeFromUTCExploded(const base::Time::Exploded & exploded,base::Time * out)59 bool SaturatedTimeFromUTCExploded(const base::Time::Exploded& exploded,
60 base::Time* out) {
61 // Try to calculate the base::Time in the normal fashion.
62 if (base::Time::FromUTCExploded(exploded, out)) {
63 // Don't return Time(0) on success.
64 if (out->is_null())
65 *out = MinNonNullTime();
66 return true;
67 }
68
69 // base::Time::FromUTCExploded() has platform-specific limits:
70 //
71 // * Windows: Years 1601 - 30827
72 // * 32-bit POSIX: Years 1970 - 2038
73 //
74 // Work around this by returning min/max valid times for times outside those
75 // ranges when imploding the time is doomed to fail.
76 //
77 // Note that the following implementation is NOT perfect. It will accept
78 // some invalid calendar dates in the out-of-range case.
79 if (!exploded.HasValidValues())
80 return false;
81
82 if (exploded.year > base::Time::kExplodedMaxYear) {
83 *out = base::Time::Max();
84 return true;
85 }
86 if (exploded.year < base::Time::kExplodedMinYear) {
87 *out = MinNonNullTime();
88 return true;
89 }
90
91 return false;
92 }
93
94 struct ComputeSameSiteContextResult {
95 ContextType context_type = ContextType::CROSS_SITE;
96 ContextMetadata metadata;
97 };
98
MakeSameSiteCookieContext(const ComputeSameSiteContextResult & result,const ComputeSameSiteContextResult & schemeful_result)99 CookieOptions::SameSiteCookieContext MakeSameSiteCookieContext(
100 const ComputeSameSiteContextResult& result,
101 const ComputeSameSiteContextResult& schemeful_result) {
102 return CookieOptions::SameSiteCookieContext(
103 result.context_type, schemeful_result.context_type, result.metadata,
104 schemeful_result.metadata);
105 }
106
107 ContextMetadata::ContextRedirectTypeBug1221316
ComputeContextRedirectTypeBug1221316(bool url_chain_is_length_one,bool same_site_initiator,bool site_for_cookies_is_same_site,bool same_site_redirect_chain)108 ComputeContextRedirectTypeBug1221316(bool url_chain_is_length_one,
109 bool same_site_initiator,
110 bool site_for_cookies_is_same_site,
111 bool same_site_redirect_chain) {
112 if (url_chain_is_length_one)
113 return ContextMetadata::ContextRedirectTypeBug1221316::kNoRedirect;
114
115 if (!same_site_initiator || !site_for_cookies_is_same_site)
116 return ContextMetadata::ContextRedirectTypeBug1221316::kCrossSiteRedirect;
117
118 if (!same_site_redirect_chain) {
119 return ContextMetadata::ContextRedirectTypeBug1221316::
120 kPartialSameSiteRedirect;
121 }
122
123 return ContextMetadata::ContextRedirectTypeBug1221316::kAllSameSiteRedirect;
124 }
125
126 // This function consolidates the common logic for computing SameSite cookie
127 // access context in various situations (HTTP vs JS; get vs set).
128 //
129 // `is_http` is whether the current cookie access request is associated with a
130 // network request (as opposed to a non-HTTP API, i.e., JavaScript).
131 //
132 // `compute_schemefully` is whether the current computation is for a
133 // schemeful_context, i.e. whether scheme should be considered when comparing
134 // two sites.
135 //
136 // See documentation of `ComputeSameSiteContextForRequest` for explanations of
137 // other parameters.
ComputeSameSiteContext(const std::vector<GURL> & url_chain,const SiteForCookies & site_for_cookies,const std::optional<url::Origin> & initiator,bool is_http,bool is_main_frame_navigation,bool compute_schemefully)138 ComputeSameSiteContextResult ComputeSameSiteContext(
139 const std::vector<GURL>& url_chain,
140 const SiteForCookies& site_for_cookies,
141 const std::optional<url::Origin>& initiator,
142 bool is_http,
143 bool is_main_frame_navigation,
144 bool compute_schemefully) {
145 DCHECK(!url_chain.empty());
146 const GURL& request_url = url_chain.back();
147 const auto is_same_site_with_site_for_cookies =
148 [&site_for_cookies, compute_schemefully](const GURL& url) {
149 return site_for_cookies.IsFirstPartyWithSchemefulMode(
150 url, compute_schemefully);
151 };
152
153 bool site_for_cookies_is_same_site =
154 is_same_site_with_site_for_cookies(request_url);
155
156 // If the request is a main frame navigation, site_for_cookies must either be
157 // null (for opaque origins, e.g., data: origins) or same-site with the
158 // request URL (both schemefully and schemelessly), and the URL cannot be
159 // ws/wss (these schemes are not navigable).
160 DCHECK(!is_main_frame_navigation || site_for_cookies_is_same_site ||
161 site_for_cookies.IsNull());
162 DCHECK(!is_main_frame_navigation || !request_url.SchemeIsWSOrWSS());
163
164 // Defaults to a cross-site context type.
165 ComputeSameSiteContextResult result;
166
167 // Create a SiteForCookies object from the initiator so that we can reuse
168 // IsFirstPartyWithSchemefulMode().
169 bool same_site_initiator =
170 !initiator ||
171 SiteForCookies::FromOrigin(initiator.value())
172 .IsFirstPartyWithSchemefulMode(request_url, compute_schemefully);
173
174 // Check that the URLs in the redirect chain are all same-site with the
175 // site_for_cookies and hence (by transitivity) same-site with the request
176 // URL. (If the URL chain only has one member, it's the request_url and we've
177 // already checked it previously.)
178 bool same_site_redirect_chain =
179 url_chain.size() == 1u ||
180 base::ranges::all_of(url_chain, is_same_site_with_site_for_cookies);
181
182 // Record what type of redirect was experienced.
183
184 result.metadata.redirect_type_bug_1221316 =
185 ComputeContextRedirectTypeBug1221316(
186 url_chain.size() == 1u, same_site_initiator,
187 site_for_cookies_is_same_site, same_site_redirect_chain);
188
189 if (!site_for_cookies_is_same_site)
190 return result;
191
192 // Whether the context would be SAME_SITE_STRICT if not considering redirect
193 // chains, but is different after considering redirect chains.
194 bool cross_site_redirect_downgraded_from_strict = false;
195 // Allows the kCookieSameSiteConsidersRedirectChain feature to override the
196 // result and use SAME_SITE_STRICT.
197 bool use_strict = false;
198
199 if (same_site_initiator) {
200 if (same_site_redirect_chain) {
201 result.context_type = ContextType::SAME_SITE_STRICT;
202 return result;
203 }
204 cross_site_redirect_downgraded_from_strict = true;
205 // If we are not supposed to consider redirect chains, record that the
206 // context result should ultimately be strictly same-site. We cannot
207 // just return early from here because we don't yet know what the context
208 // gets downgraded to, so we can't return with the correct metadata until we
209 // go through the rest of the logic below to determine that.
210 use_strict = !base::FeatureList::IsEnabled(
211 features::kCookieSameSiteConsidersRedirectChain);
212 }
213
214 if (!is_http || is_main_frame_navigation) {
215 if (cross_site_redirect_downgraded_from_strict) {
216 result.metadata.cross_site_redirect_downgrade =
217 ContextMetadata::ContextDowngradeType::kStrictToLax;
218 }
219 result.context_type =
220 use_strict ? ContextType::SAME_SITE_STRICT : ContextType::SAME_SITE_LAX;
221 return result;
222 }
223
224 if (cross_site_redirect_downgraded_from_strict) {
225 result.metadata.cross_site_redirect_downgrade =
226 ContextMetadata::ContextDowngradeType::kStrictToCross;
227 }
228 result.context_type =
229 use_strict ? ContextType::SAME_SITE_STRICT : ContextType::CROSS_SITE;
230
231 return result;
232 }
233
234 // Setting any SameSite={Strict,Lax} cookie only requires a LAX context, so
235 // normalize any strictly same-site contexts to Lax for cookie writes.
NormalizeStrictToLaxForSet(ComputeSameSiteContextResult & result)236 void NormalizeStrictToLaxForSet(ComputeSameSiteContextResult& result) {
237 if (result.context_type == ContextType::SAME_SITE_STRICT)
238 result.context_type = ContextType::SAME_SITE_LAX;
239
240 switch (result.metadata.cross_site_redirect_downgrade) {
241 case ContextMetadata::ContextDowngradeType::kStrictToLax:
242 result.metadata.cross_site_redirect_downgrade =
243 ContextMetadata::ContextDowngradeType::kNoDowngrade;
244 break;
245 case ContextMetadata::ContextDowngradeType::kStrictToCross:
246 result.metadata.cross_site_redirect_downgrade =
247 ContextMetadata::ContextDowngradeType::kLaxToCross;
248 break;
249 default:
250 break;
251 }
252 }
253
ComputeSameSiteContextForSet(const std::vector<GURL> & url_chain,const SiteForCookies & site_for_cookies,const std::optional<url::Origin> & initiator,bool is_http,bool is_main_frame_navigation)254 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForSet(
255 const std::vector<GURL>& url_chain,
256 const SiteForCookies& site_for_cookies,
257 const std::optional<url::Origin>& initiator,
258 bool is_http,
259 bool is_main_frame_navigation) {
260 CookieOptions::SameSiteCookieContext same_site_context;
261
262 ComputeSameSiteContextResult result = ComputeSameSiteContext(
263 url_chain, site_for_cookies, initiator, is_http, is_main_frame_navigation,
264 false /* compute_schemefully */);
265 ComputeSameSiteContextResult schemeful_result = ComputeSameSiteContext(
266 url_chain, site_for_cookies, initiator, is_http, is_main_frame_navigation,
267 true /* compute_schemefully */);
268
269 NormalizeStrictToLaxForSet(result);
270 NormalizeStrictToLaxForSet(schemeful_result);
271
272 return MakeSameSiteCookieContext(result, schemeful_result);
273 }
274
CookieWithAccessResultSorter(const CookieWithAccessResult & a,const CookieWithAccessResult & b)275 bool CookieWithAccessResultSorter(const CookieWithAccessResult& a,
276 const CookieWithAccessResult& b) {
277 return CookieMonster::CookieSorter(&a.cookie, &b.cookie);
278 }
279
280 } // namespace
281
FireStorageAccessHistogram(StorageAccessResult result)282 void FireStorageAccessHistogram(StorageAccessResult result) {
283 UMA_HISTOGRAM_ENUMERATION("API.StorageAccess.AllowedRequests2", result);
284 }
285
DomainIsHostOnly(const std::string & domain_string)286 bool DomainIsHostOnly(const std::string& domain_string) {
287 return (domain_string.empty() || domain_string[0] != '.');
288 }
289
CookieDomainAsHost(const std::string & cookie_domain)290 std::string CookieDomainAsHost(const std::string& cookie_domain) {
291 if (DomainIsHostOnly(cookie_domain))
292 return cookie_domain;
293 return cookie_domain.substr(1);
294 }
295
GetEffectiveDomain(const std::string & scheme,const std::string & host)296 std::string GetEffectiveDomain(const std::string& scheme,
297 const std::string& host) {
298 if (scheme == "http" || scheme == "https" || scheme == "ws" ||
299 scheme == "wss") {
300 return registry_controlled_domains::GetDomainAndRegistry(
301 host,
302 registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
303 }
304
305 return CookieDomainAsHost(host);
306 }
307
GetCookieDomainWithString(const GURL & url,const std::string & domain_string,CookieInclusionStatus & status,std::string * result)308 bool GetCookieDomainWithString(const GURL& url,
309 const std::string& domain_string,
310 CookieInclusionStatus& status,
311 std::string* result) {
312 // Disallow non-ASCII domain names.
313 if (!base::IsStringASCII(domain_string)) {
314 if (base::FeatureList::IsEnabled(features::kCookieDomainRejectNonASCII)) {
315 status.AddExclusionReason(
316 CookieInclusionStatus::EXCLUDE_DOMAIN_NON_ASCII);
317 return false;
318 }
319 status.AddWarningReason(CookieInclusionStatus::WARN_DOMAIN_NON_ASCII);
320 }
321
322 const std::string url_host(url.host());
323
324 // Disallow invalid hostnames containing multiple `.` at the end.
325 // Httpbis-rfc6265bis draft-11, §5.1.2 says to convert the request host "into
326 // a sequence of individual domain name labels"; a label can only be empty if
327 // it is the last label in the name, but a name ending in `..` would have an
328 // empty label in the penultimate position and is thus invalid.
329 if (url_host.ends_with("..")) {
330 return false;
331 }
332 // If no domain was specified in the domain string, default to a host cookie.
333 // We match IE/Firefox in allowing a domain=IPADDR if it matches (case
334 // in-sensitive) the url ip address hostname and ignoring a leading dot if one
335 // exists. It should be treated as a host cookie.
336 if (domain_string.empty() ||
337 (url.HostIsIPAddress() &&
338 (base::EqualsCaseInsensitiveASCII(url_host, domain_string) ||
339 base::EqualsCaseInsensitiveASCII("." + url_host, domain_string)))) {
340 *result = url_host;
341 // TODO(crbug.com/1453416): Once empty label support is implemented we can
342 // CHECK our assumptions here. For now, we DCHECK as DUMP_WILL_BE_CHECK is
343 // generating too many crash reports and already know why this is failing.
344 DCHECK(DomainIsHostOnly(*result));
345 return true;
346 }
347
348 // Disallow domain names with %-escaped characters.
349 for (char c : domain_string) {
350 if (c == '%')
351 return false;
352 }
353
354 url::CanonHostInfo ignored;
355 std::string cookie_domain(CanonicalizeHost(domain_string, &ignored));
356 // Get the normalized domain specified in cookie line.
357 if (cookie_domain.empty())
358 return false;
359 if (cookie_domain[0] != '.')
360 cookie_domain = "." + cookie_domain;
361
362 // Ensure |url| and |cookie_domain| have the same domain+registry.
363 const std::string url_scheme(url.scheme());
364 const std::string url_domain_and_registry(
365 GetEffectiveDomain(url_scheme, url_host));
366 if (url_domain_and_registry.empty()) {
367 // We match IE/Firefox by treating an exact match between the normalized
368 // domain attribute and the request host to be treated as a host cookie.
369 std::string normalized_domain_string = base::ToLowerASCII(
370 domain_string[0] == '.' ? domain_string.substr(1) : domain_string);
371
372 if (url_host == normalized_domain_string) {
373 *result = url_host;
374 DCHECK(DomainIsHostOnly(*result));
375 return true;
376 }
377
378 // Otherwise, IP addresses/intranet hosts/public suffixes can't set
379 // domain cookies.
380 return false;
381 }
382 const std::string cookie_domain_and_registry(
383 GetEffectiveDomain(url_scheme, cookie_domain));
384 if (url_domain_and_registry != cookie_domain_and_registry)
385 return false; // Can't set a cookie on a different domain + registry.
386
387 // Ensure |url_host| is |cookie_domain| or one of its subdomains. Given that
388 // we know the domain+registry are the same from the above checks, this is
389 // basically a simple string suffix check.
390 const bool is_suffix = (url_host.length() < cookie_domain.length()) ?
391 (cookie_domain != ("." + url_host)) :
392 (url_host.compare(url_host.length() - cookie_domain.length(),
393 cookie_domain.length(), cookie_domain) != 0);
394 if (is_suffix)
395 return false;
396
397 *result = cookie_domain;
398 return true;
399 }
400
401 // Parse a cookie expiration time. We try to be lenient, but we need to
402 // assume some order to distinguish the fields. The basic rules:
403 // - The month name must be present and prefix the first 3 letters of the
404 // full month name (jan for January, jun for June).
405 // - If the year is <= 2 digits, it must occur after the day of month.
406 // - The time must be of the format hh:mm:ss.
407 // An average cookie expiration will look something like this:
408 // Sat, 15-Apr-17 21:01:22 GMT
ParseCookieExpirationTime(const std::string & time_string)409 base::Time ParseCookieExpirationTime(const std::string& time_string) {
410 static const char* const kMonths[] = {
411 "jan", "feb", "mar", "apr", "may", "jun",
412 "jul", "aug", "sep", "oct", "nov", "dec" };
413 // We want to be pretty liberal, and support most non-ascii and non-digit
414 // characters as a delimiter. We can't treat : as a delimiter, because it
415 // is the delimiter for hh:mm:ss, and we want to keep this field together.
416 // We make sure to include - and +, since they could prefix numbers.
417 // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes
418 // will be preserved, and we will get them here. So we make sure to include
419 // quote characters, and also \ for anything that was internally escaped.
420 static const char kDelimiters[] = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
421
422 base::Time::Exploded exploded = {0};
423
424 base::StringTokenizer tokenizer(time_string, kDelimiters);
425
426 bool found_day_of_month = false;
427 bool found_month = false;
428 bool found_time = false;
429 bool found_year = false;
430
431 while (tokenizer.GetNext()) {
432 const std::string token = tokenizer.token();
433 DCHECK(!token.empty());
434 bool numerical = base::IsAsciiDigit(token[0]);
435
436 // String field
437 if (!numerical) {
438 if (!found_month) {
439 for (size_t i = 0; i < std::size(kMonths); ++i) {
440 // Match prefix, so we could match January, etc
441 if (base::StartsWith(token, std::string_view(kMonths[i], 3),
442 base::CompareCase::INSENSITIVE_ASCII)) {
443 exploded.month = static_cast<int>(i) + 1;
444 found_month = true;
445 break;
446 }
447 }
448 } else {
449 // If we've gotten here, it means we've already found and parsed our
450 // month, and we have another string, which we would expect to be the
451 // the time zone name. According to the RFC and my experiments with
452 // how sites format their expirations, we don't have much of a reason
453 // to support timezones. We don't want to ever barf on user input,
454 // but this DCHECK should pass for well-formed data.
455 // DCHECK(token == "GMT");
456 }
457 // Numeric field w/ a colon
458 } else if (token.find(':') != std::string::npos) {
459 if (!found_time &&
460 #ifdef COMPILER_MSVC
461 sscanf_s(
462 #else
463 sscanf(
464 #endif
465 token.c_str(), "%2u:%2u:%2u", &exploded.hour,
466 &exploded.minute, &exploded.second) == 3) {
467 found_time = true;
468 } else {
469 // We should only ever encounter one time-like thing. If we're here,
470 // it means we've found a second, which shouldn't happen. We keep
471 // the first. This check should be ok for well-formed input:
472 // NOTREACHED();
473 }
474 // Numeric field
475 } else {
476 // Overflow with atoi() is unspecified, so we enforce a max length.
477 if (!found_day_of_month && token.length() <= 2) {
478 exploded.day_of_month = atoi(token.c_str());
479 found_day_of_month = true;
480 } else if (!found_year && token.length() <= 5) {
481 exploded.year = atoi(token.c_str());
482 found_year = true;
483 } else {
484 // If we're here, it means we've either found an extra numeric field,
485 // or a numeric field which was too long. For well-formed input, the
486 // following check would be reasonable:
487 // NOTREACHED();
488 }
489 }
490 }
491
492 if (!found_day_of_month || !found_month || !found_time || !found_year) {
493 // We didn't find all of the fields we need. For well-formed input, the
494 // following check would be reasonable:
495 // NOTREACHED() << "Cookie parse expiration failed: " << time_string;
496 return base::Time();
497 }
498
499 // Normalize the year to expand abbreviated years to the full year.
500 if (exploded.year >= 70 && exploded.year <= 99)
501 exploded.year += 1900;
502 if (exploded.year >= 0 && exploded.year <= 69)
503 exploded.year += 2000;
504
505 // Note that clipping the date if it is outside of a platform-specific range
506 // is permitted by: https://tools.ietf.org/html/rfc6265#section-5.2.1
507 base::Time result;
508 if (SaturatedTimeFromUTCExploded(exploded, &result))
509 return result;
510
511 // One of our values was out of expected range. For well-formed input,
512 // the following check would be reasonable:
513 // NOTREACHED() << "Cookie exploded expiration failed: " << time_string;
514
515 return base::Time();
516 }
517
CookieDomainAndPathToURL(const std::string & domain,const std::string & path,const std::string & source_scheme)518 GURL CookieDomainAndPathToURL(const std::string& domain,
519 const std::string& path,
520 const std::string& source_scheme) {
521 // Note: domain_no_dot could be empty for e.g. file cookies.
522 std::string domain_no_dot = CookieDomainAsHost(domain);
523 if (domain_no_dot.empty() || source_scheme.empty())
524 return GURL();
525 return GURL(base::StrCat(
526 {source_scheme, url::kStandardSchemeSeparator, domain_no_dot, path}));
527 }
528
CookieDomainAndPathToURL(const std::string & domain,const std::string & path,bool is_https)529 GURL CookieDomainAndPathToURL(const std::string& domain,
530 const std::string& path,
531 bool is_https) {
532 return CookieDomainAndPathToURL(
533 domain, path,
534 std::string(is_https ? url::kHttpsScheme : url::kHttpScheme));
535 }
536
CookieDomainAndPathToURL(const std::string & domain,const std::string & path,CookieSourceScheme source_scheme)537 GURL CookieDomainAndPathToURL(const std::string& domain,
538 const std::string& path,
539 CookieSourceScheme source_scheme) {
540 return CookieDomainAndPathToURL(domain, path,
541 source_scheme == CookieSourceScheme::kSecure);
542 }
543
CookieOriginToURL(const std::string & domain,bool is_https)544 GURL CookieOriginToURL(const std::string& domain, bool is_https) {
545 return CookieDomainAndPathToURL(domain, "/", is_https);
546 }
547
SimulatedCookieSource(const CanonicalCookie & cookie,const std::string & source_scheme)548 GURL SimulatedCookieSource(const CanonicalCookie& cookie,
549 const std::string& source_scheme) {
550 return CookieDomainAndPathToURL(cookie.Domain(), cookie.Path(),
551 source_scheme);
552 }
553
ProvisionalAccessScheme(const GURL & source_url)554 CookieAccessScheme ProvisionalAccessScheme(const GURL& source_url) {
555 return source_url.SchemeIsCryptographic()
556 ? CookieAccessScheme::kCryptographic
557 : IsLocalhost(source_url) ? CookieAccessScheme::kTrustworthy
558 : CookieAccessScheme::kNonCryptographic;
559 }
560
IsDomainMatch(const std::string & domain,const std::string & host)561 bool IsDomainMatch(const std::string& domain, const std::string& host) {
562 // Can domain match in two ways; as a domain cookie (where the cookie
563 // domain begins with ".") or as a host cookie (where it doesn't).
564
565 // Some consumers of the CookieMonster expect to set cookies on
566 // URLs like http://.strange.url. To retrieve cookies in this instance,
567 // we allow matching as a host cookie even when the domain_ starts with
568 // a period.
569 if (host == domain)
570 return true;
571
572 // Domain cookie must have an initial ".". To match, it must be
573 // equal to url's host with initial period removed, or a suffix of
574 // it.
575
576 // Arguably this should only apply to "http" or "https" cookies, but
577 // extension cookie tests currently use the funtionality, and if we
578 // ever decide to implement that it should be done by preventing
579 // such cookies from being set.
580 if (domain.empty() || domain[0] != '.')
581 return false;
582
583 // The host with a "." prefixed.
584 if (domain.compare(1, std::string::npos, host) == 0)
585 return true;
586
587 // A pure suffix of the host (ok since we know the domain already
588 // starts with a ".")
589 return (host.length() > domain.length() &&
590 host.compare(host.length() - domain.length(), domain.length(),
591 domain) == 0);
592 }
593
IsOnPath(const std::string & cookie_path,const std::string & url_path)594 bool IsOnPath(const std::string& cookie_path, const std::string& url_path) {
595 // A zero length would be unsafe for our trailing '/' checks, and
596 // would also make no sense for our prefix match. The code that
597 // creates a CanonicalCookie should make sure the path is never zero length,
598 // but we double check anyway.
599 if (cookie_path.empty()) {
600 return false;
601 }
602
603 // The Mozilla code broke this into three cases, based on if the cookie path
604 // was longer, the same length, or shorter than the length of the url path.
605 // I think the approach below is simpler.
606
607 // Make sure the cookie path is a prefix of the url path. If the url path is
608 // shorter than the cookie path, then the cookie path can't be a prefix.
609 if (!url_path.starts_with(cookie_path)) {
610 return false;
611 }
612
613 // |url_path| is >= |cookie_path|, and |cookie_path| is a prefix of
614 // |url_path|. If they are the are the same length then they are identical,
615 // otherwise need an additional check:
616
617 // In order to avoid in correctly matching a cookie path of /blah
618 // with a request path of '/blahblah/', we need to make sure that either
619 // the cookie path ends in a trailing '/', or that we prefix up to a '/'
620 // in the url path. Since we know that the url path length is greater
621 // than the cookie path length, it's safe to index one byte past.
622 if (cookie_path.length() != url_path.length() && cookie_path.back() != '/' &&
623 url_path[cookie_path.length()] != '/') {
624 return false;
625 }
626
627 return true;
628 }
629
ParseRequestCookieLine(const std::string & header_value,ParsedRequestCookies * parsed_cookies)630 void ParseRequestCookieLine(const std::string& header_value,
631 ParsedRequestCookies* parsed_cookies) {
632 std::string::const_iterator i = header_value.begin();
633 while (i != header_value.end()) {
634 // Here we are at the beginning of a cookie.
635
636 // Eat whitespace.
637 while (i != header_value.end() && *i == ' ') ++i;
638 if (i == header_value.end()) return;
639
640 // Find cookie name.
641 std::string::const_iterator cookie_name_beginning = i;
642 while (i != header_value.end() && *i != '=') ++i;
643 auto cookie_name = base::MakeStringPiece(cookie_name_beginning, i);
644
645 // Find cookie value.
646 std::string_view cookie_value;
647 // Cookies may have no value, in this case '=' may or may not be there.
648 if (i != header_value.end() && i + 1 != header_value.end()) {
649 ++i; // Skip '='.
650 std::string::const_iterator cookie_value_beginning = i;
651 if (*i == '"') {
652 ++i; // Skip '"'.
653 while (i != header_value.end() && *i != '"') ++i;
654 if (i == header_value.end()) return;
655 ++i; // Skip '"'.
656 cookie_value = base::MakeStringPiece(cookie_value_beginning, i);
657 // i points to character after '"', potentially a ';'.
658 } else {
659 while (i != header_value.end() && *i != ';') ++i;
660 cookie_value = base::MakeStringPiece(cookie_value_beginning, i);
661 // i points to ';' or end of string.
662 }
663 }
664 parsed_cookies->emplace_back(std::string(cookie_name),
665 std::string(cookie_value));
666 // Eat ';'.
667 if (i != header_value.end()) ++i;
668 }
669 }
670
SerializeRequestCookieLine(const ParsedRequestCookies & parsed_cookies)671 std::string SerializeRequestCookieLine(
672 const ParsedRequestCookies& parsed_cookies) {
673 std::string buffer;
674 for (const auto& parsed_cookie : parsed_cookies) {
675 if (!buffer.empty())
676 buffer.append("; ");
677 buffer.append(parsed_cookie.first.begin(), parsed_cookie.first.end());
678 buffer.push_back('=');
679 buffer.append(parsed_cookie.second.begin(), parsed_cookie.second.end());
680 }
681 return buffer;
682 }
683
ComputeSameSiteContextForRequest(const std::string & http_method,const std::vector<GURL> & url_chain,const SiteForCookies & site_for_cookies,const std::optional<url::Origin> & initiator,bool is_main_frame_navigation,bool force_ignore_site_for_cookies)684 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForRequest(
685 const std::string& http_method,
686 const std::vector<GURL>& url_chain,
687 const SiteForCookies& site_for_cookies,
688 const std::optional<url::Origin>& initiator,
689 bool is_main_frame_navigation,
690 bool force_ignore_site_for_cookies) {
691 // Set SameSiteCookieContext according to the rules laid out in
692 // https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis:
693 //
694 // * Include both "strict" and "lax" same-site cookies if the request's
695 // |url|, |initiator|, and |site_for_cookies| all have the same
696 // registrable domain. Note: this also covers the case of a request
697 // without an initiator (only happens for browser-initiated main frame
698 // navigations). If computing schemefully, the schemes must also match.
699 //
700 // * Include only "lax" same-site cookies if the request's |URL| and
701 // |site_for_cookies| have the same registrable domain, _and_ the
702 // request's |http_method| is "safe" ("GET" or "HEAD"), and the request
703 // is a main frame navigation.
704 //
705 // This case should occur only for cross-site requests which
706 // target a top-level browsing context, with a "safe" method.
707 //
708 // * Include both "strict" and "lax" same-site cookies if the request is
709 // tagged with a flag allowing it.
710 //
711 // Note that this can be the case for requests initiated by extensions,
712 // which need to behave as though they are made by the document itself,
713 // but appear like cross-site ones.
714 //
715 // * Otherwise, do not include same-site cookies.
716
717 if (force_ignore_site_for_cookies)
718 return CookieOptions::SameSiteCookieContext::MakeInclusive();
719
720 ComputeSameSiteContextResult result = ComputeSameSiteContext(
721 url_chain, site_for_cookies, initiator, true /* is_http */,
722 is_main_frame_navigation, false /* compute_schemefully */);
723 ComputeSameSiteContextResult schemeful_result = ComputeSameSiteContext(
724 url_chain, site_for_cookies, initiator, true /* is_http */,
725 is_main_frame_navigation, true /* compute_schemefully */);
726
727 // If the method is safe, the context is Lax. Otherwise, make a note that
728 // the method is unsafe.
729 if (!net::HttpUtil::IsMethodSafe(http_method)) {
730 if (result.context_type == ContextType::SAME_SITE_LAX)
731 result.context_type = ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
732 if (schemeful_result.context_type == ContextType::SAME_SITE_LAX)
733 schemeful_result.context_type = ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
734 }
735
736 ContextMetadata::HttpMethod http_method_enum =
737 HttpMethodStringToEnum(http_method);
738
739 if (result.metadata.cross_site_redirect_downgrade !=
740 ContextMetadata::ContextDowngradeType::kNoDowngrade) {
741 result.metadata.http_method_bug_1221316 = http_method_enum;
742 }
743
744 if (schemeful_result.metadata.cross_site_redirect_downgrade !=
745 ContextMetadata::ContextDowngradeType::kNoDowngrade) {
746 schemeful_result.metadata.http_method_bug_1221316 = http_method_enum;
747 }
748
749 return MakeSameSiteCookieContext(result, schemeful_result);
750 }
751
752 NET_EXPORT CookieOptions::SameSiteCookieContext
ComputeSameSiteContextForScriptGet(const GURL & url,const SiteForCookies & site_for_cookies,const std::optional<url::Origin> & initiator,bool force_ignore_site_for_cookies)753 ComputeSameSiteContextForScriptGet(const GURL& url,
754 const SiteForCookies& site_for_cookies,
755 const std::optional<url::Origin>& initiator,
756 bool force_ignore_site_for_cookies) {
757 if (force_ignore_site_for_cookies)
758 return CookieOptions::SameSiteCookieContext::MakeInclusive();
759
760 // We don't check the redirect chain for script access to cookies (only the
761 // URL itself).
762 ComputeSameSiteContextResult result = ComputeSameSiteContext(
763 {url}, site_for_cookies, initiator, false /* is_http */,
764 false /* is_main_frame_navigation */, false /* compute_schemefully */);
765 ComputeSameSiteContextResult schemeful_result = ComputeSameSiteContext(
766 {url}, site_for_cookies, initiator, false /* is_http */,
767 false /* is_main_frame_navigation */, true /* compute_schemefully */);
768
769 return MakeSameSiteCookieContext(result, schemeful_result);
770 }
771
ComputeSameSiteContextForResponse(const std::vector<GURL> & url_chain,const SiteForCookies & site_for_cookies,const std::optional<url::Origin> & initiator,bool is_main_frame_navigation,bool force_ignore_site_for_cookies)772 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForResponse(
773 const std::vector<GURL>& url_chain,
774 const SiteForCookies& site_for_cookies,
775 const std::optional<url::Origin>& initiator,
776 bool is_main_frame_navigation,
777 bool force_ignore_site_for_cookies) {
778 if (force_ignore_site_for_cookies)
779 return CookieOptions::SameSiteCookieContext::MakeInclusiveForSet();
780
781 DCHECK(!url_chain.empty());
782 if (is_main_frame_navigation && !site_for_cookies.IsNull()) {
783 // If the request is a main frame navigation, site_for_cookies must either
784 // be null (for opaque origins, e.g., data: origins) or same-site with the
785 // request URL (both schemefully and schemelessly), and the URL cannot be
786 // ws/wss (these schemes are not navigable).
787 DCHECK(
788 site_for_cookies.IsFirstPartyWithSchemefulMode(url_chain.back(), true));
789 DCHECK(!url_chain.back().SchemeIsWSOrWSS());
790 CookieOptions::SameSiteCookieContext result =
791 CookieOptions::SameSiteCookieContext::MakeInclusiveForSet();
792
793 const GURL& request_url = url_chain.back();
794
795 for (bool compute_schemefully : {false, true}) {
796 bool same_site_initiator =
797 !initiator ||
798 SiteForCookies::FromOrigin(initiator.value())
799 .IsFirstPartyWithSchemefulMode(request_url, compute_schemefully);
800
801 const auto is_same_site_with_site_for_cookies =
802 [&site_for_cookies, compute_schemefully](const GURL& url) {
803 return site_for_cookies.IsFirstPartyWithSchemefulMode(
804 url, compute_schemefully);
805 };
806
807 bool same_site_redirect_chain =
808 url_chain.size() == 1u ||
809 base::ranges::all_of(url_chain, is_same_site_with_site_for_cookies);
810
811 CookieOptions::SameSiteCookieContext::ContextMetadata& result_metadata =
812 compute_schemefully ? result.schemeful_metadata() : result.metadata();
813
814 result_metadata.redirect_type_bug_1221316 =
815 ComputeContextRedirectTypeBug1221316(
816 url_chain.size() == 1u, same_site_initiator,
817 true /* site_for_cookies_is_same_site */,
818 same_site_redirect_chain);
819 }
820 return result;
821 }
822
823 return ComputeSameSiteContextForSet(url_chain, site_for_cookies, initiator,
824 true /* is_http */,
825 is_main_frame_navigation);
826 }
827
ComputeSameSiteContextForScriptSet(const GURL & url,const SiteForCookies & site_for_cookies,bool force_ignore_site_for_cookies)828 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForScriptSet(
829 const GURL& url,
830 const SiteForCookies& site_for_cookies,
831 bool force_ignore_site_for_cookies) {
832 if (force_ignore_site_for_cookies)
833 return CookieOptions::SameSiteCookieContext::MakeInclusiveForSet();
834
835 // It doesn't matter what initiator origin we pass here. Either way, the
836 // context will be considered same-site iff the site_for_cookies is same-site
837 // with the url. We don't check the redirect chain for script access to
838 // cookies (only the URL itself).
839 return ComputeSameSiteContextForSet(
840 {url}, site_for_cookies, std::nullopt /* initiator */,
841 false /* is_http */, false /* is_main_frame_navigation */);
842 }
843
ComputeSameSiteContextForSubresource(const GURL & url,const SiteForCookies & site_for_cookies,bool force_ignore_site_for_cookies)844 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForSubresource(
845 const GURL& url,
846 const SiteForCookies& site_for_cookies,
847 bool force_ignore_site_for_cookies) {
848 if (force_ignore_site_for_cookies)
849 return CookieOptions::SameSiteCookieContext::MakeInclusive();
850
851 // If the URL is same-site as site_for_cookies it's same-site as all frames
852 // in the tree from the initiator frame up --- including the initiator frame.
853
854 // Schemeless check
855 if (!site_for_cookies.IsFirstPartyWithSchemefulMode(url, false)) {
856 return CookieOptions::SameSiteCookieContext(ContextType::CROSS_SITE,
857 ContextType::CROSS_SITE);
858 }
859
860 // Schemeful check
861 if (!site_for_cookies.IsFirstPartyWithSchemefulMode(url, true)) {
862 return CookieOptions::SameSiteCookieContext(ContextType::SAME_SITE_STRICT,
863 ContextType::CROSS_SITE);
864 }
865
866 return CookieOptions::SameSiteCookieContext::MakeInclusive();
867 }
868
IsPortBoundCookiesEnabled()869 bool IsPortBoundCookiesEnabled() {
870 return base::FeatureList::IsEnabled(features::kEnablePortBoundCookies);
871 }
872
IsSchemeBoundCookiesEnabled()873 bool IsSchemeBoundCookiesEnabled() {
874 return base::FeatureList::IsEnabled(features::kEnableSchemeBoundCookies);
875 }
876
IsOriginBoundCookiesPartiallyEnabled()877 bool IsOriginBoundCookiesPartiallyEnabled() {
878 return IsPortBoundCookiesEnabled() || IsSchemeBoundCookiesEnabled();
879 }
880
IsTimeLimitedInsecureCookiesEnabled()881 bool IsTimeLimitedInsecureCookiesEnabled() {
882 return IsSchemeBoundCookiesEnabled() &&
883 base::FeatureList::IsEnabled(features::kTimeLimitedInsecureCookies);
884 }
885
IsSchemefulSameSiteEnabled()886 bool IsSchemefulSameSiteEnabled() {
887 return base::FeatureList::IsEnabled(features::kSchemefulSameSite);
888 }
889
890 std::optional<
891 std::pair<FirstPartySetMetadata, FirstPartySetsCacheFilter::MatchInfo>>
ComputeFirstPartySetMetadataMaybeAsync(const SchemefulSite & request_site,const IsolationInfo & isolation_info,const CookieAccessDelegate * cookie_access_delegate,base::OnceCallback<void (FirstPartySetMetadata,FirstPartySetsCacheFilter::MatchInfo)> callback)892 ComputeFirstPartySetMetadataMaybeAsync(
893 const SchemefulSite& request_site,
894 const IsolationInfo& isolation_info,
895 const CookieAccessDelegate* cookie_access_delegate,
896 base::OnceCallback<void(FirstPartySetMetadata,
897 FirstPartySetsCacheFilter::MatchInfo)> callback) {
898 if (cookie_access_delegate) {
899 return cookie_access_delegate->ComputeFirstPartySetMetadataMaybeAsync(
900 request_site,
901 base::OptionalToPtr(
902 isolation_info.network_isolation_key().GetTopFrameSite()),
903 std::move(callback));
904 }
905
906 return std::pair(FirstPartySetMetadata(),
907 FirstPartySetsCacheFilter::MatchInfo());
908 }
909
910 CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod
HttpMethodStringToEnum(const std::string & in)911 HttpMethodStringToEnum(const std::string& in) {
912 using HttpMethod =
913 CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod;
914 if (in == "GET")
915 return HttpMethod::kGet;
916 if (in == "HEAD")
917 return HttpMethod::kHead;
918 if (in == "POST")
919 return HttpMethod::kPost;
920 if (in == "PUT")
921 return HttpMethod::KPut;
922 if (in == "DELETE")
923 return HttpMethod::kDelete;
924 if (in == "CONNECT")
925 return HttpMethod::kConnect;
926 if (in == "OPTIONS")
927 return HttpMethod::kOptions;
928 if (in == "TRACE")
929 return HttpMethod::kTrace;
930 if (in == "PATCH")
931 return HttpMethod::kPatch;
932
933 return HttpMethod::kUnknown;
934 }
935
IsCookieAccessResultInclude(CookieAccessResult cookie_access_result)936 bool IsCookieAccessResultInclude(CookieAccessResult cookie_access_result) {
937 return cookie_access_result.status.IsInclude();
938 }
939
StripAccessResults(const CookieAccessResultList & cookie_access_results_list)940 CookieList StripAccessResults(
941 const CookieAccessResultList& cookie_access_results_list) {
942 CookieList cookies;
943 for (const CookieWithAccessResult& cookie_with_access_result :
944 cookie_access_results_list) {
945 cookies.push_back(cookie_with_access_result.cookie);
946 }
947 return cookies;
948 }
949
RecordCookiePortOmniboxHistograms(const GURL & url)950 NET_EXPORT void RecordCookiePortOmniboxHistograms(const GURL& url) {
951 int port = url.EffectiveIntPort();
952
953 if (port == url::PORT_UNSPECIFIED)
954 return;
955
956 if (IsLocalhost(url)) {
957 UMA_HISTOGRAM_ENUMERATION("Cookie.Port.OmniboxURLNavigation.Localhost",
958 ReducePortRangeForCookieHistogram(port));
959 } else {
960 UMA_HISTOGRAM_ENUMERATION("Cookie.Port.OmniboxURLNavigation.RemoteHost",
961 ReducePortRangeForCookieHistogram(port));
962 }
963 }
964
DCheckIncludedAndExcludedCookieLists(const CookieAccessResultList & included_cookies,const CookieAccessResultList & excluded_cookies)965 NET_EXPORT void DCheckIncludedAndExcludedCookieLists(
966 const CookieAccessResultList& included_cookies,
967 const CookieAccessResultList& excluded_cookies) {
968 // Check that all elements of `included_cookies` really should be included,
969 // and that all elements of `excluded_cookies` really should be excluded.
970 DCHECK(base::ranges::all_of(included_cookies,
971 [](const net::CookieWithAccessResult& cookie) {
972 return cookie.access_result.status.IsInclude();
973 }));
974 DCHECK(base::ranges::none_of(excluded_cookies,
975 [](const net::CookieWithAccessResult& cookie) {
976 return cookie.access_result.status.IsInclude();
977 }));
978
979 // Check that the included cookies are still in the correct order.
980 DCHECK(
981 base::ranges::is_sorted(included_cookies, CookieWithAccessResultSorter));
982 }
983
IsForceThirdPartyCookieBlockingEnabled()984 NET_EXPORT bool IsForceThirdPartyCookieBlockingEnabled() {
985 return base::FeatureList::IsEnabled(
986 features::kForceThirdPartyCookieBlocking) &&
987 base::FeatureList::IsEnabled(features::kThirdPartyStoragePartitioning);
988 }
989
990 } // namespace net::cookie_util
991