• 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 #include "net/cookies/canonical_cookie.h"
46 
47 #include <limits>
48 #include <utility>
49 
50 #include "base/containers/contains.h"
51 #include "base/feature_list.h"
52 #include "base/format_macros.h"
53 #include "base/logging.h"
54 #include "base/memory/ptr_util.h"
55 #include "base/metrics/histogram_functions.h"
56 #include "base/metrics/histogram_macros.h"
57 #include "base/strings/strcat.h"
58 #include "base/strings/string_number_conversions.h"
59 #include "base/strings/string_piece.h"
60 #include "base/strings/string_util.h"
61 #include "base/strings/stringprintf.h"
62 #include "net/base/features.h"
63 #include "net/base/url_util.h"
64 #include "net/cookies/cookie_constants.h"
65 #include "net/cookies/cookie_inclusion_status.h"
66 #include "net/cookies/cookie_options.h"
67 #include "net/cookies/cookie_util.h"
68 #include "net/cookies/parsed_cookie.h"
69 #include "net/http/http_util.h"
70 #include "third_party/abseil-cpp/absl/types/optional.h"
71 #include "url/gurl.h"
72 #include "url/url_canon.h"
73 #include "url/url_util.h"
74 
75 using base::Time;
76 
77 namespace net {
78 
79 static constexpr int kMinutesInTwelveHours = 12 * 60;
80 static constexpr int kMinutesInTwentyFourHours = 24 * 60;
81 
82 namespace {
83 
84 // Determine the cookie domain to use for setting the specified cookie.
GetCookieDomain(const GURL & url,const ParsedCookie & pc,CookieInclusionStatus & status,std::string * result)85 bool GetCookieDomain(const GURL& url,
86                      const ParsedCookie& pc,
87                      CookieInclusionStatus& status,
88                      std::string* result) {
89   std::string domain_string;
90   if (pc.HasDomain())
91     domain_string = pc.Domain();
92   return cookie_util::GetCookieDomainWithString(url, domain_string, status,
93                                                 result);
94 }
95 
96 // Compares cookies using name, domain and path, so that "equivalent" cookies
97 // (per RFC 2965) are equal to each other.
PartialCookieOrdering(const CanonicalCookie & a,const CanonicalCookie & b)98 int PartialCookieOrdering(const CanonicalCookie& a, const CanonicalCookie& b) {
99   int diff = a.Name().compare(b.Name());
100   if (diff != 0)
101     return diff;
102 
103   diff = a.Domain().compare(b.Domain());
104   if (diff != 0)
105     return diff;
106 
107   return a.Path().compare(b.Path());
108 }
109 
AppendCookieLineEntry(const CanonicalCookie & cookie,std::string * cookie_line)110 void AppendCookieLineEntry(const CanonicalCookie& cookie,
111                            std::string* cookie_line) {
112   if (!cookie_line->empty())
113     *cookie_line += "; ";
114   // In Mozilla, if you set a cookie like "AAA", it will have an empty token
115   // and a value of "AAA". When it sends the cookie back, it will send "AAA",
116   // so we need to avoid sending "=AAA" for a blank token value.
117   if (!cookie.Name().empty())
118     *cookie_line += cookie.Name() + "=";
119   *cookie_line += cookie.Value();
120 }
121 
122 // Captures Strict -> Lax context downgrade with Strict cookie
IsBreakingStrictToLaxDowngrade(CookieOptions::SameSiteCookieContext::ContextType context,CookieOptions::SameSiteCookieContext::ContextType schemeful_context,CookieEffectiveSameSite effective_same_site,bool is_cookie_being_set)123 bool IsBreakingStrictToLaxDowngrade(
124     CookieOptions::SameSiteCookieContext::ContextType context,
125     CookieOptions::SameSiteCookieContext::ContextType schemeful_context,
126     CookieEffectiveSameSite effective_same_site,
127     bool is_cookie_being_set) {
128   if (context ==
129           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT &&
130       schemeful_context ==
131           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX &&
132       effective_same_site == CookieEffectiveSameSite::STRICT_MODE) {
133     // This downgrade only applies when a SameSite=Strict cookie is being sent.
134     // A Strict -> Lax downgrade will not affect a Strict cookie which is being
135     // set because it will be set in either context.
136     return !is_cookie_being_set;
137   }
138 
139   return false;
140 }
141 
142 // Captures Strict -> Cross-site context downgrade with {Strict, Lax} cookie
143 // Captures Strict -> Lax Unsafe context downgrade with {Strict, Lax} cookie.
144 // This is treated as a cross-site downgrade due to the Lax Unsafe context
145 // behaving like cross-site.
IsBreakingStrictToCrossDowngrade(CookieOptions::SameSiteCookieContext::ContextType context,CookieOptions::SameSiteCookieContext::ContextType schemeful_context,CookieEffectiveSameSite effective_same_site)146 bool IsBreakingStrictToCrossDowngrade(
147     CookieOptions::SameSiteCookieContext::ContextType context,
148     CookieOptions::SameSiteCookieContext::ContextType schemeful_context,
149     CookieEffectiveSameSite effective_same_site) {
150   bool breaking_schemeful_context =
151       schemeful_context ==
152           CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE ||
153       schemeful_context == CookieOptions::SameSiteCookieContext::ContextType::
154                                SAME_SITE_LAX_METHOD_UNSAFE;
155 
156   bool strict_lax_enforcement =
157       effective_same_site == CookieEffectiveSameSite::STRICT_MODE ||
158       effective_same_site == CookieEffectiveSameSite::LAX_MODE ||
159       // Treat LAX_MODE_ALLOW_UNSAFE the same as LAX_MODE for the purposes of
160       // our SameSite enforcement check.
161       effective_same_site == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE;
162 
163   if (context ==
164           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT &&
165       breaking_schemeful_context && strict_lax_enforcement) {
166     return true;
167   }
168 
169   return false;
170 }
171 
172 // Captures Lax -> Cross context downgrade with {Strict, Lax} cookies.
173 // Ignores Lax Unsafe context.
IsBreakingLaxToCrossDowngrade(CookieOptions::SameSiteCookieContext::ContextType context,CookieOptions::SameSiteCookieContext::ContextType schemeful_context,CookieEffectiveSameSite effective_same_site,bool is_cookie_being_set)174 bool IsBreakingLaxToCrossDowngrade(
175     CookieOptions::SameSiteCookieContext::ContextType context,
176     CookieOptions::SameSiteCookieContext::ContextType schemeful_context,
177     CookieEffectiveSameSite effective_same_site,
178     bool is_cookie_being_set) {
179   bool lax_enforcement =
180       effective_same_site == CookieEffectiveSameSite::LAX_MODE ||
181       // Treat LAX_MODE_ALLOW_UNSAFE the same as LAX_MODE for the purposes of
182       // our SameSite enforcement check.
183       effective_same_site == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE;
184 
185   if (context ==
186           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX &&
187       schemeful_context ==
188           CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE) {
189     // For SameSite=Strict cookies this downgrade only applies when it is being
190     // set. A Lax -> Cross downgrade will not affect a Strict cookie which is
191     // being sent because it wouldn't be sent in either context.
192     return effective_same_site == CookieEffectiveSameSite::STRICT_MODE
193                ? is_cookie_being_set
194                : lax_enforcement;
195   }
196 
197   return false;
198 }
199 
ApplySameSiteCookieWarningToStatus(CookieSameSite samesite,CookieEffectiveSameSite effective_samesite,bool is_secure,const CookieOptions::SameSiteCookieContext & same_site_context,CookieInclusionStatus * status,bool is_cookie_being_set)200 void ApplySameSiteCookieWarningToStatus(
201     CookieSameSite samesite,
202     CookieEffectiveSameSite effective_samesite,
203     bool is_secure,
204     const CookieOptions::SameSiteCookieContext& same_site_context,
205     CookieInclusionStatus* status,
206     bool is_cookie_being_set) {
207   if (samesite == CookieSameSite::UNSPECIFIED &&
208       same_site_context.GetContextForCookieInclusion() <
209           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
210     status->AddWarningReason(
211         CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
212   }
213   if (effective_samesite == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE &&
214       same_site_context.GetContextForCookieInclusion() ==
215           CookieOptions::SameSiteCookieContext::ContextType::
216               SAME_SITE_LAX_METHOD_UNSAFE) {
217     // This warning is more specific so remove the previous, more general,
218     // warning.
219     status->RemoveWarningReason(
220         CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT);
221     status->AddWarningReason(
222         CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE);
223   }
224   if (samesite == CookieSameSite::NO_RESTRICTION && !is_secure) {
225     status->AddWarningReason(
226         CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE);
227   }
228 
229   // Add a warning if the cookie would be accessible in
230   // |same_site_context|::context but not in
231   // |same_site_context|::schemeful_context.
232   if (IsBreakingStrictToLaxDowngrade(same_site_context.context(),
233                                      same_site_context.schemeful_context(),
234                                      effective_samesite, is_cookie_being_set)) {
235     status->AddWarningReason(
236         CookieInclusionStatus::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE);
237   } else if (IsBreakingStrictToCrossDowngrade(
238                  same_site_context.context(),
239                  same_site_context.schemeful_context(), effective_samesite)) {
240     // Which warning to apply depends on the SameSite value.
241     if (effective_samesite == CookieEffectiveSameSite::STRICT_MODE) {
242       status->AddWarningReason(
243           CookieInclusionStatus::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE);
244     } else {
245       // LAX_MODE or LAX_MODE_ALLOW_UNSAFE.
246       status->AddWarningReason(
247           CookieInclusionStatus::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE);
248     }
249 
250   } else if (IsBreakingLaxToCrossDowngrade(
251                  same_site_context.context(),
252                  same_site_context.schemeful_context(), effective_samesite,
253                  is_cookie_being_set)) {
254     // Which warning to apply depends on the SameSite value.
255     if (effective_samesite == CookieEffectiveSameSite::STRICT_MODE) {
256       status->AddWarningReason(
257           CookieInclusionStatus::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE);
258     } else {
259       // LAX_MODE or LAX_MODE_ALLOW_UNSAFE.
260       // This warning applies to both set/send.
261       status->AddWarningReason(
262           CookieInclusionStatus::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE);
263     }
264   }
265 
266   // Apply warning for whether inclusion was changed by considering redirects
267   // for the SameSite context calculation. This does not look at the actual
268   // inclusion or exclusion, but only at whether the inclusion differs between
269   // considering redirects and not.
270   using ContextDowngradeType = CookieOptions::SameSiteCookieContext::
271       ContextMetadata::ContextDowngradeType;
272   const auto& metadata = same_site_context.GetMetadataForCurrentSchemefulMode();
273   bool apply_cross_site_redirect_downgrade_warning = false;
274   switch (effective_samesite) {
275     case CookieEffectiveSameSite::STRICT_MODE:
276       // Strict contexts are all normalized to lax for cookie writes, so a
277       // strict-to-{lax,cross} downgrade cannot occur for response cookies.
278       apply_cross_site_redirect_downgrade_warning =
279           is_cookie_being_set ? metadata.cross_site_redirect_downgrade ==
280                                     ContextDowngradeType::kLaxToCross
281                               : (metadata.cross_site_redirect_downgrade ==
282                                      ContextDowngradeType::kStrictToLax ||
283                                  metadata.cross_site_redirect_downgrade ==
284                                      ContextDowngradeType::kStrictToCross);
285       break;
286     case CookieEffectiveSameSite::LAX_MODE:
287     case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
288       // Note that a lax-to-cross downgrade can only happen for response
289       // cookies, because a laxly same-site context only happens for a safe
290       // top-level cross-site request, which cannot be downgraded due to a
291       // cross-site redirect to a non-top-level or unsafe cross-site request.
292       apply_cross_site_redirect_downgrade_warning =
293           metadata.cross_site_redirect_downgrade ==
294           (is_cookie_being_set ? ContextDowngradeType::kLaxToCross
295                                : ContextDowngradeType::kStrictToCross);
296       break;
297     default:
298       break;
299   }
300   if (apply_cross_site_redirect_downgrade_warning) {
301     status->AddWarningReason(
302         CookieInclusionStatus::
303             WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION);
304   }
305 
306   // If there are reasons to exclude the cookie other than SameSite, don't warn
307   // about the cookie at all.
308   status->MaybeClearSameSiteWarning();
309 }
310 
311 // Converts CookieSameSite to CookieSameSiteForMetrics by adding 1 to it.
CookieSameSiteToCookieSameSiteForMetrics(CookieSameSite enum_in)312 CookieSameSiteForMetrics CookieSameSiteToCookieSameSiteForMetrics(
313     CookieSameSite enum_in) {
314   return static_cast<CookieSameSiteForMetrics>((static_cast<int>(enum_in) + 1));
315 }
316 
317 // Checks if `port` is within [0,65535] or url::PORT_UNSPECIFIED. Returns `port`
318 // if so and url::PORT_INVALID otherwise.
ValidateAndAdjustSourcePort(int port)319 int ValidateAndAdjustSourcePort(int port) {
320   if ((port >= 0 && port <= 65535) || port == url::PORT_UNSPECIFIED) {
321     // 0 would be really weird as it has a special meaning, but it's still
322     // technically a valid tcp/ip port so we're going to accept it here.
323     return port;
324   }
325 
326   return url::PORT_INVALID;
327 }
328 
329 // Tests that a cookie has the attributes for a valid __Host- prefix without
330 // testing that the prefix is in the cookie name.
HasValidHostPrefixAttributes(const GURL & url,bool secure,const std::string & domain,const std::string & path)331 bool HasValidHostPrefixAttributes(const GURL& url,
332                                   bool secure,
333                                   const std::string& domain,
334                                   const std::string& path) {
335   if (!secure || !url.SchemeIsCryptographic() || path != "/")
336     return false;
337   return domain.empty() || (url.HostIsIPAddress() && url.host() == domain);
338 }
339 
340 }  // namespace
341 
CookieAccessParams(CookieAccessSemantics access_semantics,bool delegate_treats_url_as_trustworthy)342 CookieAccessParams::CookieAccessParams(CookieAccessSemantics access_semantics,
343                                        bool delegate_treats_url_as_trustworthy)
344     : access_semantics(access_semantics),
345       delegate_treats_url_as_trustworthy(delegate_treats_url_as_trustworthy) {}
346 
347 CanonicalCookie::CanonicalCookie() = default;
348 
349 CanonicalCookie::CanonicalCookie(const CanonicalCookie& other) = default;
350 
351 CanonicalCookie::CanonicalCookie(CanonicalCookie&& other) = default;
352 
353 CanonicalCookie& CanonicalCookie::operator=(const CanonicalCookie& other) =
354     default;
355 
356 CanonicalCookie& CanonicalCookie::operator=(CanonicalCookie&& other) = default;
357 
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,absl::optional<CookiePartitionKey> partition_key,CookieSourceScheme source_scheme,int source_port)358 CanonicalCookie::CanonicalCookie(
359     base::PassKey<CanonicalCookie> pass_key,
360     std::string name,
361     std::string value,
362     std::string domain,
363     std::string path,
364     base::Time creation,
365     base::Time expiration,
366     base::Time last_access,
367     base::Time last_update,
368     bool secure,
369     bool httponly,
370     CookieSameSite same_site,
371     CookiePriority priority,
372     absl::optional<CookiePartitionKey> partition_key,
373     CookieSourceScheme source_scheme,
374     int source_port)
375     : name_(std::move(name)),
376       value_(std::move(value)),
377       domain_(std::move(domain)),
378       path_(std::move(path)),
379       creation_date_(creation),
380       expiry_date_(expiration),
381       last_access_date_(last_access),
382       last_update_date_(last_update),
383       secure_(secure),
384       httponly_(httponly),
385       same_site_(same_site),
386       priority_(priority),
387       partition_key_(std::move(partition_key)),
388       source_scheme_(source_scheme),
389       source_port_(source_port) {}
390 
391 CanonicalCookie::~CanonicalCookie() = default;
392 
393 // static
CanonPathWithString(const GURL & url,const std::string & path_string)394 std::string CanonicalCookie::CanonPathWithString(
395     const GURL& url,
396     const std::string& path_string) {
397   // The path was supplied in the cookie, we'll take it.
398   if (!path_string.empty() && path_string[0] == '/')
399     return path_string;
400 
401   // The path was not supplied in the cookie or invalid, we will default
402   // to the current URL path.
403   // """Defaults to the path of the request URL that generated the
404   //    Set-Cookie response, up to, but not including, the
405   //    right-most /."""
406   // How would this work for a cookie on /?  We will include it then.
407   const std::string& url_path = url.path();
408 
409   size_t idx = url_path.find_last_of('/');
410 
411   // The cookie path was invalid or a single '/'.
412   if (idx == 0 || idx == std::string::npos)
413     return std::string("/");
414 
415   // Return up to the rightmost '/'.
416   return url_path.substr(0, idx);
417 }
418 
419 // static
ParseExpiration(const ParsedCookie & pc,const Time & current,const Time & server_time)420 Time CanonicalCookie::ParseExpiration(const ParsedCookie& pc,
421                                       const Time& current,
422                                       const Time& server_time) {
423   // First, try the Max-Age attribute.
424   if (pc.HasMaxAge()) {
425     int64_t max_age = 0;
426     // Use the output if StringToInt64 returns true ("perfect" conversion). This
427     // case excludes overflow/underflow, leading/trailing whitespace, non-number
428     // strings, and empty string. (ParsedCookie trims whitespace.)
429     if (base::StringToInt64(pc.MaxAge(), &max_age)) {
430       // RFC 6265bis algorithm for parsing Max-Age:
431       // "If delta-seconds is less than or equal to zero (0), let expiry-
432       // time be the earliest representable date and time. ... "
433       if (max_age <= 0)
434         return Time::Min();
435       // "... Otherwise, let the expiry-time be the current date and time plus
436       // delta-seconds seconds."
437       return current + base::Seconds(max_age);
438     } else {
439       // If the conversion wasn't perfect, but the best-effort conversion
440       // resulted in an overflow/underflow, use the min/max representable time.
441       // (This is alluded to in the spec, which says the user agent MAY clip an
442       // Expires attribute to a saturated time. We'll do the same for Max-Age.)
443       if (max_age == std::numeric_limits<int64_t>::min())
444         return Time::Min();
445       if (max_age == std::numeric_limits<int64_t>::max())
446         return Time::Max();
447     }
448   }
449 
450   // Try the Expires attribute.
451   if (pc.HasExpires() && !pc.Expires().empty()) {
452     // Adjust for clock skew between server and host.
453     Time parsed_expiry = cookie_util::ParseCookieExpirationTime(pc.Expires());
454     if (!parsed_expiry.is_null()) {
455       // Record metrics related to prevalence of clock skew.
456       base::TimeDelta clock_skew = (current - server_time);
457       // Record the magnitude (absolute value) of the skew in minutes.
458       int clock_skew_magnitude = clock_skew.magnitude().InMinutes();
459       // Determine the new expiry with clock skew factored in.
460       Time adjusted_expiry = parsed_expiry + (current - server_time);
461       if (clock_skew.is_positive() || clock_skew.is_zero()) {
462         UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes",
463                                     clock_skew_magnitude, 1,
464                                     kMinutesInTwelveHours, 100);
465         UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes12To24Hours",
466                                     clock_skew_magnitude, kMinutesInTwelveHours,
467                                     kMinutesInTwentyFourHours, 100);
468         // Also record the range of minutes added that allowed the cookie to
469         // avoid expiring immediately.
470         if (parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now()) {
471           UMA_HISTOGRAM_CUSTOM_COUNTS(
472               "Cookie.ClockSkew.WithoutAddMinutesExpires", clock_skew_magnitude,
473               1, kMinutesInTwentyFourHours, 100);
474         }
475       } else if (clock_skew.is_negative()) {
476         // These histograms only support positive numbers, so negative skews
477         // will be converted to positive (via magnitude) before recording.
478         UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.SubtractMinutes",
479                                     clock_skew_magnitude, 1,
480                                     kMinutesInTwelveHours, 100);
481         UMA_HISTOGRAM_CUSTOM_COUNTS(
482             "Cookie.ClockSkew.SubtractMinutes12To24Hours", clock_skew_magnitude,
483             kMinutesInTwelveHours, kMinutesInTwentyFourHours, 100);
484       }
485       // Record if we were going to expire the cookie before we added the clock
486       // skew.
487       UMA_HISTOGRAM_BOOLEAN(
488           "Cookie.ClockSkew.ExpiredWithoutSkew",
489           parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now());
490       return adjusted_expiry;
491     }
492   }
493 
494   // Invalid or no expiration, session cookie.
495   return Time();
496 }
497 
498 // static
ValidateAndAdjustExpiryDate(const base::Time & expiry_date,const base::Time & creation_date)499 base::Time CanonicalCookie::ValidateAndAdjustExpiryDate(
500     const base::Time& expiry_date,
501     const base::Time& creation_date) {
502   if (expiry_date.is_null())
503     return expiry_date;
504   base::Time fixed_creation_date = creation_date;
505   if (fixed_creation_date.is_null()) {
506     // TODO(crbug.com/1264458): Push this logic into
507     // CanonicalCookie::CreateSanitizedCookie. The four sites that call it
508     // with a null `creation_date` (CanonicalCookie::Create cannot be called
509     // this way) are:
510     // * GaiaCookieManagerService::ForceOnCookieChangeProcessing
511     // * CookiesSetFunction::Run
512     // * cookie_store.cc::ToCanonicalCookie
513     // * network_handler.cc::MakeCookieFromProtocolValues
514     fixed_creation_date = base::Time::Now();
515   }
516   base::Time maximum_expiry_date = fixed_creation_date + base::Days(400);
517   if (expiry_date > maximum_expiry_date) {
518     return maximum_expiry_date;
519   }
520   return expiry_date;
521 }
522 
523 // static
Create(const GURL & url,const std::string & cookie_line,const base::Time & creation_time,absl::optional<base::Time> server_time,absl::optional<CookiePartitionKey> cookie_partition_key,bool block_truncated,CookieInclusionStatus * status)524 std::unique_ptr<CanonicalCookie> CanonicalCookie::Create(
525     const GURL& url,
526     const std::string& cookie_line,
527     const base::Time& creation_time,
528     absl::optional<base::Time> server_time,
529     absl::optional<CookiePartitionKey> cookie_partition_key,
530     bool block_truncated,
531     CookieInclusionStatus* status) {
532   // Put a pointer on the stack so the rest of the function can assign to it if
533   // the default nullptr is passed in.
534   CookieInclusionStatus blank_status;
535   if (status == nullptr) {
536     status = &blank_status;
537   }
538   *status = CookieInclusionStatus();
539 
540   // Check the URL; it may be nonsense since some platform APIs may permit
541   // it to be specified directly.
542   if (!url.is_valid()) {
543     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
544     return nullptr;
545   }
546 
547   ParsedCookie parsed_cookie(cookie_line, block_truncated, status);
548 
549   // We record this metric before checking validity because the presence of an
550   // HTAB will invalidate the ParsedCookie.
551   UMA_HISTOGRAM_BOOLEAN("Cookie.NameOrValueHtab",
552                         parsed_cookie.HasInternalHtab());
553 
554   if (!parsed_cookie.IsValid()) {
555     DVLOG(net::cookie_util::kVlogSetCookies)
556         << "WARNING: Couldn't parse cookie";
557     DCHECK(!status->IsInclude());
558     // Don't continue, because an invalid ParsedCookie doesn't have any
559     // attributes.
560     // TODO(chlily): Log metrics.
561     return nullptr;
562   }
563 
564   // Record warning for non-ASCII octecs in the Domain attribute.
565   // This should lead to rejection of the cookie in the future.
566   UMA_HISTOGRAM_BOOLEAN("Cookie.DomainHasNonASCII",
567                         parsed_cookie.HasDomain() &&
568                             !base::IsStringASCII(parsed_cookie.Domain()));
569 
570   std::string cookie_domain;
571   if (!GetCookieDomain(url, parsed_cookie, *status, &cookie_domain)) {
572     DVLOG(net::cookie_util::kVlogSetCookies)
573         << "Create() failed to get a valid cookie domain";
574     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
575   }
576 
577   std::string cookie_path = CanonPathWithString(
578       url, parsed_cookie.HasPath() ? parsed_cookie.Path() : std::string());
579 
580   Time cookie_server_time(creation_time);
581   if (server_time.has_value() && !server_time->is_null())
582     cookie_server_time = server_time.value();
583 
584   DCHECK(!creation_time.is_null());
585   Time cookie_expires = CanonicalCookie::ParseExpiration(
586       parsed_cookie, creation_time, cookie_server_time);
587   cookie_expires = ValidateAndAdjustExpiryDate(cookie_expires, creation_time);
588 
589   CookiePrefix prefix_case_sensitive =
590       GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/false);
591   CookiePrefix prefix_case_insensitive =
592       GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/true);
593 
594   bool is_sensitive_prefix_valid =
595       IsCookiePrefixValid(prefix_case_sensitive, url, parsed_cookie);
596   bool is_insensitive_prefix_valid =
597       IsCookiePrefixValid(prefix_case_insensitive, url, parsed_cookie);
598   bool is_cookie_prefix_valid =
599       base::FeatureList::IsEnabled(net::features::kCaseInsensitiveCookiePrefix)
600           ? is_insensitive_prefix_valid
601           : is_sensitive_prefix_valid;
602 
603   RecordCookiePrefixMetrics(prefix_case_sensitive, prefix_case_insensitive,
604                             is_insensitive_prefix_valid);
605 
606   if (parsed_cookie.Name() == "") {
607     is_cookie_prefix_valid = !HasHiddenPrefixName(parsed_cookie.Value());
608   }
609 
610   if (!is_cookie_prefix_valid) {
611     DVLOG(net::cookie_util::kVlogSetCookies)
612         << "Create() failed because the cookie violated prefix rules.";
613     status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
614   }
615 
616   bool partition_has_nonce = CookiePartitionKey::HasNonce(cookie_partition_key);
617   bool is_partitioned_valid =
618       IsCookiePartitionedValid(url, parsed_cookie, partition_has_nonce);
619   if (!is_partitioned_valid) {
620     status->AddExclusionReason(
621         CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED);
622   }
623 
624   // Collect metrics on whether usage of the Partitioned attribute is correct.
625   // Do not include implicit nonce-based partitioned cookies in these metrics.
626   if (parsed_cookie.IsPartitioned()) {
627     if (!partition_has_nonce)
628       UMA_HISTOGRAM_BOOLEAN("Cookie.IsPartitionedValid", is_partitioned_valid);
629   } else if (!partition_has_nonce) {
630     cookie_partition_key = absl::nullopt;
631   }
632 
633   if (!status->IsInclude())
634     return nullptr;
635 
636   CookieSameSiteString samesite_string = CookieSameSiteString::kUnspecified;
637   CookieSameSite samesite = parsed_cookie.SameSite(&samesite_string);
638 
639   // The next two sections set the source_scheme_ and source_port_. Normally
640   // these are taken directly from the url's scheme and port but if the url
641   // setting this cookie is considered a trustworthy origin then we may make
642   // some modifications. Note that here we assume that a trustworthy url must
643   // have a non-secure scheme (http). Since we can't know at this point if a url
644   // is trustworthy or not, we'll assume it is if the cookie is set with the
645   // `Secure` attribute.
646   //
647   // For both convenience and to try to match expectations, cookies that have
648   // the `Secure` attribute are modified to look like they were created by a
649   // secure url. This is helpful because this cookie can be treated like any
650   // other secure cookie when we're retrieving them and helps to prevent the
651   // cookie from getting "trapped" if the url loses trustworthiness.
652 
653   CookieSourceScheme source_scheme;
654   if (parsed_cookie.IsSecure() || url.SchemeIsCryptographic()) {
655     // It's possible that a trustworthy origin is setting this cookie with the
656     // `Secure` attribute even if the url's scheme isn't secure. In that case
657     // we'll act like it was a secure scheme. This cookie will be rejected later
658     // if the url isn't allowed to access secure cookies so this isn't a
659     // problem.
660     source_scheme = CookieSourceScheme::kSecure;
661 
662     if (!url.SchemeIsCryptographic()) {
663       status->AddWarningReason(
664           CookieInclusionStatus::
665               WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
666     }
667   } else {
668     source_scheme = CookieSourceScheme::kNonSecure;
669   }
670 
671   // Get the port, this will get a default value if a port isn't explicitly
672   // provided. Similar to the source scheme, it's possible that a trustworthy
673   // origin is setting this cookie with the `Secure` attribute even if the url's
674   // scheme isn't secure. This function will return 443 to pretend like this
675   // cookie was set by a secure scheme.
676   int source_port = CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
677       url, parsed_cookie.IsSecure());
678 
679   auto cc = std::make_unique<CanonicalCookie>(
680       base::PassKey<CanonicalCookie>(), parsed_cookie.Name(),
681       parsed_cookie.Value(), cookie_domain, cookie_path, creation_time,
682       cookie_expires, creation_time,
683       /*last_update=*/base::Time::Now(), parsed_cookie.IsSecure(),
684       parsed_cookie.IsHttpOnly(), samesite, parsed_cookie.Priority(),
685       cookie_partition_key, source_scheme, source_port);
686 
687   // TODO(chlily): Log metrics.
688   if (!cc->IsCanonical()) {
689     status->AddExclusionReason(
690         net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
691     return nullptr;
692   }
693 
694   RecordCookieSameSiteAttributeValueHistogram(samesite_string);
695 
696   // These metrics capture whether or not a cookie has a Non-ASCII character in
697   // it.
698   UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Name",
699                         !base::IsStringASCII(cc->Name()));
700   UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Value",
701                         !base::IsStringASCII(cc->Value()));
702 
703   // Check for "__" prefixed names, excluding the cookie prefixes.
704   bool name_prefixed_with_underscores =
705       (prefix_case_insensitive == CanonicalCookie::COOKIE_PREFIX_NONE) &&
706       parsed_cookie.Name().starts_with("__");
707 
708   UMA_HISTOGRAM_BOOLEAN("Cookie.DoubleUnderscorePrefixedName",
709                         name_prefixed_with_underscores);
710 
711   UMA_HISTOGRAM_ENUMERATION(
712       "Cookie.TruncatingCharacterInCookieString",
713       parsed_cookie.GetTruncatingCharacterInCookieStringType());
714 
715   return cc;
716 }
717 
718 // 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,absl::optional<CookiePartitionKey> partition_key,CookieInclusionStatus * status)719 std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie(
720     const GURL& url,
721     const std::string& name,
722     const std::string& value,
723     const std::string& domain,
724     const std::string& path,
725     base::Time creation_time,
726     base::Time expiration_time,
727     base::Time last_access_time,
728     bool secure,
729     bool http_only,
730     CookieSameSite same_site,
731     CookiePriority priority,
732     absl::optional<CookiePartitionKey> partition_key,
733     CookieInclusionStatus* status) {
734   // Put a pointer on the stack so the rest of the function can assign to it if
735   // the default nullptr is passed in.
736   CookieInclusionStatus blank_status;
737   if (status == nullptr) {
738     status = &blank_status;
739   }
740   *status = CookieInclusionStatus();
741 
742   // Validate consistency of passed arguments.
743   if (ParsedCookie::ParseTokenString(name) != name) {
744     status->AddExclusionReason(
745         net::CookieInclusionStatus::EXCLUDE_DISALLOWED_CHARACTER);
746   } else if (ParsedCookie::ParseValueString(value) != value) {
747     status->AddExclusionReason(
748         net::CookieInclusionStatus::EXCLUDE_DISALLOWED_CHARACTER);
749   } else if (ParsedCookie::ParseValueString(path) != path) {
750     // NOTE: If `path` contains  "terminating characters" ('\r', '\n', and
751     // '\0'), ';', or leading / trailing whitespace, path will be rejected,
752     // but any other control characters will just get URL-encoded below.
753     status->AddExclusionReason(
754         net::CookieInclusionStatus::EXCLUDE_DISALLOWED_CHARACTER);
755   }
756 
757   // Validate name and value against character set and size limit constraints.
758   // If IsValidCookieNameValuePair identifies that `name` and/or `value` are
759   // invalid, it will add an ExclusionReason to `status`.
760   ParsedCookie::IsValidCookieNameValuePair(name, value, status);
761 
762   // Validate domain against character set and size limit constraints.
763   bool domain_is_valid = true;
764 
765   if ((ParsedCookie::ParseValueString(domain) != domain)) {
766     status->AddExclusionReason(
767         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
768     domain_is_valid = false;
769   }
770 
771   if (!ParsedCookie::CookieAttributeValueHasValidCharSet(domain)) {
772     status->AddExclusionReason(
773         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
774     domain_is_valid = false;
775   }
776   if (!ParsedCookie::CookieAttributeValueHasValidSize(domain)) {
777     status->AddExclusionReason(
778         net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
779     domain_is_valid = false;
780   }
781   const std::string& domain_attribute =
782       domain_is_valid ? domain : std::string();
783 
784   std::string cookie_domain;
785   // This validation step must happen before GetCookieDomainWithString, so it
786   // doesn't fail DCHECKs.
787   if (!cookie_util::DomainIsHostOnly(url.host())) {
788     status->AddExclusionReason(
789         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
790   } else if (!cookie_util::GetCookieDomainWithString(url, domain_attribute,
791                                                      *status, &cookie_domain)) {
792     status->AddExclusionReason(
793         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
794   }
795 
796   // The next two sections set the source_scheme_ and source_port_. Normally
797   // these are taken directly from the url's scheme and port but if the url
798   // setting this cookie is considered a trustworthy origin then we may make
799   // some modifications. Note that here we assume that a trustworthy url must
800   // have a non-secure scheme (http). Since we can't know at this point if a url
801   // is trustworthy or not, we'll assume it is if the cookie is set with the
802   // `Secure` attribute.
803   //
804   // For both convenience and to try to match expectations, cookies that have
805   // the `Secure` attribute are modified to look like they were created by a
806   // secure url. This is helpful because this cookie can be treated like any
807   // other secure cookie when we're retrieving them and helps to prevent the
808   // cookie from getting "trapped" if the url loses trustworthiness.
809 
810   CookieSourceScheme source_scheme = CookieSourceScheme::kNonSecure;
811   // This validation step must happen before SchemeIsCryptographic, so it
812   // doesn't fail DCHECKs.
813   if (!url.is_valid()) {
814     status->AddExclusionReason(
815         net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN);
816   } else {
817     // It's possible that a trustworthy origin is setting this cookie with the
818     // `Secure` attribute even if the url's scheme isn't secure. In that case
819     // we'll act like it was a secure scheme. This cookie will be rejected later
820     // if the url isn't allowed to access secure cookies so this isn't a
821     // problem.
822     source_scheme = (secure || url.SchemeIsCryptographic())
823                         ? CookieSourceScheme::kSecure
824                         : CookieSourceScheme::kNonSecure;
825 
826     if (source_scheme == CookieSourceScheme::kSecure &&
827         !url.SchemeIsCryptographic()) {
828       status->AddWarningReason(
829           CookieInclusionStatus::
830               WARN_TENTATIVELY_ALLOWING_SECURE_SOURCE_SCHEME);
831     }
832   }
833 
834   // Get the port, this will get a default value if a port isn't explicitly
835   // provided. Similar to the source scheme, it's possible that a trustworthy
836   // origin is setting this cookie with the `Secure` attribute even if the url's
837   // scheme isn't secure. This function will return 443 to pretend like this
838   // cookie was set by a secure scheme.
839   int source_port =
840       CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(url, secure);
841 
842   std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path);
843   // Canonicalize path again to make sure it escapes characters as needed.
844   url::Component path_component(0, cookie_path.length());
845   url::RawCanonOutputT<char> canon_path;
846   url::Component canon_path_component;
847   url::CanonicalizePath(cookie_path.data(), path_component, &canon_path,
848                         &canon_path_component);
849   std::string encoded_cookie_path = std::string(
850       canon_path.data() + canon_path_component.begin, canon_path_component.len);
851 
852   if (!path.empty()) {
853     if (cookie_path != path) {
854       // The path attribute was specified and found to be invalid, so record an
855       // error.
856       status->AddExclusionReason(
857           net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
858     } else if (!ParsedCookie::CookieAttributeValueHasValidSize(
859                    encoded_cookie_path)) {
860       // The path attribute was specified and encodes into a value that's longer
861       // than the length limit, so record an error.
862       status->AddExclusionReason(
863           net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE);
864     }
865   }
866 
867   CookiePrefix prefix = GetCookiePrefix(name);
868   if (!IsCookiePrefixValid(prefix, url, secure, domain_attribute,
869                            cookie_path)) {
870     status->AddExclusionReason(
871         net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
872   }
873 
874   if (name == "" && HasHiddenPrefixName(value)) {
875     status->AddExclusionReason(
876         net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX);
877   }
878 
879   if (!IsCookiePartitionedValid(url, secure,
880                                 /*is_partitioned=*/partition_key.has_value(),
881                                 /*partition_has_nonce=*/
882                                 CookiePartitionKey::HasNonce(partition_key))) {
883     status->AddExclusionReason(
884         net::CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED);
885   }
886 
887   if (!last_access_time.is_null() && creation_time.is_null()) {
888     status->AddExclusionReason(
889         net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE);
890   }
891   expiration_time = ValidateAndAdjustExpiryDate(expiration_time, creation_time);
892 
893   if (!status->IsInclude())
894     return nullptr;
895 
896   auto cc = std::make_unique<CanonicalCookie>(
897       base::PassKey<CanonicalCookie>(), name, value, cookie_domain,
898       encoded_cookie_path, creation_time, expiration_time, last_access_time,
899       /*last_update=*/base::Time::Now(), secure, http_only, same_site, priority,
900       partition_key, source_scheme, source_port);
901   DCHECK(cc->IsCanonical());
902 
903   return cc;
904 }
905 
906 // 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,absl::optional<CookiePartitionKey> partition_key,CookieSourceScheme source_scheme,int source_port)907 std::unique_ptr<CanonicalCookie> CanonicalCookie::FromStorage(
908     std::string name,
909     std::string value,
910     std::string domain,
911     std::string path,
912     base::Time creation,
913     base::Time expiration,
914     base::Time last_access,
915     base::Time last_update,
916     bool secure,
917     bool httponly,
918     CookieSameSite same_site,
919     CookiePriority priority,
920     absl::optional<CookiePartitionKey> partition_key,
921     CookieSourceScheme source_scheme,
922     int source_port) {
923   // We check source_port here because it could have concievably been
924   // corrupted and changed to out of range. Eventually this would be caught by
925   // IsCanonical*() but since the source_port is only used by metrics so far
926   // nothing else checks it. So let's normalize it here and then update this
927   // method when origin-bound cookies is implemented.
928   // TODO(crbug.com/1170548)
929   int validated_port = ValidateAndAdjustSourcePort(source_port);
930 
931   auto cc = std::make_unique<CanonicalCookie>(
932       base::PassKey<CanonicalCookie>(), std::move(name), std::move(value),
933       std::move(domain), std::move(path), creation, expiration, last_access,
934       last_update, secure, httponly, same_site, priority, partition_key,
935       source_scheme, validated_port);
936 
937   if (cc->IsCanonicalForFromStorage()) {
938     // This will help capture the number of times a cookie is canonical but does
939     // not have a valid name+value size length
940     bool valid_cookie_name_value_pair =
941         ParsedCookie::IsValidCookieNameValuePair(cc->Name(), cc->Value());
942     UMA_HISTOGRAM_BOOLEAN("Cookie.FromStorageWithValidLength",
943                           valid_cookie_name_value_pair);
944   } else {
945     return nullptr;
946   }
947   return cc;
948 }
949 
950 // 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,absl::optional<CookiePartitionKey> partition_key,CookieSourceScheme source_scheme,int source_port)951 std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateUnsafeCookieForTesting(
952     const std::string& name,
953     const std::string& value,
954     const std::string& domain,
955     const std::string& path,
956     const base::Time& creation,
957     const base::Time& expiration,
958     const base::Time& last_access,
959     const base::Time& last_update,
960     bool secure,
961     bool httponly,
962     CookieSameSite same_site,
963     CookiePriority priority,
964     absl::optional<CookiePartitionKey> partition_key,
965     CookieSourceScheme source_scheme,
966     int source_port) {
967   return std::make_unique<CanonicalCookie>(
968       base::PassKey<CanonicalCookie>(), name, value, domain, path, creation,
969       expiration, last_access, last_update, secure, httponly, same_site,
970       priority, partition_key, source_scheme, source_port);
971 }
972 
IsFirstPartyPartitioned() const973 bool CanonicalCookie::IsFirstPartyPartitioned() const {
974   return IsPartitioned() && !CookiePartitionKey::HasNonce(partition_key_) &&
975          SchemefulSite(GURL(
976              base::StrCat({url::kHttpsScheme, url::kStandardSchemeSeparator,
977                            DomainWithoutDot()}))) == partition_key_->site();
978 }
979 
IsThirdPartyPartitioned() const980 bool CanonicalCookie::IsThirdPartyPartitioned() const {
981   return IsPartitioned() && !IsFirstPartyPartitioned();
982 }
983 
DomainWithoutDot() const984 std::string CanonicalCookie::DomainWithoutDot() const {
985   return cookie_util::CookieDomainAsHost(domain_);
986 }
987 
SetSourcePort(int port)988 void CanonicalCookie::SetSourcePort(int port) {
989   source_port_ = ValidateAndAdjustSourcePort(port);
990 }
991 
UniqueKey() const992 CanonicalCookie::UniqueCookieKey CanonicalCookie::UniqueKey() const {
993   absl::optional<CookieSourceScheme> source_scheme =
994       cookie_util::IsSchemeBoundCookiesEnabled()
995           ? absl::make_optional(source_scheme_)
996           : absl::nullopt;
997   absl::optional<int> source_port = cookie_util::IsPortBoundCookiesEnabled()
998                                         ? absl::make_optional(source_port_)
999                                         : absl::nullopt;
1000 
1001   return std::make_tuple(partition_key_, name_, domain_, path_, source_scheme,
1002                          source_port);
1003 }
1004 
IsEquivalentForSecureCookieMatching(const CanonicalCookie & secure_cookie) const1005 bool CanonicalCookie::IsEquivalentForSecureCookieMatching(
1006     const CanonicalCookie& secure_cookie) const {
1007   // Partition keys must both be equivalent.
1008   bool same_partition_key = PartitionKey() == secure_cookie.PartitionKey();
1009 
1010   // Names must be the same
1011   bool same_name = name_ == secure_cookie.Name();
1012 
1013   // They should domain-match in one direction or the other. (See RFC 6265bis
1014   // section 5.1.3.)
1015   // TODO(chlily): This does not check for the IP address case. This is bad due
1016   // to https://crbug.com/1069935.
1017   bool domain_match =
1018       IsSubdomainOf(DomainWithoutDot(), secure_cookie.DomainWithoutDot()) ||
1019       IsSubdomainOf(secure_cookie.DomainWithoutDot(), DomainWithoutDot());
1020 
1021   bool path_match = secure_cookie.IsOnPath(Path());
1022 
1023   bool equivalent_for_secure_cookie_matching =
1024       same_partition_key && same_name && domain_match && path_match;
1025 
1026   // IsEquivalent() is a stricter check than this.
1027   DCHECK(!IsEquivalent(secure_cookie) || equivalent_for_secure_cookie_matching);
1028 
1029   return equivalent_for_secure_cookie_matching;
1030 }
1031 
IsOnPath(const std::string & url_path) const1032 bool CanonicalCookie::IsOnPath(const std::string& url_path) const {
1033   return cookie_util::IsOnPath(path_, url_path);
1034 }
1035 
IsDomainMatch(const std::string & host) const1036 bool CanonicalCookie::IsDomainMatch(const std::string& host) const {
1037   return cookie_util::IsDomainMatch(domain_, host);
1038 }
1039 
IncludeForRequestURL(const GURL & url,const CookieOptions & options,const CookieAccessParams & params) const1040 CookieAccessResult CanonicalCookie::IncludeForRequestURL(
1041     const GURL& url,
1042     const CookieOptions& options,
1043     const CookieAccessParams& params) const {
1044   CookieInclusionStatus status;
1045   // Filter out HttpOnly cookies, per options.
1046   if (options.exclude_httponly() && IsHttpOnly())
1047     status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_HTTP_ONLY);
1048   // Secure cookies should not be included in requests for URLs with an
1049   // insecure scheme, unless it is a localhost url, or the CookieAccessDelegate
1050   // otherwise denotes them as trustworthy
1051   // (`delegate_treats_url_as_trustworthy`).
1052   bool is_allowed_to_access_secure_cookies = false;
1053   CookieAccessScheme cookie_access_scheme =
1054       cookie_util::ProvisionalAccessScheme(url);
1055   if (cookie_access_scheme == CookieAccessScheme::kNonCryptographic &&
1056       params.delegate_treats_url_as_trustworthy) {
1057     cookie_access_scheme = CookieAccessScheme::kTrustworthy;
1058   }
1059   switch (cookie_access_scheme) {
1060     case CookieAccessScheme::kNonCryptographic:
1061       if (IsSecure())
1062         status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SECURE_ONLY);
1063       break;
1064     case CookieAccessScheme::kTrustworthy:
1065       is_allowed_to_access_secure_cookies = true;
1066       if (IsSecure() || (cookie_util::IsSchemeBoundCookiesEnabled() &&
1067                          source_scheme_ == CookieSourceScheme::kSecure)) {
1068         status.AddWarningReason(
1069             CookieInclusionStatus::
1070                 WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC);
1071       }
1072       break;
1073     case CookieAccessScheme::kCryptographic:
1074       is_allowed_to_access_secure_cookies = true;
1075       break;
1076   }
1077 
1078   // For the following two sections we're checking to see if a cookie's
1079   // `source_scheme_` and `source_port_` match that of the url's. In most cases
1080   // this is a direct comparison but it does get a bit more complicated when
1081   // trustworthy origins are taken into accounts. Note that here, a kTrustworthy
1082   // url must have a non-secure scheme (http) because otherwise it'd be a
1083   // kCryptographic url.
1084   //
1085   // Trustworthy origins are allowed to both secure and non-secure cookies. This
1086   // means that we'll match source_scheme_ for both their usual kNonSecure as
1087   // well as KSecure. For source_port_ we'll match per usual as well as any 443
1088   // ports, since those are the default values for secure cookies and we still
1089   // want to be able to access them.
1090 
1091   // A cookie with a source scheme of kSecure shouldn't be accessible by
1092   // kNonCryptographic urls. But we can skip adding a status if the cookie is
1093   // already blocked due to the `Secure` attribute.
1094   if (source_scheme_ == CookieSourceScheme::kSecure &&
1095       cookie_access_scheme == CookieAccessScheme::kNonCryptographic &&
1096       !status.HasExclusionReason(CookieInclusionStatus::EXCLUDE_SECURE_ONLY)) {
1097     if (cookie_util::IsSchemeBoundCookiesEnabled()) {
1098       status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SCHEME_MISMATCH);
1099     } else {
1100       status.AddWarningReason(CookieInclusionStatus::WARN_SCHEME_MISMATCH);
1101     }
1102   }
1103   // A cookie with a source scheme of kNonSecure shouldn't be accessible by
1104   // kCryptographic urls.
1105   else if (source_scheme_ == CookieSourceScheme::kNonSecure &&
1106            cookie_access_scheme == CookieAccessScheme::kCryptographic) {
1107     if (cookie_util::IsSchemeBoundCookiesEnabled()) {
1108       status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SCHEME_MISMATCH);
1109     } else {
1110       status.AddWarningReason(CookieInclusionStatus::WARN_SCHEME_MISMATCH);
1111     }
1112   }
1113   // Else, the cookie has a source scheme of kUnset or the access scheme is
1114   // kTrustworthy. Neither of which will block the cookie.
1115 
1116   int url_port = url.EffectiveIntPort();
1117   CHECK(url_port != url::PORT_INVALID);
1118   // The cookie's source port either must match the url's port, be
1119   // PORT_UNSPECIFIED, or the cookie must be a domain cookie.
1120   bool port_matches = url_port == source_port_ ||
1121                       source_port_ == url::PORT_UNSPECIFIED || IsDomainCookie();
1122 
1123   // Or if the url is trustworthy, we'll also match 443 (in order to get secure
1124   // cookies).
1125   bool trustworthy_and_443 =
1126       cookie_access_scheme == CookieAccessScheme::kTrustworthy &&
1127       source_port_ == 443;
1128   if (!port_matches && !trustworthy_and_443) {
1129     if (cookie_util::IsPortBoundCookiesEnabled()) {
1130       status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_PORT_MISMATCH);
1131     } else {
1132       status.AddWarningReason(CookieInclusionStatus::WARN_PORT_MISMATCH);
1133     }
1134   }
1135 
1136   // Don't include cookies for requests that don't apply to the cookie domain.
1137   if (!IsDomainMatch(url.host()))
1138     status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_DOMAIN_MISMATCH);
1139   // Don't include cookies for requests with a url path that does not path
1140   // match the cookie-path.
1141   if (!IsOnPath(url.path()))
1142     status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_NOT_ON_PATH);
1143 
1144   // For LEGACY cookies we should always return the schemeless context,
1145   // otherwise let GetContextForCookieInclusion() decide.
1146   CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context =
1147       params.access_semantics == CookieAccessSemantics::LEGACY
1148           ? options.same_site_cookie_context().context()
1149           : options.same_site_cookie_context().GetContextForCookieInclusion();
1150 
1151   // Don't include same-site cookies for cross-site requests.
1152   CookieEffectiveSameSite effective_same_site =
1153       GetEffectiveSameSite(params.access_semantics);
1154   DCHECK(effective_same_site != CookieEffectiveSameSite::UNDEFINED);
1155   UMA_HISTOGRAM_ENUMERATION(
1156       "Cookie.RequestSameSiteContext", cookie_inclusion_context,
1157       CookieOptions::SameSiteCookieContext::ContextType::COUNT);
1158 
1159   switch (effective_same_site) {
1160     case CookieEffectiveSameSite::STRICT_MODE:
1161       if (cookie_inclusion_context <
1162           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT) {
1163         status.AddExclusionReason(
1164             CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT);
1165       }
1166       break;
1167     case CookieEffectiveSameSite::LAX_MODE:
1168       if (cookie_inclusion_context <
1169           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
1170         status.AddExclusionReason(
1171             (SameSite() == CookieSameSite::UNSPECIFIED)
1172                 ? CookieInclusionStatus::
1173                       EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX
1174                 : CookieInclusionStatus::EXCLUDE_SAMESITE_LAX);
1175       }
1176       break;
1177     // TODO(crbug.com/990439): Add a browsertest for this behavior.
1178     case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
1179       DCHECK(SameSite() == CookieSameSite::UNSPECIFIED);
1180       if (cookie_inclusion_context <
1181           CookieOptions::SameSiteCookieContext::ContextType::
1182               SAME_SITE_LAX_METHOD_UNSAFE) {
1183         // TODO(chlily): Do we need a separate CookieInclusionStatus for this?
1184         status.AddExclusionReason(
1185             CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
1186       }
1187       break;
1188     default:
1189       break;
1190   }
1191 
1192   // For the metric, we only want to consider first party partitioned cookies.
1193   if (IsFirstPartyPartitioned()) {
1194     UMA_HISTOGRAM_BOOLEAN(
1195         "Cookie.FirstPartyPartitioned.HasCrossSiteAncestor",
1196         cookie_inclusion_context ==
1197             CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE);
1198   }
1199 
1200   // Unless legacy access semantics are in effect, SameSite=None cookies without
1201   // the Secure attribute should be ignored. This can apply to cookies which
1202   // were created before "SameSite=None requires Secure" was enabled (as
1203   // SameSite=None insecure cookies cannot be set while the options are on).
1204   if (params.access_semantics != CookieAccessSemantics::LEGACY &&
1205       SameSite() == CookieSameSite::NO_RESTRICTION && !IsSecure()) {
1206     status.AddExclusionReason(
1207         CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE);
1208   }
1209 
1210   ApplySameSiteCookieWarningToStatus(SameSite(), effective_same_site,
1211                                      IsSecure(),
1212                                      options.same_site_cookie_context(),
1213                                      &status, false /* is_cookie_being_set */);
1214 
1215   if (status.IsInclude()) {
1216     UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedRequestEffectiveSameSite",
1217                               effective_same_site,
1218                               CookieEffectiveSameSite::COUNT);
1219   }
1220 
1221   using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext::
1222       ContextMetadata::ContextRedirectTypeBug1221316;
1223 
1224   ContextRedirectTypeBug1221316 redirect_type_for_metrics =
1225       options.same_site_cookie_context()
1226           .GetMetadataForCurrentSchemefulMode()
1227           .redirect_type_bug_1221316;
1228   if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) {
1229     UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Read",
1230                               redirect_type_for_metrics);
1231   }
1232 
1233   if (status.HasWarningReason(
1234           CookieInclusionStatus::
1235               WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
1236     UMA_HISTOGRAM_ENUMERATION(
1237         "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Read",
1238         CookieSameSiteToCookieSameSiteForMetrics(SameSite()));
1239 
1240     using HttpMethod =
1241         CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod;
1242 
1243     HttpMethod http_method_enum = options.same_site_cookie_context()
1244                                       .GetMetadataForCurrentSchemefulMode()
1245                                       .http_method_bug_1221316;
1246 
1247     DCHECK(http_method_enum != HttpMethod::kUnset);
1248 
1249     UMA_HISTOGRAM_ENUMERATION(
1250         "Cookie.CrossSiteRedirectDowngradeChangesInclusionHttpMethod",
1251         http_method_enum);
1252 
1253     base::TimeDelta cookie_age = base::Time::Now() - creation_date_;
1254     UMA_HISTOGRAM_EXACT_LINEAR(
1255         "Cookie.CrossSiteRedirectDowngradeChangesInclusionAge",
1256         cookie_age.InMinutes(), 30);
1257   }
1258 
1259   return CookieAccessResult(effective_same_site, status,
1260                             params.access_semantics,
1261                             is_allowed_to_access_secure_cookies);
1262 }
1263 
IsSetPermittedInContext(const GURL & source_url,const CookieOptions & options,const CookieAccessParams & params,const std::vector<std::string> & cookieable_schemes,const absl::optional<CookieAccessResult> & cookie_access_result) const1264 CookieAccessResult CanonicalCookie::IsSetPermittedInContext(
1265     const GURL& source_url,
1266     const CookieOptions& options,
1267     const CookieAccessParams& params,
1268     const std::vector<std::string>& cookieable_schemes,
1269     const absl::optional<CookieAccessResult>& cookie_access_result) const {
1270   CookieAccessResult access_result;
1271   if (cookie_access_result) {
1272     access_result = *cookie_access_result;
1273   }
1274 
1275   if (!base::Contains(cookieable_schemes, source_url.scheme())) {
1276     access_result.status.AddExclusionReason(
1277         CookieInclusionStatus::EXCLUDE_NONCOOKIEABLE_SCHEME);
1278   }
1279 
1280   CookieAccessScheme access_scheme =
1281       cookie_util::ProvisionalAccessScheme(source_url);
1282   if (access_scheme == CookieAccessScheme::kNonCryptographic &&
1283       params.delegate_treats_url_as_trustworthy) {
1284     access_scheme = CookieAccessScheme::kTrustworthy;
1285   }
1286 
1287   switch (access_scheme) {
1288     case CookieAccessScheme::kNonCryptographic:
1289       access_result.is_allowed_to_access_secure_cookies = false;
1290       if (IsSecure()) {
1291         access_result.status.AddExclusionReason(
1292             CookieInclusionStatus::EXCLUDE_SECURE_ONLY);
1293       }
1294       break;
1295 
1296     case CookieAccessScheme::kCryptographic:
1297       // All cool!
1298       access_result.is_allowed_to_access_secure_cookies = true;
1299       break;
1300 
1301     case CookieAccessScheme::kTrustworthy:
1302       access_result.is_allowed_to_access_secure_cookies = true;
1303       if (IsSecure()) {
1304         // OK, but want people aware of this.
1305         // Note, we also want to apply this warning to cookies whose source
1306         // scheme is kSecure but are set by non-cryptographic (but trustworthy)
1307         // urls. Helpfully, since those cookies only get a kSecure source scheme
1308         // when they also specify "Secure" this if statement will already apply
1309         // to them.
1310         access_result.status.AddWarningReason(
1311             CookieInclusionStatus::
1312                 WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC);
1313       }
1314       break;
1315   }
1316 
1317   access_result.access_semantics = params.access_semantics;
1318   if (options.exclude_httponly() && IsHttpOnly()) {
1319     DVLOG(net::cookie_util::kVlogSetCookies)
1320         << "HttpOnly cookie not permitted in script context.";
1321     access_result.status.AddExclusionReason(
1322         CookieInclusionStatus::EXCLUDE_HTTP_ONLY);
1323   }
1324 
1325   // Unless legacy access semantics are in effect, SameSite=None cookies without
1326   // the Secure attribute will be rejected.
1327   if (params.access_semantics != CookieAccessSemantics::LEGACY &&
1328       SameSite() == CookieSameSite::NO_RESTRICTION && !IsSecure()) {
1329     DVLOG(net::cookie_util::kVlogSetCookies)
1330         << "SetCookie() rejecting insecure cookie with SameSite=None.";
1331     access_result.status.AddExclusionReason(
1332         CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE);
1333   }
1334 
1335   // For LEGACY cookies we should always return the schemeless context,
1336   // otherwise let GetContextForCookieInclusion() decide.
1337   CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context =
1338       params.access_semantics == CookieAccessSemantics::LEGACY
1339           ? options.same_site_cookie_context().context()
1340           : options.same_site_cookie_context().GetContextForCookieInclusion();
1341 
1342   access_result.effective_same_site =
1343       GetEffectiveSameSite(params.access_semantics);
1344   DCHECK(access_result.effective_same_site !=
1345          CookieEffectiveSameSite::UNDEFINED);
1346   switch (access_result.effective_same_site) {
1347     case CookieEffectiveSameSite::STRICT_MODE:
1348       // This intentionally checks for `< SAME_SITE_LAX`, as we allow
1349       // `SameSite=Strict` cookies to be set for top-level navigations that
1350       // qualify for receipt of `SameSite=Lax` cookies.
1351       if (cookie_inclusion_context <
1352           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
1353         DVLOG(net::cookie_util::kVlogSetCookies)
1354             << "Trying to set a `SameSite=Strict` cookie from a "
1355                "cross-site URL.";
1356         access_result.status.AddExclusionReason(
1357             CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT);
1358       }
1359       break;
1360     case CookieEffectiveSameSite::LAX_MODE:
1361     case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE:
1362       if (cookie_inclusion_context <
1363           CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) {
1364         if (SameSite() == CookieSameSite::UNSPECIFIED) {
1365           DVLOG(net::cookie_util::kVlogSetCookies)
1366               << "Cookies with no known SameSite attribute being treated as "
1367                  "lax; attempt to set from a cross-site URL denied.";
1368           access_result.status.AddExclusionReason(
1369               CookieInclusionStatus::
1370                   EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX);
1371         } else {
1372           DVLOG(net::cookie_util::kVlogSetCookies)
1373               << "Trying to set a `SameSite=Lax` cookie from a cross-site URL.";
1374           access_result.status.AddExclusionReason(
1375               CookieInclusionStatus::EXCLUDE_SAMESITE_LAX);
1376         }
1377       }
1378       break;
1379     default:
1380       break;
1381   }
1382 
1383   ApplySameSiteCookieWarningToStatus(
1384       SameSite(), access_result.effective_same_site, IsSecure(),
1385       options.same_site_cookie_context(), &access_result.status,
1386       true /* is_cookie_being_set */);
1387 
1388   if (access_result.status.IsInclude()) {
1389     UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedResponseEffectiveSameSite",
1390                               access_result.effective_same_site,
1391                               CookieEffectiveSameSite::COUNT);
1392   }
1393 
1394   using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext::
1395       ContextMetadata::ContextRedirectTypeBug1221316;
1396 
1397   ContextRedirectTypeBug1221316 redirect_type_for_metrics =
1398       options.same_site_cookie_context()
1399           .GetMetadataForCurrentSchemefulMode()
1400           .redirect_type_bug_1221316;
1401   if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) {
1402     UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Write",
1403                               redirect_type_for_metrics);
1404   }
1405 
1406   if (access_result.status.HasWarningReason(
1407           CookieInclusionStatus::
1408               WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) {
1409     UMA_HISTOGRAM_ENUMERATION(
1410         "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Write",
1411         CookieSameSiteToCookieSameSiteForMetrics(SameSite()));
1412   }
1413 
1414   return access_result;
1415 }
1416 
DebugString() const1417 std::string CanonicalCookie::DebugString() const {
1418   return base::StringPrintf(
1419       "name: %s value: %s domain: %s path: %s creation: %" PRId64,
1420       name_.c_str(), value_.c_str(), domain_.c_str(), path_.c_str(),
1421       static_cast<int64_t>(creation_date_.ToTimeT()));
1422 }
1423 
PartialCompare(const CanonicalCookie & other) const1424 bool CanonicalCookie::PartialCompare(const CanonicalCookie& other) const {
1425   return PartialCookieOrdering(*this, other) < 0;
1426 }
1427 
IsCanonical() const1428 bool CanonicalCookie::IsCanonical() const {
1429   // TODO(crbug.com/1244172) Eventually we should check the size of name+value,
1430   // assuming we collect metrics and determine that a low percentage of cookies
1431   // would fail this check. Note that we still don't want to enforce length
1432   // checks on domain or path for the reason stated above.
1433 
1434   // TODO(crbug.com/1264458): Eventually we should push this logic into
1435   // IsCanonicalForFromStorage, but for now we allow cookies already stored with
1436   // high expiration dates to be retrieved.
1437   if (ValidateAndAdjustExpiryDate(expiry_date_, creation_date_) != expiry_date_)
1438     return false;
1439 
1440   return IsCanonicalForFromStorage();
1441 }
1442 
IsCanonicalForFromStorage() const1443 bool CanonicalCookie::IsCanonicalForFromStorage() const {
1444   // Not checking domain or path against ParsedCookie as it may have
1445   // come purely from the URL. Also, don't call IsValidCookieNameValuePair()
1446   // here because we don't want to enforce the size checks on names or values
1447   // that may have been reconstituted from the cookie store.
1448   if (ParsedCookie::ParseTokenString(name_) != name_ ||
1449       !ParsedCookie::ValueMatchesParsedValue(value_)) {
1450     return false;
1451   }
1452 
1453   if (!ParsedCookie::IsValidCookieName(name_) ||
1454       !ParsedCookie::IsValidCookieValue(value_)) {
1455     return false;
1456   }
1457 
1458   if (!last_access_date_.is_null() && creation_date_.is_null())
1459     return false;
1460 
1461   url::CanonHostInfo canon_host_info;
1462   std::string canonical_domain(CanonicalizeHost(domain_, &canon_host_info));
1463 
1464   // TODO(rdsmith): This specifically allows for empty domains.  The spec
1465   // suggests this is invalid (if a domain attribute is empty, the cookie's
1466   // domain is set to the canonicalized request host; see
1467   // https://tools.ietf.org/html/rfc6265#section-5.3).  However, it is
1468   // needed for Chrome extension cookies.
1469   // See http://crbug.com/730633 for more information.
1470   if (canonical_domain != domain_)
1471     return false;
1472 
1473   if (path_.empty() || path_[0] != '/')
1474     return false;
1475 
1476   CookiePrefix prefix = GetCookiePrefix(name_);
1477   switch (prefix) {
1478     case COOKIE_PREFIX_HOST:
1479       if (!secure_ || path_ != "/" || domain_.empty() || domain_[0] == '.')
1480         return false;
1481       break;
1482     case COOKIE_PREFIX_SECURE:
1483       if (!secure_)
1484         return false;
1485       break;
1486     default:
1487       break;
1488   }
1489 
1490   if (name_ == "" && HasHiddenPrefixName(value_))
1491     return false;
1492 
1493   if (IsPartitioned()) {
1494     if (CookiePartitionKey::HasNonce(partition_key_))
1495       return true;
1496     if (!secure_)
1497       return false;
1498   }
1499 
1500   return true;
1501 }
1502 
IsEffectivelySameSiteNone(CookieAccessSemantics access_semantics) const1503 bool CanonicalCookie::IsEffectivelySameSiteNone(
1504     CookieAccessSemantics access_semantics) const {
1505   return GetEffectiveSameSite(access_semantics) ==
1506          CookieEffectiveSameSite::NO_RESTRICTION;
1507 }
1508 
GetEffectiveSameSiteForTesting(CookieAccessSemantics access_semantics) const1509 CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSiteForTesting(
1510     CookieAccessSemantics access_semantics) const {
1511   return GetEffectiveSameSite(access_semantics);
1512 }
1513 
1514 // static
BuildCookieLine(const CookieList & cookies)1515 std::string CanonicalCookie::BuildCookieLine(const CookieList& cookies) {
1516   std::string cookie_line;
1517   for (const auto& cookie : cookies) {
1518     AppendCookieLineEntry(cookie, &cookie_line);
1519   }
1520   return cookie_line;
1521 }
1522 
1523 // static
BuildCookieLine(const CookieAccessResultList & cookie_access_result_list)1524 std::string CanonicalCookie::BuildCookieLine(
1525     const CookieAccessResultList& cookie_access_result_list) {
1526   std::string cookie_line;
1527   for (const auto& cookie_with_access_result : cookie_access_result_list) {
1528     const CanonicalCookie& cookie = cookie_with_access_result.cookie;
1529     AppendCookieLineEntry(cookie, &cookie_line);
1530   }
1531   return cookie_line;
1532 }
1533 
1534 // static
BuildCookieAttributesLine(const CanonicalCookie & cookie)1535 std::string CanonicalCookie::BuildCookieAttributesLine(
1536     const CanonicalCookie& cookie) {
1537   std::string cookie_line;
1538   // In Mozilla, if you set a cookie like "AAA", it will have an empty token
1539   // and a value of "AAA". When it sends the cookie back, it will send "AAA",
1540   // so we need to avoid sending "=AAA" for a blank token value.
1541   if (!cookie.Name().empty())
1542     cookie_line += cookie.Name() + "=";
1543   cookie_line += cookie.Value();
1544   if (!cookie.Domain().empty())
1545     cookie_line += "; domain=" + cookie.Domain();
1546   if (!cookie.Path().empty())
1547     cookie_line += "; path=" + cookie.Path();
1548   if (cookie.ExpiryDate() != base::Time())
1549     cookie_line += "; expires=" + HttpUtil::TimeFormatHTTP(cookie.ExpiryDate());
1550   if (cookie.IsSecure())
1551     cookie_line += "; secure";
1552   if (cookie.IsHttpOnly())
1553     cookie_line += "; httponly";
1554   if (cookie.IsPartitioned() &&
1555       !CookiePartitionKey::HasNonce(cookie.PartitionKey())) {
1556     cookie_line += "; partitioned";
1557   }
1558   switch (cookie.SameSite()) {
1559     case CookieSameSite::NO_RESTRICTION:
1560       cookie_line += "; samesite=none";
1561       break;
1562     case CookieSameSite::LAX_MODE:
1563       cookie_line += "; samesite=lax";
1564       break;
1565     case CookieSameSite::STRICT_MODE:
1566       cookie_line += "; samesite=strict";
1567       break;
1568     case CookieSameSite::UNSPECIFIED:
1569       // Don't append any text if the samesite attribute wasn't explicitly set.
1570       break;
1571   }
1572   return cookie_line;
1573 }
1574 
1575 // static
GetCookiePrefix(const std::string & name,bool check_insensitively)1576 CanonicalCookie::CookiePrefix CanonicalCookie::GetCookiePrefix(
1577     const std::string& name,
1578     bool check_insensitively) {
1579   const char kSecurePrefix[] = "__Secure-";
1580   const char kHostPrefix[] = "__Host-";
1581 
1582   base::CompareCase case_sensitivity =
1583       check_insensitively ? base::CompareCase::INSENSITIVE_ASCII
1584                           : base::CompareCase::SENSITIVE;
1585 
1586   if (base::StartsWith(name, kSecurePrefix, case_sensitivity))
1587     return CanonicalCookie::COOKIE_PREFIX_SECURE;
1588   if (base::StartsWith(name, kHostPrefix, case_sensitivity))
1589     return CanonicalCookie::COOKIE_PREFIX_HOST;
1590   return CanonicalCookie::COOKIE_PREFIX_NONE;
1591 }
1592 
1593 // static
RecordCookiePrefixMetrics(CookiePrefix prefix_case_sensitive,CookiePrefix prefix_case_insensitive,bool is_insensitive_prefix_valid)1594 void CanonicalCookie::RecordCookiePrefixMetrics(
1595     CookiePrefix prefix_case_sensitive,
1596     CookiePrefix prefix_case_insensitive,
1597     bool is_insensitive_prefix_valid) {
1598   const char kCookiePrefixHistogram[] = "Cookie.CookiePrefix";
1599   UMA_HISTOGRAM_ENUMERATION(kCookiePrefixHistogram, prefix_case_sensitive,
1600                             CanonicalCookie::COOKIE_PREFIX_LAST);
1601 
1602   // For this to be true there must a prefix, so we know it's not
1603   // COOKIE_PREFIX_NONE.
1604   bool is_case_variant = prefix_case_insensitive != prefix_case_sensitive;
1605 
1606   if (is_case_variant) {
1607     const char kCookiePrefixVariantHistogram[] =
1608         "Cookie.CookiePrefix.CaseVariant";
1609     UMA_HISTOGRAM_ENUMERATION(kCookiePrefixVariantHistogram,
1610                               prefix_case_insensitive,
1611                               CanonicalCookie::COOKIE_PREFIX_LAST);
1612 
1613     const char kVariantValidHistogram[] =
1614         "Cookie.CookiePrefix.CaseVariantValid";
1615     UMA_HISTOGRAM_BOOLEAN(kVariantValidHistogram, is_insensitive_prefix_valid);
1616   }
1617 
1618   const char kVariantCountHistogram[] = "Cookie.CookiePrefix.CaseVariantCount";
1619   if (prefix_case_insensitive > CookiePrefix::COOKIE_PREFIX_NONE) {
1620     UMA_HISTOGRAM_BOOLEAN(kVariantCountHistogram, is_case_variant);
1621   }
1622 }
1623 
1624 // Returns true if the cookie does not violate any constraints imposed
1625 // by the cookie name's prefix, as described in
1626 // https://tools.ietf.org/html/draft-west-cookie-prefixes
1627 //
1628 // static
IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,const GURL & url,const ParsedCookie & parsed_cookie)1629 bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,
1630                                           const GURL& url,
1631                                           const ParsedCookie& parsed_cookie) {
1632   return CanonicalCookie::IsCookiePrefixValid(
1633       prefix, url, parsed_cookie.IsSecure(),
1634       parsed_cookie.HasDomain() ? parsed_cookie.Domain() : "",
1635       parsed_cookie.HasPath() ? parsed_cookie.Path() : "");
1636 }
1637 
IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,const GURL & url,bool secure,const std::string & domain,const std::string & path)1638 bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix,
1639                                           const GURL& url,
1640                                           bool secure,
1641                                           const std::string& domain,
1642                                           const std::string& path) {
1643   if (prefix == CanonicalCookie::COOKIE_PREFIX_SECURE)
1644     return secure && url.SchemeIsCryptographic();
1645   if (prefix == CanonicalCookie::COOKIE_PREFIX_HOST) {
1646     return HasValidHostPrefixAttributes(url, secure, domain, path);
1647   }
1648   return true;
1649 }
1650 
GetEffectiveSameSite(CookieAccessSemantics access_semantics) const1651 CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSite(
1652     CookieAccessSemantics access_semantics) const {
1653   base::TimeDelta lax_allow_unsafe_threshold_age =
1654       base::FeatureList::IsEnabled(
1655           features::kSameSiteDefaultChecksMethodRigorously)
1656           ? base::TimeDelta::Min()
1657           : (base::FeatureList::IsEnabled(
1658                  features::kShortLaxAllowUnsafeThreshold)
1659                  ? kShortLaxAllowUnsafeMaxAge
1660                  : kLaxAllowUnsafeMaxAge);
1661 
1662   switch (SameSite()) {
1663     // If a cookie does not have a SameSite attribute, the effective SameSite
1664     // mode depends on the access semantics and whether the cookie is
1665     // recently-created.
1666     case CookieSameSite::UNSPECIFIED:
1667       return (access_semantics == CookieAccessSemantics::LEGACY)
1668                  ? CookieEffectiveSameSite::NO_RESTRICTION
1669                  : (IsRecentlyCreated(lax_allow_unsafe_threshold_age)
1670                         ? CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE
1671                         : CookieEffectiveSameSite::LAX_MODE);
1672     case CookieSameSite::NO_RESTRICTION:
1673       return CookieEffectiveSameSite::NO_RESTRICTION;
1674     case CookieSameSite::LAX_MODE:
1675       return CookieEffectiveSameSite::LAX_MODE;
1676     case CookieSameSite::STRICT_MODE:
1677       return CookieEffectiveSameSite::STRICT_MODE;
1678   }
1679 }
1680 
1681 // static
GetAndAdjustPortForTrustworthyUrls(const GURL & source_url,bool url_is_trustworthy)1682 int CanonicalCookie::GetAndAdjustPortForTrustworthyUrls(
1683     const GURL& source_url,
1684     bool url_is_trustworthy) {
1685   // If the url isn't trustworthy, or if `source_url` is cryptographic then
1686   // return the port of `source_url`.
1687   if (!url_is_trustworthy || source_url.SchemeIsCryptographic()) {
1688     return source_url.EffectiveIntPort();
1689   }
1690 
1691   // Only http and ws are cookieable schemes that have a port component. For
1692   // both of these schemes their default port is 80 whereas their secure
1693   // components have a default port of 443.
1694   //
1695   // Only in cases where we have an http/ws scheme with a default should we
1696   // return 443.
1697   if ((source_url.SchemeIs(url::kHttpScheme) ||
1698        source_url.SchemeIs(url::kWsScheme)) &&
1699       source_url.EffectiveIntPort() == 80) {
1700     return 443;
1701   }
1702 
1703   // Different schemes, or non-default port values should keep the same port
1704   // value.
1705   return source_url.EffectiveIntPort();
1706 }
1707 
1708 // static
HasHiddenPrefixName(const base::StringPiece cookie_value)1709 bool CanonicalCookie::HasHiddenPrefixName(
1710     const base::StringPiece cookie_value) {
1711   // Skip BWS as defined by HTTPSEM as SP or HTAB (0x20 or 0x9).
1712   base::StringPiece value_without_BWS =
1713       base::TrimString(cookie_value, " \t", base::TRIM_LEADING);
1714 
1715   const base::StringPiece host_prefix = "__Host-";
1716 
1717   // Compare the value to the host_prefix.
1718   if (base::StartsWith(value_without_BWS, host_prefix,
1719                        base::CompareCase::INSENSITIVE_ASCII)) {
1720     // This value contains a hidden prefix name.
1721     return true;
1722   }
1723 
1724   // Do a similar check for the secure prefix
1725   const base::StringPiece secure_prefix = "__Secure-";
1726 
1727   if (base::StartsWith(value_without_BWS, secure_prefix,
1728                        base::CompareCase::INSENSITIVE_ASCII)) {
1729     return true;
1730   }
1731 
1732   return false;
1733 }
1734 
IsRecentlyCreated(base::TimeDelta age_threshold) const1735 bool CanonicalCookie::IsRecentlyCreated(base::TimeDelta age_threshold) const {
1736   return (base::Time::Now() - creation_date_) <= age_threshold;
1737 }
1738 
1739 // static
IsCookiePartitionedValid(const GURL & url,const ParsedCookie & parsed_cookie,bool partition_has_nonce)1740 bool CanonicalCookie::IsCookiePartitionedValid(
1741     const GURL& url,
1742     const ParsedCookie& parsed_cookie,
1743     bool partition_has_nonce) {
1744   return IsCookiePartitionedValid(
1745       url, /*secure=*/parsed_cookie.IsSecure(),
1746       /*is_partitioned=*/parsed_cookie.IsPartitioned(), partition_has_nonce);
1747 }
1748 
1749 // static
IsCookiePartitionedValid(const GURL & url,bool secure,bool is_partitioned,bool partition_has_nonce)1750 bool CanonicalCookie::IsCookiePartitionedValid(const GURL& url,
1751                                                bool secure,
1752                                                bool is_partitioned,
1753                                                bool partition_has_nonce) {
1754   if (!is_partitioned)
1755     return true;
1756   if (partition_has_nonce)
1757     return true;
1758   CookieAccessScheme scheme = cookie_util::ProvisionalAccessScheme(url);
1759   bool result = (scheme != CookieAccessScheme::kNonCryptographic) && secure;
1760   DLOG_IF(WARNING, !result)
1761       << "CanonicalCookie has invalid Partitioned attribute";
1762   return result;
1763 }
1764 
1765 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult() = default;
1766 
CookieAndLineWithAccessResult(absl::optional<CanonicalCookie> cookie,std::string cookie_string,CookieAccessResult access_result)1767 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
1768     absl::optional<CanonicalCookie> cookie,
1769     std::string cookie_string,
1770     CookieAccessResult access_result)
1771     : cookie(std::move(cookie)),
1772       cookie_string(std::move(cookie_string)),
1773       access_result(access_result) {}
1774 
1775 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
1776     const CookieAndLineWithAccessResult&) = default;
1777 
1778 CookieAndLineWithAccessResult& CookieAndLineWithAccessResult::operator=(
1779     const CookieAndLineWithAccessResult& cookie_and_line_with_access_result) =
1780     default;
1781 
1782 CookieAndLineWithAccessResult::CookieAndLineWithAccessResult(
1783     CookieAndLineWithAccessResult&&) = default;
1784 
1785 CookieAndLineWithAccessResult::~CookieAndLineWithAccessResult() = default;
1786 
1787 }  // namespace net
1788