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