• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 <utility>
11 
12 #include "base/check.h"
13 #include "base/feature_list.h"
14 #include "base/functional/bind.h"
15 #include "base/functional/callback.h"
16 #include "base/metrics/histogram_functions.h"
17 #include "base/metrics/histogram_macros.h"
18 #include "base/notreached.h"
19 #include "base/strings/strcat.h"
20 #include "base/strings/string_piece.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 absl::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 absl::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 absl::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 absl::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 
FireStorageAccessInputHistogram(bool has_opt_in,bool has_grant)286 void FireStorageAccessInputHistogram(bool has_opt_in, bool has_grant) {
287   StorageAccessInputState input_state;
288   if (has_opt_in && has_grant) {
289     input_state = StorageAccessInputState::kOptInWithGrant;
290   } else if (has_opt_in && !has_grant) {
291     input_state = StorageAccessInputState::kOptInWithoutGrant;
292   } else if (!has_opt_in && has_grant) {
293     input_state = StorageAccessInputState::kGrantWithoutOptIn;
294   } else if (!has_opt_in && !has_grant) {
295     input_state = StorageAccessInputState::kNoOptInNoGrant;
296   } else {
297     NOTREACHED_NORETURN();
298   }
299   base::UmaHistogramEnumeration("API.StorageAccess.InputState", input_state);
300 }
301 
DomainIsHostOnly(const std::string & domain_string)302 bool DomainIsHostOnly(const std::string& domain_string) {
303   return (domain_string.empty() || domain_string[0] != '.');
304 }
305 
CookieDomainAsHost(const std::string & cookie_domain)306 std::string CookieDomainAsHost(const std::string& cookie_domain) {
307   if (DomainIsHostOnly(cookie_domain))
308     return cookie_domain;
309   return cookie_domain.substr(1);
310 }
311 
GetEffectiveDomain(const std::string & scheme,const std::string & host)312 std::string GetEffectiveDomain(const std::string& scheme,
313                                const std::string& host) {
314   if (scheme == "http" || scheme == "https" || scheme == "ws" ||
315       scheme == "wss") {
316     return registry_controlled_domains::GetDomainAndRegistry(
317         host,
318         registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
319   }
320 
321   return CookieDomainAsHost(host);
322 }
323 
GetCookieDomainWithString(const GURL & url,const std::string & domain_string,CookieInclusionStatus & status,std::string * result)324 bool GetCookieDomainWithString(const GURL& url,
325                                const std::string& domain_string,
326                                CookieInclusionStatus& status,
327                                std::string* result) {
328   // Disallow non-ASCII domain names.
329   if (!base::IsStringASCII(domain_string)) {
330     if (base::FeatureList::IsEnabled(features::kCookieDomainRejectNonASCII)) {
331       status.AddExclusionReason(
332           CookieInclusionStatus::EXCLUDE_DOMAIN_NON_ASCII);
333       return false;
334     }
335     status.AddWarningReason(CookieInclusionStatus::WARN_DOMAIN_NON_ASCII);
336   }
337 
338   const std::string url_host(url.host());
339 
340   // Disallow invalid hostnames containing multiple `.` at the end.
341   // Httpbis-rfc6265bis draft-11, §5.1.2 says to convert the request host "into
342   // a sequence of individual domain name labels"; a label can only be empty if
343   // it is the last label in the name, but a name ending in `..` would have an
344   // empty label in the penultimate position and is thus invalid.
345   if (url_host.ends_with("..")) {
346     return false;
347   }
348   // If no domain was specified in the domain string, default to a host cookie.
349   // We match IE/Firefox in allowing a domain=IPADDR if it matches (case
350   // in-sensitive) the url ip address hostname and ignoring a leading dot if one
351   // exists. It should be treated as a host cookie.
352   if (domain_string.empty() ||
353       (url.HostIsIPAddress() &&
354        (base::EqualsCaseInsensitiveASCII(url_host, domain_string) ||
355         base::EqualsCaseInsensitiveASCII("." + url_host, domain_string)))) {
356     *result = url_host;
357     // TODO(crbug.com/1453416): Once empty label support is implemented we can
358     // CHECK our assumptions here. For now, we DCHECK as DUMP_WILL_BE_CHECK is
359     // generating too many crash reports and already know why this is failing.
360     DCHECK(DomainIsHostOnly(*result));
361     return true;
362   }
363 
364   // Disallow domain names with %-escaped characters.
365   for (char c : domain_string) {
366     if (c == '%')
367       return false;
368   }
369 
370   url::CanonHostInfo ignored;
371   std::string cookie_domain(CanonicalizeHost(domain_string, &ignored));
372   // Get the normalized domain specified in cookie line.
373   if (cookie_domain.empty())
374     return false;
375   if (cookie_domain[0] != '.')
376     cookie_domain = "." + cookie_domain;
377 
378   // Ensure |url| and |cookie_domain| have the same domain+registry.
379   const std::string url_scheme(url.scheme());
380   const std::string url_domain_and_registry(
381       GetEffectiveDomain(url_scheme, url_host));
382   if (url_domain_and_registry.empty()) {
383     // We match IE/Firefox by treating an exact match between the normalized
384     // domain attribute and the request host to be treated as a host cookie.
385     std::string normalized_domain_string = base::ToLowerASCII(
386         domain_string[0] == '.' ? domain_string.substr(1) : domain_string);
387 
388     if (url_host == normalized_domain_string) {
389       *result = url_host;
390       DCHECK(DomainIsHostOnly(*result));
391       return true;
392     }
393 
394     // Otherwise, IP addresses/intranet hosts/public suffixes can't set
395     // domain cookies.
396     return false;
397   }
398   const std::string cookie_domain_and_registry(
399       GetEffectiveDomain(url_scheme, cookie_domain));
400   if (url_domain_and_registry != cookie_domain_and_registry)
401     return false;  // Can't set a cookie on a different domain + registry.
402 
403   // Ensure |url_host| is |cookie_domain| or one of its subdomains.  Given that
404   // we know the domain+registry are the same from the above checks, this is
405   // basically a simple string suffix check.
406   const bool is_suffix = (url_host.length() < cookie_domain.length()) ?
407       (cookie_domain != ("." + url_host)) :
408       (url_host.compare(url_host.length() - cookie_domain.length(),
409                         cookie_domain.length(), cookie_domain) != 0);
410   if (is_suffix)
411     return false;
412 
413   *result = cookie_domain;
414   return true;
415 }
416 
417 // Parse a cookie expiration time.  We try to be lenient, but we need to
418 // assume some order to distinguish the fields.  The basic rules:
419 //  - The month name must be present and prefix the first 3 letters of the
420 //    full month name (jan for January, jun for June).
421 //  - If the year is <= 2 digits, it must occur after the day of month.
422 //  - The time must be of the format hh:mm:ss.
423 // An average cookie expiration will look something like this:
424 //   Sat, 15-Apr-17 21:01:22 GMT
ParseCookieExpirationTime(const std::string & time_string)425 base::Time ParseCookieExpirationTime(const std::string& time_string) {
426   static const char* const kMonths[] = {
427     "jan", "feb", "mar", "apr", "may", "jun",
428     "jul", "aug", "sep", "oct", "nov", "dec" };
429   // We want to be pretty liberal, and support most non-ascii and non-digit
430   // characters as a delimiter.  We can't treat : as a delimiter, because it
431   // is the delimiter for hh:mm:ss, and we want to keep this field together.
432   // We make sure to include - and +, since they could prefix numbers.
433   // If the cookie attribute came in in quotes (ex expires="XXX"), the quotes
434   // will be preserved, and we will get them here.  So we make sure to include
435   // quote characters, and also \ for anything that was internally escaped.
436   static const char kDelimiters[] = "\t !\"#$%&'()*+,-./;<=>?@[\\]^_`{|}~";
437 
438   base::Time::Exploded exploded = {0};
439 
440   base::StringTokenizer tokenizer(time_string, kDelimiters);
441 
442   bool found_day_of_month = false;
443   bool found_month = false;
444   bool found_time = false;
445   bool found_year = false;
446 
447   while (tokenizer.GetNext()) {
448     const std::string token = tokenizer.token();
449     DCHECK(!token.empty());
450     bool numerical = base::IsAsciiDigit(token[0]);
451 
452     // String field
453     if (!numerical) {
454       if (!found_month) {
455         for (size_t i = 0; i < std::size(kMonths); ++i) {
456           // Match prefix, so we could match January, etc
457           if (base::StartsWith(token, base::StringPiece(kMonths[i], 3),
458                                base::CompareCase::INSENSITIVE_ASCII)) {
459             exploded.month = static_cast<int>(i) + 1;
460             found_month = true;
461             break;
462           }
463         }
464       } else {
465         // If we've gotten here, it means we've already found and parsed our
466         // month, and we have another string, which we would expect to be the
467         // the time zone name.  According to the RFC and my experiments with
468         // how sites format their expirations, we don't have much of a reason
469         // to support timezones.  We don't want to ever barf on user input,
470         // but this DCHECK should pass for well-formed data.
471         // DCHECK(token == "GMT");
472       }
473     // Numeric field w/ a colon
474     } else if (token.find(':') != std::string::npos) {
475       if (!found_time &&
476 #ifdef COMPILER_MSVC
477           sscanf_s(
478 #else
479           sscanf(
480 #endif
481                  token.c_str(), "%2u:%2u:%2u", &exploded.hour,
482                  &exploded.minute, &exploded.second) == 3) {
483         found_time = true;
484       } else {
485         // We should only ever encounter one time-like thing.  If we're here,
486         // it means we've found a second, which shouldn't happen.  We keep
487         // the first.  This check should be ok for well-formed input:
488         // NOTREACHED();
489       }
490     // Numeric field
491     } else {
492       // Overflow with atoi() is unspecified, so we enforce a max length.
493       if (!found_day_of_month && token.length() <= 2) {
494         exploded.day_of_month = atoi(token.c_str());
495         found_day_of_month = true;
496       } else if (!found_year && token.length() <= 5) {
497         exploded.year = atoi(token.c_str());
498         found_year = true;
499       } else {
500         // If we're here, it means we've either found an extra numeric field,
501         // or a numeric field which was too long.  For well-formed input, the
502         // following check would be reasonable:
503         // NOTREACHED();
504       }
505     }
506   }
507 
508   if (!found_day_of_month || !found_month || !found_time || !found_year) {
509     // We didn't find all of the fields we need.  For well-formed input, the
510     // following check would be reasonable:
511     // NOTREACHED() << "Cookie parse expiration failed: " << time_string;
512     return base::Time();
513   }
514 
515   // Normalize the year to expand abbreviated years to the full year.
516   if (exploded.year >= 70 && exploded.year <= 99)
517     exploded.year += 1900;
518   if (exploded.year >= 0 && exploded.year <= 69)
519     exploded.year += 2000;
520 
521   // Note that clipping the date if it is outside of a platform-specific range
522   // is permitted by: https://tools.ietf.org/html/rfc6265#section-5.2.1
523   base::Time result;
524   if (SaturatedTimeFromUTCExploded(exploded, &result))
525     return result;
526 
527   // One of our values was out of expected range.  For well-formed input,
528   // the following check would be reasonable:
529   // NOTREACHED() << "Cookie exploded expiration failed: " << time_string;
530 
531   return base::Time();
532 }
533 
CookieDomainAndPathToURL(const std::string & domain,const std::string & path,const std::string & source_scheme)534 GURL CookieDomainAndPathToURL(const std::string& domain,
535                               const std::string& path,
536                               const std::string& source_scheme) {
537   // Note: domain_no_dot could be empty for e.g. file cookies.
538   std::string domain_no_dot = CookieDomainAsHost(domain);
539   if (domain_no_dot.empty() || source_scheme.empty())
540     return GURL();
541   return GURL(base::StrCat(
542       {source_scheme, url::kStandardSchemeSeparator, domain_no_dot, path}));
543 }
544 
CookieDomainAndPathToURL(const std::string & domain,const std::string & path,bool is_https)545 GURL CookieDomainAndPathToURL(const std::string& domain,
546                               const std::string& path,
547                               bool is_https) {
548   return CookieDomainAndPathToURL(
549       domain, path,
550       std::string(is_https ? url::kHttpsScheme : url::kHttpScheme));
551 }
552 
CookieDomainAndPathToURL(const std::string & domain,const std::string & path,CookieSourceScheme source_scheme)553 GURL CookieDomainAndPathToURL(const std::string& domain,
554                               const std::string& path,
555                               CookieSourceScheme source_scheme) {
556   return CookieDomainAndPathToURL(domain, path,
557                                   source_scheme == CookieSourceScheme::kSecure);
558 }
559 
CookieOriginToURL(const std::string & domain,bool is_https)560 GURL CookieOriginToURL(const std::string& domain, bool is_https) {
561   return CookieDomainAndPathToURL(domain, "/", is_https);
562 }
563 
SimulatedCookieSource(const CanonicalCookie & cookie,const std::string & source_scheme)564 GURL SimulatedCookieSource(const CanonicalCookie& cookie,
565                            const std::string& source_scheme) {
566   return CookieDomainAndPathToURL(cookie.Domain(), cookie.Path(),
567                                   source_scheme);
568 }
569 
ProvisionalAccessScheme(const GURL & source_url)570 CookieAccessScheme ProvisionalAccessScheme(const GURL& source_url) {
571   return source_url.SchemeIsCryptographic()
572              ? CookieAccessScheme::kCryptographic
573              : IsLocalhost(source_url) ? CookieAccessScheme::kTrustworthy
574                                        : CookieAccessScheme::kNonCryptographic;
575 }
576 
IsDomainMatch(const std::string & domain,const std::string & host)577 bool IsDomainMatch(const std::string& domain, const std::string& host) {
578   // Can domain match in two ways; as a domain cookie (where the cookie
579   // domain begins with ".") or as a host cookie (where it doesn't).
580 
581   // Some consumers of the CookieMonster expect to set cookies on
582   // URLs like http://.strange.url.  To retrieve cookies in this instance,
583   // we allow matching as a host cookie even when the domain_ starts with
584   // a period.
585   if (host == domain)
586     return true;
587 
588   // Domain cookie must have an initial ".".  To match, it must be
589   // equal to url's host with initial period removed, or a suffix of
590   // it.
591 
592   // Arguably this should only apply to "http" or "https" cookies, but
593   // extension cookie tests currently use the funtionality, and if we
594   // ever decide to implement that it should be done by preventing
595   // such cookies from being set.
596   if (domain.empty() || domain[0] != '.')
597     return false;
598 
599   // The host with a "." prefixed.
600   if (domain.compare(1, std::string::npos, host) == 0)
601     return true;
602 
603   // A pure suffix of the host (ok since we know the domain already
604   // starts with a ".")
605   return (host.length() > domain.length() &&
606           host.compare(host.length() - domain.length(), domain.length(),
607                        domain) == 0);
608 }
609 
IsOnPath(const std::string & cookie_path,const std::string & url_path)610 bool IsOnPath(const std::string& cookie_path, const std::string& url_path) {
611   // A zero length would be unsafe for our trailing '/' checks, and
612   // would also make no sense for our prefix match.  The code that
613   // creates a CanonicalCookie should make sure the path is never zero length,
614   // but we double check anyway.
615   if (cookie_path.empty()) {
616     return false;
617   }
618 
619   // The Mozilla code broke this into three cases, based on if the cookie path
620   // was longer, the same length, or shorter than the length of the url path.
621   // I think the approach below is simpler.
622 
623   // Make sure the cookie path is a prefix of the url path.  If the url path is
624   // shorter than the cookie path, then the cookie path can't be a prefix.
625   if (!url_path.starts_with(cookie_path)) {
626     return false;
627   }
628 
629   // |url_path| is >= |cookie_path|, and |cookie_path| is a prefix of
630   // |url_path|.  If they are the are the same length then they are identical,
631   // otherwise need an additional check:
632 
633   // In order to avoid in correctly matching a cookie path of /blah
634   // with a request path of '/blahblah/', we need to make sure that either
635   // the cookie path ends in a trailing '/', or that we prefix up to a '/'
636   // in the url path.  Since we know that the url path length is greater
637   // than the cookie path length, it's safe to index one byte past.
638   if (cookie_path.length() != url_path.length() && cookie_path.back() != '/' &&
639       url_path[cookie_path.length()] != '/') {
640     return false;
641   }
642 
643   return true;
644 }
645 
ParseRequestCookieLine(const std::string & header_value,ParsedRequestCookies * parsed_cookies)646 void ParseRequestCookieLine(const std::string& header_value,
647                             ParsedRequestCookies* parsed_cookies) {
648   std::string::const_iterator i = header_value.begin();
649   while (i != header_value.end()) {
650     // Here we are at the beginning of a cookie.
651 
652     // Eat whitespace.
653     while (i != header_value.end() && *i == ' ') ++i;
654     if (i == header_value.end()) return;
655 
656     // Find cookie name.
657     std::string::const_iterator cookie_name_beginning = i;
658     while (i != header_value.end() && *i != '=') ++i;
659     auto cookie_name = base::MakeStringPiece(cookie_name_beginning, i);
660 
661     // Find cookie value.
662     base::StringPiece cookie_value;
663     // Cookies may have no value, in this case '=' may or may not be there.
664     if (i != header_value.end() && i + 1 != header_value.end()) {
665       ++i;  // Skip '='.
666       std::string::const_iterator cookie_value_beginning = i;
667       if (*i == '"') {
668         ++i;  // Skip '"'.
669         while (i != header_value.end() && *i != '"') ++i;
670         if (i == header_value.end()) return;
671         ++i;  // Skip '"'.
672         cookie_value = base::MakeStringPiece(cookie_value_beginning, i);
673         // i points to character after '"', potentially a ';'.
674       } else {
675         while (i != header_value.end() && *i != ';') ++i;
676         cookie_value = base::MakeStringPiece(cookie_value_beginning, i);
677         // i points to ';' or end of string.
678       }
679     }
680     parsed_cookies->emplace_back(std::string(cookie_name),
681                                  std::string(cookie_value));
682     // Eat ';'.
683     if (i != header_value.end()) ++i;
684   }
685 }
686 
SerializeRequestCookieLine(const ParsedRequestCookies & parsed_cookies)687 std::string SerializeRequestCookieLine(
688     const ParsedRequestCookies& parsed_cookies) {
689   std::string buffer;
690   for (const auto& parsed_cookie : parsed_cookies) {
691     if (!buffer.empty())
692       buffer.append("; ");
693     buffer.append(parsed_cookie.first.begin(), parsed_cookie.first.end());
694     buffer.push_back('=');
695     buffer.append(parsed_cookie.second.begin(), parsed_cookie.second.end());
696   }
697   return buffer;
698 }
699 
ComputeSameSiteContextForRequest(const std::string & http_method,const std::vector<GURL> & url_chain,const SiteForCookies & site_for_cookies,const absl::optional<url::Origin> & initiator,bool is_main_frame_navigation,bool force_ignore_site_for_cookies)700 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForRequest(
701     const std::string& http_method,
702     const std::vector<GURL>& url_chain,
703     const SiteForCookies& site_for_cookies,
704     const absl::optional<url::Origin>& initiator,
705     bool is_main_frame_navigation,
706     bool force_ignore_site_for_cookies) {
707   // Set SameSiteCookieContext according to the rules laid out in
708   // https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis:
709   //
710   // * Include both "strict" and "lax" same-site cookies if the request's
711   //   |url|, |initiator|, and |site_for_cookies| all have the same
712   //   registrable domain. Note: this also covers the case of a request
713   //   without an initiator (only happens for browser-initiated main frame
714   //   navigations). If computing schemefully, the schemes must also match.
715   //
716   // * Include only "lax" same-site cookies if the request's |URL| and
717   //   |site_for_cookies| have the same registrable domain, _and_ the
718   //   request's |http_method| is "safe" ("GET" or "HEAD"), and the request
719   //   is a main frame navigation.
720   //
721   //   This case should occur only for cross-site requests which
722   //   target a top-level browsing context, with a "safe" method.
723   //
724   // * Include both "strict" and "lax" same-site cookies if the request is
725   //   tagged with a flag allowing it.
726   //
727   //   Note that this can be the case for requests initiated by extensions,
728   //   which need to behave as though they are made by the document itself,
729   //   but appear like cross-site ones.
730   //
731   // * Otherwise, do not include same-site cookies.
732 
733   if (force_ignore_site_for_cookies)
734     return CookieOptions::SameSiteCookieContext::MakeInclusive();
735 
736   ComputeSameSiteContextResult result = ComputeSameSiteContext(
737       url_chain, site_for_cookies, initiator, true /* is_http */,
738       is_main_frame_navigation, false /* compute_schemefully */);
739   ComputeSameSiteContextResult schemeful_result = ComputeSameSiteContext(
740       url_chain, site_for_cookies, initiator, true /* is_http */,
741       is_main_frame_navigation, true /* compute_schemefully */);
742 
743   // If the method is safe, the context is Lax. Otherwise, make a note that
744   // the method is unsafe.
745   if (!net::HttpUtil::IsMethodSafe(http_method)) {
746     if (result.context_type == ContextType::SAME_SITE_LAX)
747       result.context_type = ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
748     if (schemeful_result.context_type == ContextType::SAME_SITE_LAX)
749       schemeful_result.context_type = ContextType::SAME_SITE_LAX_METHOD_UNSAFE;
750   }
751 
752   ContextMetadata::HttpMethod http_method_enum =
753       HttpMethodStringToEnum(http_method);
754 
755   if (result.metadata.cross_site_redirect_downgrade !=
756       ContextMetadata::ContextDowngradeType::kNoDowngrade) {
757     result.metadata.http_method_bug_1221316 = http_method_enum;
758   }
759 
760   if (schemeful_result.metadata.cross_site_redirect_downgrade !=
761       ContextMetadata::ContextDowngradeType::kNoDowngrade) {
762     schemeful_result.metadata.http_method_bug_1221316 = http_method_enum;
763   }
764 
765   return MakeSameSiteCookieContext(result, schemeful_result);
766 }
767 
768 NET_EXPORT CookieOptions::SameSiteCookieContext
ComputeSameSiteContextForScriptGet(const GURL & url,const SiteForCookies & site_for_cookies,const absl::optional<url::Origin> & initiator,bool force_ignore_site_for_cookies)769 ComputeSameSiteContextForScriptGet(const GURL& url,
770                                    const SiteForCookies& site_for_cookies,
771                                    const absl::optional<url::Origin>& initiator,
772                                    bool force_ignore_site_for_cookies) {
773   if (force_ignore_site_for_cookies)
774     return CookieOptions::SameSiteCookieContext::MakeInclusive();
775 
776   // We don't check the redirect chain for script access to cookies (only the
777   // URL itself).
778   ComputeSameSiteContextResult result = ComputeSameSiteContext(
779       {url}, site_for_cookies, initiator, false /* is_http */,
780       false /* is_main_frame_navigation */, false /* compute_schemefully */);
781   ComputeSameSiteContextResult schemeful_result = ComputeSameSiteContext(
782       {url}, site_for_cookies, initiator, false /* is_http */,
783       false /* is_main_frame_navigation */, true /* compute_schemefully */);
784 
785   return MakeSameSiteCookieContext(result, schemeful_result);
786 }
787 
ComputeSameSiteContextForResponse(const std::vector<GURL> & url_chain,const SiteForCookies & site_for_cookies,const absl::optional<url::Origin> & initiator,bool is_main_frame_navigation,bool force_ignore_site_for_cookies)788 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForResponse(
789     const std::vector<GURL>& url_chain,
790     const SiteForCookies& site_for_cookies,
791     const absl::optional<url::Origin>& initiator,
792     bool is_main_frame_navigation,
793     bool force_ignore_site_for_cookies) {
794   if (force_ignore_site_for_cookies)
795     return CookieOptions::SameSiteCookieContext::MakeInclusiveForSet();
796 
797   DCHECK(!url_chain.empty());
798   if (is_main_frame_navigation && !site_for_cookies.IsNull()) {
799     // If the request is a main frame navigation, site_for_cookies must either
800     // be null (for opaque origins, e.g., data: origins) or same-site with the
801     // request URL (both schemefully and schemelessly), and the URL cannot be
802     // ws/wss (these schemes are not navigable).
803     DCHECK(
804         site_for_cookies.IsFirstPartyWithSchemefulMode(url_chain.back(), true));
805     DCHECK(!url_chain.back().SchemeIsWSOrWSS());
806     CookieOptions::SameSiteCookieContext result =
807         CookieOptions::SameSiteCookieContext::MakeInclusiveForSet();
808 
809     const GURL& request_url = url_chain.back();
810 
811     for (bool compute_schemefully : {false, true}) {
812       bool same_site_initiator =
813           !initiator ||
814           SiteForCookies::FromOrigin(initiator.value())
815               .IsFirstPartyWithSchemefulMode(request_url, compute_schemefully);
816 
817       const auto is_same_site_with_site_for_cookies =
818           [&site_for_cookies, compute_schemefully](const GURL& url) {
819             return site_for_cookies.IsFirstPartyWithSchemefulMode(
820                 url, compute_schemefully);
821           };
822 
823       bool same_site_redirect_chain =
824           url_chain.size() == 1u ||
825           base::ranges::all_of(url_chain, is_same_site_with_site_for_cookies);
826 
827       CookieOptions::SameSiteCookieContext::ContextMetadata& result_metadata =
828           compute_schemefully ? result.schemeful_metadata() : result.metadata();
829 
830       result_metadata.redirect_type_bug_1221316 =
831           ComputeContextRedirectTypeBug1221316(
832               url_chain.size() == 1u, same_site_initiator,
833               true /* site_for_cookies_is_same_site */,
834               same_site_redirect_chain);
835     }
836     return result;
837   }
838 
839   return ComputeSameSiteContextForSet(url_chain, site_for_cookies, initiator,
840                                       true /* is_http */,
841                                       is_main_frame_navigation);
842 }
843 
ComputeSameSiteContextForScriptSet(const GURL & url,const SiteForCookies & site_for_cookies,bool force_ignore_site_for_cookies)844 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForScriptSet(
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::MakeInclusiveForSet();
850 
851   // It doesn't matter what initiator origin we pass here. Either way, the
852   // context will be considered same-site iff the site_for_cookies is same-site
853   // with the url. We don't check the redirect chain for script access to
854   // cookies (only the URL itself).
855   return ComputeSameSiteContextForSet(
856       {url}, site_for_cookies, absl::nullopt /* initiator */,
857       false /* is_http */, false /* is_main_frame_navigation */);
858 }
859 
ComputeSameSiteContextForSubresource(const GURL & url,const SiteForCookies & site_for_cookies,bool force_ignore_site_for_cookies)860 CookieOptions::SameSiteCookieContext ComputeSameSiteContextForSubresource(
861     const GURL& url,
862     const SiteForCookies& site_for_cookies,
863     bool force_ignore_site_for_cookies) {
864   if (force_ignore_site_for_cookies)
865     return CookieOptions::SameSiteCookieContext::MakeInclusive();
866 
867   // If the URL is same-site as site_for_cookies it's same-site as all frames
868   // in the tree from the initiator frame up --- including the initiator frame.
869 
870   // Schemeless check
871   if (!site_for_cookies.IsFirstPartyWithSchemefulMode(url, false)) {
872     return CookieOptions::SameSiteCookieContext(ContextType::CROSS_SITE,
873                                                 ContextType::CROSS_SITE);
874   }
875 
876   // Schemeful check
877   if (!site_for_cookies.IsFirstPartyWithSchemefulMode(url, true)) {
878     return CookieOptions::SameSiteCookieContext(ContextType::SAME_SITE_STRICT,
879                                                 ContextType::CROSS_SITE);
880   }
881 
882   return CookieOptions::SameSiteCookieContext::MakeInclusive();
883 }
884 
IsPortBoundCookiesEnabled()885 bool IsPortBoundCookiesEnabled() {
886   return base::FeatureList::IsEnabled(features::kEnablePortBoundCookies);
887 }
888 
IsSchemeBoundCookiesEnabled()889 bool IsSchemeBoundCookiesEnabled() {
890   return base::FeatureList::IsEnabled(features::kEnableSchemeBoundCookies);
891 }
892 
IsSchemefulSameSiteEnabled()893 bool IsSchemefulSameSiteEnabled() {
894   return base::FeatureList::IsEnabled(features::kSchemefulSameSite);
895 }
896 
897 absl::optional<
898     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)899 ComputeFirstPartySetMetadataMaybeAsync(
900     const SchemefulSite& request_site,
901     const IsolationInfo& isolation_info,
902     const CookieAccessDelegate* cookie_access_delegate,
903     base::OnceCallback<void(FirstPartySetMetadata,
904                             FirstPartySetsCacheFilter::MatchInfo)> callback) {
905   if (cookie_access_delegate) {
906     return cookie_access_delegate->ComputeFirstPartySetMetadataMaybeAsync(
907         request_site,
908         base::OptionalToPtr(
909             isolation_info.network_isolation_key().GetTopFrameSite()),
910         std::move(callback));
911   }
912 
913   return std::make_pair(FirstPartySetMetadata(),
914                         FirstPartySetsCacheFilter::MatchInfo());
915 }
916 
917 CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod
HttpMethodStringToEnum(const std::string & in)918 HttpMethodStringToEnum(const std::string& in) {
919   using HttpMethod =
920       CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod;
921   if (in == "GET")
922     return HttpMethod::kGet;
923   if (in == "HEAD")
924     return HttpMethod::kHead;
925   if (in == "POST")
926     return HttpMethod::kPost;
927   if (in == "PUT")
928     return HttpMethod::KPut;
929   if (in == "DELETE")
930     return HttpMethod::kDelete;
931   if (in == "CONNECT")
932     return HttpMethod::kConnect;
933   if (in == "OPTIONS")
934     return HttpMethod::kOptions;
935   if (in == "TRACE")
936     return HttpMethod::kTrace;
937   if (in == "PATCH")
938     return HttpMethod::kPatch;
939 
940   return HttpMethod::kUnknown;
941 }
942 
IsCookieAccessResultInclude(CookieAccessResult cookie_access_result)943 bool IsCookieAccessResultInclude(CookieAccessResult cookie_access_result) {
944   return cookie_access_result.status.IsInclude();
945 }
946 
StripAccessResults(const CookieAccessResultList & cookie_access_results_list)947 CookieList StripAccessResults(
948     const CookieAccessResultList& cookie_access_results_list) {
949   CookieList cookies;
950   for (const CookieWithAccessResult& cookie_with_access_result :
951        cookie_access_results_list) {
952     cookies.push_back(cookie_with_access_result.cookie);
953   }
954   return cookies;
955 }
956 
RecordCookiePortOmniboxHistograms(const GURL & url)957 NET_EXPORT void RecordCookiePortOmniboxHistograms(const GURL& url) {
958   int port = url.EffectiveIntPort();
959 
960   if (port == url::PORT_UNSPECIFIED)
961     return;
962 
963   if (IsLocalhost(url)) {
964     UMA_HISTOGRAM_ENUMERATION("Cookie.Port.OmniboxURLNavigation.Localhost",
965                               ReducePortRangeForCookieHistogram(port));
966   } else {
967     UMA_HISTOGRAM_ENUMERATION("Cookie.Port.OmniboxURLNavigation.RemoteHost",
968                               ReducePortRangeForCookieHistogram(port));
969   }
970 }
971 
DCheckIncludedAndExcludedCookieLists(const CookieAccessResultList & included_cookies,const CookieAccessResultList & excluded_cookies)972 NET_EXPORT void DCheckIncludedAndExcludedCookieLists(
973     const CookieAccessResultList& included_cookies,
974     const CookieAccessResultList& excluded_cookies) {
975   // Check that all elements of `included_cookies` really should be included,
976   // and that all elements of `excluded_cookies` really should be excluded.
977   DCHECK(base::ranges::all_of(included_cookies,
978                               [](const net::CookieWithAccessResult& cookie) {
979                                 return cookie.access_result.status.IsInclude();
980                               }));
981   DCHECK(base::ranges::none_of(excluded_cookies,
982                                [](const net::CookieWithAccessResult& cookie) {
983                                  return cookie.access_result.status.IsInclude();
984                                }));
985 
986   // Check that the included cookies are still in the correct order.
987   DCHECK(
988       base::ranges::is_sorted(included_cookies, CookieWithAccessResultSorter));
989 }
990 
IsForceThirdPartyCookieBlockingEnabled()991 NET_EXPORT bool IsForceThirdPartyCookieBlockingEnabled() {
992   return base::FeatureList::IsEnabled(
993              features::kForceThirdPartyCookieBlocking) &&
994          base::FeatureList::IsEnabled(features::kThirdPartyStoragePartitioning);
995 }
996 
997 }  // namespace net::cookie_util
998