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