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