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