xref: /aosp_15_r20/external/cronet/net/cookies/canonical_cookie.cc (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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 // Portions of this code based on Mozilla:
6 //   (netwerk/cookie/src/nsCookieService.cpp)
7 /* ***** BEGIN LICENSE BLOCK *****
8  * Version: MPL 1.1/GPL 2.0/LGPL 2.1
9  *
10  * The contents of this file are subject to the Mozilla Public License Version
11  * 1.1 (the "License"); you may not use this file except in compliance with
12  * the License. You may obtain a copy of the License at
13  * http://www.mozilla.org/MPL/
14  *
15  * Software distributed under the License is distributed on an "AS IS" basis,
16  * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
17  * for the specific language governing rights and limitations under the
18  * License.
19  *
20  * The Original Code is mozilla.org code.
21  *
22  * The Initial Developer of the Original Code is
23  * Netscape Communications Corporation.
24  * Portions created by the Initial Developer are Copyright (C) 2003
25  * the Initial Developer. All Rights Reserved.
26  *
27  * Contributor(s):
28  *   Daniel Witte ([email protected])
29  *   Michiel van Leeuwen ([email protected])
30  *
31  * Alternatively, the contents of this file may be used under the terms of
32  * either the GNU General Public License Version 2 or later (the "GPL"), or
33  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
34  * in which case the provisions of the GPL or the LGPL are applicable instead
35  * of those above. If you wish to allow use of your version of this file only
36  * under the terms of either the GPL or the LGPL, and not to allow others to
37  * use your version of this file under the terms of the MPL, indicate your
38  * decision by deleting the provisions above and replace them with the notice
39  * and other provisions required by the GPL or the LGPL. If you do not delete
40  * the provisions above, a recipient may use your version of this file under
41  * the terms of any one of the MPL, the GPL or the LGPL.
42  *
43  * ***** END LICENSE BLOCK ***** */
44 
45 #include "net/cookies/canonical_cookie.h"
46 
47 #include <limits>
48 #include <optional>
49 #include <string_view>
50 #include <tuple>
51 #include <utility>
52 
53 #include "base/containers/contains.h"
54 #include "base/feature_list.h"
55 #include "base/format_macros.h"
56 #include "base/logging.h"
57 #include "base/memory/ptr_util.h"
58 #include "base/metrics/histogram_functions.h"
59 #include "base/metrics/histogram_macros.h"
60 #include "base/strings/strcat.h"
61 #include "base/strings/string_number_conversions.h"
62 #include "base/strings/string_util.h"
63 #include "base/strings/stringprintf.h"
64 #include "net/base/features.h"
65 #include "net/base/url_util.h"
66 #include "net/cookies/cookie_constants.h"
67 #include "net/cookies/cookie_inclusion_status.h"
68 #include "net/cookies/cookie_options.h"
69 #include "net/cookies/cookie_util.h"
70 #include "net/cookies/parsed_cookie.h"
71 #include "net/http/http_util.h"
72 #include "url/gurl.h"
73 #include "url/url_canon.h"
74 #include "url/url_util.h"
75 
76 using base::Time;
77 
78 namespace net {
79 
80 namespace {
81 
82 static constexpr int kMinutesInTwelveHours = 12 * 60;
83 static constexpr int kMinutesInTwentyFourHours = 24 * 60;
84 
85 // Determine the cookie domain to use for setting the specified cookie.
GetCookieDomain(const GURL & url,const ParsedCookie & pc,CookieInclusionStatus & status,std::string * result)86 bool GetCookieDomain(const GURL& url,
87                      const ParsedCookie& pc,
88                      CookieInclusionStatus& status,
89                      std::string* result) {
90   std::string domain_string;
91   if (pc.HasDomain())
92     domain_string = pc.Domain();
93   return cookie_util::GetCookieDomainWithString(url, domain_string, status,
94                                                 result);
95 }
96 
97 // Compares cookies using name, domain and path, so that "equivalent" cookies
98 // (per RFC 2965) are equal to each other.
PartialCookieOrdering(const CanonicalCookie & a,const CanonicalCookie & b)99 int PartialCookieOrdering(const CanonicalCookie& a, const CanonicalCookie& b) {
100   int diff = a.Name().compare(b.Name());
101   if (diff != 0)
102     return diff;
103 
104   diff = a.Domain().compare(b.Domain());
105   if (diff != 0)
106     return diff;
107 
108   return a.Path().compare(b.Path());
109 }
110 
AppendCookieLineEntry(const CanonicalCookie & cookie,std::string * cookie_line)111 void AppendCookieLineEntry(const CanonicalCookie& cookie,
112                            std::string* cookie_line) {
113   if (!cookie_line->empty())
114     *cookie_line += "; ";
115   // In Mozilla, if you set a cookie like "AAA", it will have an empty token
116   // and a value of "AAA". When it sends the cookie back, it will send "AAA",
117   // so we need to avoid sending "=AAA" for a blank token value.
118   if (!cookie.Name().empty())
119     *cookie_line += cookie.Name() + "=";
120   *cookie_line += cookie.Value();
121 }
122 
123 // Converts CookieSameSite to CookieSameSiteForMetrics by adding 1 to it.
CookieSameSiteToCookieSameSiteForMetrics(CookieSameSite enum_in)124 CookieSameSiteForMetrics CookieSameSiteToCookieSameSiteForMetrics(
125     CookieSameSite enum_in) {
126   return static_cast<CookieSameSiteForMetrics>((static_cast<int>(enum_in) + 1));
127 }
128 
129 // Tests that a cookie has the attributes for a valid __Host- prefix without
130 // testing that the prefix is in the cookie name.
HasValidHostPrefixAttributes(const GURL & url,bool secure,const std::string & domain,const std::string & path)131 bool HasValidHostPrefixAttributes(const GURL& url,
132                                   bool secure,
133                                   const std::string& domain,
134                                   const std::string& path) {
135   if (!secure || !url.SchemeIsCryptographic() || path != "/")
136     return false;
137   return domain.empty() || (url.HostIsIPAddress() && url.host() == domain);
138 }
139 
GetAllDataMembersAsTuple(const CanonicalCookie & c)140 auto GetAllDataMembersAsTuple(const CanonicalCookie& c) {
141   return std::make_tuple(c.CreationDate(), c.LastAccessDate(), c.ExpiryDate(),
142                          c.SecureAttribute(), c.IsHttpOnly(), c.SameSite(),
143                          c.Priority(), c.PartitionKey(), c.Name(), c.Value(),
144                          c.Domain(), c.Path(), c.LastUpdateDate(),
145                          c.SourceScheme(), c.SourcePort(), c.SourceType());
146 }
147 
148 }  // namespace
149 
CookieAccessParams(CookieAccessSemantics access_semantics,bool delegate_treats_url_as_trustworthy)150 CookieAccessParams::CookieAccessParams(CookieAccessSemantics access_semantics,
151                                        bool delegate_treats_url_as_trustworthy)
152     : access_semantics(access_semantics),
153       delegate_treats_url_as_trustworthy(delegate_treats_url_as_trustworthy) {}
154 
155 CanonicalCookie::CanonicalCookie() = default;
156 
157 CanonicalCookie::CanonicalCookie(const CanonicalCookie& other) = default;
158 
159 CanonicalCookie::CanonicalCookie(CanonicalCookie&& other) = default;
160 
161 CanonicalCookie& CanonicalCookie::operator=(const CanonicalCookie& other) =
162     default;
163 
164 CanonicalCookie& CanonicalCookie::operator=(CanonicalCookie&& other) = default;
165 
CanonicalCookie(base::PassKey<CanonicalCookie> pass_key,std::string name,std::string value,std::string domain,std::string path,base::Time creation,base::Time expiration,base::Time last_access,base::Time last_update,bool secure,bool httponly,CookieSameSite same_site,CookiePriority priority,std::optional<CookiePartitionKey> partition_key,CookieSourceScheme source_scheme,int source_port,CookieSourceType source_type)166 CanonicalCookie::CanonicalCookie(
167     base::PassKey<CanonicalCookie> pass_key,
168     std::string name,
169     std::string value,
170     std::string domain,
171     std::string path,
172     base::Time creation,
173     base::Time expiration,
174     base::Time last_access,
175     base::Time last_update,
176     bool secure,
177     bool httponly,
178     CookieSameSite same_site,
179     CookiePriority priority,
180     std::optional<CookiePartitionKey> partition_key,
181     CookieSourceScheme source_scheme,
182     int source_port,
183     CookieSourceType source_type)
184     : CookieBase(std::move(name),
185                  std::move(domain),
186                  std::move(path),
187                  creation,
188                  secure,
189                  httponly,
190                  same_site,
191                  std::move(partition_key),
192                  source_scheme,
193                  source_port),
194       value_(std::move(value)),
195       expiry_date_(expiration),
196       last_access_date_(last_access),
197       last_update_date_(last_update),
198       priority_(priority),
199       source_type_(source_type) {}
200 
201 CanonicalCookie::~CanonicalCookie() = default;
202 
203 // static
CanonPathWithString(const GURL & url,const std::string & path_string)204 std::string CanonicalCookie::CanonPathWithString(
205     const GURL& url,
206     const std::string& path_string) {
207   // The path was supplied in the cookie, we'll take it.
208   if (!path_string.empty() && path_string[0] == '/')
209     return path_string;
210 
211   // The path was not supplied in the cookie or invalid, we will default
212   // to the current URL path.
213   // """Defaults to the path of the request URL that generated the
214   //    Set-Cookie response, up to, but not including, the
215   //    right-most /."""
216   // How would this work for a cookie on /?  We will include it then.
217   const std::string& url_path = url.path();
218 
219   size_t idx = url_path.find_last_of('/');
220 
221   // The cookie path was invalid or a single '/'.
222   if (idx == 0 || idx == std::string::npos)
223     return std::string("/");
224 
225   // Return up to the rightmost '/'.
226   return url_path.substr(0, idx);
227 }
228 
229 // static
ParseExpiration(const ParsedCookie & pc,const Time & current,const Time & server_time)230 Time CanonicalCookie::ParseExpiration(const ParsedCookie& pc,
231                                       const Time& current,
232                                       const Time& server_time) {
233   // First, try the Max-Age attribute.
234   if (pc.HasMaxAge()) {
235     int64_t max_age = 0;
236     // Use the output if StringToInt64 returns true ("perfect" conversion). This
237     // case excludes overflow/underflow, leading/trailing whitespace, non-number
238     // strings, and empty string. (ParsedCookie trims whitespace.)
239     if (base::StringToInt64(pc.MaxAge(), &max_age)) {
240       // RFC 6265bis algorithm for parsing Max-Age:
241       // "If delta-seconds is less than or equal to zero (0), let expiry-
242       // time be the earliest representable date and time. ... "
243       if (max_age <= 0)
244         return Time::Min();
245       // "... Otherwise, let the expiry-time be the current date and time plus
246       // delta-seconds seconds."
247       return current + base::Seconds(max_age);
248     } else {
249       // If the conversion wasn't perfect, but the best-effort conversion
250       // resulted in an overflow/underflow, use the min/max representable time.
251       // (This is alluded to in the spec, which says the user agent MAY clip an
252       // Expires attribute to a saturated time. We'll do the same for Max-Age.)
253       if (max_age == std::numeric_limits<int64_t>::min())
254         return Time::Min();
255       if (max_age == std::numeric_limits<int64_t>::max())
256         return Time::Max();
257     }
258   }
259 
260   // Try the Expires attribute.
261   if (pc.HasExpires() && !pc.Expires().empty()) {
262     // Adjust for clock skew between server and host.
263     Time parsed_expiry = cookie_util::ParseCookieExpirationTime(pc.Expires());
264     if (!parsed_expiry.is_null()) {
265       // Record metrics related to prevalence of clock skew.
266       base::TimeDelta clock_skew = (current - server_time);
267       // Record the magnitude (absolute value) of the skew in minutes.
268       int clock_skew_magnitude = clock_skew.magnitude().InMinutes();
269       // Determine the new expiry with clock skew factored in.
270       Time adjusted_expiry = parsed_expiry + (current - server_time);
271       if (clock_skew.is_positive() || clock_skew.is_zero()) {
272         UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes",
273                                     clock_skew_magnitude, 1,
274                                     kMinutesInTwelveHours, 100);
275         UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes12To24Hours",
276                                     clock_skew_magnitude, kMinutesInTwelveHours,
277                                     kMinutesInTwentyFourHours, 100);
278         // Also record the range of minutes added that allowed the cookie to
279         // avoid expiring immediately.
280         if (parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now()) {
281           UMA_HISTOGRAM_CUSTOM_COUNTS(
282               "Cookie.ClockSkew.WithoutAddMinutesExpires", clock_skew_magnitude,
283               1, kMinutesInTwentyFourHours, 100);
284         }
285       } else if (clock_skew.is_negative()) {
286         // These histograms only support positive numbers, so negative skews
287         // will be converted to positive (via magnitude) before recording.
288         UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.SubtractMinutes",
289                                     clock_skew_magnitude, 1,
290                                     kMinutesInTwelveHours, 100);
291         UMA_HISTOGRAM_CUSTOM_COUNTS(
292             "Cookie.ClockSkew.SubtractMinutes12To24Hours", clock_skew_magnitude,
293             kMinutesInTwelveHours, kMinutesInTwentyFourHours, 100);
294       }
295       // Record if we were going to expire the cookie before we added the clock
296       // skew.
297       UMA_HISTOGRAM_BOOLEAN(
298           "Cookie.ClockSkew.ExpiredWithoutSkew",
299           parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now());
300       return adjusted_expiry;
301     }
302   }
303 
304   // Invalid or no expiration, session cookie.
305   return Time();
306 }
307 
308 // static
ValidateAndAdjustExpiryDate(const base::Time & expiry_date,const base::Time & creation_date,net::CookieSourceScheme scheme)309 base::Time CanonicalCookie::ValidateAndAdjustExpiryDate(
310     const base::Time& expiry_date,
311     const base::Time& creation_date,
312     net::CookieSourceScheme scheme) {
313   if (expiry_date.is_null())
314     return expiry_date;
315   base::Time fixed_creation_date = creation_date;
316   if (fixed_creation_date.is_null()) {
317     // TODO(crbug.com/1264458): Push this logic into
318     // CanonicalCookie::CreateSanitizedCookie. The four sites that call it
319     // with a null `creation_date` (CanonicalCookie::Create cannot be called
320     // this way) are:
321     // * GaiaCookieManagerService::ForceOnCookieChangeProcessing
322     // * CookiesSetFunction::Run
323     // * cookie_store.cc::ToCanonicalCookie
324     // * network_handler.cc::MakeCookieFromProtocolValues
325     fixed_creation_date = base::Time::Now();
326   }
327   base::Time maximum_expiry_date;
328   if (!cookie_util::IsTimeLimitedInsecureCookiesEnabled() ||
329       scheme == net::CookieSourceScheme::kSecure) {
330     maximum_expiry_date = fixed_creation_date + base::Days(400);
331   } else {
332     maximum_expiry_date = fixed_creation_date + base::Hours(3);
333   }
334   if (expiry_date > maximum_expiry_date) {
335     return maximum_expiry_date;
336   }
337   return expiry_date;
338 }
339 
340 // static
Create(const GURL & url,const std::string & cookie_line,const base::Time & creation_time,std::optional<base::Time> server_time,std::optional<CookiePartitionKey> cookie_partition_key,bool block_truncated,CookieSourceType source_type,CookieInclusionStatus * status)341 std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
342     const GURL& url,
343     const std::string& cookie_line,
344     const base::Time& creation_time,
345     std::optional<base::Time> server_time,
346     std::optional<CookiePartitionKey> cookie_partition_key,
347     bool block_truncated,
348     CookieSourceType source_type,
349     CookieInclusionStatus* status) {
350   // Put a pointer on the stack so the rest of the function can assign to it if
351   // the default nullptr is passed in.
352   CookieInclusionStatus blank_status;
353   if (status == nullptr) {
354     status = &blank_status;
355   }
356   *status = CookieInclusionStatus();
357 
358   // Check the URL; it may be nonsense since some platform APIs may permit
359   // it to be specified directly.
360   if (!url.is_valid()) {
361     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
362     return nullptr;
363   }
364 
365   ParsedCookie parsed_cookie(cookie_line, block_truncated, status);
366 
367   // We record this metric before checking validity because the presence of an
368   // HTAB will invalidate the ParsedCookie.
369   UMA_HISTOGRAM_BOOLEAN("Cookie.NameOrValueHtab",
370                         parsed_cookie.HasInternalHtab());
371 
372   if (!parsed_cookie.IsValid()) {
373     DVLOG(net::cookie_util::kVlogSetCookies)
374         << "WARNING: Couldn't parse cookie";
375     DCHECK(!status->IsInclude());
376     // Don't continue, because an invalid ParsedCookie doesn't have any
377     // attributes.
378     // TODO(chlily): Log metrics.
379     return nullptr;
380   }
381 
382   // Record warning for non-ASCII octecs in the Domain attribute.
383   // This should lead to rejection of the cookie in the future.
384   UMA_HISTOGRAM_BOOLEAN("Cookie.DomainHasNonASCII",
385                         parsed_cookie.HasDomain() &&
386                             !base::IsStringASCII(parsed_cookie.Domain()));
387 
388   std::string cookie_domain;
389   if (!GetCookieDomain(url, parsed_cookie, *status, &cookie_domain)) {
390     DVLOG(net::cookie_util::kVlogSetCookies)
391         << "Create() failed to get a valid cookie domain";
392     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
393   }
394 
395   std::string cookie_path = CanonPathWithString(
396       url, parsed_cookie.HasPath() ? parsed_cookie.Path() : std::string());
397 
398   Time cookie_server_time(creation_time);
399   if (server_time.has_value() && !server_time->is_null())
400     cookie_server_time = server_time.value();
401 
402   DCHECK(!creation_time.is_null());
403 
404   CookiePrefix prefix_case_sensitive =
405       GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/false);
406   CookiePrefix prefix_case_insensitive =
407       GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/true);
408 
409   bool is_sensitive_prefix_valid =
410       IsCookiePrefixValid(prefix_case_sensitive, url, parsed_cookie);
411   bool is_insensitive_prefix_valid =
412       IsCookiePrefixValid(prefix_case_insensitive, url, parsed_cookie);
413   bool is_cookie_prefix_valid =
414       base::FeatureList::IsEnabled(net::features::kCaseInsensitiveCookiePrefix)
415           ? is_insensitive_prefix_valid
416           : is_sensitive_prefix_valid;
417 
418   RecordCookiePrefixMetrics(prefix_case_sensitive, prefix_case_insensitive,
419                             is_insensitive_prefix_valid);
420 
421   if (parsed_cookie.Name() == "") {
422     is_cookie_prefix_valid = !HasHiddenPrefixName(parsed_cookie.Value());
423   }
424 
425   if (!is_cookie_prefix_valid) {
426     DVLOG(net::cookie_util::kVlogSetCookies)
427         << "Create() failed because the cookie violated prefix rules.";
428     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
429   }
430 
431   bool partition_has_nonce = CookiePartitionKey::HasNonce(cookie_partition_key);
432   bool is_partitioned_valid =
433       IsCookiePartitionedValid(url, parsed_cookie, partition_has_nonce);
434   if (!is_partitioned_valid) {
435     status->AddExclusionReason(
436         CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED);
437   }
438 
439   // Collect metrics on whether usage of the Partitioned attribute is correct.
440   // Do not include implicit nonce-based partitioned cookies in these metrics.
441   if (parsed_cookie.IsPartitioned()) {
442     if (!partition_has_nonce)
443       UMA_HISTOGRAM_BOOLEAN("Cookie.IsPartitionedValid", is_partitioned_valid);
444   } else if (!partition_has_nonce) {
445     cookie_partition_key = std::nullopt;
446   }
447 
448   if (!status->IsInclude())
449     return nullptr;
450 
451   CookieSameSiteString samesite_string = CookieSameSiteString::kUnspecified;
452   CookieSameSite samesite = parsed_cookie.SameSite(&samesite_string);
453 
454   // The next two sections set the source_scheme_ and source_port_. Normally
455   // these are taken directly from the url's scheme and port but if the url
456   // setting this cookie is considered a trustworthy origin then we may make
457   // some modifications. Note that here we assume that a trustworthy url must
458   // have a non-secure scheme (http). Since we can't know at this point if a url
459   // is trustworthy or not, we'll assume it is if the cookie is set with the
460   // `Secure` attribute.
461   //
462   // For both convenience and to try to match expectations, cookies that have
463   // the `Secure` attribute are modified to look like they were created by a
464   // secure url. This is helpful because this cookie can be treated like any
465   // other secure cookie when we're retrieving them and helps to prevent the
466   // cookie from getting "trapped" if the url loses trustworthiness.
467 
468   CookieSourceScheme source_scheme;
469   if (parsed_cookie.IsSecure() || url.SchemeIsCryptographic()) {
470     // It's possible that a trustworthy origin is setting this cookie with the
471     // `Secure` attribute even if the url's scheme isn't secure. In that case
472     // we'll act like it was a secure scheme. This cookie will be rejected later
473     // if the url isn't allowed to access secure cookies so this isn't a
474     // problem.
475     source_scheme = CookieSourceScheme::kSecure;
476 
477     if (!url.SchemeIsCryptographic()) {
478       status->AddWarningReason(
479           CookieInclusionStatus::
480               WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
481     }
482   } else {
483     source_scheme = CookieSourceScheme::kNonSecure;
484   }
485 
486   // Get the port, this will get a default value if a port isn't explicitly
487   // provided. Similar to the source scheme, it's possible that a trustworthy
488   // origin is setting this cookie with the `Secure` attribute even if the url's
489   // scheme isn't secure. This function will return 443 to pretend like this
490   // cookie was set by a secure scheme.
491   int source_port = CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
492       url, parsed_cookie.IsSecure());
493 
494   Time cookie_expires = CanonicalCookie::ParseExpiration(
495       parsed_cookie, creation_time, cookie_server_time);
496   cookie_expires =
497       ValidateAndAdjustExpiryDate(cookie_expires, creation_time, source_scheme);
498 
499   auto cc = std::make_unique<CanonicalCookie>(
500       base::PassKey<CanonicalCookie>(), parsed_cookie.Name(),
501       parsed_cookie.Value(), cookie_domain, cookie_path, creation_time,
502       cookie_expires, creation_time,
503       /*last_update=*/base::Time::Now(), parsed_cookie.IsSecure(),
504       parsed_cookie.IsHttpOnly(), samesite, parsed_cookie.Priority(),
505       cookie_partition_key, source_scheme, source_port, source_type);
506 
507   // TODO(chlily): Log metrics.
508   if (!cc->IsCanonical()) {
509     status->AddExclusionReason(
510         net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
511     return nullptr;
512   }
513 
514   RecordCookieSameSiteAttributeValueHistogram(samesite_string);
515 
516   // These metrics capture whether or not a cookie has a Non-ASCII character in
517   // it.
518   UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Name",
519                         !base::IsStringASCII(cc->Name()));
520   UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Value",
521                         !base::IsStringASCII(cc->Value()));
522 
523   // Check for "__" prefixed names, excluding the cookie prefixes.
524   bool name_prefixed_with_underscores =
525       (prefix_case_insensitive == CanonicalCookie::COOKIE_PREFIX_NONE) &&
526       parsed_cookie.Name().starts_with("__");
527 
528   UMA_HISTOGRAM_BOOLEAN("Cookie.DoubleUnderscorePrefixedName",
529                         name_prefixed_with_underscores);
530 
531   UMA_HISTOGRAM_ENUMERATION(
532       "Cookie.TruncatingCharacterInCookieString",
533       parsed_cookie.GetTruncatingCharacterInCookieStringType());
534 
535   return cc;
536 }
537 
538 // static
CreateSanitizedCookie(const GURL & url,const std::string & name,const std::string & value,const std::string & domain,const std::string & path,base::Time creation_time,base::Time expiration_time,base::Time last_access_time,bool secure,bool http_only,CookieSameSite same_site,CookiePriority priority,std::optional<CookiePartitionKey> partition_key,CookieInclusionStatus * status)539 std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie(
540     const GURL& url,
541     const std::string& name,
542     const std::string& value,
543     const std::string& domain,
544     const std::string& path,
545     base::Time creation_time,
546     base::Time expiration_time,
547     base::Time last_access_time,
548     bool secure,
549     bool http_only,
550     CookieSameSite same_site,
551     CookiePriority priority,
552     std::optional<CookiePartitionKey> partition_key,
553     CookieInclusionStatus* status) {
554   // Put a pointer on the stack so the rest of the function can assign to it if
555   // the default nullptr is passed in.
556   CookieInclusionStatus blank_status;
557   if (status == nullptr) {
558     status = &blank_status;
559   }
560   *status = CookieInclusionStatus();
561 
562   // Validate consistency of passed arguments.
563   if (ParsedCookie::ParseTokenString(name) != name) {
564     status->AddExclusionReason(
565         net::CookieInclusionStatus::EXCLUDE_DISALLOWED_CHARACTER);
566   } else if (ParsedCookie::ParseValueString(value) != value) {
567     status->AddExclusionReason(
568         net::CookieInclusionStatus::EXCLUDE_DISALLOWED_CHARACTER);
569   } else if (ParsedCookie::ParseValueString(path) != path) {
570     // NOTE: If `path` contains  "terminating characters" ('\r', '\n', and
571     // '\0'), ';', or leading / trailing whitespace, path will be rejected,
572     // but any other control characters will just get URL-encoded below.
573     status->AddExclusionReason(
574         net::CookieInclusionStatus::EXCLUDE_DISALLOWED_CHARACTER);
575   }
576 
577   // Validate name and value against character set and size limit constraints.
578   // If IsValidCookieNameValuePair identifies that `name` and/or `value` are
579   // invalid, it will add an ExclusionReason to `status`.
580   ParsedCookie::IsValidCookieNameValuePair(name, value, status);
581 
582   // Validate domain against character set and size limit constraints.
583   bool domain_is_valid = true;
584 
585   if ((ParsedCookie::ParseValueString(domain) != domain)) {
586     status->AddExclusionReason(
587         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
588     domain_is_valid = false;
589   }
590 
591   if (!ParsedCookie::CookieAttributeValueHasValidCharSet(domain)) {
592     status->AddExclusionReason(
593         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
594     domain_is_valid = false;
595   }
596   if (!ParsedCookie::CookieAttributeValueHasValidSize(domain)) {
597     status->AddExclusionReason(
598         net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
599     domain_is_valid = false;
600   }
601   const std::string& domain_attribute =
602       domain_is_valid ? domain : std::string();
603 
604   std::string cookie_domain;
605   // This validation step must happen before GetCookieDomainWithString, so it
606   // doesn't fail DCHECKs.
607   if (!cookie_util::DomainIsHostOnly(url.host())) {
608     status->AddExclusionReason(
609         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
610   } else if (!cookie_util::GetCookieDomainWithString(url, domain_attribute,
611                                                      *status, &cookie_domain)) {
612     status->AddExclusionReason(
613         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
614   }
615 
616   // The next two sections set the source_scheme_ and source_port_. Normally
617   // these are taken directly from the url's scheme and port but if the url
618   // setting this cookie is considered a trustworthy origin then we may make
619   // some modifications. Note that here we assume that a trustworthy url must
620   // have a non-secure scheme (http). Since we can't know at this point if a url
621   // is trustworthy or not, we'll assume it is if the cookie is set with the
622   // `Secure` attribute.
623   //
624   // For both convenience and to try to match expectations, cookies that have
625   // the `Secure` attribute are modified to look like they were created by a
626   // secure url. This is helpful because this cookie can be treated like any
627   // other secure cookie when we're retrieving them and helps to prevent the
628   // cookie from getting "trapped" if the url loses trustworthiness.
629 
630   CookieSourceScheme source_scheme = CookieSourceScheme::kNonSecure;
631   // This validation step must happen before SchemeIsCryptographic, so it
632   // doesn't fail DCHECKs.
633   if (!url.is_valid()) {
634     status->AddExclusionReason(
635         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
636   } else {
637     // It's possible that a trustworthy origin is setting this cookie with the
638     // `Secure` attribute even if the url's scheme isn't secure. In that case
639     // we'll act like it was a secure scheme. This cookie will be rejected later
640     // if the url isn't allowed to access secure cookies so this isn't a
641     // problem.
642     source_scheme = (secure || url.SchemeIsCryptographic())
643                         ? CookieSourceScheme::kSecure
644                         : CookieSourceScheme::kNonSecure;
645 
646     if (source_scheme == CookieSourceScheme::kSecure &&
647         !url.SchemeIsCryptographic()) {
648       status->AddWarningReason(
649           CookieInclusionStatus::
650               WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
651     }
652   }
653 
654   // Get the port, this will get a default value if a port isn't explicitly
655   // provided. Similar to the source scheme, it's possible that a trustworthy
656   // origin is setting this cookie with the `Secure` attribute even if the url's
657   // scheme isn't secure. This function will return 443 to pretend like this
658   // cookie was set by a secure scheme.
659   int source_port =
660       CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(url, secure);
661 
662   std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path);
663   // Canonicalize path again to make sure it escapes characters as needed.
664   url::Component path_component(0, cookie_path.length());
665   url::RawCanonOutputT<char> canon_path;
666   url::Component canon_path_component;
667   url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
668                         &canon_path_component);
669   std::string encoded_cookie_path = std::string(
670       canon_path.data() + canon_path_component.begin, canon_path_component.len);
671 
672   if (!path.empty()) {
673     if (cookie_path != path) {
674       // The path attribute was specified and found to be invalid, so record an
675       // error.
676       status->AddExclusionReason(
677           net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
678     } else if (!ParsedCookie::CookieAttributeValueHasValidSize(
679                    encoded_cookie_path)) {
680       // The path attribute was specified and encodes into a value that's longer
681       // than the length limit, so record an error.
682       status->AddExclusionReason(
683           net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
684     }
685   }
686 
687   CookiePrefix prefix = GetCookiePrefix(name);
688   if (!IsCookiePrefixValid(prefix, url, secure, domain_attribute,
689                            cookie_path)) {
690     status->AddExclusionReason(
691         net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
692   }
693 
694   if (name == "" && HasHiddenPrefixName(value)) {
695     status->AddExclusionReason(
696         net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
697   }
698 
699   if (!IsCookiePartitionedValid(url, secure,
700                                 /*is_partitioned=*/partition_key.has_value(),
701                                 /*partition_has_nonce=*/
702                                 CookiePartitionKey::HasNonce(partition_key))) {
703     status->AddExclusionReason(
704         net::CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED);
705   }
706 
707   if (!last_access_time.is_null() && creation_time.is_null()) {
708     status->AddExclusionReason(
709         net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
710   }
711   expiration_time = ValidateAndAdjustExpiryDate(expiration_time, creation_time,
712                                                 source_scheme);
713 
714   if (!status->IsInclude())
715     return nullptr;
716 
717   auto cc = std::make_unique<CanonicalCookie>(
718       base::PassKey<CanonicalCookie>(), name, value, cookie_domain,
719       encoded_cookie_path, creation_time, expiration_time, last_access_time,
720       /*last_update=*/base::Time::Now(), secure, http_only, same_site, priority,
721       partition_key, source_scheme, source_port, CookieSourceType::kOther);
722   DCHECK(cc->IsCanonical());
723 
724   return cc;
725 }
726 
727 // static
FromStorage(std::string name,std::string value,std::string domain,std::string path,base::Time creation,base::Time expiration,base::Time last_access,base::Time last_update,bool secure,bool httponly,CookieSameSite same_site,CookiePriority priority,std::optional<CookiePartitionKey> partition_key,CookieSourceScheme source_scheme,int source_port,CookieSourceType source_type)728 std::unique_ptr<CanonicalCookie> CanonicalCookie::FromStorage(
729     std::string name,
730     std::string value,
731     std::string domain,
732     std::string path,
733     base::Time creation,
734     base::Time expiration,
735     base::Time last_access,
736     base::Time last_update,
737     bool secure,
738     bool httponly,
739     CookieSameSite same_site,
740     CookiePriority priority,
741     std::optional<CookiePartitionKey> partition_key,
742     CookieSourceScheme source_scheme,
743     int source_port,
744     CookieSourceType source_type) {
745   // We check source_port here because it could have concievably been
746   // corrupted and changed to out of range. Eventually this would be caught by
747   // IsCanonical*() but since the source_port is only used by metrics so far
748   // nothing else checks it. So let's normalize it here and then update this
749   // method when origin-bound cookies is implemented.
750   // TODO(crbug.com/1170548)
751   int validated_port = CookieBase::ValidateAndAdjustSourcePort(source_port);
752 
753   auto cc = std::make_unique<CanonicalCookie>(
754       base::PassKey<CanonicalCookie>(), std::move(name), std::move(value),
755       std::move(domain), std::move(path), creation, expiration, last_access,
756       last_update, secure, httponly, same_site, priority, partition_key,
757       source_scheme, validated_port, source_type);
758 
759   if (cc->IsCanonicalForFromStorage()) {
760     // This will help capture the number of times a cookie is canonical but does
761     // not have a valid name+value size length
762     bool valid_cookie_name_value_pair =
763         ParsedCookie::IsValidCookieNameValuePair(cc->Name(), cc->Value());
764     UMA_HISTOGRAM_BOOLEAN("Cookie.FromStorageWithValidLength",
765                           valid_cookie_name_value_pair);
766   } else {
767     return nullptr;
768   }
769   return cc;
770 }
771 
772 // static
CreateUnsafeCookieForTesting(const std::string & name,const std::string & value,const std::string & domain,const std::string & path,const base::Time & creation,const base::Time & expiration,const base::Time & last_access,const base::Time & last_update,bool secure,bool httponly,CookieSameSite same_site,CookiePriority priority,std::optional<CookiePartitionKey> partition_key,CookieSourceScheme source_scheme,int source_port,CookieSourceType source_type)773 std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateUnsafeCookieForTesting(
774     const std::string& name,
775     const std::string& value,
776     const std::string& domain,
777     const std::string& path,
778     const base::Time& creation,
779     const base::Time& expiration,
780     const base::Time& last_access,
781     const base::Time& last_update,
782     bool secure,
783     bool httponly,
784     CookieSameSite same_site,
785     CookiePriority priority,
786     std::optional<CookiePartitionKey> partition_key,
787     CookieSourceScheme source_scheme,
788     int source_port,
789     CookieSourceType source_type) {
790   return std::make_unique<CanonicalCookie>(
791       base::PassKey<CanonicalCookie>(), name, value, domain, path, creation,
792       expiration, last_access, last_update, secure, httponly, same_site,
793       priority, partition_key, source_scheme, source_port, source_type);
794 }
795 
796 // static
CreateForTesting(const GURL & url,const std::string & cookie_line,const base::Time & creation_time,std::optional<base::Time> server_time,std::optional<CookiePartitionKey> cookie_partition_key,bool block_truncated,CookieSourceType source_type,CookieInclusionStatus * status)797 std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateForTesting(
798     const GURL& url,
799     const std::string& cookie_line,
800     const base::Time& creation_time,
801     std::optional<base::Time> server_time,
802     std::optional<CookiePartitionKey> cookie_partition_key,
803     bool block_truncated,
804     CookieSourceType source_type,
805     CookieInclusionStatus* status) {
806   return CanonicalCookie::Create(url, cookie_line, creation_time, server_time,
807                                  cookie_partition_key, block_truncated,
808                                  source_type, status);
809 }
810 
IsEquivalentForSecureCookieMatching(const CanonicalCookie & secure_cookie) const811 bool CanonicalCookie::IsEquivalentForSecureCookieMatching(
812     const CanonicalCookie& secure_cookie) const {
813   // Partition keys must both be equivalent.
814   bool same_partition_key = PartitionKey() == secure_cookie.PartitionKey();
815 
816   // Names must be the same
817   bool same_name = Name() == secure_cookie.Name();
818 
819   // They should domain-match in one direction or the other. (See RFC 6265bis
820   // section 5.1.3.)
821   // TODO(chlily): This does not check for the IP address case. This is bad due
822   // to https://crbug.com/1069935.
823   bool domain_match =
824       IsSubdomainOf(DomainWithoutDot(), secure_cookie.DomainWithoutDot()) ||
825       IsSubdomainOf(secure_cookie.DomainWithoutDot(), DomainWithoutDot());
826 
827   bool path_match = secure_cookie.IsOnPath(Path());
828 
829   bool equivalent_for_secure_cookie_matching =
830       same_partition_key && same_name && domain_match && path_match;
831 
832   // IsEquivalent() is a stricter check than this.
833   DCHECK(!IsEquivalent(secure_cookie) || equivalent_for_secure_cookie_matching);
834 
835   return equivalent_for_secure_cookie_matching;
836 }
837 
HasEquivalentDataMembers(const CanonicalCookie & other) const838 bool CanonicalCookie::HasEquivalentDataMembers(
839     const CanonicalCookie& other) const {
840   return GetAllDataMembersAsTuple(*this) == GetAllDataMembersAsTuple(other);
841 }
842 
DataMembersPrecede(const CanonicalCookie & other) const843 bool CanonicalCookie::DataMembersPrecede(const CanonicalCookie& other) const {
844   return GetAllDataMembersAsTuple(*this) < GetAllDataMembersAsTuple(other);
845 }
846 
PostIncludeForRequestURL(const CookieAccessResult & access_result,const CookieOptions & options_used,CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context_used) const847 void CanonicalCookie::PostIncludeForRequestURL(
848     const CookieAccessResult& access_result,
849     const CookieOptions& options_used,
850     CookieOptions::SameSiteCookieContext::ContextType
851         cookie_inclusion_context_used) const {
852   UMA_HISTOGRAM_ENUMERATION(
853       "Cookie.RequestSameSiteContext", cookie_inclusion_context_used,
854       CookieOptions::SameSiteCookieContext::ContextType::COUNT);
855 
856   // For the metric, we only want to consider first party partitioned cookies.
857   if (IsFirstPartyPartitioned()) {
858     UMA_HISTOGRAM_BOOLEAN(
859         "Cookie.FirstPartyPartitioned.HasCrossSiteAncestor",
860         cookie_inclusion_context_used ==
861             CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE);
862   }
863 
864   if (access_result.status.IsInclude()) {
865     UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedRequestEffectiveSameSite",
866                               access_result.effective_same_site,
867                               CookieEffectiveSameSite::COUNT);
868   }
869 
870   using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext::
871       ContextMetadata::ContextRedirectTypeBug1221316;
872 
873   ContextRedirectTypeBug1221316 redirect_type_for_metrics =
874       options_used.same_site_cookie_context()
875           .GetMetadataForCurrentSchemefulMode()
876           .redirect_type_bug_1221316;
877   if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) {
878     UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Read",
879                               redirect_type_for_metrics);
880   }
881 
882   if (access_result.status.HasWarningReason(
883           CookieInclusionStatus::
884               WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
885     UMA_HISTOGRAM_ENUMERATION(
886         "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Read",
887         CookieSameSiteToCookieSameSiteForMetrics(SameSite()));
888 
889     using HttpMethod =
890         CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod;
891 
892     HttpMethod http_method_enum = options_used.same_site_cookie_context()
893                                       .GetMetadataForCurrentSchemefulMode()
894                                       .http_method_bug_1221316;
895 
896     DCHECK(http_method_enum != HttpMethod::kUnset);
897 
898     UMA_HISTOGRAM_ENUMERATION(
899         "Cookie.CrossSiteRedirectDowngradeChangesInclusionHttpMethod",
900         http_method_enum);
901 
902     base::TimeDelta cookie_age = base::Time::Now() - CreationDate();
903     UMA_HISTOGRAM_EXACT_LINEAR(
904         "Cookie.CrossSiteRedirectDowngradeChangesInclusionAge",
905         cookie_age.InMinutes(), 30);
906   }
907 }
908 
PostIsSetPermittedInContext(const CookieAccessResult & access_result,const CookieOptions & options_used) const909 void CanonicalCookie::PostIsSetPermittedInContext(
910     const CookieAccessResult& access_result,
911     const CookieOptions& options_used) const {
912   if (access_result.status.IsInclude()) {
913     UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedResponseEffectiveSameSite",
914                               access_result.effective_same_site,
915                               CookieEffectiveSameSite::COUNT);
916   }
917 
918   using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext::
919       ContextMetadata::ContextRedirectTypeBug1221316;
920 
921   ContextRedirectTypeBug1221316 redirect_type_for_metrics =
922       options_used.same_site_cookie_context()
923           .GetMetadataForCurrentSchemefulMode()
924           .redirect_type_bug_1221316;
925   if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) {
926     UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Write",
927                               redirect_type_for_metrics);
928   }
929 
930   if (access_result.status.HasWarningReason(
931           CookieInclusionStatus::
932               WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
933     UMA_HISTOGRAM_ENUMERATION(
934         "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Write",
935         CookieSameSiteToCookieSameSiteForMetrics(SameSite()));
936   }
937 }
938 
DebugString() const939 std::string CanonicalCookie::DebugString() const {
940   return base::StringPrintf(
941       "name: %s value: %s domain: %s path: %s creation: %" PRId64,
942       Name().c_str(), value_.c_str(), Domain().c_str(), Path().c_str(),
943       static_cast<int64_t>(CreationDate().ToTimeT()));
944 }
945 
PartialCompare(const CanonicalCookie & other) const946 bool CanonicalCookie::PartialCompare(const CanonicalCookie& other) const {
947   return PartialCookieOrdering(*this, other) < 0;
948 }
949 
IsCanonical() const950 bool CanonicalCookie::IsCanonical() const {
951   // TODO(crbug.com/1244172) Eventually we should check the size of name+value,
952   // assuming we collect metrics and determine that a low percentage of cookies
953   // would fail this check. Note that we still don't want to enforce length
954   // checks on domain or path for the reason stated above.
955 
956   // TODO(crbug.com/1264458): Eventually we should push this logic into
957   // IsCanonicalForFromStorage, but for now we allow cookies already stored with
958   // high expiration dates to be retrieved.
959   if (ValidateAndAdjustExpiryDate(expiry_date_, CreationDate(),
960                                   SourceScheme()) != expiry_date_) {
961     return false;
962   }
963 
964   return IsCanonicalForFromStorage();
965 }
966 
IsCanonicalForFromStorage() const967 bool CanonicalCookie::IsCanonicalForFromStorage() const {
968   // Not checking domain or path against ParsedCookie as it may have
969   // come purely from the URL. Also, don't call IsValidCookieNameValuePair()
970   // here because we don't want to enforce the size checks on names or values
971   // that may have been reconstituted from the cookie store.
972   if (ParsedCookie::ParseTokenString(Name()) != Name() ||
973       !ParsedCookie::ValueMatchesParsedValue(value_)) {
974     return false;
975   }
976 
977   if (!ParsedCookie::IsValidCookieName(Name()) ||
978       !ParsedCookie::IsValidCookieValue(value_)) {
979     return false;
980   }
981 
982   if (!last_access_date_.is_null() && CreationDate().is_null()) {
983     return false;
984   }
985 
986   url::CanonHostInfo canon_host_info;
987   std::string canonical_domain(CanonicalizeHost(Domain(), &canon_host_info));
988 
989   // TODO(rdsmith): This specifically allows for empty domains.  The spec
990   // suggests this is invalid (if a domain attribute is empty, the cookie's
991   // domain is set to the canonicalized request host; see
992   // https://tools.ietf.org/html/rfc6265#section-5.3).  However, it is
993   // needed for Chrome extension cookies.
994   // See http://crbug.com/730633 for more information.
995   if (canonical_domain != Domain()) {
996     return false;
997   }
998 
999   if (Path().empty() || Path()[0] != '/') {
1000     return false;
1001   }
1002 
1003   CookiePrefix prefix = GetCookiePrefix(Name());
1004   switch (prefix) {
1005     case COOKIE_PREFIX_HOST:
1006       if (!SecureAttribute() || Path() != "/" || Domain().empty() ||
1007           Domain()[0] == '.') {
1008         return false;
1009       }
1010       break;
1011     case COOKIE_PREFIX_SECURE:
1012       if (!SecureAttribute()) {
1013         return false;
1014       }
1015       break;
1016     default:
1017       break;
1018   }
1019 
1020   if (Name() == "" && HasHiddenPrefixName(value_)) {
1021     return false;
1022   }
1023 
1024   if (IsPartitioned()) {
1025     if (CookiePartitionKey::HasNonce(PartitionKey())) {
1026       return true;
1027     }
1028     if (!SecureAttribute()) {
1029       return false;
1030     }
1031   }
1032 
1033   return true;
1034 }
1035 
IsEffectivelySameSiteNone(CookieAccessSemantics access_semantics) const1036 bool CanonicalCookie::IsEffectivelySameSiteNone(
1037     CookieAccessSemantics access_semantics) const {
1038   return GetEffectiveSameSite(access_semantics) ==
1039          CookieEffectiveSameSite::NO_RESTRICTION;
1040 }
1041 
GetEffectiveSameSiteForTesting(CookieAccessSemantics access_semantics) const1042 CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSiteForTesting(
1043     CookieAccessSemantics access_semantics) const {
1044   return GetEffectiveSameSite(access_semantics);
1045 }
1046 
1047 // static
BuildCookieLine(const CookieList & cookies)1048 std::string CanonicalCookie::BuildCookieLine(const CookieList& cookies) {
1049   std::string cookie_line;
1050   for (const auto& cookie : cookies) {
1051     AppendCookieLineEntry(cookie, &cookie_line);
1052   }
1053   return cookie_line;
1054 }
1055 
1056 // static
BuildCookieLine(const CookieAccessResultList & cookie_access_result_list)1057 std::string CanonicalCookie::BuildCookieLine(
1058     const CookieAccessResultList& cookie_access_result_list) {
1059   std::string cookie_line;
1060   for (const auto& cookie_with_access_result : cookie_access_result_list) {
1061     const CanonicalCookie& cookie = cookie_with_access_result.cookie;
1062     AppendCookieLineEntry(cookie, &cookie_line);
1063   }
1064   return cookie_line;
1065 }
1066 
1067 // static
BuildCookieAttributesLine(const CanonicalCookie & cookie)1068 std::string CanonicalCookie::BuildCookieAttributesLine(
1069     const CanonicalCookie& cookie) {
1070   std::string cookie_line;
1071   // In Mozilla, if you set a cookie like "AAA", it will have an empty token
1072   // and a value of "AAA". When it sends the cookie back, it will send "AAA",
1073   // so we need to avoid sending "=AAA" for a blank token value.
1074   if (!cookie.Name().empty())
1075     cookie_line += cookie.Name() + "=";
1076   cookie_line += cookie.Value();
1077   if (!cookie.Domain().empty())
1078     cookie_line += "; domain=" + cookie.Domain();
1079   if (!cookie.Path().empty())
1080     cookie_line += "; path=" + cookie.Path();
1081   if (cookie.ExpiryDate() != base::Time())
1082     cookie_line += "; expires=" + HttpUtil::TimeFormatHTTP(cookie.ExpiryDate());
1083   if (cookie.SecureAttribute()) {
1084     cookie_line += "; secure";
1085   }
1086   if (cookie.IsHttpOnly())
1087     cookie_line += "; httponly";
1088   if (cookie.IsPartitioned() &&
1089       !CookiePartitionKey::HasNonce(cookie.PartitionKey())) {
1090     cookie_line += "; partitioned";
1091   }
1092   switch (cookie.SameSite()) {
1093     case CookieSameSite::NO_RESTRICTION:
1094       cookie_line += "; samesite=none";
1095       break;
1096     case CookieSameSite::LAX_MODE:
1097       cookie_line += "; samesite=lax";
1098       break;
1099     case CookieSameSite::STRICT_MODE:
1100       cookie_line += "; samesite=strict";
1101       break;
1102     case CookieSameSite::UNSPECIFIED:
1103       // Don't append any text if the samesite attribute wasn't explicitly set.
1104       break;
1105   }
1106   return cookie_line;
1107 }
1108 
1109 // static
GetCookiePrefix(const std::string & name,bool check_insensitively)1110 CanonicalCookie::CookiePrefix CanonicalCookie::GetCookiePrefix(
1111     const std::string& name,
1112     bool check_insensitively) {
1113   const char kSecurePrefix[] = "__Secure-";
1114   const char kHostPrefix[] = "__Host-";
1115 
1116   base::CompareCase case_sensitivity =
1117       check_insensitively ? base::CompareCase::INSENSITIVE_ASCII
1118                           : base::CompareCase::SENSITIVE;
1119 
1120   if (base::StartsWith(name, kSecurePrefix, case_sensitivity))
1121     return CanonicalCookie::COOKIE_PREFIX_SECURE;
1122   if (base::StartsWith(name, kHostPrefix, case_sensitivity))
1123     return CanonicalCookie::COOKIE_PREFIX_HOST;
1124   return CanonicalCookie::COOKIE_PREFIX_NONE;
1125 }
1126 
1127 // static
RecordCookiePrefixMetrics(CookiePrefix prefix_case_sensitive,CookiePrefix prefix_case_insensitive,bool is_insensitive_prefix_valid)1128 void CanonicalCookie::RecordCookiePrefixMetrics(
1129     CookiePrefix prefix_case_sensitive,
1130     CookiePrefix prefix_case_insensitive,
1131     bool is_insensitive_prefix_valid) {
1132   const char kCookiePrefixHistogram[] = "Cookie.CookiePrefix";
1133   UMA_HISTOGRAM_ENUMERATION(kCookiePrefixHistogram, prefix_case_sensitive,
1134                             CanonicalCookie::COOKIE_PREFIX_LAST);
1135 
1136   // For this to be true there must a prefix, so we know it's not
1137   // COOKIE_PREFIX_NONE.
1138   bool is_case_variant = prefix_case_insensitive != prefix_case_sensitive;
1139 
1140   if (is_case_variant) {
1141     const char kCookiePrefixVariantHistogram[] =
1142         "Cookie.CookiePrefix.CaseVariant";
1143     UMA_HISTOGRAM_ENUMERATION(kCookiePrefixVariantHistogram,
1144                               prefix_case_insensitive,
1145                               CanonicalCookie::COOKIE_PREFIX_LAST);
1146 
1147     const char kVariantValidHistogram[] =
1148         "Cookie.CookiePrefix.CaseVariantValid";
1149     UMA_HISTOGRAM_BOOLEAN(kVariantValidHistogram, is_insensitive_prefix_valid);
1150   }
1151 
1152   const char kVariantCountHistogram[] = "Cookie.CookiePrefix.CaseVariantCount";
1153   if (prefix_case_insensitive > CookiePrefix::COOKIE_PREFIX_NONE) {
1154     UMA_HISTOGRAM_BOOLEAN(kVariantCountHistogram, is_case_variant);
1155   }
1156 }
1157 
1158 // Returns true if the cookie does not violate any constraints imposed
1159 // by the cookie name's prefix, as described in
1160 // https://tools.ietf.org/html/draft-west-cookie-prefixes
1161 //
1162 // static
IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,const GURL & url,const ParsedCookie & parsed_cookie)1163 bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,
1164                                           const GURL& url,
1165                                           const ParsedCookie& parsed_cookie) {
1166   return CanonicalCookie::IsCookiePrefixValid(
1167       prefix, url, parsed_cookie.IsSecure(),
1168       parsed_cookie.HasDomain() ? parsed_cookie.Domain() : "",
1169       parsed_cookie.HasPath() ? parsed_cookie.Path() : "");
1170 }
1171 
IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,const GURL & url,bool secure,const std::string & domain,const std::string & path)1172 bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,
1173                                           const GURL& url,
1174                                           bool secure,
1175                                           const std::string& domain,
1176                                           const std::string& path) {
1177   if (prefix == CanonicalCookie::COOKIE_PREFIX_SECURE)
1178     return secure && url.SchemeIsCryptographic();
1179   if (prefix == CanonicalCookie::COOKIE_PREFIX_HOST) {
1180     return HasValidHostPrefixAttributes(url, secure, domain, path);
1181   }
1182   return true;
1183 }
1184 
1185 // static
GetAndAdjustPortForTrustworthyUrls(const GURL & source_url,bool url_is_trustworthy)1186 int CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
1187     const GURL& source_url,
1188     bool url_is_trustworthy) {
1189   // If the url isn't trustworthy, or if `source_url` is cryptographic then
1190   // return the port of `source_url`.
1191   if (!url_is_trustworthy || source_url.SchemeIsCryptographic()) {
1192     return source_url.EffectiveIntPort();
1193   }
1194 
1195   // Only http and ws are cookieable schemes that have a port component. For
1196   // both of these schemes their default port is 80 whereas their secure
1197   // components have a default port of 443.
1198   //
1199   // Only in cases where we have an http/ws scheme with a default should we
1200   // return 443.
1201   if ((source_url.SchemeIs(url::kHttpScheme) ||
1202        source_url.SchemeIs(url::kWsScheme)) &&
1203       source_url.EffectiveIntPort() == 80) {
1204     return 443;
1205   }
1206 
1207   // Different schemes, or non-default port values should keep the same port
1208   // value.
1209   return source_url.EffectiveIntPort();
1210 }
1211 
1212 // static
HasHiddenPrefixName(const std::string_view cookie_value)1213 bool CanonicalCookie::HasHiddenPrefixName(const std::string_view cookie_value) {
1214   // Skip BWS as defined by HTTPSEM as SP or HTAB (0x20 or 0x9).
1215   std::string_view value_without_BWS =
1216       base::TrimString(cookie_value, " \t", base::TRIM_LEADING);
1217 
1218   const std::string_view host_prefix = "__Host-";
1219 
1220   // Compare the value to the host_prefix.
1221   if (base::StartsWith(value_without_BWS, host_prefix,
1222                        base::CompareCase::INSENSITIVE_ASCII)) {
1223     // This value contains a hidden prefix name.
1224     return true;
1225   }
1226 
1227   // Do a similar check for the secure prefix
1228   const std::string_view secure_prefix = "__Secure-";
1229 
1230   if (base::StartsWith(value_without_BWS, secure_prefix,
1231                        base::CompareCase::INSENSITIVE_ASCII)) {
1232     return true;
1233   }
1234 
1235   return false;
1236 }
1237 
1238 // static
IsCookiePartitionedValid(const GURL & url,const ParsedCookie & parsed_cookie,bool partition_has_nonce)1239 bool CanonicalCookie::IsCookiePartitionedValid(
1240     const GURL& url,
1241     const ParsedCookie& parsed_cookie,
1242     bool partition_has_nonce) {
1243   return IsCookiePartitionedValid(
1244       url, /*secure=*/parsed_cookie.IsSecure(),
1245       /*is_partitioned=*/parsed_cookie.IsPartitioned(), partition_has_nonce);
1246 }
1247 
1248 // static
IsCookiePartitionedValid(const GURL & url,bool secure,bool is_partitioned,bool partition_has_nonce)1249 bool CanonicalCookie::IsCookiePartitionedValid(const GURL& url,
1250                                                bool secure,
1251                                                bool is_partitioned,
1252                                                bool partition_has_nonce) {
1253   if (!is_partitioned)
1254     return true;
1255   if (partition_has_nonce)
1256     return true;
1257   CookieAccessScheme scheme = cookie_util::ProvisionalAccessScheme(url);
1258   bool result = (scheme != CookieAccessScheme::kNonCryptographic) && secure;
1259   DLOG_IF(WARNING, !result)
1260       << "CanonicalCookie has invalid Partitioned attribute";
1261   return result;
1262 }
1263 
1264 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult() = default;
1265 
CookieAndLineWithAccessResult(std::optional<CanonicalCookie> cookie,std::string cookie_string,CookieAccessResult access_result)1266 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
1267     std::optional<CanonicalCookie> cookie,
1268     std::string cookie_string,
1269     CookieAccessResult access_result)
1270     : cookie(std::move(cookie)),
1271       cookie_string(std::move(cookie_string)),
1272       access_result(access_result) {}
1273 
1274 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
1275     const CookieAndLineWithAccessResult&) = default;
1276 
1277 CookieAndLineWithAccessResult& CookieAndLineWithAccessResult::operator=(
1278     const CookieAndLineWithAccessResult& cookie_and_line_with_access_result) =
1279     default;
1280 
1281 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
1282     CookieAndLineWithAccessResult&&) = default;
1283 
1284 CookieAndLineWithAccessResult::~CookieAndLineWithAccessResult() = default;
1285 
1286 }  // namespace net
1287