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