| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| // Portions of this code based on Mozilla: |
| // (netwerk/cookie/src/nsCookieService.cpp) |
| /* ***** BEGIN LICENSE BLOCK ***** |
| * Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
| * |
| * The contents of this file are subject to the Mozilla Public License Version |
| * 1.1 (the "License"); you may not use this file except in compliance with |
| * the License. You may obtain a copy of the License at |
| * http://www.mozilla.org/MPL/ |
| * |
| * Software distributed under the License is distributed on an "AS IS" basis, |
| * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
| * for the specific language governing rights and limitations under the |
| * License. |
| * |
| * The Original Code is mozilla.org code. |
| * |
| * The Initial Developer of the Original Code is |
| * Netscape Communications Corporation. |
| * Portions created by the Initial Developer are Copyright (C) 2003 |
| * the Initial Developer. All Rights Reserved. |
| * |
| * Contributor(s): |
| * Daniel Witte (dwitte@stanford.edu) |
| * Michiel van Leeuwen (mvl@exedo.nl) |
| * |
| * Alternatively, the contents of this file may be used under the terms of |
| * either the GNU General Public License Version 2 or later (the "GPL"), or |
| * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
| * in which case the provisions of the GPL or the LGPL are applicable instead |
| * of those above. If you wish to allow use of your version of this file only |
| * under the terms of either the GPL or the LGPL, and not to allow others to |
| * use your version of this file under the terms of the MPL, indicate your |
| * decision by deleting the provisions above and replace them with the notice |
| * and other provisions required by the GPL or the LGPL. If you do not delete |
| * the provisions above, a recipient may use your version of this file under |
| * the terms of any one of the MPL, the GPL or the LGPL. |
| * |
| * ***** END LICENSE BLOCK ***** */ |
| |
| #include "net/cookies/canonical_cookie.h" |
| |
| #include <limits> |
| #include <utility> |
| |
| #include "base/containers/contains.h" |
| #include "base/feature_list.h" |
| #include "base/format_macros.h" |
| #include "base/logging.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/metrics/histogram_functions.h" |
| #include "base/metrics/histogram_macros.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_piece.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/stringprintf.h" |
| #include "net/base/features.h" |
| #include "net/base/url_util.h" |
| #include "net/cookies/cookie_constants.h" |
| #include "net/cookies/cookie_inclusion_status.h" |
| #include "net/cookies/cookie_options.h" |
| #include "net/cookies/cookie_util.h" |
| #include "net/cookies/parsed_cookie.h" |
| #include "third_party/abseil-cpp/absl/types/optional.h" |
| #include "url/gurl.h" |
| #include "url/url_canon.h" |
| #include "url/url_util.h" |
| |
| using base::Time; |
| |
| namespace net { |
| |
| static constexpr int kMinutesInTwelveHours = 12 * 60; |
| static constexpr int kMinutesInTwentyFourHours = 24 * 60; |
| |
| namespace { |
| |
| // Determine the cookie domain to use for setting the specified cookie. |
| bool GetCookieDomain(const GURL& url, |
| const ParsedCookie& pc, |
| CookieInclusionStatus& status, |
| std::string* result) { |
| std::string domain_string; |
| if (pc.HasDomain()) |
| domain_string = pc.Domain(); |
| return cookie_util::GetCookieDomainWithString(url, domain_string, status, |
| result); |
| } |
| |
| // Compares cookies using name, domain and path, so that "equivalent" cookies |
| // (per RFC 2965) are equal to each other. |
| int PartialCookieOrdering(const CanonicalCookie& a, const CanonicalCookie& b) { |
| int diff = a.Name().compare(b.Name()); |
| if (diff != 0) |
| return diff; |
| |
| diff = a.Domain().compare(b.Domain()); |
| if (diff != 0) |
| return diff; |
| |
| return a.Path().compare(b.Path()); |
| } |
| |
| void AppendCookieLineEntry(const CanonicalCookie& cookie, |
| std::string* cookie_line) { |
| if (!cookie_line->empty()) |
| *cookie_line += "; "; |
| // In Mozilla, if you set a cookie like "AAA", it will have an empty token |
| // and a value of "AAA". When it sends the cookie back, it will send "AAA", |
| // so we need to avoid sending "=AAA" for a blank token value. |
| if (!cookie.Name().empty()) |
| *cookie_line += cookie.Name() + "="; |
| *cookie_line += cookie.Value(); |
| } |
| |
| // Captures Strict -> Lax context downgrade with Strict cookie |
| bool IsBreakingStrictToLaxDowngrade( |
| CookieOptions::SameSiteCookieContext::ContextType context, |
| CookieOptions::SameSiteCookieContext::ContextType schemeful_context, |
| CookieEffectiveSameSite effective_same_site, |
| bool is_cookie_being_set) { |
| if (context == |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT && |
| schemeful_context == |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX && |
| effective_same_site == CookieEffectiveSameSite::STRICT_MODE) { |
| // This downgrade only applies when a SameSite=Strict cookie is being sent. |
| // A Strict -> Lax downgrade will not affect a Strict cookie which is being |
| // set because it will be set in either context. |
| return !is_cookie_being_set; |
| } |
| |
| return false; |
| } |
| |
| // Captures Strict -> Cross-site context downgrade with {Strict, Lax} cookie |
| // Captures Strict -> Lax Unsafe context downgrade with {Strict, Lax} cookie. |
| // This is treated as a cross-site downgrade due to the Lax Unsafe context |
| // behaving like cross-site. |
| bool IsBreakingStrictToCrossDowngrade( |
| CookieOptions::SameSiteCookieContext::ContextType context, |
| CookieOptions::SameSiteCookieContext::ContextType schemeful_context, |
| CookieEffectiveSameSite effective_same_site) { |
| bool breaking_schemeful_context = |
| schemeful_context == |
| CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE || |
| schemeful_context == CookieOptions::SameSiteCookieContext::ContextType:: |
| SAME_SITE_LAX_METHOD_UNSAFE; |
| |
| bool strict_lax_enforcement = |
| effective_same_site == CookieEffectiveSameSite::STRICT_MODE || |
| effective_same_site == CookieEffectiveSameSite::LAX_MODE || |
| // Treat LAX_MODE_ALLOW_UNSAFE the same as LAX_MODE for the purposes of |
| // our SameSite enforcement check. |
| effective_same_site == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE; |
| |
| if (context == |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT && |
| breaking_schemeful_context && strict_lax_enforcement) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| // Captures Lax -> Cross context downgrade with {Strict, Lax} cookies. |
| // Ignores Lax Unsafe context. |
| bool IsBreakingLaxToCrossDowngrade( |
| CookieOptions::SameSiteCookieContext::ContextType context, |
| CookieOptions::SameSiteCookieContext::ContextType schemeful_context, |
| CookieEffectiveSameSite effective_same_site, |
| bool is_cookie_being_set) { |
| bool lax_enforcement = |
| effective_same_site == CookieEffectiveSameSite::LAX_MODE || |
| // Treat LAX_MODE_ALLOW_UNSAFE the same as LAX_MODE for the purposes of |
| // our SameSite enforcement check. |
| effective_same_site == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE; |
| |
| if (context == |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX && |
| schemeful_context == |
| CookieOptions::SameSiteCookieContext::ContextType::CROSS_SITE) { |
| // For SameSite=Strict cookies this downgrade only applies when it is being |
| // set. A Lax -> Cross downgrade will not affect a Strict cookie which is |
| // being sent because it wouldn't be sent in either context. |
| return effective_same_site == CookieEffectiveSameSite::STRICT_MODE |
| ? is_cookie_being_set |
| : lax_enforcement; |
| } |
| |
| return false; |
| } |
| |
| void ApplySameSiteCookieWarningToStatus( |
| CookieSameSite samesite, |
| CookieEffectiveSameSite effective_samesite, |
| bool is_secure, |
| const CookieOptions::SameSiteCookieContext& same_site_context, |
| CookieInclusionStatus* status, |
| bool is_cookie_being_set) { |
| if (samesite == CookieSameSite::UNSPECIFIED && |
| same_site_context.GetContextForCookieInclusion() < |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) { |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT); |
| } |
| if (effective_samesite == CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE && |
| same_site_context.GetContextForCookieInclusion() == |
| CookieOptions::SameSiteCookieContext::ContextType:: |
| SAME_SITE_LAX_METHOD_UNSAFE) { |
| // This warning is more specific so remove the previous, more general, |
| // warning. |
| status->RemoveWarningReason( |
| CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT); |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE); |
| } |
| if (samesite == CookieSameSite::NO_RESTRICTION && !is_secure) { |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE); |
| } |
| |
| // Add a warning if the cookie would be accessible in |
| // |same_site_context|::context but not in |
| // |same_site_context|::schemeful_context. |
| if (IsBreakingStrictToLaxDowngrade(same_site_context.context(), |
| same_site_context.schemeful_context(), |
| effective_samesite, is_cookie_being_set)) { |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_STRICT_LAX_DOWNGRADE_STRICT_SAMESITE); |
| } else if (IsBreakingStrictToCrossDowngrade( |
| same_site_context.context(), |
| same_site_context.schemeful_context(), effective_samesite)) { |
| // Which warning to apply depends on the SameSite value. |
| if (effective_samesite == CookieEffectiveSameSite::STRICT_MODE) { |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_STRICT_CROSS_DOWNGRADE_STRICT_SAMESITE); |
| } else { |
| // LAX_MODE or LAX_MODE_ALLOW_UNSAFE. |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_STRICT_CROSS_DOWNGRADE_LAX_SAMESITE); |
| } |
| |
| } else if (IsBreakingLaxToCrossDowngrade( |
| same_site_context.context(), |
| same_site_context.schemeful_context(), effective_samesite, |
| is_cookie_being_set)) { |
| // Which warning to apply depends on the SameSite value. |
| if (effective_samesite == CookieEffectiveSameSite::STRICT_MODE) { |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_LAX_CROSS_DOWNGRADE_STRICT_SAMESITE); |
| } else { |
| // LAX_MODE or LAX_MODE_ALLOW_UNSAFE. |
| // This warning applies to both set/send. |
| status->AddWarningReason( |
| CookieInclusionStatus::WARN_LAX_CROSS_DOWNGRADE_LAX_SAMESITE); |
| } |
| } |
| |
| // Apply warning for whether inclusion was changed by considering redirects |
| // for the SameSite context calculation. This does not look at the actual |
| // inclusion or exclusion, but only at whether the inclusion differs between |
| // considering redirects and not. |
| using ContextDowngradeType = CookieOptions::SameSiteCookieContext:: |
| ContextMetadata::ContextDowngradeType; |
| const auto& metadata = same_site_context.GetMetadataForCurrentSchemefulMode(); |
| bool apply_cross_site_redirect_downgrade_warning = false; |
| switch (effective_samesite) { |
| case CookieEffectiveSameSite::STRICT_MODE: |
| // Strict contexts are all normalized to lax for cookie writes, so a |
| // strict-to-{lax,cross} downgrade cannot occur for response cookies. |
| apply_cross_site_redirect_downgrade_warning = |
| is_cookie_being_set ? metadata.cross_site_redirect_downgrade == |
| ContextDowngradeType::kLaxToCross |
| : (metadata.cross_site_redirect_downgrade == |
| ContextDowngradeType::kStrictToLax || |
| metadata.cross_site_redirect_downgrade == |
| ContextDowngradeType::kStrictToCross); |
| break; |
| case CookieEffectiveSameSite::LAX_MODE: |
| case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE: |
| // Note that a lax-to-cross downgrade can only happen for response |
| // cookies, because a laxly same-site context only happens for a safe |
| // top-level cross-site request, which cannot be downgraded due to a |
| // cross-site redirect to a non-top-level or unsafe cross-site request. |
| apply_cross_site_redirect_downgrade_warning = |
| metadata.cross_site_redirect_downgrade == |
| (is_cookie_being_set ? ContextDowngradeType::kLaxToCross |
| : ContextDowngradeType::kStrictToCross); |
| break; |
| default: |
| break; |
| } |
| if (apply_cross_site_redirect_downgrade_warning) { |
| status->AddWarningReason( |
| CookieInclusionStatus:: |
| WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION); |
| } |
| |
| // If there are reasons to exclude the cookie other than SameSite, don't warn |
| // about the cookie at all. |
| status->MaybeClearSameSiteWarning(); |
| } |
| |
| // Converts CookieSameSite to CookieSameSiteForMetrics by adding 1 to it. |
| CookieSameSiteForMetrics CookieSameSiteToCookieSameSiteForMetrics( |
| CookieSameSite enum_in) { |
| return static_cast<CookieSameSiteForMetrics>((static_cast<int>(enum_in) + 1)); |
| } |
| |
| // Checks if `port` is within [0,65535] or url::PORT_UNSPECIFIED. Returns `port` |
| // if so and url::PORT_INVALID otherwise. |
| int ValidateAndAdjustSourcePort(int port) { |
| if ((port >= 0 && port <= 65535) || port == url::PORT_UNSPECIFIED) { |
| // 0 would be really weird as it has a special meaning, but it's still |
| // technically a valid tcp/ip port so we're going to accept it here. |
| return port; |
| } |
| |
| return url::PORT_INVALID; |
| } |
| |
| // Tests that a cookie has the attributes for a valid __Host- prefix without |
| // testing that the prefix is in the cookie name. |
| bool HasValidHostPrefixAttributes(const GURL& url, |
| bool secure, |
| const std::string& domain, |
| const std::string& path) { |
| if (!secure || !url.SchemeIsCryptographic() || path != "/") |
| return false; |
| return domain.empty() || (url.HostIsIPAddress() && url.host() == domain); |
| } |
| |
| } // namespace |
| |
| CookieAccessParams::CookieAccessParams(CookieAccessSemantics access_semantics, |
| bool delegate_treats_url_as_trustworthy, |
| CookieSamePartyStatus same_party_status) |
| : access_semantics(access_semantics), |
| delegate_treats_url_as_trustworthy(delegate_treats_url_as_trustworthy), |
| same_party_status(same_party_status) {} |
| |
| CanonicalCookie::CanonicalCookie() = default; |
| |
| CanonicalCookie::CanonicalCookie(const CanonicalCookie& other) = default; |
| |
| CanonicalCookie::CanonicalCookie(CanonicalCookie&& other) = default; |
| |
| CanonicalCookie& CanonicalCookie::operator=(const CanonicalCookie& other) = |
| default; |
| |
| CanonicalCookie& CanonicalCookie::operator=(CanonicalCookie&& other) = default; |
| |
| CanonicalCookie::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) |
| : name_(std::move(name)), |
| value_(std::move(value)), |
| domain_(std::move(domain)), |
| path_(std::move(path)), |
| creation_date_(creation), |
| expiry_date_(expiration), |
| last_access_date_(last_access), |
| last_update_date_(last_update), |
| secure_(secure), |
| httponly_(httponly), |
| same_site_(same_site), |
| priority_(priority), |
| same_party_(same_party), |
| partition_key_(std::move(partition_key)), |
| source_scheme_(source_scheme), |
| source_port_(source_port) {} |
| |
| CanonicalCookie::~CanonicalCookie() = default; |
| |
| // static |
| std::string CanonicalCookie::CanonPathWithString( |
| const GURL& url, |
| const std::string& path_string) { |
| // The path was supplied in the cookie, we'll take it. |
| if (!path_string.empty() && path_string[0] == '/') |
| return path_string; |
| |
| // The path was not supplied in the cookie or invalid, we will default |
| // to the current URL path. |
| // """Defaults to the path of the request URL that generated the |
| // Set-Cookie response, up to, but not including, the |
| // right-most /.""" |
| // How would this work for a cookie on /? We will include it then. |
| const std::string& url_path = url.path(); |
| |
| size_t idx = url_path.find_last_of('/'); |
| |
| // The cookie path was invalid or a single '/'. |
| if (idx == 0 || idx == std::string::npos) |
| return std::string("/"); |
| |
| // Return up to the rightmost '/'. |
| return url_path.substr(0, idx); |
| } |
| |
| // static |
| Time CanonicalCookie::ParseExpiration(const ParsedCookie& pc, |
| const Time& current, |
| const Time& server_time) { |
| // First, try the Max-Age attribute. |
| if (pc.HasMaxAge()) { |
| int64_t max_age = 0; |
| // Use the output if StringToInt64 returns true ("perfect" conversion). This |
| // case excludes overflow/underflow, leading/trailing whitespace, non-number |
| // strings, and empty string. (ParsedCookie trims whitespace.) |
| if (base::StringToInt64(pc.MaxAge(), &max_age)) { |
| // RFC 6265bis algorithm for parsing Max-Age: |
| // "If delta-seconds is less than or equal to zero (0), let expiry- |
| // time be the earliest representable date and time. ... " |
| if (max_age <= 0) |
| return Time::Min(); |
| // "... Otherwise, let the expiry-time be the current date and time plus |
| // delta-seconds seconds." |
| return current + base::Seconds(max_age); |
| } else { |
| // If the conversion wasn't perfect, but the best-effort conversion |
| // resulted in an overflow/underflow, use the min/max representable time. |
| // (This is alluded to in the spec, which says the user agent MAY clip an |
| // Expires attribute to a saturated time. We'll do the same for Max-Age.) |
| if (max_age == std::numeric_limits<int64_t>::min()) |
| return Time::Min(); |
| if (max_age == std::numeric_limits<int64_t>::max()) |
| return Time::Max(); |
| } |
| } |
| |
| // Try the Expires attribute. |
| if (pc.HasExpires() && !pc.Expires().empty()) { |
| // Adjust for clock skew between server and host. |
| Time parsed_expiry = cookie_util::ParseCookieExpirationTime(pc.Expires()); |
| if (!parsed_expiry.is_null()) { |
| // Record metrics related to prevalence of clock skew. |
| base::TimeDelta clock_skew = (current - server_time); |
| // Record the magnitude (absolute value) of the skew in minutes. |
| int clock_skew_magnitude = clock_skew.magnitude().InMinutes(); |
| // Determine the new expiry with clock skew factored in. |
| Time adjusted_expiry = parsed_expiry + (current - server_time); |
| if (clock_skew.is_positive() || clock_skew.is_zero()) { |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes", |
| clock_skew_magnitude, 1, |
| kMinutesInTwelveHours, 100); |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.AddMinutes12To24Hours", |
| clock_skew_magnitude, kMinutesInTwelveHours, |
| kMinutesInTwentyFourHours, 100); |
| // Also record the range of minutes added that allowed the cookie to |
| // avoid expiring immediately. |
| if (parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now()) { |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Cookie.ClockSkew.WithoutAddMinutesExpires", clock_skew_magnitude, |
| 1, kMinutesInTwentyFourHours, 100); |
| } |
| } else if (clock_skew.is_negative()) { |
| // These histograms only support positive numbers, so negative skews |
| // will be converted to positive (via magnitude) before recording. |
| UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ClockSkew.SubtractMinutes", |
| clock_skew_magnitude, 1, |
| kMinutesInTwelveHours, 100); |
| UMA_HISTOGRAM_CUSTOM_COUNTS( |
| "Cookie.ClockSkew.SubtractMinutes12To24Hours", clock_skew_magnitude, |
| kMinutesInTwelveHours, kMinutesInTwentyFourHours, 100); |
| } |
| // Record if we were going to expire the cookie before we added the clock |
| // skew. |
| UMA_HISTOGRAM_BOOLEAN( |
| "Cookie.ClockSkew.ExpiredWithoutSkew", |
| parsed_expiry <= Time::Now() && adjusted_expiry > Time::Now()); |
| return adjusted_expiry; |
| } |
| } |
| |
| // Invalid or no expiration, session cookie. |
| return Time(); |
| } |
| |
| // static |
| base::Time CanonicalCookie::ValidateAndAdjustExpiryDate( |
| const base::Time& expiry_date, |
| const base::Time& creation_date) { |
| if (expiry_date.is_null()) |
| return expiry_date; |
| base::Time fixed_creation_date = creation_date; |
| if (fixed_creation_date.is_null()) { |
| // TODO(crbug.com/1264458): Push this logic into |
| // CanonicalCookie::CreateSanitizedCookie. The four sites that call it |
| // with a null `creation_date` (CanonicalCookie::Create cannot be called |
| // this way) are: |
| // * GaiaCookieManagerService::ForceOnCookieChangeProcessing |
| // * CookiesSetFunction::Run |
| // * cookie_store.cc::ToCanonicalCookie |
| // * network_handler.cc::MakeCookieFromProtocolValues |
| fixed_creation_date = base::Time::Now(); |
| } |
| if (base::FeatureList::IsEnabled(features::kClampCookieExpiryTo400Days)) { |
| base::Time maximum_expiry_date = fixed_creation_date + base::Days(400); |
| if (expiry_date > maximum_expiry_date) |
| return maximum_expiry_date; |
| } |
| return expiry_date; |
| } |
| |
| // static |
| std::unique_ptr<CanonicalCookie> CanonicalCookie::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) { |
| // Put a pointer on the stack so the rest of the function can assign to it if |
| // the default nullptr is passed in. |
| CookieInclusionStatus blank_status; |
| if (status == nullptr) { |
| status = &blank_status; |
| } |
| *status = CookieInclusionStatus(); |
| |
| // Check the URL; it may be nonsense since some platform APIs may permit |
| // it to be specified directly. |
| if (!url.is_valid()) { |
| status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| return nullptr; |
| } |
| |
| ParsedCookie parsed_cookie(cookie_line, status); |
| |
| // We record this metric before checking validity because the presence of an |
| // HTAB will invalidate the ParsedCookie. |
| UMA_HISTOGRAM_BOOLEAN("Cookie.NameOrValueHtab", |
| parsed_cookie.HasInternalHtab()); |
| |
| if (!parsed_cookie.IsValid()) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "WARNING: Couldn't parse cookie"; |
| DCHECK(!status->IsInclude()); |
| // Don't continue, because an invalid ParsedCookie doesn't have any |
| // attributes. |
| // TODO(chlily): Log metrics. |
| return nullptr; |
| } |
| |
| // Record warning for non-ASCII octecs in the Domain attribute. |
| // This should lead to rejection of the cookie in the future. |
| UMA_HISTOGRAM_BOOLEAN("Cookie.DomainHasNonASCII", |
| parsed_cookie.HasDomain() && |
| !base::IsStringASCII(parsed_cookie.Domain())); |
| |
| std::string cookie_domain; |
| if (!GetCookieDomain(url, parsed_cookie, *status, &cookie_domain)) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "Create() failed to get a valid cookie domain"; |
| status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); |
| } |
| |
| std::string cookie_path = CanonPathWithString( |
| url, parsed_cookie.HasPath() ? parsed_cookie.Path() : std::string()); |
| |
| Time cookie_server_time(creation_time); |
| if (server_time.has_value() && !server_time->is_null()) |
| cookie_server_time = server_time.value(); |
| |
| DCHECK(!creation_time.is_null()); |
| Time cookie_expires = CanonicalCookie::ParseExpiration( |
| parsed_cookie, creation_time, cookie_server_time); |
| cookie_expires = ValidateAndAdjustExpiryDate(cookie_expires, creation_time); |
| |
| CookiePrefix prefix_case_sensitive = |
| GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/false); |
| CookiePrefix prefix_case_insensitive = |
| GetCookiePrefix(parsed_cookie.Name(), /*check_insensitively=*/true); |
| |
| bool is_sensitive_prefix_valid = |
| IsCookiePrefixValid(prefix_case_sensitive, url, parsed_cookie); |
| bool is_insensitive_prefix_valid = |
| IsCookiePrefixValid(prefix_case_insensitive, url, parsed_cookie); |
| bool is_cookie_prefix_valid = |
| base::FeatureList::IsEnabled(net::features::kCaseInsensitiveCookiePrefix) |
| ? is_insensitive_prefix_valid |
| : is_sensitive_prefix_valid; |
| |
| RecordCookiePrefixMetrics(prefix_case_sensitive, prefix_case_insensitive, |
| is_insensitive_prefix_valid); |
| |
| if (parsed_cookie.Name() == "") { |
| is_cookie_prefix_valid = !HasHiddenPrefixName(parsed_cookie.Value()); |
| } |
| |
| if (!is_cookie_prefix_valid) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "Create() failed because the cookie violated prefix rules."; |
| status->AddExclusionReason(CookieInclusionStatus::EXCLUDE_INVALID_PREFIX); |
| } |
| |
| bool is_same_party_valid = IsCookieSamePartyValid(parsed_cookie); |
| if (!is_same_party_valid) { |
| status->AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY); |
| } |
| |
| bool partition_has_nonce = CookiePartitionKey::HasNonce(cookie_partition_key); |
| bool is_partitioned_valid = |
| IsCookiePartitionedValid(url, parsed_cookie, partition_has_nonce); |
| if (!is_partitioned_valid) { |
| status->AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED); |
| } |
| |
| // Collect metrics on whether usage of the Partitioned attribute is correct. |
| // Do not include implicit nonce-based partitioned cookies in these metrics. |
| if (parsed_cookie.IsPartitioned()) { |
| if (!partition_has_nonce) |
| UMA_HISTOGRAM_BOOLEAN("Cookie.IsPartitionedValid", is_partitioned_valid); |
| } else if (!partition_has_nonce) { |
| cookie_partition_key = absl::nullopt; |
| } |
| |
| if (!status->IsInclude()) |
| return nullptr; |
| |
| CookieSameSiteString samesite_string = CookieSameSiteString::kUnspecified; |
| CookieSameSite samesite = parsed_cookie.SameSite(&samesite_string); |
| |
| CookieSourceScheme source_scheme = url.SchemeIsCryptographic() |
| ? CookieSourceScheme::kSecure |
| : CookieSourceScheme::kNonSecure; |
| // Get the port, this will get a default value if a port isn't provided. |
| int source_port = ValidateAndAdjustSourcePort(url.EffectiveIntPort()); |
| |
| auto cc = std::make_unique<CanonicalCookie>( |
| base::PassKey<CanonicalCookie>(), parsed_cookie.Name(), |
| parsed_cookie.Value(), cookie_domain, cookie_path, creation_time, |
| cookie_expires, creation_time, |
| /*last_update=*/base::Time::Now(), parsed_cookie.IsSecure(), |
| parsed_cookie.IsHttpOnly(), samesite, parsed_cookie.Priority(), |
| parsed_cookie.IsSameParty(), cookie_partition_key, source_scheme, |
| source_port); |
| |
| // TODO(chlily): Log metrics. |
| if (!cc->IsCanonical()) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| return nullptr; |
| } |
| |
| RecordCookieSameSiteAttributeValueHistogram(samesite_string); |
| |
| // These metrics capture whether or not a cookie has a Non-ASCII character in |
| // it. |
| UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Name", |
| !base::IsStringASCII(cc->Name())); |
| UMA_HISTOGRAM_BOOLEAN("Cookie.HasNonASCII.Value", |
| !base::IsStringASCII(cc->Value())); |
| |
| // Check for "__" prefixed names, excluding the cookie prefixes. |
| bool name_prefixed_with_underscores = |
| (prefix_case_insensitive == CanonicalCookie::COOKIE_PREFIX_NONE) && |
| base::StartsWith(parsed_cookie.Name(), "__"); |
| |
| UMA_HISTOGRAM_BOOLEAN("Cookie.DoubleUnderscorePrefixedName", |
| name_prefixed_with_underscores); |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Cookie.TruncatingCharacterInCookieString", |
| parsed_cookie.GetTruncatingCharacterInCookieStringType()); |
| |
| return cc; |
| } |
| |
| // static |
| std::unique_ptr<CanonicalCookie> CanonicalCookie::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) { |
| // Put a pointer on the stack so the rest of the function can assign to it if |
| // the default nullptr is passed in. |
| CookieInclusionStatus blank_status; |
| if (status == nullptr) { |
| status = &blank_status; |
| } |
| *status = CookieInclusionStatus(); |
| |
| // Validate consistency of passed arguments. |
| if (ParsedCookie::ParseTokenString(name) != name) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| } else if (ParsedCookie::ParseValueString(value) != value) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| } else if (ParsedCookie::ParseValueString(path) != path) { |
| // NOTE: If `path` contains "terminating characters" ('\r', '\n', and |
| // '\0'), ';', or leading / trailing whitespace, path will be rejected, |
| // but any other control characters will just get URL-encoded below. |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| } |
| |
| // Validate name and value against character set and size limit constraints. |
| // If IsValidCookieNameValuePair identifies that `name` and/or `value` are |
| // invalid, it will add an ExclusionReason to `status`. |
| ParsedCookie::IsValidCookieNameValuePair(name, value, status); |
| |
| // Validate domain against character set and size limit constraints. |
| bool domain_is_valid = true; |
| |
| if ((ParsedCookie::ParseValueString(domain) != domain)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); |
| domain_is_valid = false; |
| } |
| |
| if (!ParsedCookie::CookieAttributeValueHasValidCharSet(domain)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); |
| domain_is_valid = false; |
| } |
| if (!ParsedCookie::CookieAttributeValueHasValidSize(domain)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE); |
| domain_is_valid = false; |
| } |
| const std::string& domain_attribute = |
| domain_is_valid ? domain : std::string(); |
| |
| std::string cookie_domain; |
| // This validation step must happen before GetCookieDomainWithString, so it |
| // doesn't fail DCHECKs. |
| if (!cookie_util::DomainIsHostOnly(url.host())) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); |
| } else if (!cookie_util::GetCookieDomainWithString(url, domain_attribute, |
| *status, &cookie_domain)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); |
| } |
| |
| CookieSourceScheme source_scheme = CookieSourceScheme::kNonSecure; |
| // This validation step must happen before SchemeIsCryptographic, so it |
| // doesn't fail DCHECKs. |
| if (!url.is_valid()) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); |
| } else { |
| source_scheme = url.SchemeIsCryptographic() |
| ? CookieSourceScheme::kSecure |
| : CookieSourceScheme::kNonSecure; |
| } |
| |
| // Get the port, this will get a default value if a port isn't provided. |
| int source_port = ValidateAndAdjustSourcePort(url.EffectiveIntPort()); |
| |
| std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path); |
| // Canonicalize path again to make sure it escapes characters as needed. |
| url::Component path_component(0, cookie_path.length()); |
| url::RawCanonOutputT<char> canon_path; |
| url::Component canon_path_component; |
| url::CanonicalizePath(cookie_path.data(), path_component, &canon_path, |
| &canon_path_component); |
| std::string encoded_cookie_path = std::string( |
| canon_path.data() + canon_path_component.begin, canon_path_component.len); |
| |
| if (!path.empty()) { |
| if (cookie_path != path) { |
| // The path attribute was specified and found to be invalid, so record an |
| // error. |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| } else if (!ParsedCookie::CookieAttributeValueHasValidSize( |
| encoded_cookie_path)) { |
| // The path attribute was specified and encodes into a value that's longer |
| // than the length limit, so record an error. |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE); |
| } |
| } |
| |
| CookiePrefix prefix = GetCookiePrefix(name); |
| if (!IsCookiePrefixValid(prefix, url, secure, domain_attribute, |
| cookie_path)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX); |
| } |
| |
| if (name == "" && HasHiddenPrefixName(value)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX); |
| } |
| |
| if (!IsCookieSamePartyValid(same_party, secure, same_site)) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY); |
| } |
| if (!IsCookiePartitionedValid(url, secure, |
| /*is_partitioned=*/partition_key.has_value(), |
| /*partition_has_nonce=*/ |
| CookiePartitionKey::HasNonce(partition_key))) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_INVALID_PARTITIONED); |
| } |
| |
| if (!last_access_time.is_null() && creation_time.is_null()) { |
| status->AddExclusionReason( |
| net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); |
| } |
| expiration_time = ValidateAndAdjustExpiryDate(expiration_time, creation_time); |
| |
| if (!status->IsInclude()) |
| return nullptr; |
| |
| auto cc = std::make_unique<CanonicalCookie>( |
| base::PassKey<CanonicalCookie>(), name, value, cookie_domain, |
| encoded_cookie_path, creation_time, expiration_time, last_access_time, |
| /*last_update=*/base::Time::Now(), secure, http_only, same_site, priority, |
| same_party, partition_key, source_scheme, source_port); |
| DCHECK(cc->IsCanonical()); |
| |
| return cc; |
| } |
| |
| // static |
| std::unique_ptr<CanonicalCookie> CanonicalCookie::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) { |
| // We check source_port here because it could have concievably been |
| // corrupted and changed to out of range. Eventually this would be caught by |
| // IsCanonical*() but since the source_port is only used by metrics so far |
| // nothing else checks it. So let's normalize it here and then update this |
| // method when origin-bound cookies is implemented. |
| // TODO(crbug.com/1170548) |
| int validated_port = ValidateAndAdjustSourcePort(source_port); |
| |
| auto cc = std::make_unique<CanonicalCookie>( |
| base::PassKey<CanonicalCookie>(), std::move(name), std::move(value), |
| std::move(domain), std::move(path), creation, expiration, last_access, |
| last_update, secure, httponly, same_site, priority, same_party, |
| partition_key, source_scheme, validated_port); |
| |
| if (cc->IsCanonicalForFromStorage()) { |
| // This will help capture the number of times a cookie is canonical but does |
| // not have a valid name+value size length |
| bool valid_cookie_name_value_pair = |
| ParsedCookie::IsValidCookieNameValuePair(cc->Name(), cc->Value()); |
| UMA_HISTOGRAM_BOOLEAN("Cookie.FromStorageWithValidLength", |
| valid_cookie_name_value_pair); |
| } else { |
| return nullptr; |
| } |
| return cc; |
| } |
| |
| // static |
| std::unique_ptr<CanonicalCookie> CanonicalCookie::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) { |
| return std::make_unique<CanonicalCookie>( |
| base::PassKey<CanonicalCookie>(), name, value, domain, path, creation, |
| expiration, last_access, last_update, secure, httponly, same_site, |
| priority, same_party, partition_key, source_scheme, source_port); |
| } |
| |
| std::string CanonicalCookie::DomainWithoutDot() const { |
| return cookie_util::CookieDomainAsHost(domain_); |
| } |
| |
| void CanonicalCookie::SetSourcePort(int port) { |
| source_port_ = ValidateAndAdjustSourcePort(port); |
| } |
| |
| bool CanonicalCookie::IsEquivalentForSecureCookieMatching( |
| const CanonicalCookie& secure_cookie) const { |
| // Partition keys must both be equivalent. |
| bool same_partition_key = PartitionKey() == secure_cookie.PartitionKey(); |
| |
| // Names must be the same |
| bool same_name = name_ == secure_cookie.Name(); |
| |
| // They should domain-match in one direction or the other. (See RFC 6265bis |
| // section 5.1.3.) |
| // TODO(chlily): This does not check for the IP address case. This is bad due |
| // to https://crbug.com/1069935. |
| bool domain_match = |
| IsSubdomainOf(DomainWithoutDot(), secure_cookie.DomainWithoutDot()) || |
| IsSubdomainOf(secure_cookie.DomainWithoutDot(), DomainWithoutDot()); |
| |
| bool path_match = secure_cookie.IsOnPath(Path()); |
| |
| bool equivalent_for_secure_cookie_matching = |
| same_partition_key && same_name && domain_match && path_match; |
| |
| // IsEquivalent() is a stricter check than this. |
| DCHECK(!IsEquivalent(secure_cookie) || equivalent_for_secure_cookie_matching); |
| |
| return equivalent_for_secure_cookie_matching; |
| } |
| |
| bool CanonicalCookie::IsOnPath(const std::string& url_path) const { |
| return cookie_util::IsOnPath(path_, url_path); |
| } |
| |
| bool CanonicalCookie::IsDomainMatch(const std::string& host) const { |
| return cookie_util::IsDomainMatch(domain_, host); |
| } |
| |
| CookieAccessResult CanonicalCookie::IncludeForRequestURL( |
| const GURL& url, |
| const CookieOptions& options, |
| const CookieAccessParams& params) const { |
| CookieInclusionStatus status; |
| // Filter out HttpOnly cookies, per options. |
| if (options.exclude_httponly() && IsHttpOnly()) |
| status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_HTTP_ONLY); |
| // Secure cookies should not be included in requests for URLs with an |
| // insecure scheme, unless it is a localhost url, or the CookieAccessDelegate |
| // otherwise denotes them as trustworthy |
| // (`delegate_treats_url_as_trustworthy`). |
| bool is_allowed_to_access_secure_cookies = false; |
| CookieAccessScheme cookie_access_scheme = |
| cookie_util::ProvisionalAccessScheme(url); |
| if (cookie_access_scheme == CookieAccessScheme::kNonCryptographic && |
| params.delegate_treats_url_as_trustworthy) { |
| cookie_access_scheme = CookieAccessScheme::kTrustworthy; |
| } |
| switch (cookie_access_scheme) { |
| case CookieAccessScheme::kNonCryptographic: |
| if (IsSecure()) |
| status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_SECURE_ONLY); |
| break; |
| case CookieAccessScheme::kTrustworthy: |
| is_allowed_to_access_secure_cookies = true; |
| if (IsSecure()) { |
| status.AddWarningReason( |
| CookieInclusionStatus:: |
| WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC); |
| } |
| break; |
| case CookieAccessScheme::kCryptographic: |
| is_allowed_to_access_secure_cookies = true; |
| break; |
| } |
| // Don't include cookies for requests that don't apply to the cookie domain. |
| if (!IsDomainMatch(url.host())) |
| status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_DOMAIN_MISMATCH); |
| // Don't include cookies for requests with a url path that does not path |
| // match the cookie-path. |
| if (!IsOnPath(url.path())) |
| status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_NOT_ON_PATH); |
| |
| // For LEGACY cookies we should always return the schemeless context, |
| // otherwise let GetContextForCookieInclusion() decide. |
| CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context = |
| params.access_semantics == CookieAccessSemantics::LEGACY |
| ? options.same_site_cookie_context().context() |
| : options.same_site_cookie_context().GetContextForCookieInclusion(); |
| |
| // Don't include same-site cookies for cross-site requests. |
| CookieEffectiveSameSite effective_same_site = |
| GetEffectiveSameSite(params.access_semantics); |
| DCHECK(effective_same_site != CookieEffectiveSameSite::UNDEFINED); |
| UMA_HISTOGRAM_ENUMERATION( |
| "Cookie.RequestSameSiteContext", cookie_inclusion_context, |
| CookieOptions::SameSiteCookieContext::ContextType::COUNT); |
| |
| switch (effective_same_site) { |
| case CookieEffectiveSameSite::STRICT_MODE: |
| if (cookie_inclusion_context < |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_STRICT) { |
| status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT); |
| } |
| break; |
| case CookieEffectiveSameSite::LAX_MODE: |
| if (cookie_inclusion_context < |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) { |
| status.AddExclusionReason( |
| (SameSite() == CookieSameSite::UNSPECIFIED) |
| ? CookieInclusionStatus:: |
| EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX |
| : CookieInclusionStatus::EXCLUDE_SAMESITE_LAX); |
| } |
| break; |
| // TODO(crbug.com/990439): Add a browsertest for this behavior. |
| case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE: |
| DCHECK(SameSite() == CookieSameSite::UNSPECIFIED); |
| if (cookie_inclusion_context < |
| CookieOptions::SameSiteCookieContext::ContextType:: |
| SAME_SITE_LAX_METHOD_UNSAFE) { |
| // TODO(chlily): Do we need a separate CookieInclusionStatus for this? |
| status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX); |
| } |
| break; |
| default: |
| break; |
| } |
| |
| // Unless legacy access semantics are in effect, SameSite=None cookies without |
| // the Secure attribute should be ignored. This can apply to cookies which |
| // were created before "SameSite=None requires Secure" was enabled (as |
| // SameSite=None insecure cookies cannot be set while the options are on). |
| if (params.access_semantics != CookieAccessSemantics::LEGACY && |
| SameSite() == CookieSameSite::NO_RESTRICTION && !IsSecure()) { |
| status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE); |
| } |
| |
| switch (params.same_party_status) { |
| case CookieSamePartyStatus::kEnforceSamePartyExclude: |
| DCHECK(IsSameParty()); |
| status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT); |
| [[fallthrough]]; |
| case CookieSamePartyStatus::kEnforceSamePartyInclude: { |
| status.AddWarningReason(CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY); |
| // Remove any SameSite exclusion reasons, since SameParty overrides |
| // SameSite. |
| DCHECK(!status.HasExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT)); |
| DCHECK_NE(effective_same_site, CookieEffectiveSameSite::STRICT_MODE); |
| bool included_by_samesite = |
| !status.HasExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_LAX) && |
| !status.HasExclusionReason( |
| CookieInclusionStatus:: |
| EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX); |
| if (!included_by_samesite) { |
| status.RemoveExclusionReasons({ |
| CookieInclusionStatus::EXCLUDE_SAMESITE_LAX, |
| CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX, |
| }); |
| } |
| |
| // Update metrics. |
| if (status.HasOnlyExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT) && |
| included_by_samesite) { |
| status.AddWarningReason( |
| CookieInclusionStatus::WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE); |
| } |
| if (status.IsInclude()) { |
| if (!included_by_samesite) { |
| status.AddWarningReason( |
| CookieInclusionStatus:: |
| WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE); |
| } |
| } |
| break; |
| } |
| case CookieSamePartyStatus::kNoSamePartyEnforcement: |
| // Only apply SameSite-related warnings if SameParty is not in effect. |
| ApplySameSiteCookieWarningToStatus( |
| SameSite(), effective_same_site, IsSecure(), |
| options.same_site_cookie_context(), &status, |
| false /* is_cookie_being_set */); |
| break; |
| } |
| |
| if (status.IsInclude()) { |
| UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedRequestEffectiveSameSite", |
| effective_same_site, |
| CookieEffectiveSameSite::COUNT); |
| } |
| |
| using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext:: |
| ContextMetadata::ContextRedirectTypeBug1221316; |
| |
| ContextRedirectTypeBug1221316 redirect_type_for_metrics = |
| options.same_site_cookie_context() |
| .GetMetadataForCurrentSchemefulMode() |
| .redirect_type_bug_1221316; |
| if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) { |
| UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Read", |
| redirect_type_for_metrics); |
| } |
| |
| if (status.HasWarningReason( |
| CookieInclusionStatus:: |
| WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Read", |
| CookieSameSiteToCookieSameSiteForMetrics(SameSite())); |
| |
| using HttpMethod = |
| CookieOptions::SameSiteCookieContext::ContextMetadata::HttpMethod; |
| |
| HttpMethod http_method_enum = options.same_site_cookie_context() |
| .GetMetadataForCurrentSchemefulMode() |
| .http_method_bug_1221316; |
| |
| DCHECK(http_method_enum != HttpMethod::kUnset); |
| |
| UMA_HISTOGRAM_ENUMERATION( |
| "Cookie.CrossSiteRedirectDowngradeChangesInclusionHttpMethod", |
| http_method_enum); |
| |
| base::TimeDelta cookie_age = base::Time::Now() - creation_date_; |
| UMA_HISTOGRAM_EXACT_LINEAR( |
| "Cookie.CrossSiteRedirectDowngradeChangesInclusionAge", |
| cookie_age.InMinutes(), 30); |
| } |
| |
| return CookieAccessResult(effective_same_site, status, |
| params.access_semantics, |
| is_allowed_to_access_secure_cookies); |
| } |
| |
| CookieAccessResult CanonicalCookie::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) const { |
| CookieAccessResult access_result; |
| if (cookie_access_result) { |
| access_result = *cookie_access_result; |
| } |
| |
| if (!base::Contains(cookieable_schemes, source_url.scheme())) { |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_NONCOOKIEABLE_SCHEME); |
| } |
| |
| CookieAccessScheme access_scheme = |
| cookie_util::ProvisionalAccessScheme(source_url); |
| if (access_scheme == CookieAccessScheme::kNonCryptographic && |
| params.delegate_treats_url_as_trustworthy) { |
| access_scheme = CookieAccessScheme::kTrustworthy; |
| } |
| |
| switch (access_scheme) { |
| case CookieAccessScheme::kNonCryptographic: |
| access_result.is_allowed_to_access_secure_cookies = false; |
| if (IsSecure()) { |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SECURE_ONLY); |
| } |
| break; |
| |
| case CookieAccessScheme::kCryptographic: |
| // All cool! |
| access_result.is_allowed_to_access_secure_cookies = true; |
| break; |
| |
| case CookieAccessScheme::kTrustworthy: |
| access_result.is_allowed_to_access_secure_cookies = true; |
| if (IsSecure()) { |
| // OK, but want people aware of this. |
| access_result.status.AddWarningReason( |
| CookieInclusionStatus:: |
| WARN_SECURE_ACCESS_GRANTED_NON_CRYPTOGRAPHIC); |
| } |
| break; |
| } |
| |
| access_result.access_semantics = params.access_semantics; |
| if (options.exclude_httponly() && IsHttpOnly()) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "HttpOnly cookie not permitted in script context."; |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_HTTP_ONLY); |
| } |
| |
| // Unless legacy access semantics are in effect, SameSite=None cookies without |
| // the Secure attribute will be rejected. |
| if (params.access_semantics != CookieAccessSemantics::LEGACY && |
| SameSite() == CookieSameSite::NO_RESTRICTION && !IsSecure()) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "SetCookie() rejecting insecure cookie with SameSite=None."; |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_NONE_INSECURE); |
| } |
| |
| // For LEGACY cookies we should always return the schemeless context, |
| // otherwise let GetContextForCookieInclusion() decide. |
| CookieOptions::SameSiteCookieContext::ContextType cookie_inclusion_context = |
| params.access_semantics == CookieAccessSemantics::LEGACY |
| ? options.same_site_cookie_context().context() |
| : options.same_site_cookie_context().GetContextForCookieInclusion(); |
| |
| access_result.effective_same_site = |
| GetEffectiveSameSite(params.access_semantics); |
| DCHECK(access_result.effective_same_site != |
| CookieEffectiveSameSite::UNDEFINED); |
| switch (access_result.effective_same_site) { |
| case CookieEffectiveSameSite::STRICT_MODE: |
| // This intentionally checks for `< SAME_SITE_LAX`, as we allow |
| // `SameSite=Strict` cookies to be set for top-level navigations that |
| // qualify for receipt of `SameSite=Lax` cookies. |
| if (cookie_inclusion_context < |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "Trying to set a `SameSite=Strict` cookie from a " |
| "cross-site URL."; |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT); |
| } |
| break; |
| case CookieEffectiveSameSite::LAX_MODE: |
| case CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE: |
| if (cookie_inclusion_context < |
| CookieOptions::SameSiteCookieContext::ContextType::SAME_SITE_LAX) { |
| if (SameSite() == CookieSameSite::UNSPECIFIED) { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "Cookies with no known SameSite attribute being treated as " |
| "lax; attempt to set from a cross-site URL denied."; |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus:: |
| EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX); |
| } else { |
| DVLOG(net::cookie_util::kVlogSetCookies) |
| << "Trying to set a `SameSite=Lax` cookie from a cross-site URL."; |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_LAX); |
| } |
| } |
| break; |
| default: |
| break; |
| } |
| |
| switch (params.same_party_status) { |
| case CookieSamePartyStatus::kEnforceSamePartyExclude: |
| DCHECK(IsSameParty()); |
| access_result.status.AddExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT); |
| [[fallthrough]]; |
| case CookieSamePartyStatus::kEnforceSamePartyInclude: { |
| DCHECK(IsSameParty()); |
| access_result.status.AddWarningReason( |
| CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY); |
| // Remove any SameSite exclusion reasons, since SameParty overrides |
| // SameSite. |
| DCHECK(!access_result.status.HasExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT)); |
| DCHECK_NE(access_result.effective_same_site, |
| CookieEffectiveSameSite::STRICT_MODE); |
| bool included_by_samesite = |
| !access_result.status.HasExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMESITE_LAX) && |
| !access_result.status.HasExclusionReason( |
| CookieInclusionStatus:: |
| EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX); |
| if (!included_by_samesite) { |
| access_result.status.RemoveExclusionReasons({ |
| CookieInclusionStatus::EXCLUDE_SAMESITE_LAX, |
| CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX, |
| }); |
| } |
| |
| // Update metrics. |
| if (access_result.status.HasOnlyExclusionReason( |
| CookieInclusionStatus::EXCLUDE_SAMEPARTY_CROSS_PARTY_CONTEXT) && |
| included_by_samesite) { |
| access_result.status.AddWarningReason( |
| CookieInclusionStatus::WARN_SAMEPARTY_EXCLUSION_OVERRULED_SAMESITE); |
| } |
| if (access_result.status.IsInclude()) { |
| if (!included_by_samesite) { |
| access_result.status.AddWarningReason( |
| CookieInclusionStatus:: |
| WARN_SAMEPARTY_INCLUSION_OVERRULED_SAMESITE); |
| } |
| } |
| break; |
| } |
| case CookieSamePartyStatus::kNoSamePartyEnforcement: |
| // Only apply SameSite-related warnings if SameParty is not in effect. |
| ApplySameSiteCookieWarningToStatus( |
| SameSite(), access_result.effective_same_site, IsSecure(), |
| options.same_site_cookie_context(), &access_result.status, |
| true /* is_cookie_being_set */); |
| break; |
| } |
| |
| if (access_result.status.IsInclude()) { |
| UMA_HISTOGRAM_ENUMERATION("Cookie.IncludedResponseEffectiveSameSite", |
| access_result.effective_same_site, |
| CookieEffectiveSameSite::COUNT); |
| } |
| |
| using ContextRedirectTypeBug1221316 = CookieOptions::SameSiteCookieContext:: |
| ContextMetadata::ContextRedirectTypeBug1221316; |
| |
| ContextRedirectTypeBug1221316 redirect_type_for_metrics = |
| options.same_site_cookie_context() |
| .GetMetadataForCurrentSchemefulMode() |
| .redirect_type_bug_1221316; |
| if (redirect_type_for_metrics != ContextRedirectTypeBug1221316::kUnset) { |
| UMA_HISTOGRAM_ENUMERATION("Cookie.CrossSiteRedirectType.Write", |
| redirect_type_for_metrics); |
| } |
| |
| if (access_result.status.HasWarningReason( |
| CookieInclusionStatus:: |
| WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION)) { |
| UMA_HISTOGRAM_ENUMERATION( |
| "Cookie.CrossSiteRedirectDowngradeChangesInclusion2.Write", |
| CookieSameSiteToCookieSameSiteForMetrics(SameSite())); |
| } |
| |
| return access_result; |
| } |
| |
| std::string CanonicalCookie::DebugString() const { |
| return base::StringPrintf( |
| "name: %s value: %s domain: %s path: %s creation: %" PRId64, |
| name_.c_str(), value_.c_str(), domain_.c_str(), path_.c_str(), |
| static_cast<int64_t>(creation_date_.ToTimeT())); |
| } |
| |
| bool CanonicalCookie::PartialCompare(const CanonicalCookie& other) const { |
| return PartialCookieOrdering(*this, other) < 0; |
| } |
| |
| bool CanonicalCookie::IsCanonical() const { |
| // TODO(crbug.com/1244172) Eventually we should check the size of name+value, |
| // assuming we collect metrics and determine that a low percentage of cookies |
| // would fail this check. Note that we still don't want to enforce length |
| // checks on domain or path for the reason stated above. |
| |
| // TODO(crbug.com/1264458): Eventually we should push this logic into |
| // IsCanonicalForFromStorage, but for now we allow cookies already stored with |
| // high expiration dates to be retrieved. |
| if (ValidateAndAdjustExpiryDate(expiry_date_, creation_date_) != expiry_date_) |
| return false; |
| |
| return IsCanonicalForFromStorage(); |
| } |
| |
| bool CanonicalCookie::IsCanonicalForFromStorage() const { |
| // Not checking domain or path against ParsedCookie as it may have |
| // come purely from the URL. Also, don't call IsValidCookieNameValuePair() |
| // here because we don't want to enforce the size checks on names or values |
| // that may have been reconstituted from the cookie store. |
| if (ParsedCookie::ParseTokenString(name_) != name_ || |
| !ParsedCookie::ValueMatchesParsedValue(value_)) { |
| return false; |
| } |
| |
| if (!ParsedCookie::IsValidCookieName(name_) || |
| !ParsedCookie::IsValidCookieValue(value_)) { |
| return false; |
| } |
| |
| if (!last_access_date_.is_null() && creation_date_.is_null()) |
| return false; |
| |
| url::CanonHostInfo canon_host_info; |
| std::string canonical_domain(CanonicalizeHost(domain_, &canon_host_info)); |
| |
| // TODO(rdsmith): This specifically allows for empty domains. The spec |
| // suggests this is invalid (if a domain attribute is empty, the cookie's |
| // domain is set to the canonicalized request host; see |
| // https://tools.ietf.org/html/rfc6265#section-5.3). However, it is |
| // needed for Chrome extension cookies. |
| // See http://crbug.com/730633 for more information. |
| if (canonical_domain != domain_) |
| return false; |
| |
| if (path_.empty() || path_[0] != '/') |
| return false; |
| |
| CookiePrefix prefix = GetCookiePrefix(name_); |
| switch (prefix) { |
| case COOKIE_PREFIX_HOST: |
| if (!secure_ || path_ != "/" || domain_.empty() || domain_[0] == '.') |
| return false; |
| break; |
| case COOKIE_PREFIX_SECURE: |
| if (!secure_) |
| return false; |
| break; |
| default: |
| break; |
| } |
| |
| if (name_ == "" && HasHiddenPrefixName(value_)) |
| return false; |
| |
| if (!IsCookieSamePartyValid(same_party_, secure_, same_site_)) |
| return false; |
| |
| if (IsPartitioned()) { |
| if (CookiePartitionKey::HasNonce(partition_key_)) |
| return true; |
| if (!secure_) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| bool CanonicalCookie::IsEffectivelySameSiteNone( |
| CookieAccessSemantics access_semantics) const { |
| return GetEffectiveSameSite(access_semantics) == |
| CookieEffectiveSameSite::NO_RESTRICTION; |
| } |
| |
| CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSiteForTesting( |
| CookieAccessSemantics access_semantics) const { |
| return GetEffectiveSameSite(access_semantics); |
| } |
| |
| // static |
| std::string CanonicalCookie::BuildCookieLine(const CookieList& cookies) { |
| std::string cookie_line; |
| for (const auto& cookie : cookies) { |
| AppendCookieLineEntry(cookie, &cookie_line); |
| } |
| return cookie_line; |
| } |
| |
| // static |
| std::string CanonicalCookie::BuildCookieLine( |
| const CookieAccessResultList& cookie_access_result_list) { |
| std::string cookie_line; |
| for (const auto& cookie_with_access_result : cookie_access_result_list) { |
| const CanonicalCookie& cookie = cookie_with_access_result.cookie; |
| AppendCookieLineEntry(cookie, &cookie_line); |
| } |
| return cookie_line; |
| } |
| |
| // static |
| std::string CanonicalCookie::BuildCookieAttributesLine( |
| const CanonicalCookie& cookie) { |
| std::string cookie_line; |
| // In Mozilla, if you set a cookie like "AAA", it will have an empty token |
| // and a value of "AAA". When it sends the cookie back, it will send "AAA", |
| // so we need to avoid sending "=AAA" for a blank token value. |
| if (!cookie.Name().empty()) |
| cookie_line += cookie.Name() + "="; |
| cookie_line += cookie.Value(); |
| if (!cookie.Domain().empty()) |
| cookie_line += "; domain=" + cookie.Domain(); |
| if (!cookie.Path().empty()) |
| cookie_line += "; path=" + cookie.Path(); |
| if (cookie.ExpiryDate() != base::Time()) |
| cookie_line += "; expires=" + TimeFormatHTTP(cookie.ExpiryDate()); |
| if (cookie.IsSecure()) |
| cookie_line += "; secure"; |
| if (cookie.IsHttpOnly()) |
| cookie_line += "; httponly"; |
| switch (cookie.SameSite()) { |
| case CookieSameSite::NO_RESTRICTION: |
| cookie_line += "; samesite=none"; |
| break; |
| case CookieSameSite::LAX_MODE: |
| cookie_line += "; samesite=lax"; |
| break; |
| case CookieSameSite::STRICT_MODE: |
| cookie_line += "; samesite=strict"; |
| break; |
| case CookieSameSite::UNSPECIFIED: |
| // Don't append any text if the samesite attribute wasn't explicitly set. |
| break; |
| } |
| return cookie_line; |
| } |
| |
| // static |
| CanonicalCookie::CookiePrefix CanonicalCookie::GetCookiePrefix( |
| const std::string& name, |
| bool check_insensitively) { |
| const char kSecurePrefix[] = "__Secure-"; |
| const char kHostPrefix[] = "__Host-"; |
| |
| base::CompareCase case_sensitivity = |
| check_insensitively ? base::CompareCase::INSENSITIVE_ASCII |
| : base::CompareCase::SENSITIVE; |
| |
| if (base::StartsWith(name, kSecurePrefix, case_sensitivity)) |
| return CanonicalCookie::COOKIE_PREFIX_SECURE; |
| if (base::StartsWith(name, kHostPrefix, case_sensitivity)) |
| return CanonicalCookie::COOKIE_PREFIX_HOST; |
| return CanonicalCookie::COOKIE_PREFIX_NONE; |
| } |
| |
| // static |
| void CanonicalCookie::RecordCookiePrefixMetrics( |
| CookiePrefix prefix_case_sensitive, |
| CookiePrefix prefix_case_insensitive, |
| bool is_insensitive_prefix_valid) { |
| const char kCookiePrefixHistogram[] = "Cookie.CookiePrefix"; |
| UMA_HISTOGRAM_ENUMERATION(kCookiePrefixHistogram, prefix_case_sensitive, |
| CanonicalCookie::COOKIE_PREFIX_LAST); |
| |
| // For this to be true there must a prefix, so we know it's not |
| // COOKIE_PREFIX_NONE. |
| bool is_case_variant = prefix_case_insensitive != prefix_case_sensitive; |
| |
| if (is_case_variant) { |
| const char kCookiePrefixVariantHistogram[] = |
| "Cookie.CookiePrefix.CaseVariant"; |
| UMA_HISTOGRAM_ENUMERATION(kCookiePrefixVariantHistogram, |
| prefix_case_insensitive, |
| CanonicalCookie::COOKIE_PREFIX_LAST); |
| |
| const char kVariantValidHistogram[] = |
| "Cookie.CookiePrefix.CaseVariantValid"; |
| UMA_HISTOGRAM_BOOLEAN(kVariantValidHistogram, is_insensitive_prefix_valid); |
| } |
| |
| const char kVariantCountHistogram[] = "Cookie.CookiePrefix.CaseVariantCount"; |
| if (prefix_case_insensitive > CookiePrefix::COOKIE_PREFIX_NONE) { |
| UMA_HISTOGRAM_BOOLEAN(kVariantCountHistogram, is_case_variant); |
| } |
| } |
| |
| // Returns true if the cookie does not violate any constraints imposed |
| // by the cookie name's prefix, as described in |
| // https://tools.ietf.org/html/draft-west-cookie-prefixes |
| // |
| // static |
| bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix, |
| const GURL& url, |
| const ParsedCookie& parsed_cookie) { |
| return CanonicalCookie::IsCookiePrefixValid( |
| prefix, url, parsed_cookie.IsSecure(), |
| parsed_cookie.HasDomain() ? parsed_cookie.Domain() : "", |
| parsed_cookie.HasPath() ? parsed_cookie.Path() : ""); |
| } |
| |
| bool CanonicalCookie::IsCookiePrefixValid(CanonicalCookie::CookiePrefix prefix, |
| const GURL& url, |
| bool secure, |
| const std::string& domain, |
| const std::string& path) { |
| if (prefix == CanonicalCookie::COOKIE_PREFIX_SECURE) |
| return secure && url.SchemeIsCryptographic(); |
| if (prefix == CanonicalCookie::COOKIE_PREFIX_HOST) { |
| return HasValidHostPrefixAttributes(url, secure, domain, path); |
| } |
| return true; |
| } |
| |
| CookieEffectiveSameSite CanonicalCookie::GetEffectiveSameSite( |
| CookieAccessSemantics access_semantics) const { |
| base::TimeDelta lax_allow_unsafe_threshold_age = |
| base::FeatureList::IsEnabled( |
| features::kSameSiteDefaultChecksMethodRigorously) |
| ? base::TimeDelta::Min() |
| : (base::FeatureList::IsEnabled( |
| features::kShortLaxAllowUnsafeThreshold) |
| ? kShortLaxAllowUnsafeMaxAge |
| : kLaxAllowUnsafeMaxAge); |
| |
| switch (SameSite()) { |
| // If a cookie does not have a SameSite attribute, the effective SameSite |
| // mode depends on the access semantics and whether the cookie is |
| // recently-created. |
| case CookieSameSite::UNSPECIFIED: |
| return (access_semantics == CookieAccessSemantics::LEGACY) |
| ? CookieEffectiveSameSite::NO_RESTRICTION |
| : (IsRecentlyCreated(lax_allow_unsafe_threshold_age) |
| ? CookieEffectiveSameSite::LAX_MODE_ALLOW_UNSAFE |
| : CookieEffectiveSameSite::LAX_MODE); |
| case CookieSameSite::NO_RESTRICTION: |
| return CookieEffectiveSameSite::NO_RESTRICTION; |
| case CookieSameSite::LAX_MODE: |
| return CookieEffectiveSameSite::LAX_MODE; |
| case CookieSameSite::STRICT_MODE: |
| return CookieEffectiveSameSite::STRICT_MODE; |
| } |
| } |
| |
| // static |
| bool CanonicalCookie::HasHiddenPrefixName( |
| const base::StringPiece cookie_value) { |
| // Skip BWS as defined by HTTPSEM as SP or HTAB (0x20 or 0x9). |
| base::StringPiece value_without_BWS = |
| base::TrimString(cookie_value, " \t", base::TRIM_LEADING); |
| |
| const base::StringPiece host_prefix = "__Host-"; |
| |
| // Compare the value to the host_prefix. |
| if (base::StartsWith(value_without_BWS, host_prefix, |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| // This value contains a hidden prefix name. |
| return true; |
| } |
| |
| // Do a similar check for the secure prefix |
| const base::StringPiece secure_prefix = "__Secure-"; |
| |
| if (base::StartsWith(value_without_BWS, secure_prefix, |
| base::CompareCase::INSENSITIVE_ASCII)) { |
| return true; |
| } |
| |
| return false; |
| } |
| |
| bool CanonicalCookie::IsRecentlyCreated(base::TimeDelta age_threshold) const { |
| return (base::Time::Now() - creation_date_) <= age_threshold; |
| } |
| |
| // static |
| bool CanonicalCookie::IsCookieSamePartyValid( |
| const ParsedCookie& parsed_cookie) { |
| return IsCookieSamePartyValid(parsed_cookie.IsSameParty(), |
| parsed_cookie.IsSecure(), |
| parsed_cookie.SameSite()); |
| } |
| |
| // static |
| bool CanonicalCookie::IsCookieSamePartyValid(bool is_same_party, |
| bool is_secure, |
| CookieSameSite same_site) { |
| if (!is_same_party) |
| return true; |
| return is_secure && (same_site != CookieSameSite::STRICT_MODE); |
| } |
| |
| // static |
| bool CanonicalCookie::IsCookiePartitionedValid( |
| const GURL& url, |
| const ParsedCookie& parsed_cookie, |
| bool partition_has_nonce) { |
| return IsCookiePartitionedValid( |
| url, /*secure=*/parsed_cookie.IsSecure(), |
| /*is_partitioned=*/parsed_cookie.IsPartitioned(), partition_has_nonce); |
| } |
| |
| // static |
| bool CanonicalCookie::IsCookiePartitionedValid(const GURL& url, |
| bool secure, |
| bool is_partitioned, |
| bool partition_has_nonce) { |
| if (!is_partitioned) |
| return true; |
| if (partition_has_nonce) |
| return true; |
| CookieAccessScheme scheme = cookie_util::ProvisionalAccessScheme(url); |
| bool result = (scheme != CookieAccessScheme::kNonCryptographic) && secure; |
| DLOG_IF(WARNING, !result) |
| << "CanonicalCookie has invalid Partitioned attribute"; |
| return result; |
| } |
| |
| CookieAndLineWithAccessResult::CookieAndLineWithAccessResult() = default; |
| |
| CookieAndLineWithAccessResult::CookieAndLineWithAccessResult( |
| absl::optional<CanonicalCookie> cookie, |
| std::string cookie_string, |
| CookieAccessResult access_result) |
| : cookie(std::move(cookie)), |
| cookie_string(std::move(cookie_string)), |
| access_result(access_result) {} |
| |
| CookieAndLineWithAccessResult::CookieAndLineWithAccessResult( |
| const CookieAndLineWithAccessResult&) = default; |
| |
| CookieAndLineWithAccessResult& CookieAndLineWithAccessResult::operator=( |
| const CookieAndLineWithAccessResult& cookie_and_line_with_access_result) = |
| default; |
| |
| CookieAndLineWithAccessResult::CookieAndLineWithAccessResult( |
| CookieAndLineWithAccessResult&&) = default; |
| |
| CookieAndLineWithAccessResult::~CookieAndLineWithAccessResult() = default; |
| |
| } // namespace net |