diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-02-02 12:21:57 +0100 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2022-02-12 08:13:00 +0000 |
commit | 606d85f2a5386472314d39923da28c70c60dc8e7 (patch) | |
tree | a8f4d7bf997f349f45605e6058259fba0630e4d7 /chromium/net/cookies | |
parent | 5786336dda477d04fb98483dca1a5426eebde2d7 (diff) | |
download | qtwebengine-chromium-606d85f2a5386472314d39923da28c70c60dc8e7.tar.gz |
BASELINE: Update Chromium to 96.0.4664.181
Change-Id: I762cd1da89d73aa6313b4a753fe126c34833f046
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/cookies')
46 files changed, 3454 insertions, 839 deletions
diff --git a/chromium/net/cookies/COMMON_METADATA b/chromium/net/cookies/COMMON_METADATA new file mode 100644 index 00000000000..191a115309d --- /dev/null +++ b/chromium/net/cookies/COMMON_METADATA @@ -0,0 +1,3 @@ +monorail { + component: "Internals>Network>Cookies" +}
\ No newline at end of file diff --git a/chromium/net/cookies/DIR_METADATA b/chromium/net/cookies/DIR_METADATA index 69f95ee3873..7499c3237e1 100644 --- a/chromium/net/cookies/DIR_METADATA +++ b/chromium/net/cookies/DIR_METADATA @@ -6,6 +6,4 @@ # For the schema of this file, see Metadata message: # https://source.chromium.org/chromium/infra/infra/+/main:go/src/infra/tools/dirmd/proto/dir_metadata.proto -monorail { - component: "Internals>Network>Cookies" -}
\ No newline at end of file +mixins: "//net/cookies/COMMON_METADATA" diff --git a/chromium/net/cookies/canonical_cookie.cc b/chromium/net/cookies/canonical_cookie.cc index e9d0122b6c2..e3720d750ae 100644 --- a/chromium/net/cookies/canonical_cookie.cc +++ b/chromium/net/cookies/canonical_cookie.cc @@ -71,7 +71,6 @@ #include "url/url_util.h" using base::Time; -using base::TimeDelta; namespace net { @@ -427,7 +426,7 @@ Time CanonicalCookie::CanonExpiration(const ParsedCookie& pc, return Time::Min(); // "... Otherwise, let the expiry-time be the current date and time plus // delta-seconds seconds." - return current + TimeDelta::FromSeconds(max_age); + 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. @@ -474,9 +473,7 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::Create( if (!parsed_cookie.IsValid()) { DVLOG(net::cookie_util::kVlogSetCookies) << "WARNING: Couldn't parse cookie"; - // TODO(crbug.com/1228815): Apply more specific exclusion reasons. - DCHECK(status->HasExclusionReason( - CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE)); + DCHECK(!status->IsInclude()); // Don't continue, because an invalid ParsedCookie doesn't have any // attributes. // TODO(chlily): Log metrics. @@ -568,6 +565,10 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::Create( UMA_HISTOGRAM_BOOLEAN("Cookie.ControlCharacterTruncation", parsed_cookie.HasTruncatedNameOrValue()); + UMA_HISTOGRAM_ENUMERATION( + "Cookie.TruncatingCharacterInCookieString", + parsed_cookie.GetTruncatingCharacterInCookieStringType()); + return cc; } @@ -597,26 +598,56 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie( *status = CookieInclusionStatus(); // Validate consistency of passed arguments. - if (ParsedCookie::ParseTokenString(name) != name || - !ParsedCookie::IsValidCookieAttributeValue(name)) { + if (ParsedCookie::ParseTokenString(name) != name) { status->AddExclusionReason( net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); - } else if (ParsedCookie::ParseValueString(value) != value || - !ParsedCookie::IsValidCookieAttributeValue(value)) { + } 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); - } else if (name.empty() && value.empty()) { + } + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + // 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); + + } else if (!ParsedCookie::IsValidCookieAttributeValueLegacy(name) || + !ParsedCookie::IsValidCookieAttributeValueLegacy(value) || + (name.empty() && value.empty())) { status->AddExclusionReason( net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); } - if (ParsedCookie::ParseValueString(domain) != domain) { + // 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 (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + 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 @@ -624,7 +655,7 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie( if (!cookie_util::DomainIsHostOnly(url.host())) { status->AddExclusionReason( net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); - } else if (!cookie_util::GetCookieDomainWithString(url, domain, + } else if (!cookie_util::GetCookieDomainWithString(url, domain_attribute, &cookie_domain)) { status->AddExclusionReason( net::CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN); @@ -646,13 +677,35 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie( int source_port = url.EffectiveIntPort(); std::string cookie_path = CanonicalCookie::CanonPathWithString(url, path); - if (!path.empty() && cookie_path != path) { - status->AddExclusionReason( - net::CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + // 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 (base::FeatureList::IsEnabled( + features::kExtraCookieValidityChecks) && + !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, cookie_path)) { + if (!IsCookiePrefixValid(prefix, url, secure, domain_attribute, + cookie_path)) { status->AddExclusionReason( net::CookieInclusionStatus::EXCLUDE_INVALID_PREFIX); } @@ -675,19 +728,10 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::CreateSanitizedCookie( if (!status->IsInclude()) return nullptr; - // 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); - cookie_path = std::string(canon_path.data() + canon_path_component.begin, - canon_path_component.len); - std::unique_ptr<CanonicalCookie> cc = base::WrapUnique(new CanonicalCookie( - name, value, cookie_domain, cookie_path, creation_time, expiration_time, - last_access_time, secure, http_only, same_site, priority, same_party, - partition_key, source_scheme, source_port)); + name, value, cookie_domain, encoded_cookie_path, creation_time, + expiration_time, last_access_time, secure, http_only, same_site, priority, + same_party, partition_key, source_scheme, source_port)); DCHECK(cc->IsCanonical()); return cc; @@ -714,8 +758,17 @@ std::unique_ptr<CanonicalCookie> CanonicalCookie::FromStorage( std::move(name), std::move(value), std::move(domain), std::move(path), creation, expiration, last_access, secure, httponly, same_site, priority, same_party, partition_key, source_scheme, source_port)); - if (!cc->IsCanonical()) + + if (cc->IsCanonical()) { + // 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; } @@ -758,6 +811,9 @@ void CanonicalCookie::SetSourcePort(int 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(); @@ -772,7 +828,7 @@ bool CanonicalCookie::IsEquivalentForSecureCookieMatching( bool path_match = secure_cookie.IsOnPath(Path()); bool equivalent_for_secure_cookie_matching = - same_name && domain_match && path_match; + 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); @@ -1273,14 +1329,31 @@ bool CanonicalCookie::PartialCompare(const CanonicalCookie& other) const { bool CanonicalCookie::IsCanonical() const { // Not checking domain or path against ParsedCookie as it may have - // come purely from the URL. + // 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. + // 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. + if (ParsedCookie::ParseTokenString(name_) != name_ || - !ParsedCookie::ValueMatchesParsedValue(value_) || - !ParsedCookie::IsValidCookieAttributeValue(name_) || - !ParsedCookie::IsValidCookieAttributeValue(value_)) { + !ParsedCookie::ValueMatchesParsedValue(value_)) { return false; } + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + if (!ParsedCookie::IsValidCookieName(name_) || + !ParsedCookie::IsValidCookieValue(value_)) { + return false; + } + } else { + if (!ParsedCookie::IsValidCookieAttributeValueLegacy(name_) || + !ParsedCookie::IsValidCookieAttributeValueLegacy(value_)) { + return false; + } + } + if (!last_access_date_.is_null() && creation_date_.is_null()) return false; diff --git a/chromium/net/cookies/canonical_cookie.h b/chromium/net/cookies/canonical_cookie.h index a8dd4c7a704..a67345da239 100644 --- a/chromium/net/cookies/canonical_cookie.h +++ b/chromium/net/cookies/canonical_cookie.h @@ -167,6 +167,12 @@ class NET_EXPORT CanonicalCookie { CookieSourceScheme scheme_secure = CookieSourceScheme::kUnset, int source_port = url::PORT_UNSPECIFIED); + bool operator<(const CanonicalCookie& other) const { + // Use the cookie properties that uniquely identify a cookie to determine + // ordering. + return UniqueKey() < other.UniqueKey(); + } + const std::string& Name() const { return name_; } const std::string& Value() const { return value_; } // We represent the cookie's host-only-flag as the absence of a leading dot in @@ -189,24 +195,6 @@ class NET_EXPORT CanonicalCookie { return partition_key_; } - // Methods for serializing and deserializing a partition key to/from a string. - // This will be used for Android, storing persistent partitioned cookies, and - // loading partitioned cookies into Java code. - // - // This function returns if the partition key is not opaque. We do not want - // to serialize cookies with opaque origins in their partition key to disk, - // because if the browser session ends we will not be able to attach the - // saved cookie to any future requests. This is because opaque origins' nonces - // are only stored in volatile memory. - bool SerializePartitionKey(std::string& out) const WARN_UNUSED_RESULT; - // Deserializes the result of the method above. - // If the result is abls::nullopt, the resulting cookie is not partitioned. - // - // Returns if the resulting partition key is valid. - static bool DeserializePartitionKey(const std::string& in, - absl::optional<SchemefulSite>& out) - WARN_UNUSED_RESULT; - // Returns an enum indicating the scheme of the origin that // set this cookie. This is not part of the cookie spec but is being used to // collect metrics for a potential change to the cookie spec @@ -245,10 +233,10 @@ class NET_EXPORT CanonicalCookie { // Returns a key such that two cookies with the same UniqueKey() are // guaranteed to be equivalent in the sense of IsEquivalent(). + // The `partition_key_` field will always be nullopt when partitioned cookies + // are not enabled. UniqueCookieKey UniqueKey() const { - if (base::FeatureList::IsEnabled(features::kPartitionedCookies)) - return std::make_tuple(partition_key_, name_, domain_, path_); - return std::make_tuple(absl::nullopt, name_, domain_, path_); + return std::make_tuple(partition_key_, name_, domain_, path_); } // Checks a looser set of equivalency rules than 'IsEquivalent()' in order @@ -260,6 +248,8 @@ class NET_EXPORT CanonicalCookie { // Returns 'true' if this cookie's name matches |secure_cookie|, and this // cookie is a domain-match for |secure_cookie| (or vice versa), and // |secure_cookie|'s path is "on" this cookie's path (as per 'IsOnPath()'). + // If partitioned cookies are enabled, it also checks that the cookie has + // the same partition key as |secure_cookie|. // // Note that while the domain-match cuts both ways (e.g. 'example.com' // matches 'www.example.com' in either direction), the path-match is @@ -287,6 +277,19 @@ class NET_EXPORT CanonicalCookie { bool IsEquivalentForSecureCookieMatching( const CanonicalCookie& secure_cookie) const; + // Returns true if the |other| cookie's data members (instance variables) + // match, for comparing cookies in colletions. + bool HasEquivalentDataMembers(const CanonicalCookie& other) const { + return creation_date_ == other.creation_date_ && + last_access_date_ == other.last_access_date_ && + expiry_date_ == other.expiry_date_ && secure_ == other.secure_ && + httponly_ == other.httponly_ && same_site_ == other.same_site_ && + priority_ == other.priority_ && same_party_ == other.same_party_ && + partition_key_ == other.partition_key_ && name_ == other.name_ && + value_ == other.value_ && domain_ == other.domain_ && + path_ == other.path_; + } + void SetSourceScheme(CookieSourceScheme source_scheme) { source_scheme_ = source_scheme; } @@ -345,6 +348,9 @@ class NET_EXPORT CanonicalCookie { std::string DebugString() const; + // Returns the canonical path based on the specified url and path attribute + // value. Note that this method does not enforce character set or size + // checks on `path_string`. static std::string CanonPathWithString(const GURL& url, const std::string& path_string); diff --git a/chromium/net/cookies/canonical_cookie_fuzzer.cc b/chromium/net/cookies/canonical_cookie_fuzzer.cc index aa3bb25fa0f..558cd88e2aa 100644 --- a/chromium/net/cookies/canonical_cookie_fuzzer.cc +++ b/chromium/net/cookies/canonical_cookie_fuzzer.cc @@ -25,10 +25,14 @@ const base::Time getRandomTime(FuzzedDataProvider* data_provider) { extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { FuzzedDataProvider data_provider(data, size); - const std::string name = data_provider.ConsumeRandomLengthString(800); - const std::string value = data_provider.ConsumeRandomLengthString(800); - const std::string domain = data_provider.ConsumeRandomLengthString(800); - const std::string path = data_provider.ConsumeRandomLengthString(800); + const std::string name = data_provider.ConsumeRandomLengthString( + net::ParsedCookie::kMaxCookieNamePlusValueSize + 10); + const std::string value = data_provider.ConsumeRandomLengthString( + net::ParsedCookie::kMaxCookieNamePlusValueSize + 10); + const std::string domain = data_provider.ConsumeRandomLengthString( + net::ParsedCookie::kMaxCookieAttributeValueSize + 10); + const std::string path = data_provider.ConsumeRandomLengthString( + net::ParsedCookie::kMaxCookieAttributeValueSize + 10); const GURL url(data_provider.ConsumeRandomLengthString(800)); if (!url.is_valid()) diff --git a/chromium/net/cookies/canonical_cookie_unittest.cc b/chromium/net/cookies/canonical_cookie_unittest.cc index 55d49f245f1..d2e84a10bad 100644 --- a/chromium/net/cookies/canonical_cookie_unittest.cc +++ b/chromium/net/cookies/canonical_cookie_unittest.cc @@ -12,6 +12,7 @@ #include "net/cookies/cookie_constants.h" #include "net/cookies/cookie_inclusion_status.h" #include "net/cookies/cookie_options.h" +#include "net/cookies/parsed_cookie.h" #include "testing/gmock/include/gmock/gmock-matchers.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -130,7 +131,7 @@ TEST(CanonicalCookieTest, Constructor) { EXPECT_EQ(cookie6->SourcePort(), url::PORT_INVALID); } -TEST(CanonicalCookie, CreationCornerCases) { +TEST(CanonicalCookieTest, CreationCornerCases) { base::Time creation_time = base::Time::Now(); std::unique_ptr<CanonicalCookie> cookie; absl::optional<base::Time> server_time = absl::nullopt; @@ -162,6 +163,23 @@ TEST(CanonicalCookie, CreationCornerCases) { EXPECT_FALSE(cookie.get()); EXPECT_TRUE(status.HasExclusionReason( CookieInclusionStatus::ExclusionReason::EXCLUDE_FAILURE_TO_STORE)); + + // The ParsedCookie constructor unit tests cover many edge cases related to + // invalid sizes when parsing a cookie line, and since CanonicalCookie::Create + // creates a ParsedCookie immediately, there's no point in replicating all + // of those tests here. We should test that the corresponding ExclusionReason + // gets passed back correctly, though. + std::string too_long_value(ParsedCookie::kMaxCookieNamePlusValueSize + 1, + 'a'); + + cookie = CanonicalCookie::Create(GURL("http://www.example.com/test/foo.html"), + too_long_value, creation_time, server_time, + absl::nullopt /* cookie_partition_key */, + &status); + EXPECT_FALSE(cookie.get()); + EXPECT_TRUE( + status.HasExclusionReason(CookieInclusionStatus::ExclusionReason:: + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)); } TEST(CanonicalCookieTest, Create) { @@ -503,8 +521,7 @@ TEST(CanonicalCookieTest, CreateWithMaxAge) { EXPECT_TRUE(cookie.get()); EXPECT_TRUE(cookie->IsPersistent()); EXPECT_FALSE(cookie->IsExpired(creation_time)); - EXPECT_EQ(base::TimeDelta::FromSeconds(60) + creation_time, - cookie->ExpiryDate()); + EXPECT_EQ(base::Seconds(60) + creation_time, cookie->ExpiryDate()); // Max-age with expires (max-age should take precedence). cookie = CanonicalCookie::Create( @@ -513,8 +530,7 @@ TEST(CanonicalCookieTest, CreateWithMaxAge) { EXPECT_TRUE(cookie.get()); EXPECT_TRUE(cookie->IsPersistent()); EXPECT_FALSE(cookie->IsExpired(creation_time)); - EXPECT_EQ(base::TimeDelta::FromSeconds(60) + creation_time, - cookie->ExpiryDate()); + EXPECT_EQ(base::Seconds(60) + creation_time, cookie->ExpiryDate()); // Max-age=0 should create an expired cookie with expiry equal to the earliest // representable time. @@ -543,8 +559,7 @@ TEST(CanonicalCookieTest, CreateWithMaxAge) { EXPECT_TRUE(cookie.get()); EXPECT_TRUE(cookie->IsPersistent()); EXPECT_FALSE(cookie->IsExpired(creation_time)); - EXPECT_EQ(base::TimeDelta::FromSeconds(60) + creation_time, - cookie->ExpiryDate()); + EXPECT_EQ(base::Seconds(60) + creation_time, cookie->ExpiryDate()); // Max-age with non-integer should be ignored. cookie = CanonicalCookie::Create(url, "A=1; max-age=abcd", creation_time, @@ -597,7 +612,7 @@ TEST(CanonicalCookieTest, EmptyExpiry) { EXPECT_EQ(base::Time(), cookie->ExpiryDate()); // With a stale server time - server_time = creation_time - base::TimeDelta::FromHours(1); + server_time = creation_time - base::Hours(1); cookie = CanonicalCookie::Create(url, cookie_line, creation_time, server_time, absl::nullopt /* cookie_partition_key */); EXPECT_TRUE(cookie.get()); @@ -606,7 +621,7 @@ TEST(CanonicalCookieTest, EmptyExpiry) { EXPECT_EQ(base::Time(), cookie->ExpiryDate()); // With a future server time - server_time = creation_time + base::TimeDelta::FromHours(1); + server_time = creation_time + base::Hours(1); cookie = CanonicalCookie::Create(url, cookie_line, creation_time, server_time, absl::nullopt /* cookie_partition_key */); EXPECT_TRUE(cookie.get()); @@ -622,7 +637,7 @@ TEST(CanonicalCookieTest, IsEquivalent) { std::string cookie_domain = ".www.example.com"; std::string cookie_path = "/path"; base::Time creation_time = base::Time::Now(); - base::Time expiration_time = creation_time + base::TimeDelta::FromDays(2); + base::Time expiration_time = creation_time + base::Days(2); bool secure = false; bool httponly = false; CookieSameSite same_site = CookieSameSite::NO_RESTRICTION; @@ -654,8 +669,7 @@ TEST(CanonicalCookieTest, IsEquivalent) { EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie)); EXPECT_TRUE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie)); - base::Time other_creation_time = - creation_time + base::TimeDelta::FromMinutes(2); + base::Time other_creation_time = creation_time + base::Minutes(2); other_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( cookie_name, "2", cookie_domain, cookie_path, other_creation_time, expiration_time, base::Time(), secure, httponly, same_site, @@ -749,77 +763,38 @@ TEST(CanonicalCookieTest, IsEquivalent) { EXPECT_FALSE(cookie->IsEquivalent(*other_cookie)); EXPECT_TRUE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie)); EXPECT_FALSE(other_cookie->IsEquivalentForSecureCookieMatching(*cookie)); -} -class PartitionedCanonicalCookieTest : public testing::TestWithParam<bool> { - protected: - void SetUp() override { - if (PartitionedCookiesEnabled()) - scoped_feature_list_.InitAndEnableFeature(features::kPartitionedCookies); - testing::TestWithParam<bool>::SetUp(); - } - - bool PartitionedCookiesEnabled() { return GetParam(); } - - base::test::ScopedFeatureList scoped_feature_list_; -}; - -INSTANTIATE_TEST_SUITE_P(/* no label */, - PartitionedCanonicalCookieTest, - testing::Bool()); - -TEST_P(PartitionedCanonicalCookieTest, IsEquivalent) { - const std::string name = "__Host-foo"; - const std::string value = "bar"; - const std::string domain = ""; - const std::string path = "/"; - const base::Time creation_time = base::Time(); - const base::Time expiration_time = base::Time(); - const base::Time last_accessed_time = base::Time(); - const bool secure = true; - const bool http_only = false; - const CookieSameSite same_site = CookieSameSite::NO_RESTRICTION; - const CookiePriority cookie_priority = COOKIE_PRIORITY_DEFAULT; - const bool same_party = false; - - const absl::optional<CookiePartitionKey> cookie_partition_key = + // Partitioned cookies are not equivalent to unpartitioned cookies. + other_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( + cookie_name, cookie_value, cookie_domain, cookie_path, creation_time, + expiration_time, base::Time(), secure, httponly, same_site, + COOKIE_PRIORITY_MEDIUM, same_party, absl::make_optional( - CookiePartitionKey::FromURLForTesting(GURL("https://foo.com"))); - const absl::optional<CookiePartitionKey> other_cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")))); + EXPECT_FALSE(cookie->IsEquivalent(*other_cookie)); + EXPECT_FALSE(cookie->IsEquivalentForSecureCookieMatching(*other_cookie)); + + // Partitioned cookies are equal if they have the same partition key. + auto paritioned_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( + cookie_name, cookie_value, cookie_domain, cookie_path, creation_time, + expiration_time, base::Time(), secure, httponly, same_site, + COOKIE_PRIORITY_MEDIUM, same_party, absl::make_optional( - CookiePartitionKey::FromURLForTesting(GURL("https://bar.com"))); - - auto unpartitioned_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( - name, value, domain, path, creation_time, expiration_time, - last_accessed_time, secure, http_only, same_site, cookie_priority, - same_party, absl::nullopt); - auto partitioned_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( - name, value, domain, path, creation_time, expiration_time, - last_accessed_time, secure, http_only, same_site, cookie_priority, - same_party, cookie_partition_key); - - // Check that a cookie without a partition key is not equivalent to a cookie - // with one when partitioned cookie are enabled. If they are disabled the - // cookies should be equivalent. - EXPECT_NE(PartitionedCookiesEnabled(), - unpartitioned_cookie->IsEquivalent(*partitioned_cookie)); - - // Check that cookies with the same partition key are equivalent. This should - // be true regardless of the partitioned cookies setting. - auto other_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( - name, value, domain, path, creation_time, expiration_time, - last_accessed_time, secure, http_only, same_site, cookie_priority, - same_party, cookie_partition_key); - EXPECT_TRUE(partitioned_cookie->IsEquivalent(*other_cookie)); + CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")))); + EXPECT_TRUE(paritioned_cookie->IsEquivalent(*other_cookie)); + EXPECT_TRUE( + paritioned_cookie->IsEquivalentForSecureCookieMatching(*other_cookie)); - // Check that cookies with different partition keys are not equivalent only - // when partitioned cookies are enabled. + // Partitioned cookies with different partition keys are not equal other_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( - name, value, domain, path, creation_time, expiration_time, - last_accessed_time, secure, http_only, same_site, cookie_priority, - same_party, other_cookie_partition_key); - EXPECT_NE(PartitionedCookiesEnabled(), - partitioned_cookie->IsEquivalent(*other_cookie)); + cookie_name, cookie_value, cookie_domain, cookie_path, creation_time, + expiration_time, base::Time(), secure, httponly, same_site, + COOKIE_PRIORITY_MEDIUM, same_party, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://bar.com")))); + EXPECT_FALSE(paritioned_cookie->IsEquivalent(*other_cookie)); + EXPECT_FALSE( + paritioned_cookie->IsEquivalentForSecureCookieMatching(*other_cookie)); } TEST(CanonicalCookieTest, IsEquivalentForSecureCookieMatching) { @@ -828,6 +803,7 @@ TEST(CanonicalCookieTest, IsEquivalentForSecureCookieMatching) { const char* name; const char* domain; const char* path; + absl::optional<CookiePartitionKey> cookie_partition_key = absl::nullopt; } cookie, secure_cookie; bool equivalent; bool is_symmetric; // Whether the reverse comparison has the same result. @@ -852,18 +828,46 @@ TEST(CanonicalCookieTest, IsEquivalentForSecureCookieMatching) { // Different paths don't match {{"A", "a.foo.com", "/sub"}, {"A", "a.foo.com", "/other"}, false, true}, {{"A", "a.foo.com", "/a/b"}, {"A", "a.foo.com", "/a/c"}, false, true}, + // Partitioned cookies are not equivalent to unpartitioned cookies. + {{"A", ".a.foo.com", "/"}, + {"A", ".a.foo.com", "/", + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://bar.com")))}, + false, + true}, + // Partitioned cookies are equivalent if they have the same partition key. + {{"A", "a.foo.com", "/", + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://bar.com")))}, + {"A", "a.foo.com", "/", + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://bar.com")))}, + true, + true}, + // Partitioned cookies are *not* equivalent if they have the different + // partition keys. + {{"A", "a.foo.com", "/", + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://bar.com")))}, + {"A", "a.foo.com", "/", + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://baz.com")))}, + false, + true}, }; for (auto test : kTests) { auto cookie = CanonicalCookie::CreateUnsafeCookieForTesting( test.cookie.name, "value1", test.cookie.domain, test.cookie.path, - base::Time(), base::Time(), base::Time(), false /* secure */, false, - CookieSameSite::LAX_MODE, COOKIE_PRIORITY_MEDIUM, false); + base::Time(), base::Time(), base::Time(), false /* secure */, + false /* httponly */, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_MEDIUM, + false /* sameparty */, test.cookie.cookie_partition_key); auto secure_cookie = CanonicalCookie::CreateUnsafeCookieForTesting( test.secure_cookie.name, "value2", test.secure_cookie.domain, test.secure_cookie.path, base::Time(), base::Time(), base::Time(), - true /* secure */, false, CookieSameSite::LAX_MODE, - COOKIE_PRIORITY_MEDIUM, false); + true /* secure */, false /* httponly */, CookieSameSite::LAX_MODE, + COOKIE_PRIORITY_MEDIUM, false /* sameparty */, + test.secure_cookie.cookie_partition_key); EXPECT_EQ(test.equivalent, cookie->IsEquivalentForSecureCookieMatching(*secure_cookie)); @@ -2377,6 +2381,20 @@ TEST(CanonicalCookieTest, IsCanonical) { COOKIE_PRIORITY_LOW, false) ->IsCanonical()); + // "localhost" as domain. + EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", "localhost", "/path", base::Time(), base::Time(), + base::Time(), false, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + // Localhost IPv4 address as domain. + EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", "127.0.0.1", "/path", base::Time(), base::Time(), + base::Time(), false, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + // Simple IPv4 address as domain. EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "1.2.3.4", "/path", base::Time(), base::Time(), @@ -2384,7 +2402,28 @@ TEST(CanonicalCookieTest, IsCanonical) { COOKIE_PRIORITY_LOW, false) ->IsCanonical()); - // NOn-canonical IPv4 address as domain. + // period-prefixed IPv4 address as domain. + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", ".1.3.2.4", "/path", base::Time(), base::Time(), + base::Time(), false, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + // period-prefixed truncated IPv4 address as domain. + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", ".3.2.4", "/path", base::Time(), base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + // truncated IPv4 address as domain. + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", "3.2.4", "/path", base::Time(), base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + // Non-canonical IPv4 address as domain. EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( "A", "B", "01.2.03.4", "/path", base::Time(), base::Time(), base::Time(), false, false, CookieSameSite::NO_RESTRICTION, @@ -2436,6 +2475,20 @@ TEST(CanonicalCookieTest, IsCanonical) { CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_LOW, false) ->IsCanonical()); + // Lowercased hex IPv6 address as domain for domain cookie. + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", ".[2001:db8:ac10:fe01::]", "/path", base::Time(), + base::Time(), base::Time(), false, false, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + + // Incomplete lowercased hex IPv6 address as domain. + EXPECT_FALSE(CanonicalCookie::CreateUnsafeCookieForTesting( + "A", "B", "[2001:db8:ac10:fe01:]", "/path", base::Time(), + base::Time(), base::Time(), false, false, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_LOW, false) + ->IsCanonical()); + // Properly formatted host cookie. EXPECT_TRUE(CanonicalCookie::CreateUnsafeCookieForTesting( "__Host-A", "B", "x.y", "/", base::Time(), base::Time(), @@ -2630,23 +2683,28 @@ TEST(CanonicalCookieTest, BuildCookieLine) { url, "D=E", now, server_time, absl::nullopt /* cookie_partition_key */)); MatchCookieLineToVector("A=B; C; D=E", cookies); // BuildCookieLine doesn't reorder the list, it relies on the caller to do so. - cookies.push_back(CanonicalCookie::Create( - url, "F=G", now - base::TimeDelta::FromSeconds(1), server_time, - absl::nullopt /* cookie_partition_key */)); + cookies.push_back( + CanonicalCookie::Create(url, "F=G", now - base::Seconds(1), server_time, + absl::nullopt /* cookie_partition_key */)); MatchCookieLineToVector("A=B; C; D=E; F=G", cookies); // BuildCookieLine doesn't deduplicate. - cookies.push_back(CanonicalCookie::Create( - url, "D=E", now - base::TimeDelta::FromSeconds(2), server_time, - absl::nullopt /* cookie_partition_key */)); + cookies.push_back( + CanonicalCookie::Create(url, "D=E", now - base::Seconds(2), server_time, + absl::nullopt /* cookie_partition_key */)); MatchCookieLineToVector("A=B; C; D=E; F=G; D=E", cookies); + // BuildCookieLine should match the spec in the case of an empty name with a + // value containing an equal sign (even if it currently produces "invalid" + // cookie lines). + cookies.push_back(CanonicalCookie::Create( + url, "=H=I", now, server_time, absl::nullopt /* cookie_partition_key */)); + MatchCookieLineToVector("A=B; C; D=E; F=G; D=E; H=I", cookies); } // Confirm that input arguments are reflected in the output cookie. TEST(CanonicalCookieTest, CreateSanitizedCookie_Inputs) { - base::Time two_hours_ago = base::Time::Now() - base::TimeDelta::FromHours(2); - base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1); - base::Time one_hour_from_now = - base::Time::Now() + base::TimeDelta::FromHours(1); + base::Time two_hours_ago = base::Time::Now() - base::Hours(2); + base::Time one_hour_ago = base::Time::Now() - base::Hours(1); + base::Time one_hour_from_now = base::Time::Now() + base::Hours(1); CookieInclusionStatus status; std::unique_ptr<CanonicalCookie> cc; @@ -2785,10 +2843,9 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Inputs) { // Make sure sanitization and blocking of cookies works correctly. TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) { - base::Time two_hours_ago = base::Time::Now() - base::TimeDelta::FromHours(2); - base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1); - base::Time one_hour_from_now = - base::Time::Now() + base::TimeDelta::FromHours(1); + base::Time two_hours_ago = base::Time::Now() - base::Hours(2); + base::Time one_hour_ago = base::Time::Now() - base::Hours(1); + base::Time one_hour_from_now = base::Time::Now() + base::Hours(1); CookieInclusionStatus status; // Simple path and domain variations. @@ -3011,7 +3068,7 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) { // Path with unusual characters escaped. cc = CanonicalCookie::CreateSanitizedCookie( - GURL("http://www.foo.com"), "A", "B", std::string(), "/foo", + GURL("http://www.foo.com"), "A", "B", std::string(), "/foo\x7F", base::Time(), base::Time(), base::Time(), false /*secure*/, false /*httponly*/, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false /*same_party*/, @@ -3020,6 +3077,41 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) { EXPECT_EQ("/foo%7F", cc->Path()); EXPECT_TRUE(status.IsInclude()); + // Ensure that all characters get escaped the same on all platforms. This is + // also useful for visualizing which characters will actually be escaped. + std::stringstream ss; + ss << "/"; + for (uint8_t character = 0; character < 0xFF; character++) { + // Skip any "terminating characters" that CreateSanitizedCookie does not + // allow to be in `path`. + if (character == '\0' || character == '\n' || character == '\r' || + character == ';') { + continue; + } + ss << character; + } + ss << "\xFF"; + std::string initial(ss.str()); + std::string expected = + "/%01%02%03%04%05%06%07%08%09%0B%0C%0E%0F%10%11%12%13%14%15%16%17%18%19%" + "1A%1B%1C%1D%1E%1F%20!%22%23$%&'()*+,-./" + "0123456789:%3C=%3E%3F@ABCDEFGHIJKLMNOPQRSTUVWXYZ[/" + "]%5E_%60abcdefghijklmnopqrstuvwxyz%7B%7C%7D~%7F%80%81%82%83%84%85%86%87%" + "88%89%8A%8B%8C%8D%8E%8F%90%91%92%93%94%95%96%97%98%99%9A%9B%9C%9D%9E%9F%" + "A0%A1%A2%A3%A4%A5%A6%A7%A8%A9%AA%AB%AC%AD%AE%AF%B0%B1%B2%B3%B4%B5%B6%B7%" + "B8%B9%BA%BB%BC%BD%BE%BF%C0%C1%C2%C3%C4%C5%C6%C7%C8%C9%CA%CB%CC%CD%CE%CF%" + "D0%D1%D2%D3%D4%D5%D6%D7%D8%D9%DA%DB%DC%DD%DE%DF%E0%E1%E2%E3%E4%E5%E6%E7%" + "E8%E9%EA%EB%EC%ED%EE%EF%F0%F1%F2%F3%F4%F5%F6%F7%F8%F9%FA%FB%FC%FD%FE%FF"; + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com"), "A", "B", std::string(), initial, + base::Time(), base::Time(), base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + ASSERT_TRUE(cc); + EXPECT_EQ(expected, cc->Path()); + EXPECT_TRUE(status.IsInclude()); + // Empty name and value. EXPECT_FALSE(CanonicalCookie::CreateSanitizedCookie( GURL("http://www.foo.com"), "", "", std::string(), "/", base::Time(), @@ -3029,6 +3121,38 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) { EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( {CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE})); + // Check that value can contain an equal sign, even when no name is present. + // Note that in newer drafts of RFC6265bis, it is specified that a cookie with + // an empty name and a value containing an equal sign should result in a + // corresponding cookie line that omits the preceding equal sign. This means + // that the cookie line won't be deserialized into the original cookie in this + // case. For now, we'll test for compliance with the spec here, but we aim to + // collect metrics and hopefully fix this in the spec (and then in + // CanonicalCookie) at some point. + // For reference, see: https://github.com/httpwg/http-extensions/pull/1592 + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com"), "", "ambiguous=value", std::string(), + std::string(), base::Time(), base::Time(), base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + EXPECT_TRUE(cc); + std::vector<std::unique_ptr<CanonicalCookie>> cookies; + cookies.push_back(std::move(cc)); + MatchCookieLineToVector("ambiguous=value", cookies); + + // Check that name can't contain an equal sign ("ambiguous=name=value" should + // correctly be parsed as name: "ambiguous" and value "name=value", so + // allowing this case would result in cookies that can't serialize correctly). + EXPECT_FALSE(CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com"), "ambiguous=name", "value", std::string(), + std::string(), base::Time(), base::Time(), base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE})); + // A __Secure- cookie must be Secure. EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( GURL("https://www.foo.com"), "__Secure-A", "B", ".www.foo.com", "/", @@ -3265,13 +3389,199 @@ TEST(CanonicalCookieTest, CreateSanitizedCookie_Logic) { {CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE, CookieInclusionStatus::EXCLUDE_INVALID_DOMAIN, CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY})); + + // Check that RFC6265bis name + value string length limits are enforced. + std::string max_name(ParsedCookie::kMaxCookieNamePlusValueSize, 'a'); + std::string max_value(ParsedCookie::kMaxCookieNamePlusValueSize, 'b'); + std::string almost_max_name = max_name.substr(1, std::string::npos); + std::string almost_max_value = max_value.substr(1, std::string::npos); + + EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/foo"), max_name, "", std::string(), "/foo", + one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.IsInclude()); + EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/foo"), "", max_value, std::string(), "/foo", + one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.IsInclude()); + EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/foo"), almost_max_name, "b", std::string(), + "/foo", one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.IsInclude()); + EXPECT_TRUE(CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/foo"), "a", almost_max_value, std::string(), + "/foo", one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status)); + EXPECT_TRUE(status.IsInclude()); + + for (const bool toggle : {false, true}) { + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState( + features::kExtraCookieValidityChecks, toggle); + + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/foo"), max_name, "X", std::string(), "/foo", + one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + EXPECT_FALSE(cc); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE})); + } else { + EXPECT_TRUE(cc); + EXPECT_TRUE(status.IsInclude()); + } + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/foo"), "X", max_value, std::string(), "/foo", + one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + EXPECT_FALSE(cc); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE})); + } else { + EXPECT_TRUE(cc); + EXPECT_TRUE(status.IsInclude()); + } + } + + // Check that the RFC6265bis attribute value size limits apply to the Path + // attribute value. + std::string almost_max_path(ParsedCookie::kMaxCookieAttributeValueSize - 1, + 'c'); + std::string max_path = "/" + almost_max_path; + std::string too_long_path = "/X" + almost_max_path; + + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com" + max_path), "name", "value", std::string(), + max_path, one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + EXPECT_TRUE(cc); + EXPECT_EQ(max_path, cc->Path()); + EXPECT_TRUE(status.IsInclude()); + + for (const bool toggle : {false, true}) { + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState( + features::kExtraCookieValidityChecks, toggle); + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/path-attr-from-url/"), "name", "value", + std::string(), too_long_path, one_hour_ago, one_hour_from_now, + base::Time(), false /*secure*/, false /*httponly*/, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status); + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + EXPECT_FALSE(cc); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE})); + } else { + EXPECT_TRUE(cc); + EXPECT_EQ(too_long_path, cc->Path()); + EXPECT_TRUE(status.IsInclude()); + } + } + + // Check that length limits on the Path attribute value are not enforced in + // the case where no Path attribute is specified and the path value is + // implicitly set from the URL. + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com" + too_long_path + "/"), "name", "value", + std::string(), std::string(), one_hour_ago, one_hour_from_now, + base::Time(), false /*secure*/, false /*httponly*/, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status); + EXPECT_TRUE(cc); + EXPECT_EQ(too_long_path, cc->Path()); + EXPECT_TRUE(status.IsInclude()); + + // The Path attribute value gets URL-encoded, so ensure that the size limit is + // enforced after this (to avoid setting cookies where the Path attribute + // value would otherwise exceed the lengths specified in the RFC). + for (const bool toggle : {false, true}) { + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState( + features::kExtraCookieValidityChecks, toggle); + std::string expanding_path(ParsedCookie::kMaxCookieAttributeValueSize / 2, + '#'); + expanding_path = "/" + expanding_path; + + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.foo.com/path-attr-from-url/"), "name", "value", + std::string(), expanding_path, one_hour_ago, one_hour_from_now, + base::Time(), false /*secure*/, false /*httponly*/, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, &status); + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + EXPECT_FALSE(cc); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE})); + } else { + EXPECT_TRUE(cc); + // "#" expands into "%23"; -2 because '/' doesn't expand + EXPECT_EQ((expanding_path.size() * 3) - 2, cc->Path().size()); + EXPECT_TRUE(status.IsInclude()); + } + } + + // Check that the RFC6265bis attribute value size limits apply to the Domain + // attribute value. + std::string max_domain(ParsedCookie::kMaxCookieAttributeValueSize, 'd'); + max_domain.replace(ParsedCookie::kMaxCookieAttributeValueSize - 4, 4, ".com"); + std::string too_long_domain = "x" + max_domain; + + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://" + max_domain + "/"), "name", "value", max_domain, "/", + one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + EXPECT_TRUE(cc); + EXPECT_EQ(max_domain, cc->DomainWithoutDot()); + EXPECT_TRUE(status.IsInclude()); + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://www.domain-from-url.com/"), "name", "value", too_long_domain, + "/", one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + EXPECT_FALSE(cc); + EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE})); + // Check that length limits on the Domain attribute value are not enforced in + // the case where no Domain attribute is specified and the domain value is + // implicitly set from the URL. + cc = CanonicalCookie::CreateSanitizedCookie( + GURL("http://" + too_long_domain + "/"), "name", "value", std::string(), + "/", one_hour_ago, one_hour_from_now, base::Time(), false /*secure*/, + false /*httponly*/, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /*same_party*/, + absl::nullopt /*partition_key*/, &status); + EXPECT_TRUE(cc); + EXPECT_EQ(too_long_domain, cc->DomainWithoutDot()); + EXPECT_TRUE(status.IsInclude()); } TEST(CanonicalCookieTest, FromStorage) { - base::Time two_hours_ago = base::Time::Now() - base::TimeDelta::FromHours(2); - base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1); - base::Time one_hour_from_now = - base::Time::Now() + base::TimeDelta::FromHours(1); + base::Time two_hours_ago = base::Time::Now() - base::Hours(2); + base::Time one_hour_ago = base::Time::Now() - base::Hours(1); + base::Time one_hour_from_now = base::Time::Now() + base::Hours(1); std::unique_ptr<CanonicalCookie> cc = CanonicalCookie::FromStorage( "A", "B", "www.foo.com", "/bar", two_hours_ago, one_hour_from_now, @@ -4433,4 +4743,48 @@ TEST(CanonicalCookieTest, IsSetPermittedInContext_RedirectDowngradeWarning) { } } +TEST(CanonicalCookieTest, TestIsCanonicalWithInvalidSizeHistograms) { + base::HistogramTester histograms; + const char kFromStorageWithValidLengthHistogram[] = + "Cookie.FromStorageWithValidLength"; + const base::HistogramBase::Sample kInValid = 0; + const base::HistogramBase::Sample kValid = 1; + + base::Time two_hours_ago = base::Time::Now() - base::Hours(2); + base::Time one_hour_ago = base::Time::Now() - base::Hours(1); + base::Time one_hour_from_now = base::Time::Now() + base::Hours(1); + + // Test a cookie that is canonical and valid size + EXPECT_TRUE(CanonicalCookie::FromStorage( + "A", "B", "www.foo.com", "/bar", two_hours_ago, one_hour_from_now, + one_hour_ago, false /*secure*/, false /*httponly*/, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, + CookieSourceScheme::kSecure, 87)); + + histograms.ExpectBucketCount(kFromStorageWithValidLengthHistogram, kInValid, + 0); + histograms.ExpectBucketCount(kFromStorageWithValidLengthHistogram, kValid, 1); + + // Test loading a couple of cookies which are canonical but with an invalid + // size + const std::string kCookieBig(4096, 'a'); + EXPECT_TRUE(CanonicalCookie::FromStorage( + kCookieBig, "B", "www.foo.com", "/bar", two_hours_ago, one_hour_from_now, + one_hour_ago, false /*secure*/, false /*httponly*/, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, + CookieSourceScheme::kSecure, 87)); + EXPECT_TRUE(CanonicalCookie::FromStorage( + "A", kCookieBig, "www.foo.com", "/bar", two_hours_ago, one_hour_from_now, + one_hour_ago, false /*secure*/, false /*httponly*/, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, + false /*same_party*/, absl::nullopt /*partition_key*/, + CookieSourceScheme::kSecure, 87)); + + histograms.ExpectBucketCount(kFromStorageWithValidLengthHistogram, kInValid, + 2); + histograms.ExpectBucketCount(kFromStorageWithValidLengthHistogram, kValid, 1); +} + } // namespace net diff --git a/chromium/net/cookies/cookie_access_delegate.h b/chromium/net/cookies/cookie_access_delegate.h index 2c5715ef14e..0dfba9e99f2 100644 --- a/chromium/net/cookies/cookie_access_delegate.h +++ b/chromium/net/cookies/cookie_access_delegate.h @@ -21,6 +21,10 @@ class SiteForCookies; class NET_EXPORT CookieAccessDelegate { public: CookieAccessDelegate(); + + CookieAccessDelegate(const CookieAccessDelegate&) = delete; + CookieAccessDelegate& operator=(const CookieAccessDelegate&) = delete; + virtual ~CookieAccessDelegate(); // Returns true if the passed in |url| should be permitted to access secure @@ -60,9 +64,6 @@ class NET_EXPORT CookieAccessDelegate { // Returns the First-Party Sets. virtual base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> RetrieveFirstPartySets() const = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(CookieAccessDelegate); }; } // namespace net diff --git a/chromium/net/cookies/cookie_access_result.h b/chromium/net/cookies/cookie_access_result.h index 0c99a19381d..8e38b4dbc15 100644 --- a/chromium/net/cookies/cookie_access_result.h +++ b/chromium/net/cookies/cookie_access_result.h @@ -33,6 +33,14 @@ struct NET_EXPORT CookieAccessResult { ~CookieAccessResult(); + bool operator==(const CookieAccessResult& other) const { + return status == other.status && + effective_same_site == other.effective_same_site && + access_semantics == other.access_semantics && + is_allowed_to_access_secure_cookies == + other.is_allowed_to_access_secure_cookies; + } + CookieInclusionStatus status; CookieEffectiveSameSite effective_same_site = CookieEffectiveSameSite::UNDEFINED; diff --git a/chromium/net/cookies/cookie_change_dispatcher.h b/chromium/net/cookies/cookie_change_dispatcher.h index ffa9a003da7..0f6fc31183c 100644 --- a/chromium/net/cookies/cookie_change_dispatcher.h +++ b/chromium/net/cookies/cookie_change_dispatcher.h @@ -98,10 +98,11 @@ using CookieChangeCallback = class CookieChangeSubscription { public: CookieChangeSubscription() = default; - virtual ~CookieChangeSubscription() = default; - private: - DISALLOW_COPY_AND_ASSIGN(CookieChangeSubscription); + CookieChangeSubscription(const CookieChangeSubscription&) = delete; + CookieChangeSubscription& operator=(const CookieChangeSubscription&) = delete; + + virtual ~CookieChangeSubscription() = default; }; // Exposes changes to a CookieStore's contents. @@ -122,29 +123,45 @@ class CookieChangeSubscription { class CookieChangeDispatcher { public: CookieChangeDispatcher() = default; + + CookieChangeDispatcher(const CookieChangeDispatcher&) = delete; + CookieChangeDispatcher& operator=(const CookieChangeDispatcher&) = delete; + virtual ~CookieChangeDispatcher() = default; - // Observe changes to all cookies named |name| that would be sent in a - // request to |url|. + // Observe changes to all cookies named `name` that would be sent in a + // request to `url`. + // + // If `cookie_partition_key` is nullopt, then we ignore all change events for + // partitioned cookies. Otherwise it only subscribes to change events for + // partitioned cookies with the same provided key. + // Unpartitioned cookies are not affected by the `cookie_partition_key` + // parameter. virtual std::unique_ptr<CookieChangeSubscription> AddCallbackForCookie( const GURL& url, const std::string& name, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) WARN_UNUSED_RESULT = 0; - // Observe changes to the cookies that would be sent for a request to |url|. + // Observe changes to the cookies that would be sent for a request to `url`. + // + // If `cookie_partition_key` is nullopt, then we ignore all change events for + // partitioned cookies. Otherwise it only subscribes to change events for + // partitioned cookies with the same provided key. + // Unpartitioned cookies are not affected by the `cookie_partition_key` + // parameter. virtual std::unique_ptr<CookieChangeSubscription> AddCallbackForUrl( const GURL& url, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) WARN_UNUSED_RESULT = 0; // Observe all the CookieStore's changes. // // The callback will not observe a few bookkeeping changes. // See kChangeCauseMapping in cookie_monster.cc for details. + // TODO(crbug.com/1225444): Add support for Partitioned cookies. virtual std::unique_ptr<CookieChangeSubscription> AddCallbackForAllChanges( CookieChangeCallback callback) WARN_UNUSED_RESULT = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(CookieChangeDispatcher); }; } // namespace net diff --git a/chromium/net/cookies/cookie_constants.cc b/chromium/net/cookies/cookie_constants.cc index acb6142e961..a443d13d57f 100644 --- a/chromium/net/cookies/cookie_constants.cc +++ b/chromium/net/cookies/cookie_constants.cc @@ -12,9 +12,8 @@ namespace net { -const base::TimeDelta kLaxAllowUnsafeMaxAge = base::TimeDelta::FromMinutes(2); -const base::TimeDelta kShortLaxAllowUnsafeMaxAge = - base::TimeDelta::FromSeconds(10); +const base::TimeDelta kLaxAllowUnsafeMaxAge = base::Minutes(2); +const base::TimeDelta kShortLaxAllowUnsafeMaxAge = base::Seconds(10); namespace { diff --git a/chromium/net/cookies/cookie_constants.h b/chromium/net/cookies/cookie_constants.h index 8e014241066..5285462cd51 100644 --- a/chromium/net/cookies/cookie_constants.h +++ b/chromium/net/cookies/cookie_constants.h @@ -353,6 +353,23 @@ CookieSourceSchemeName GetSchemeNameEnum(const GURL& url); // Empty string was chosen because it is the smallest, non-null value. NET_EXPORT extern const char kEmptyCookiePartitionKey[]; +// Used for a histogram that measures which character caused the cookie +// string to be truncated. +// +// Do not reorder or renumber. Used for metrics. +enum class TruncatingCharacterInCookieStringType { + // No truncating character in the cookie line. + kTruncatingCharNone = 0, + // Cookie line truncated because of \x0. + kTruncatingCharNull = 1, + // Cookie line truncated because of \xD. + kTruncatingCharNewline = 2, + // Cookie line truncated because of \xA. + kTruncatingCharLineFeed = 3, + + kMaxValue = kTruncatingCharLineFeed, // Keep as the last value. +}; + } // namespace net #endif // NET_COOKIES_COOKIE_CONSTANTS_H_ diff --git a/chromium/net/cookies/cookie_inclusion_status.cc b/chromium/net/cookies/cookie_inclusion_status.cc index 0b70482c6cc..3dc25274b9a 100644 --- a/chromium/net/cookies/cookie_inclusion_status.cc +++ b/chromium/net/cookies/cookie_inclusion_status.cc @@ -9,30 +9,27 @@ namespace net { -namespace { +CookieInclusionStatus::CookieInclusionStatus() = default; -uint32_t GetExclusionBitmask(CookieInclusionStatus::ExclusionReason reason) { - return 1u << static_cast<uint32_t>(reason); +CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason) { + exclusion_reasons_[reason] = true; } -uint32_t GetWarningBitmask(CookieInclusionStatus::WarningReason reason) { - return 1u << static_cast<uint32_t>(reason); +CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason, + WarningReason warning) { + exclusion_reasons_[reason] = true; + warning_reasons_[warning] = true; } -} // namespace - -CookieInclusionStatus::CookieInclusionStatus() = default; +CookieInclusionStatus::CookieInclusionStatus(WarningReason warning) { + warning_reasons_[warning] = true; +} -CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason) - : exclusion_reasons_(GetExclusionBitmask(reason)) {} +CookieInclusionStatus::CookieInclusionStatus( + const CookieInclusionStatus& other) = default; -CookieInclusionStatus::CookieInclusionStatus(ExclusionReason reason, - WarningReason warning) - : exclusion_reasons_(GetExclusionBitmask(reason)), - warning_reasons_(GetWarningBitmask(warning)) {} - -CookieInclusionStatus::CookieInclusionStatus(WarningReason warning) - : warning_reasons_(GetWarningBitmask(warning)) {} +CookieInclusionStatus& CookieInclusionStatus::operator=( + const CookieInclusionStatus& other) = default; bool CookieInclusionStatus::operator==( const CookieInclusionStatus& other) const { @@ -46,27 +43,27 @@ bool CookieInclusionStatus::operator!=( } bool CookieInclusionStatus::IsInclude() const { - return exclusion_reasons_ == 0u; + return exclusion_reasons_.none(); } bool CookieInclusionStatus::HasExclusionReason(ExclusionReason reason) const { - return exclusion_reasons_ & GetExclusionBitmask(reason); + return exclusion_reasons_[reason]; } bool CookieInclusionStatus::HasOnlyExclusionReason( ExclusionReason reason) const { - return exclusion_reasons_ == GetExclusionBitmask(reason); + return exclusion_reasons_[reason] && exclusion_reasons_.count() == 1; } void CookieInclusionStatus::AddExclusionReason(ExclusionReason reason) { - exclusion_reasons_ |= GetExclusionBitmask(reason); + exclusion_reasons_[reason] = true; // If the cookie would be excluded for reasons other than the new SameSite // rules, don't bother warning about it. MaybeClearSameSiteWarning(); } void CookieInclusionStatus::RemoveExclusionReason(ExclusionReason reason) { - exclusion_reasons_ &= ~(GetExclusionBitmask(reason)); + exclusion_reasons_[reason] = false; } void CookieInclusionStatus::RemoveExclusionReasons( @@ -74,13 +71,14 @@ void CookieInclusionStatus::RemoveExclusionReasons( exclusion_reasons_ = ExclusionReasonsWithout(reasons); } -uint32_t CookieInclusionStatus::ExclusionReasonsWithout( +CookieInclusionStatus::ExclusionReasonBitset +CookieInclusionStatus::ExclusionReasonsWithout( const std::vector<ExclusionReason>& reasons) const { - uint32_t mask = 0u; + CookieInclusionStatus::ExclusionReasonBitset result(exclusion_reasons_); for (const ExclusionReason reason : reasons) { - mask |= GetExclusionBitmask(reason); + result[reason] = false; } - return exclusion_reasons_ & ~mask; + return result; } void CookieInclusionStatus::MaybeClearSameSiteWarning() { @@ -113,11 +111,11 @@ bool CookieInclusionStatus::ShouldRecordDowngradeMetrics() const { } bool CookieInclusionStatus::ShouldWarn() const { - return warning_reasons_ != 0u; + return warning_reasons_.any(); } bool CookieInclusionStatus::HasWarningReason(WarningReason reason) const { - return warning_reasons_ & GetWarningBitmask(reason); + return warning_reasons_[reason]; } bool CookieInclusionStatus::HasDowngradeWarning( @@ -147,11 +145,11 @@ bool CookieInclusionStatus::HasDowngradeWarning( } void CookieInclusionStatus::AddWarningReason(WarningReason reason) { - warning_reasons_ |= GetWarningBitmask(reason); + warning_reasons_[reason] = true; } void CookieInclusionStatus::RemoveWarningReason(WarningReason reason) { - warning_reasons_ &= ~(GetWarningBitmask(reason)); + warning_reasons_[reason] = false; } CookieInclusionStatus::ContextDowngradeMetricValues @@ -222,6 +220,10 @@ std::string CookieInclusionStatus::GetDebugString() const { {EXCLUDE_INVALID_PREFIX, "EXCLUDE_INVALID_PREFIX"}, {EXCLUDE_INVALID_SAMEPARTY, "EXCLUDE_INVALID_SAMEPARTY"}, {EXCLUDE_INVALID_PARTITIONED, "EXCLUDE_INVALID_PARTITIONED"}, + {EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE, + "EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE"}, + {EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE, + "EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"}, }) { if (HasExclusionReason(reason.first)) base::StrAppend(&out, {reason.second, ", "}); @@ -267,6 +269,8 @@ std::string CookieInclusionStatus::GetDebugString() const { "WARN_SAMESITE_NONE_INCLUDED_BY_SAMESITE_STRICT"}, {WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION, "WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION"}, + {WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE, + "WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE"}, }) { if (HasWarningReason(reason.first)) base::StrAppend(&out, {reason.second, ", "}); @@ -278,14 +282,6 @@ std::string CookieInclusionStatus::GetDebugString() const { return out; } -bool CookieInclusionStatus::IsValid() const { - // Bit positions where there should not be any true bits. - uint32_t exclusion_mask = ~0u << static_cast<int>(NUM_EXCLUSION_REASONS); - uint32_t warning_mask = ~0u << static_cast<int>(NUM_WARNING_REASONS); - return (exclusion_mask & exclusion_reasons_) == 0u && - (warning_mask & warning_reasons_) == 0u; -} - bool CookieInclusionStatus::HasExactlyExclusionReasonsForTesting( std::vector<CookieInclusionStatus::ExclusionReason> reasons) const { CookieInclusionStatus expected = MakeFromReasonsForTesting(reasons); @@ -299,6 +295,17 @@ bool CookieInclusionStatus::HasExactlyWarningReasonsForTesting( } // static +bool CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + uint32_t exclusion_reasons, + uint32_t warning_reasons) { + uint32_t exclusion_mask = + static_cast<uint32_t>(~0ul << ExclusionReason::NUM_EXCLUSION_REASONS); + uint32_t warning_mask = + static_cast<uint32_t>(~0ul << WarningReason::NUM_WARNING_REASONS); + return (exclusion_reasons & exclusion_mask) == 0 && + (warning_reasons & warning_mask) == 0; +} + CookieInclusionStatus CookieInclusionStatus::MakeFromReasonsForTesting( std::vector<ExclusionReason> reasons, std::vector<WarningReason> warnings) { diff --git a/chromium/net/cookies/cookie_inclusion_status.h b/chromium/net/cookies/cookie_inclusion_status.h index 1ee8e9efc03..f6388598fbb 100644 --- a/chromium/net/cookies/cookie_inclusion_status.h +++ b/chromium/net/cookies/cookie_inclusion_status.h @@ -5,6 +5,7 @@ #ifndef NET_COOKIES_COOKIE_INCLUSION_STATUS_H_ #define NET_COOKIES_COOKIE_INCLUSION_STATUS_H_ +#include <bitset> #include <ostream> #include <string> #include <vector> @@ -84,6 +85,14 @@ class NET_EXPORT CookieInclusionStatus { // valid if the cookie has a __Host- prefix and does not have the SameParty // attribute. EXCLUDE_INVALID_PARTITIONED = 18, + // Cookie exceeded the name/value pair size limit. + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE = 19, + // Cookie exceeded the attribute size limit. Note that this exclusion value + // won't be used by code that parses cookie lines since RFC6265bis + // indicates that large attributes should be ignored instead of causing the + // whole cookie to be rejected. There will be a corresponding WarningReason + // to notify users that an attribute value was ignored in that case. + EXCLUDE_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 20, // This should be kept last. NUM_EXCLUSION_REASONS @@ -208,6 +217,12 @@ class NET_EXPORT CookieInclusionStatus { // included/excluded in both cases. WARN_CROSS_SITE_REDIRECT_DOWNGRADE_CHANGES_INCLUSION = 17, + // The cookie exceeded the attribute size limit. RFC6265bis indicates that + // large attributes should be ignored instead of causing the whole cookie + // to be rejected. This is applied by the code that parses cookie lines and + // notifies the user that an attribute value was ignored. + WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE = 18, + // This should be kept last. NUM_WARNING_REASONS }; @@ -239,6 +254,11 @@ class NET_EXPORT CookieInclusionStatus { // Keep last. kMaxValue = LAX_CROSS_LAX_SECURE }; + + using ExclusionReasonBitset = + std::bitset<ExclusionReason::NUM_EXCLUSION_REASONS>; + using WarningReasonBitset = std::bitset<WarningReason::NUM_WARNING_REASONS>; + // Makes a status that says include and should not warn. CookieInclusionStatus(); @@ -249,6 +269,10 @@ class NET_EXPORT CookieInclusionStatus { // Makes a status that contains the given warning. explicit CookieInclusionStatus(WarningReason warning); + // Copyable. + CookieInclusionStatus(const CookieInclusionStatus& other); + CookieInclusionStatus& operator=(const CookieInclusionStatus& other); + bool operator==(const CookieInclusionStatus& other) const; bool operator!=(const CookieInclusionStatus& other) const; @@ -303,13 +327,13 @@ class NET_EXPORT CookieInclusionStatus { void RemoveWarningReason(WarningReason reason); // Used for serialization/deserialization. - uint32_t exclusion_reasons() const { return exclusion_reasons_; } - void set_exclusion_reasons(uint32_t exclusion_reasons) { + ExclusionReasonBitset exclusion_reasons() const { return exclusion_reasons_; } + void set_exclusion_reasons(ExclusionReasonBitset exclusion_reasons) { exclusion_reasons_ = exclusion_reasons; } - uint32_t warning_reasons() const { return warning_reasons_; } - void set_warning_reasons(uint32_t warning_reasons) { + WarningReasonBitset warning_reasons() const { return warning_reasons_; } + void set_warning_reasons(WarningReasonBitset warning_reasons) { warning_reasons_ = warning_reasons; } @@ -319,11 +343,6 @@ class NET_EXPORT CookieInclusionStatus { // Get exclusion reason(s) and warning in string format. std::string GetDebugString() const; - // Checks that the underlying bit vector representation doesn't contain any - // extraneous bits that are not mapped to any enum values. Does not check - // for reasons which semantically cannot coexist. - bool IsValid() const; - // Checks whether the exclusion reasons are exactly the set of exclusion // reasons in the vector. (Ignores warnings.) bool HasExactlyExclusionReasonsForTesting( @@ -334,6 +353,10 @@ class NET_EXPORT CookieInclusionStatus { bool HasExactlyWarningReasonsForTesting( std::vector<WarningReason> reasons) const; + // Validates mojo data, since mojo does not support bitsets. + static bool ValidateExclusionAndWarningFromWire(uint32_t exclusion_reasons, + uint32_t warning_reasons); + // Makes a status that contains the given exclusion reasons and warning. static CookieInclusionStatus MakeFromReasonsForTesting( std::vector<ExclusionReason> reasons, @@ -341,14 +364,14 @@ class NET_EXPORT CookieInclusionStatus { private: // Returns the `exclusion_reasons_` with the given `reasons` unset. - uint32_t ExclusionReasonsWithout( + ExclusionReasonBitset ExclusionReasonsWithout( const std::vector<ExclusionReason>& reasons) const; // A bit vector of the applicable exclusion reasons. - uint32_t exclusion_reasons_ = 0u; + ExclusionReasonBitset exclusion_reasons_; // A bit vector of the applicable warning reasons. - uint32_t warning_reasons_ = 0u; + WarningReasonBitset warning_reasons_; }; NET_EXPORT inline std::ostream& operator<<(std::ostream& os, diff --git a/chromium/net/cookies/cookie_inclusion_status_unittest.cc b/chromium/net/cookies/cookie_inclusion_status_unittest.cc index 3efec01415c..2ea50787594 100644 --- a/chromium/net/cookies/cookie_inclusion_status_unittest.cc +++ b/chromium/net/cookies/cookie_inclusion_status_unittest.cc @@ -15,7 +15,6 @@ TEST(CookieInclusionStatusTest, IncludeStatus) { static_cast<int>(CookieInclusionStatus::NUM_WARNING_REASONS); // Zero-argument constructor CookieInclusionStatus status; - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.IsInclude()); for (int i = 0; i < num_exclusion_reasons; ++i) { EXPECT_FALSE(status.HasExclusionReason( @@ -36,7 +35,6 @@ TEST(CookieInclusionStatusTest, ExcludeStatus) { for (int i = 0; i < num_exclusion_reasons; ++i) { auto reason1 = static_cast<CookieInclusionStatus::ExclusionReason>(i); CookieInclusionStatus status_one_reason(reason1); - EXPECT_TRUE(status_one_reason.IsValid()); EXPECT_FALSE(status_one_reason.IsInclude()); EXPECT_TRUE(status_one_reason.HasExclusionReason(reason1)); EXPECT_TRUE(status_one_reason.HasOnlyExclusionReason(reason1)); @@ -51,7 +49,6 @@ TEST(CookieInclusionStatusTest, ExcludeStatus) { CookieInclusionStatus status_two_reasons = status_one_reason; status_two_reasons.AddExclusionReason(reason2); - EXPECT_TRUE(status_two_reasons.IsValid()); EXPECT_FALSE(status_two_reasons.IsInclude()); EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason1)); EXPECT_TRUE(status_two_reasons.HasExclusionReason(reason2)); @@ -61,40 +58,11 @@ TEST(CookieInclusionStatusTest, ExcludeStatus) { } } -TEST(CookieInclusionStatusTest, NotValid) { - CookieInclusionStatus status; - int num_exclusion_reasons = - static_cast<int>(CookieInclusionStatus::NUM_EXCLUSION_REASONS); - int num_warning_reasons = - static_cast<int>(CookieInclusionStatus::NUM_WARNING_REASONS); - status.set_exclusion_reasons(1 << num_exclusion_reasons); - EXPECT_FALSE(status.IsInclude()); - EXPECT_FALSE(status.IsValid()); - - status.set_exclusion_reasons(~0u); - EXPECT_FALSE(status.IsInclude()); - EXPECT_FALSE(status.IsValid()); - - status.set_warning_reasons(1 << num_warning_reasons); - EXPECT_FALSE(status.IsInclude()); - EXPECT_FALSE(status.IsValid()); - - status.set_warning_reasons(~0u); - EXPECT_FALSE(status.IsInclude()); - EXPECT_FALSE(status.IsValid()); - - status.set_exclusion_reasons(1 << num_exclusion_reasons); - status.set_warning_reasons(1 << num_warning_reasons); - EXPECT_FALSE(status.IsInclude()); - EXPECT_FALSE(status.IsValid()); -} - TEST(CookieInclusionStatusTest, AddExclusionReason) { CookieInclusionStatus status; status.AddWarningReason( CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_LAX_ALLOW_UNSAFE); status.AddExclusionReason(CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR); - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( {CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR})); // Adding an exclusion reason other than @@ -107,7 +75,6 @@ TEST(CookieInclusionStatusTest, AddExclusionReason) { CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT); status.AddExclusionReason( CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX); - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting( {CookieInclusionStatus::EXCLUDE_SAMESITE_UNSPECIFIED_TREATED_AS_LAX})); EXPECT_TRUE(status.HasExactlyWarningReasonsForTesting( @@ -123,7 +90,6 @@ TEST(CookieInclusionStatusTest, CheckEachWarningReason) { for (int i = 0; i < num_warning_reasons; ++i) { auto reason = static_cast<CookieInclusionStatus::WarningReason>(i); status.AddWarningReason(reason); - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.IsInclude()); EXPECT_TRUE(status.ShouldWarn()); EXPECT_TRUE(status.HasWarningReason(reason)); @@ -140,12 +106,10 @@ TEST(CookieInclusionStatusTest, CheckEachWarningReason) { TEST(CookieInclusionStatusTest, RemoveExclusionReason) { CookieInclusionStatus status(CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR); - EXPECT_TRUE(status.IsValid()); ASSERT_TRUE( status.HasExclusionReason(CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR)); status.RemoveExclusionReason(CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR); - EXPECT_TRUE(status.IsValid()); EXPECT_FALSE( status.HasExclusionReason(CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR)); @@ -153,7 +117,6 @@ TEST(CookieInclusionStatusTest, RemoveExclusionReason) { ASSERT_FALSE( status.HasExclusionReason(CookieInclusionStatus::NUM_EXCLUSION_REASONS)); status.RemoveExclusionReason(CookieInclusionStatus::NUM_EXCLUSION_REASONS); - EXPECT_TRUE(status.IsValid()); EXPECT_FALSE( status.HasExclusionReason(CookieInclusionStatus::NUM_EXCLUSION_REASONS)); } @@ -162,14 +125,12 @@ TEST(CookieInclusionStatusTest, RemoveWarningReason) { CookieInclusionStatus status( CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR, CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE); - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.ShouldWarn()); ASSERT_TRUE(status.HasWarningReason( CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE)); status.RemoveWarningReason( CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE); - EXPECT_TRUE(status.IsValid()); EXPECT_FALSE(status.ShouldWarn()); EXPECT_FALSE(status.HasWarningReason( CookieInclusionStatus::WARN_SAMESITE_NONE_INSECURE)); @@ -179,7 +140,6 @@ TEST(CookieInclusionStatusTest, RemoveWarningReason) { CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT)); status.RemoveWarningReason( CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT); - EXPECT_TRUE(status.IsValid()); EXPECT_FALSE(status.ShouldWarn()); EXPECT_FALSE(status.HasWarningReason( CookieInclusionStatus::WARN_SAMESITE_UNSPECIFIED_CROSS_SITE_CONTEXT)); @@ -258,7 +218,6 @@ TEST(CookieInclusionStatusTest, RemoveExclusionReasons) { CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT, CookieInclusionStatus::EXCLUDE_USER_PREFERENCES, }); - EXPECT_TRUE(status.IsValid()); ASSERT_TRUE(status.HasExactlyExclusionReasonsForTesting({ CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR, CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT, @@ -269,7 +228,6 @@ TEST(CookieInclusionStatusTest, RemoveExclusionReasons) { {CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR, CookieInclusionStatus::EXCLUDE_UNKNOWN_ERROR, CookieInclusionStatus::EXCLUDE_SAMESITE_STRICT}); - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting({ CookieInclusionStatus::EXCLUDE_USER_PREFERENCES, })); @@ -278,10 +236,42 @@ TEST(CookieInclusionStatusTest, RemoveExclusionReasons) { ASSERT_FALSE( status.HasExclusionReason(CookieInclusionStatus::NUM_EXCLUSION_REASONS)); status.RemoveExclusionReasons({CookieInclusionStatus::NUM_EXCLUSION_REASONS}); - EXPECT_TRUE(status.IsValid()); EXPECT_TRUE(status.HasExactlyExclusionReasonsForTesting({ CookieInclusionStatus::EXCLUDE_USER_PREFERENCES, })); } +TEST(CookieInclusionStatusTest, ValidateExclusionAndWarningFromWire) { + uint32_t exclusion_reasons = 0ul; + uint32_t warning_reasons = 0ul; + + EXPECT_TRUE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + exclusion_reasons, warning_reasons)); + + exclusion_reasons = static_cast<uint32_t>(~0ul); + warning_reasons = static_cast<uint32_t>(~0ul); + EXPECT_FALSE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + exclusion_reasons, warning_reasons)); + EXPECT_FALSE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + exclusion_reasons, 0u)); + EXPECT_FALSE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + 0u, warning_reasons)); + + exclusion_reasons = (1u << CookieInclusionStatus::EXCLUDE_DOMAIN_MISMATCH); + warning_reasons = (1u << CookieInclusionStatus::WARN_TREATED_AS_SAMEPARTY); + EXPECT_TRUE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + exclusion_reasons, warning_reasons)); + + exclusion_reasons = (1u << CookieInclusionStatus::NUM_EXCLUSION_REASONS); + warning_reasons = (1u << CookieInclusionStatus::NUM_WARNING_REASONS); + EXPECT_FALSE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + exclusion_reasons, warning_reasons)); + + exclusion_reasons = + (1u << (CookieInclusionStatus::NUM_EXCLUSION_REASONS - 1)); + warning_reasons = (1u << (CookieInclusionStatus::NUM_WARNING_REASONS - 1)); + EXPECT_TRUE(CookieInclusionStatus::ValidateExclusionAndWarningFromWire( + exclusion_reasons, warning_reasons)); +} + } // namespace net diff --git a/chromium/net/cookies/cookie_monster.cc b/chromium/net/cookies/cookie_monster.cc index f13f2ce6133..5c3c232d596 100644 --- a/chromium/net/cookies/cookie_monster.cc +++ b/chromium/net/cookies/cookie_monster.cc @@ -64,7 +64,6 @@ #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/threading/thread_task_runner_handle.h" -#include "base/trace_event/process_memory_dump.h" #include "net/base/features.h" #include "net/base/isolation_info.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" @@ -83,7 +82,6 @@ #include "url/url_canon.h" using base::Time; -using base::TimeDelta; using base::TimeTicks; using TimeRange = net::CookieDeletionInfo::TimeRange; @@ -154,6 +152,8 @@ const size_t CookieMonster::kPurgeCookies = 300; const size_t CookieMonster::kMaxDomainPurgedKeys = 100; +const size_t CookieMonster::kPerPartitionDomainMaxCookies = 10; + const size_t CookieMonster::kDomainCookiesQuotaLow = 30; const size_t CookieMonster::kDomainCookiesQuotaMedium = 50; const size_t CookieMonster::kDomainCookiesQuotaHigh = @@ -269,6 +269,8 @@ const ChangeCausePair kChangeCauseMapping[] = { {CookieChangeCause::EVICTED, true}, // DELETE_COOKIE_NON_SECURE {CookieChangeCause::EVICTED, true}, + // DELETE_COOKIE_EVICTED_PER_PARTITION_DOMAIN + {CookieChangeCause::EVICTED, true}, // DELETE_COOKIE_LAST_ENTRY {CookieChangeCause::EXPLICIT, false}}; @@ -320,15 +322,15 @@ void HistogramExpirationDuration(const CanonicalCookie& cookie, CookieMonster::CookieMonster(scoped_refptr<PersistentCookieStore> store, NetLog* net_log) - : CookieMonster( - std::move(store), - base::TimeDelta::FromSeconds(kDefaultAccessUpdateThresholdSeconds), - net_log) {} + : CookieMonster(std::move(store), + base::Seconds(kDefaultAccessUpdateThresholdSeconds), + net_log) {} CookieMonster::CookieMonster(scoped_refptr<PersistentCookieStore> store, base::TimeDelta last_access_threshold, NetLog* net_log) : num_keys_(0u), + num_partitioned_cookies_(0u), change_dispatcher_(this), initialized_(false), started_fetching_all_cookies_(false), @@ -398,6 +400,7 @@ void CookieMonster::SetCanonicalCookieAsync( void CookieMonster::GetCookieListWithOptionsAsync( const GURL& url, const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain, GetCookieListCallback callback) { DoCookieCallbackForURL( base::BindOnce( @@ -405,7 +408,7 @@ void CookieMonster::GetCookieListWithOptionsAsync( // the callback on |*this|, so the callback will not outlive // the object. &CookieMonster::GetCookieListWithOptions, base::Unretained(this), url, - options, std::move(callback)), + options, cookie_partition_keychain, std::move(callback)), url); } @@ -525,32 +528,6 @@ CookieChangeDispatcher& CookieMonster::GetChangeDispatcher() { return change_dispatcher_; } -void CookieMonster::DumpMemoryStats( - base::trace_event::ProcessMemoryDump* pmd, - const std::string& parent_absolute_name) const { - const char kRelPath[] = "/cookie_monster"; - - pmd->CreateAllocatorDump(parent_absolute_name + kRelPath + "/cookies") - ->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, - base::trace_event::MemoryAllocatorDump::kUnitsObjects, - cookies_.size()); - - pmd->CreateAllocatorDump(parent_absolute_name + kRelPath + - "/tasks_pending_global") - ->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, - base::trace_event::MemoryAllocatorDump::kUnitsObjects, - tasks_pending_.size()); - - size_t total_pending_for_key = 0; - for (const auto& kv : tasks_pending_for_key_) - total_pending_for_key += kv.second.size(); - pmd->CreateAllocatorDump(parent_absolute_name + kRelPath + - "/tasks_pending_for_key") - ->AddScalar(base::trace_event::MemoryAllocatorDump::kNameObjectCount, - base::trace_event::MemoryAllocatorDump::kUnitsObjects, - total_pending_for_key); -} - CookieMonster::~CookieMonster() { DCHECK(thread_checker_.CalledOnValidThread()); net_log_.EndEvent(NetLogEventType::COOKIE_STORE_ALIVE); @@ -580,6 +557,7 @@ void CookieMonster::GetAllCookies(GetAllCookiesCallback callback) { // exceeded them) the way that calling GarbageCollect() would. GarbageCollectExpired( Time::Now(), CookieMapItPair(cookies_.begin(), cookies_.end()), nullptr); + GarbageCollectAllExpiredPartitionedCookies(Time::Now()); // Copy the CanonicalCookie pointers from the map so that we can use the same // sorter as elsewhere, then copy the result out. @@ -587,6 +565,12 @@ void CookieMonster::GetAllCookies(GetAllCookiesCallback callback) { cookie_ptrs.reserve(cookies_.size()); for (const auto& cookie : cookies_) cookie_ptrs.push_back(cookie.second.get()); + + for (const auto& cookie_partition : partitioned_cookies_) { + for (const auto& cookie : *cookie_partition.second.get()) + cookie_ptrs.push_back(cookie.second.get()); + } + std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter); CookieList cookie_list; @@ -608,9 +592,11 @@ void CookieMonster::AttachAccessSemanticsListForCookieList( access_semantics_list); } -void CookieMonster::GetCookieListWithOptions(const GURL& url, - const CookieOptions& options, - GetCookieListCallback callback) { +void CookieMonster::GetCookieListWithOptions( + const GURL& url, + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain, + GetCookieListCallback callback) { DCHECK(thread_checker_.CalledOnValidThread()); CookieAccessResultList included_cookies; @@ -618,6 +604,24 @@ void CookieMonster::GetCookieListWithOptions(const GURL& url, if (HasCookieableScheme(url)) { std::vector<CanonicalCookie*> cookie_ptrs = FindCookiesForRegistryControlledHost(url); + if (!cookie_partition_keychain.IsEmpty()) { + if (cookie_partition_keychain.ContainsAllKeys()) { + for (const auto& it : partitioned_cookies_) { + std::vector<CanonicalCookie*> partitioned_cookie_ptrs = + FindPartitionedCookiesForRegistryControlledHost(it.first, url); + cookie_ptrs.insert(cookie_ptrs.end(), partitioned_cookie_ptrs.begin(), + partitioned_cookie_ptrs.end()); + } + } else { + for (const CookiePartitionKey& key : + cookie_partition_keychain.PartitionKeys()) { + std::vector<CanonicalCookie*> partitioned_cookie_ptrs = + FindPartitionedCookiesForRegistryControlledHost(key, url); + cookie_ptrs.insert(cookie_ptrs.end(), partitioned_cookie_ptrs.begin(), + partitioned_cookie_ptrs.end()); + } + } + } std::sort(cookie_ptrs.begin(), cookie_ptrs.end(), CookieSorter); included_cookies.reserve(cookie_ptrs.size()); @@ -646,6 +650,30 @@ void CookieMonster::DeleteAllCreatedInTimeRange(const TimeRange& creation_range, } } + for (PartitionedCookieMap::iterator partition_it = + partitioned_cookies_.begin(); + partition_it != partitioned_cookies_.end();) { + auto cur_partition_it = partition_it; + CookieMap::iterator cookie_it = cur_partition_it->second->begin(); + CookieMap::iterator cookie_end = cur_partition_it->second->end(); + // InternalDeletePartitionedCookie may delete this cookie partition if it + // only has one cookie, so we need to increment the iterator beforehand. + ++partition_it; + + while (cookie_it != cookie_end) { + auto cur_cookie_it = cookie_it; + CanonicalCookie* cc = cur_cookie_it->second.get(); + ++cookie_it; + + if (creation_range.Contains(cc->CreationDate())) { + InternalDeletePartitionedCookie(cur_partition_it, cur_cookie_it, + true /*sync_to_store*/, + DELETE_COOKIE_EXPLICIT); + ++num_deleted; + } + } + } + FlushStore( base::BindOnce(&MaybeRunDeleteCallback, weak_ptr_factory_.GetWeakPtr(), callback ? base::BindOnce(std::move(callback), num_deleted) @@ -675,20 +703,37 @@ bool CookieMonster::MatchCookieDeletionInfo( void CookieMonster::DeleteCanonicalCookie(const CanonicalCookie& cookie, DeleteCallback callback) { DCHECK(thread_checker_.CalledOnValidThread()); - uint32_t result = 0u; - for (CookieMapItPair its = cookies_.equal_range(GetKey(cookie.Domain())); - its.first != its.second; ++its.first) { - const std::unique_ptr<CanonicalCookie>& candidate = its.first->second; - // Historically, this has refused modification if the cookie has changed - // value in between the CanonicalCookie object was returned by a getter - // and when this ran. The later parts of the conditional (everything but - // the equivalence check) attempt to preserve this behavior. - if (candidate->IsEquivalent(cookie) && - candidate->Value() == cookie.Value()) { - InternalDeleteCookie(its.first, true, DELETE_COOKIE_EXPLICIT); - result = 1u; - break; + CookieMap* cookie_map = nullptr; + PartitionedCookieMap::iterator cookie_partition_it; + + if (cookie.IsPartitioned()) { + cookie_partition_it = + partitioned_cookies_.find(cookie.PartitionKey().value()); + if (cookie_partition_it != partitioned_cookies_.end()) + cookie_map = cookie_partition_it->second.get(); + } else { + cookie_map = &cookies_; + } + if (cookie_map) { + for (CookieMapItPair its = cookie_map->equal_range(GetKey(cookie.Domain())); + its.first != its.second; ++its.first) { + const std::unique_ptr<CanonicalCookie>& candidate = its.first->second; + // Historically, this has refused modification if the cookie has changed + // value in between the CanonicalCookie object was returned by a getter + // and when this ran. The later parts of the conditional (everything but + // the equivalence check) attempt to preserve this behavior. + if (candidate->IsEquivalent(cookie) && + candidate->Value() == cookie.Value()) { + if (cookie.IsPartitioned()) { + InternalDeletePartitionedCookie(cookie_partition_it, its.first, true, + DELETE_COOKIE_EXPLICIT); + } else { + InternalDeleteCookie(its.first, true, DELETE_COOKIE_EXPLICIT); + } + result = 1u; + break; + } } } FlushStore( @@ -713,6 +758,27 @@ void CookieMonster::DeleteMatchingCookies(DeletePredicate predicate, ++num_deleted; } } + for (auto partition_it = partitioned_cookies_.begin(); + partition_it != partitioned_cookies_.end();) { + // InternalDeletePartitionedCookie may invalidate |partition_it| if that + // cookie partition only has one cookie. + auto cur_partition_it = partition_it; + CookieMap::iterator cookie_it = cur_partition_it->second->begin(); + CookieMap::iterator cookie_end = cur_partition_it->second->end(); + ++partition_it; + + while (cookie_it != cookie_end) { + auto cur_cookie_it = cookie_it; + CanonicalCookie* cc = cur_cookie_it->second.get(); + ++cookie_it; + + if (predicate.Run(*cc)) { + InternalDeletePartitionedCookie(cur_partition_it, cur_cookie_it, true, + cause); + ++num_deleted; + } + } + } FlushStore( base::BindOnce(&MaybeRunDeleteCallback, weak_ptr_factory_.GetWeakPtr(), @@ -751,9 +817,9 @@ void CookieMonster::OnLoaded( std::vector<std::unique_ptr<CanonicalCookie>> cookies) { DCHECK(thread_checker_.CalledOnValidThread()); StoreLoadedCookies(std::move(cookies)); - base::UmaHistogramCustomTimes( - "Cookie.TimeBlockedOnLoad", base::TimeTicks::Now() - beginning_time, - TimeDelta::FromMilliseconds(1), TimeDelta::FromMinutes(1), 50); + base::UmaHistogramCustomTimes("Cookie.TimeBlockedOnLoad", + base::TimeTicks::Now() - beginning_time, + base::Milliseconds(1), base::Minutes(1), 50); // Invoke the task queue of cookie request. InvokeQueue(); @@ -794,24 +860,40 @@ void CookieMonster::StoreLoadedCookies( // Even if a key is expired, insert it so it can be garbage collected, // removed, and sync'd. CookieItVector cookies_with_control_chars; + std::vector<PartitionedCookieMapIterators> + partitioned_cookies_with_control_chars; for (auto& cookie : cookies) { CanonicalCookie* cookie_ptr = cookie.get(); CookieAccessResult access_result; access_result.access_semantics = CookieAccessSemantics::UNKNOWN; - auto inserted = InternalInsertCookie( - GetKey(cookie_ptr->Domain()), std::move(cookie), - false /* sync_to_store */, access_result, false /* dispatch_change */); + + if (cookie_ptr->IsPartitioned()) { + auto inserted = InternalInsertPartitionedCookie( + GetKey(cookie_ptr->Domain()), std::move(cookie), + false /* sync_to_store */, access_result, + false /* dispatch_change */); + if (ContainsControlCharacter(cookie_ptr->Name()) || + ContainsControlCharacter(cookie_ptr->Value())) { + partitioned_cookies_with_control_chars.push_back(inserted); + } + } else { + auto inserted = + InternalInsertCookie(GetKey(cookie_ptr->Domain()), std::move(cookie), + false /* sync_to_store */, access_result, + false /* dispatch_change */); + + if (ContainsControlCharacter(cookie_ptr->Name()) || + ContainsControlCharacter(cookie_ptr->Value())) { + cookies_with_control_chars.push_back(inserted); + } + } + const Time cookie_access_time(cookie_ptr->LastAccessDate()); if (earliest_access_time_.is_null() || cookie_access_time < earliest_access_time_) { earliest_access_time_ = cookie_access_time; } - - if (ContainsControlCharacter(cookie_ptr->Name()) || - ContainsControlCharacter(cookie_ptr->Value())) { - cookies_with_control_chars.push_back(inserted); - } } // Any cookies that contain control characters that we have loaded from the @@ -820,9 +902,17 @@ void CookieMonster::StoreLoadedCookies( it != cookies_with_control_chars.end();) { auto curit = it; ++it; - InternalDeleteCookie(*curit, true, DELETE_COOKIE_CONTROL_CHAR); } + for (auto it = partitioned_cookies_with_control_chars.begin(); + it != partitioned_cookies_with_control_chars.end();) { + // InternalDeletePartitionedCookie may invalidate the current iterator, so + // we increment the iterator in the loop before calling the function. + auto curit = it; + ++it; + InternalDeletePartitionedCookie(curit->first, curit->second, true, + DELETE_COOKIE_CONTROL_CHAR); + } // After importing cookies from the PersistentCookieStore, verify that // none of our other constraints are violated. @@ -875,7 +965,28 @@ void CookieMonster::EnsureCookiesMapIsValid() { prev_range_end = cur_range_end; // Ensure no equivalent cookies for this host. - TrimDuplicateCookiesForKey(key, cur_range_begin, cur_range_end); + TrimDuplicateCookiesForKey(key, cur_range_begin, cur_range_end, + absl::nullopt); + } + + for (auto cookie_partition_it = partitioned_cookies_.begin(); + cookie_partition_it != partitioned_cookies_.end();) { + auto cur_cookie_partition_it = cookie_partition_it; + ++cookie_partition_it; + + // Iterate through the cookies in this partition, grouped by host. + CookieMap* cookie_partition = cur_cookie_partition_it->second.get(); + auto prev_range_end = cookie_partition->begin(); + while (prev_range_end != cookie_partition->end()) { + auto cur_range_begin = prev_range_end; + const std::string key = cur_range_begin->first; // Keep a copy. + auto cur_range_end = cookie_partition->upper_bound(key); + prev_range_end = cur_range_end; + + // Ensure no equivalent cookies for this host and cookie partition key. + TrimDuplicateCookiesForKey(key, cur_range_begin, cur_range_end, + absl::make_optional(cur_cookie_partition_it)); + } } } @@ -885,9 +996,11 @@ void CookieMonster::EnsureCookiesMapIsValid() { // (2) For each list with more than 1 entry, keep the cookie having the // most recent creation time, and delete the others. // -void CookieMonster::TrimDuplicateCookiesForKey(const std::string& key, - CookieMap::iterator begin, - CookieMap::iterator end) { +void CookieMonster::TrimDuplicateCookiesForKey( + const std::string& key, + CookieMap::iterator begin, + CookieMap::iterator end, + absl::optional<PartitionedCookieMap::iterator> cookie_partition_it) { DCHECK(thread_checker_.CalledOnValidThread()); // Set of cookies ordered by creation time. @@ -954,24 +1067,34 @@ void CookieMonster::TrimDuplicateCookiesForKey(const std::string& key, // list of iterators one at a time, since |cookies_| is a multimap (they // don't invalidate existing iterators following deletion). for (const CookieMap::iterator& dupe : dupes) { - InternalDeleteCookie(dupe, true, - DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE); + if (cookie_partition_it) { + InternalDeletePartitionedCookie( + cookie_partition_it.value(), dupe, true, + DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE); + } else { + InternalDeleteCookie(dupe, true, + DELETE_COOKIE_DUPLICATE_IN_BACKING_STORE); + } } } DCHECK_EQ(num_duplicates, num_duplicates_found); } std::vector<CanonicalCookie*> -CookieMonster::FindCookiesForRegistryControlledHost(const GURL& url) { +CookieMonster::FindCookiesForRegistryControlledHost(const GURL& url, + CookieMap* cookie_map) { DCHECK(thread_checker_.CalledOnValidThread()); + if (!cookie_map) + cookie_map = &cookies_; + Time current_time = Time::Now(); // Retrieve all cookies for a given key const std::string key(GetKey(url.host_piece())); std::vector<CanonicalCookie*> cookies; - for (CookieMapItPair its = cookies_.equal_range(key); + for (CookieMapItPair its = cookie_map->equal_range(key); its.first != its.second;) { auto curit = its.first; CanonicalCookie* cc = curit->second.get(); @@ -987,6 +1110,20 @@ CookieMonster::FindCookiesForRegistryControlledHost(const GURL& url) { return cookies; } +std::vector<CanonicalCookie*> +CookieMonster::FindPartitionedCookiesForRegistryControlledHost( + const CookiePartitionKey& cookie_partition_key, + const GURL& url) { + DCHECK(thread_checker_.CalledOnValidThread()); + + PartitionedCookieMap::iterator it = + partitioned_cookies_.find(cookie_partition_key); + if (it == partitioned_cookies_.end()) + return std::vector<CanonicalCookie*>(); + + return FindCookiesForRegistryControlledHost(url, it->second.get()); +} + void CookieMonster::FilterCookiesWithOptions( const GURL url, const CookieOptions options, @@ -1077,19 +1214,25 @@ void CookieMonster::MaybeDeleteEquivalentCookieAndUpdateStatus( bool skip_httponly, bool already_expired, base::Time* creation_date_to_inherit, - CookieInclusionStatus* status) { + CookieInclusionStatus* status, + absl::optional<PartitionedCookieMap::iterator> cookie_partition_it) { DCHECK(thread_checker_.CalledOnValidThread()); DCHECK(!status->HasExclusionReason( CookieInclusionStatus::EXCLUDE_OVERWRITE_SECURE)); DCHECK(!status->HasExclusionReason( CookieInclusionStatus::EXCLUDE_OVERWRITE_HTTP_ONLY)); + CookieMap* cookie_map = &cookies_; + if (cookie_partition_it) { + cookie_map = cookie_partition_it.value()->second.get(); + } + bool found_equivalent_cookie = false; - CookieMap::iterator deletion_candidate_it = cookies_.end(); + CookieMap::iterator deletion_candidate_it = cookie_map->end(); CanonicalCookie* skipped_secure_cookie = nullptr; // Check every cookie matching this domain key for equivalence. - CookieMapItPair range_its = cookies_.equal_range(key); + CookieMapItPair range_its = cookie_map->equal_range(key); for (auto cur_it = range_its.first; cur_it != range_its.second; ++cur_it) { CanonicalCookie* cur_existing_cookie = cur_it->second.get(); @@ -1125,7 +1268,7 @@ void CookieMonster::MaybeDeleteEquivalentCookieAndUpdateStatus( // overwrite each other. CHECK(!found_equivalent_cookie) << "Duplicate equivalent cookies found, cookie store is corrupted."; - DCHECK(deletion_candidate_it == cookies_.end()); + DCHECK(deletion_candidate_it == cookie_map->end()); found_equivalent_cookie = true; // The |cookie_being_set| is rejected for trying to overwrite an httponly @@ -1145,14 +1288,22 @@ void CookieMonster::MaybeDeleteEquivalentCookieAndUpdateStatus( } } - if (deletion_candidate_it != cookies_.end()) { + if (deletion_candidate_it != cookie_map->end()) { CanonicalCookie* deletion_candidate = deletion_candidate_it->second.get(); if (deletion_candidate->Value() == cookie_being_set.Value()) *creation_date_to_inherit = deletion_candidate->CreationDate(); if (status->IsInclude()) { - InternalDeleteCookie(deletion_candidate_it, true /* sync_to_store */, - already_expired ? DELETE_COOKIE_EXPIRED_OVERWRITE - : DELETE_COOKIE_OVERWRITE); + if (cookie_being_set.IsPartitioned()) { + InternalDeletePartitionedCookie( + cookie_partition_it.value(), deletion_candidate_it, + true /* sync_to_store */, + already_expired ? DELETE_COOKIE_EXPIRED_OVERWRITE + : DELETE_COOKIE_OVERWRITE); + } else { + InternalDeleteCookie(deletion_candidate_it, true /* sync_to_store */, + already_expired ? DELETE_COOKIE_EXPIRED_OVERWRITE + : DELETE_COOKIE_OVERWRITE); + } } else if (status->HasExclusionReason( CookieInclusionStatus::EXCLUDE_OVERWRITE_SECURE)) { // Log that we preserved a cookie that would have been deleted due to @@ -1185,20 +1336,11 @@ CookieMonster::CookieMap::iterator CookieMonster::InternalInsertCookie( return NetLogCookieMonsterCookieAdded( cc.get(), sync_to_store, capture_mode); }); - if ((cc_ptr->IsPersistent() || persist_session_cookies_) && store_.get() && - sync_to_store) { + if (ShouldUpdatePersistentStore(cc_ptr) && sync_to_store) store_->AddCookie(*cc_ptr); - } auto inserted = cookies_.insert(CookieMap::value_type(key, std::move(cc))); - int32_t type_sample = - !cc_ptr->IsEffectivelySameSiteNone(access_result.access_semantics) - ? 1 << COOKIE_TYPE_SAME_SITE - : 0; - type_sample |= cc_ptr->IsHttpOnly() ? 1 << COOKIE_TYPE_HTTPONLY : 0; - type_sample |= cc_ptr->IsSecure() ? 1 << COOKIE_TYPE_SECURE : 0; - UMA_HISTOGRAM_EXACT_LINEAR("Cookie.Type", type_sample, - (1 << COOKIE_TYPE_LAST_ENTRY)); + LogCookieTypeToUMA(cc_ptr, access_result); DCHECK(access_result.status.IsInclude()); if (dispatch_change) { @@ -1224,6 +1366,69 @@ CookieMonster::CookieMap::iterator CookieMonster::InternalInsertCookie( return inserted; } +bool CookieMonster::ShouldUpdatePersistentStore(CanonicalCookie* cc) { + return (cc->IsPersistent() || persist_session_cookies_) && store_.get(); +} + +void CookieMonster::LogCookieTypeToUMA( + CanonicalCookie* cc, + const CookieAccessResult& access_result) { + int32_t type_sample = + !cc->IsEffectivelySameSiteNone(access_result.access_semantics) + ? 1 << COOKIE_TYPE_SAME_SITE + : 0; + type_sample |= cc->IsHttpOnly() ? 1 << COOKIE_TYPE_HTTPONLY : 0; + type_sample |= cc->IsSecure() ? 1 << COOKIE_TYPE_SECURE : 0; + UMA_HISTOGRAM_EXACT_LINEAR("Cookie.Type", type_sample, + (1 << COOKIE_TYPE_LAST_ENTRY)); +} + +CookieMonster::PartitionedCookieMapIterators +CookieMonster::InternalInsertPartitionedCookie( + std::string key, + std::unique_ptr<CanonicalCookie> cc, + bool sync_to_store, + const CookieAccessResult& access_result, + bool dispatch_change) { + DCHECK(cc->IsPartitioned()); + DCHECK(thread_checker_.CalledOnValidThread()); + CanonicalCookie* cc_ptr = cc.get(); + + net_log_.AddEvent(NetLogEventType::COOKIE_STORE_COOKIE_ADDED, + [&](NetLogCaptureMode capture_mode) { + return NetLogCookieMonsterCookieAdded( + cc.get(), sync_to_store, capture_mode); + }); + if (ShouldUpdatePersistentStore(cc_ptr) && sync_to_store) + store_->AddCookie(*cc_ptr); + + CookiePartitionKey partition_key(cc->PartitionKey().value()); + PartitionedCookieMap::iterator partition_it = + partitioned_cookies_.find(partition_key); + if (partition_it == partitioned_cookies_.end()) { + partition_it = + partitioned_cookies_ + .insert(PartitionedCookieMap::value_type( + std::move(partition_key), std::make_unique<CookieMap>())) + .first; + } + + CookieMap::iterator cookie_it = partition_it->second->insert( + CookieMap::value_type(std::move(key), std::move(cc))); + ++num_partitioned_cookies_; + + LogCookieTypeToUMA(cc_ptr, access_result); + + DCHECK(access_result.status.IsInclude()); + if (dispatch_change) { + change_dispatcher_.DispatchChange( + CookieChangeInfo(*cc_ptr, access_result, CookieChangeCause::INSERTED), + true); + } + + return std::make_pair(partition_it, cookie_it); +} + void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, const GURL& source_url, const CookieOptions& options, @@ -1252,13 +1457,29 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, base::Time creation_date_to_inherit; + absl::optional<PartitionedCookieMap::iterator> cookie_partition_it; + bool should_try_to_delete_duplicates = true; + + if (cc->IsPartitioned()) { + auto it = partitioned_cookies_.find(cc->PartitionKey().value()); + if (it == partitioned_cookies_.end()) { + // This is the first cookie in its partition, so it won't have any + // duplicates. + should_try_to_delete_duplicates = false; + } else { + cookie_partition_it = absl::make_optional(it); + } + } + // Iterates through existing cookies for the same eTLD+1, and potentially // deletes an existing cookie, so any ExclusionReasons in |status| that would // prevent such deletion should be finalized beforehand. - MaybeDeleteEquivalentCookieAndUpdateStatus( - key, *cc, access_result.is_allowed_to_access_secure_cookies, - options.exclude_httponly(), already_expired, &creation_date_to_inherit, - &access_result.status); + if (should_try_to_delete_duplicates) { + MaybeDeleteEquivalentCookieAndUpdateStatus( + key, *cc, access_result.is_allowed_to_access_secure_cookies, + options.exclude_httponly(), already_expired, &creation_date_to_inherit, + &access_result.status, cookie_partition_it); + } if (access_result.status.HasExclusionReason( CookieInclusionStatus::EXCLUDE_OVERWRITE_SECURE) || @@ -1290,6 +1511,11 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, 1 + IsolationInfo::kPartyContextMaxSize); } + bool is_partitioned_cookie = cc->IsPartitioned(); + CookiePartitionKey cookie_partition_key; + if (is_partitioned_cookie) + cookie_partition_key = cc->PartitionKey().value(); + // Realize that we might be setting an expired cookie, and the only point // was to delete the cookie which we've already done. if (!already_expired) { @@ -1319,7 +1545,12 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, cc->SetCreationDate(creation_date_to_inherit); } - InternalInsertCookie(key, std::move(cc), true, access_result); + if (is_partitioned_cookie) { + InternalInsertPartitionedCookie(key, std::move(cc), true, + access_result); + } else { + InternalInsertCookie(key, std::move(cc), true, access_result); + } } else { DVLOG(net::cookie_util::kVlogSetCookies) << "SetCookie() not storing already expired cookie."; @@ -1330,7 +1561,12 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, // make sure that we garbage collect... We can also make the assumption // that if a cookie was set, in the common case it will be used soon after, // and we will purge the expired cookies in GetCookies(). - GarbageCollect(creation_date, key); + if (is_partitioned_cookie) { + GarbageCollectPartitionedCookies(creation_date, cookie_partition_key, + key); + } else { + GarbageCollect(creation_date, key); + } if (IsLocalhost(source_url)) { UMA_HISTOGRAM_ENUMERATION( @@ -1376,9 +1612,17 @@ void CookieMonster::SetAllCookies(CookieList list, CookieAccessResult access_result; access_result.access_semantics = GetAccessSemanticsForCookie(cookie); - InternalInsertCookie(key, std::make_unique<CanonicalCookie>(cookie), true, - access_result); - GarbageCollect(creation_time, key); + + if (cookie.IsPartitioned()) { + InternalInsertPartitionedCookie( + key, std::make_unique<CanonicalCookie>(cookie), true, access_result); + GarbageCollectPartitionedCookies(creation_time, + cookie.PartitionKey().value(), key); + } else { + InternalInsertCookie(key, std::make_unique<CanonicalCookie>(cookie), true, + access_result); + GarbageCollect(creation_time, key); + } } // TODO(rdsmith): If this function always returns the same value, it @@ -1400,7 +1644,7 @@ void CookieMonster::InternalUpdateCookieAccessTime(CanonicalCookie* cc, return; cc->SetLastAccessDate(current); - if ((cc->IsPersistent() || persist_session_cookies_) && store_.get()) + if (ShouldUpdatePersistentStore(cc)) store_->UpdateCookieAccessTime(*cc); } @@ -1431,10 +1675,9 @@ void CookieMonster::InternalDeleteCookie(CookieMap::iterator it, }); } - if ((cc->IsPersistent() || persist_session_cookies_) && store_.get() && - sync_to_store) { + if (ShouldUpdatePersistentStore(cc) && sync_to_store) store_->DeleteCookie(*cc); - } + change_dispatcher_.DispatchChange( CookieChangeInfo( *cc, @@ -1457,6 +1700,54 @@ void CookieMonster::InternalDeleteCookie(CookieMap::iterator it, cookies_.erase(it); } +void CookieMonster::InternalDeletePartitionedCookie( + PartitionedCookieMap::iterator partition_it, + CookieMap::iterator cookie_it, + bool sync_to_store, + DeletionCause deletion_cause) { + DCHECK(thread_checker_.CalledOnValidThread()); + + // Ideally, this would be asserted up where we define kChangeCauseMapping, + // but DeletionCause's visibility (or lack thereof) forces us to make + // this check here. + static_assert(base::size(kChangeCauseMapping) == DELETE_COOKIE_LAST_ENTRY + 1, + "kChangeCauseMapping size should match DeletionCause size"); + + CanonicalCookie* cc = cookie_it->second.get(); + DCHECK(cc->IsPartitioned()); + DVLOG(net::cookie_util::kVlogSetCookies) + << "InternalDeletePartitionedCookie()" + << ", cause:" << deletion_cause << ", cc: " << cc->DebugString(); + + ChangeCausePair mapping = kChangeCauseMapping[deletion_cause]; + if (deletion_cause != DELETE_COOKIE_DONT_RECORD) { + net_log_.AddEvent(NetLogEventType::COOKIE_STORE_COOKIE_DELETED, + [&](NetLogCaptureMode capture_mode) { + return NetLogCookieMonsterCookieDeleted( + cc, mapping.cause, sync_to_store, capture_mode); + }); + } + + if (ShouldUpdatePersistentStore(cc) && sync_to_store) + store_->DeleteCookie(*cc); + + change_dispatcher_.DispatchChange( + CookieChangeInfo( + *cc, + CookieAccessResult(CookieEffectiveSameSite::UNDEFINED, + CookieInclusionStatus(), + GetAccessSemanticsForCookie(*cc), + true /* is_allowed_to_access_secure_cookies */), + mapping.cause), + mapping.notify); + + partition_it->second->erase(cookie_it); + --num_partitioned_cookies_; + + if (partition_it->second->empty()) + partitioned_cookies_.erase(partition_it); +} + // Domain expiry behavior is unchanged by key/expiry scheme (the // meaning of the key is different, but that's not visible to this routine). size_t CookieMonster::GarbageCollect(const Time& current, @@ -1464,7 +1755,7 @@ size_t CookieMonster::GarbageCollect(const Time& current, DCHECK(thread_checker_.CalledOnValidThread()); size_t num_deleted = 0; - Time safe_date(Time::Now() - TimeDelta::FromDays(kSafeFromGlobalPurgeDays)); + Time safe_date(Time::Now() - base::Days(kSafeFromGlobalPurgeDays)); // Collect garbage for this key, minding cookie priorities. if (cookies_.count(key) > kDomainMaxCookies) { @@ -1625,6 +1916,50 @@ size_t CookieMonster::GarbageCollect(const Time& current, return num_deleted; } +size_t CookieMonster::GarbageCollectPartitionedCookies( + const base::Time& current, + const CookiePartitionKey& cookie_partition_key, + const std::string& key) { + DCHECK(thread_checker_.CalledOnValidThread()); + + size_t num_deleted = 0; + PartitionedCookieMap::iterator cookie_partition_it = + partitioned_cookies_.find(cookie_partition_key); + DCHECK(cookie_partition_it != partitioned_cookies_.end()); + + if (cookie_partition_it->second->count(key) > kPerPartitionDomainMaxCookies) { + // TODO(crbug.com/1225444): Log garbage collection for partitioned cookies. + + CookieItVector non_expired_cookie_its; + num_deleted += GarbageCollectExpiredPartitionedCookies( + current, cookie_partition_it, + cookie_partition_it->second->equal_range(key), &non_expired_cookie_its); + + if (non_expired_cookie_its.size() > kPerPartitionDomainMaxCookies) { + // TODO(crbug.com/1225444): Log deep garbage collection for partitioned + // cookies. + + // For now, just delete the least recently accessed partition cookies + // until we are under the per-partition domain limit. All partitioned + // cookies are Secure since they require the __Host- prefix. + std::sort(non_expired_cookie_its.begin(), non_expired_cookie_its.end(), + LRACookieSorter); + for (size_t i = 0; + i < (non_expired_cookie_its.size() - kPerPartitionDomainMaxCookies); + ++i) { + InternalDeletePartitionedCookie( + cookie_partition_it, non_expired_cookie_its[i], true, + DELETE_COOKIE_EVICTED_PER_PARTITION_DOMAIN); + ++num_deleted; + } + } + } + + // TODO(crbug.com/1225444): Enforce global limit on partitioned cookies. + + return num_deleted; +} + size_t CookieMonster::PurgeLeastRecentMatches(CookieItVector* cookies, CookiePriority priority, size_t to_protect, @@ -1698,6 +2033,47 @@ size_t CookieMonster::GarbageCollectExpired(const Time& current, return num_deleted; } +size_t CookieMonster::GarbageCollectExpiredPartitionedCookies( + const Time& current, + const PartitionedCookieMap::iterator& cookie_partition_it, + const CookieMapItPair& itpair, + CookieItVector* cookie_its) { + DCHECK(thread_checker_.CalledOnValidThread()); + + int num_deleted = 0; + for (CookieMap::iterator it = itpair.first, end = itpair.second; it != end;) { + auto curit = it; + ++it; + + if (curit->second->IsExpired(current)) { + InternalDeletePartitionedCookie(cookie_partition_it, curit, true, + DELETE_COOKIE_EXPIRED); + ++num_deleted; + } else if (cookie_its) { + cookie_its->push_back(curit); + } + } + + return num_deleted; +} + +void CookieMonster::GarbageCollectAllExpiredPartitionedCookies( + const Time& current) { + for (auto it = partitioned_cookies_.begin(); + it != partitioned_cookies_.end();) { + // GarbageCollectExpiredPartitionedCookies calls + // InternalDeletePartitionedCookie which may invalidate + // |cur_cookie_partition_it|. + auto cur_cookie_partition_it = it; + ++it; + GarbageCollectExpiredPartitionedCookies( + current, cur_cookie_partition_it, + CookieMapItPair(cur_cookie_partition_it->second->begin(), + cur_cookie_partition_it->second->end()), + nullptr /*cookie_its*/); + } +} + size_t CookieMonster::GarbageCollectDeleteRange( const Time& current, DeletionCause cause, @@ -1809,7 +2185,7 @@ void CookieMonster::RecordPeriodicStats(const base::Time& current_time) { DCHECK(thread_checker_.CalledOnValidThread()); const base::TimeDelta kRecordStatisticsIntervalTime( - base::TimeDelta::FromSeconds(kRecordStatisticsIntervalSeconds)); + base::Seconds(kRecordStatisticsIntervalSeconds)); // If we've taken statistics recently, return. if (current_time - last_statistic_record_time_ <= @@ -1821,6 +2197,7 @@ void CookieMonster::RecordPeriodicStats(const base::Time& current_time) { last_statistic_record_time_ = current_time; } +// TODO(crbug.com/1225444): Record periodic stats for Partitioned cookies. bool CookieMonster::DoRecordPeriodicStats() { // These values are all bogus if we have only partially loaded the cookies. if (started_fetching_all_cookies_ && !finished_fetching_all_cookies_) diff --git a/chromium/net/cookies/cookie_monster.h b/chromium/net/cookies/cookie_monster.h index 8439ba30a16..5828ff23cdb 100644 --- a/chromium/net/cookies/cookie_monster.h +++ b/chromium/net/cookies/cookie_monster.h @@ -85,6 +85,8 @@ class NET_EXPORT CookieMonster : public CookieStore { // not legal to have domain cookies without an eTLD+1). This rule // excludes cookies for, e.g, ".com", ".co.uk", or ".internalnetwork". // This behavior is the same as the behavior in Firefox v 3.6.10. + // CookieMap does not store cookies that were set with the Partitioned + // attribute, those are stored in PartitionedCookieMap. // NOTE(deanm): // I benchmarked hash_multimap vs multimap. We're going to be query-heavy @@ -100,6 +102,18 @@ class NET_EXPORT CookieMonster : public CookieStore { using CookieMapItPair = std::pair<CookieMap::iterator, CookieMap::iterator>; using CookieItVector = std::vector<CookieMap::iterator>; + // PartitionedCookieMap only stores cookies that were set with the Partitioned + // attribute. The map is double-keyed on cookie's partition key and + // the cookie's effective domain of the cookie (the key of CookieMap). + // We store partitioned cookies in a separate map so that the queries for a + // request's unpartitioned and partitioned cookies will both be more + // efficient (since querying two smaller maps is more efficient that querying + // one larger map twice). + using PartitionedCookieMap = + std::map<CookiePartitionKey, std::unique_ptr<CookieMap>>; + using PartitionedCookieMapIterators = + std::pair<PartitionedCookieMap::iterator, CookieMap::iterator>; + // Cookie garbage collection thresholds. Based off of the Mozilla defaults. // When the number of cookies gets to k{Domain,}MaxCookies // purge down to k{Domain,}MaxCookies - k{Domain,}PurgeCookies. @@ -122,6 +136,10 @@ class NET_EXPORT CookieMonster : public CookieStore { // Max number of keys to store for domains that have been purged. static const size_t kMaxDomainPurgedKeys; + // Partitioned cookie garbage collection thresholds. + static const size_t kPerPartitionDomainMaxCookies; + // TODO(crbug.com/1225444): Add global limit to number of partitioned cookies. + // Quota for cookies with {low, medium, high} priorities within a domain. static const size_t kDomainCookiesQuotaLow; static const size_t kDomainCookiesQuotaMedium; @@ -145,6 +163,9 @@ class NET_EXPORT CookieMonster : public CookieStore { base::TimeDelta last_access_threshold, NetLog* net_log); + CookieMonster(const CookieMonster&) = delete; + CookieMonster& operator=(const CookieMonster&) = delete; + ~CookieMonster() override; // Writes all the cookies in |list| into the store, replacing all cookies @@ -162,6 +183,7 @@ class NET_EXPORT CookieMonster : public CookieStore { SetCookiesCallback callback) override; void GetCookieListWithOptionsAsync(const GURL& url, const CookieOptions& options, + const CookiePartitionKeychain& s, GetCookieListCallback callback) override; void GetAllCookiesAsync(GetAllCookiesCallback callback) override; void GetAllCookiesWithAccessSemanticsAsync( @@ -191,9 +213,6 @@ class NET_EXPORT CookieMonster : public CookieStore { static const char* const kDefaultCookieableSchemes[]; static const int kDefaultCookieableSchemesCount; - void DumpMemoryStats(base::trace_event::ProcessMemoryDump* pmd, - const std::string& parent_absolute_name) const override; - // Find a key based on the given domain, which will be used to find all // cookies potentially relevant to it. This is used for lookup in cookies_ as // well as for PersistentCookieStore::LoadCookiesForKey. See comment on keys @@ -257,7 +276,7 @@ class NET_EXPORT CookieMonster : public CookieStore { // Cookies evicted during domain-level garbage collection. DELETE_COOKIE_EVICTED_DOMAIN = 6, - // Cookies evicted during global garbage collection (which takes place after + // Cookies evicted during global garbage collection, which takes place after // domain-level garbage collection fails to bring the cookie store under // the overall quota. DELETE_COOKIE_EVICTED_GLOBAL = 7, @@ -278,7 +297,11 @@ class NET_EXPORT CookieMonster : public CookieStore { // right after expired cookies. DELETE_COOKIE_NON_SECURE = 12, - DELETE_COOKIE_LAST_ENTRY = 13 + // Partitioned cookies evicted during per-partition domain-level garbage + // collection. + DELETE_COOKIE_EVICTED_PER_PARTITION_DOMAIN = 13, + + DELETE_COOKIE_LAST_ENTRY = 14, }; // This enum is used to generate a histogramed bitmask measureing the types @@ -356,9 +379,11 @@ class NET_EXPORT CookieMonster : public CookieStore { GetAllCookiesWithAccessSemanticsCallback callback, const CookieList& cookie_list); - void GetCookieListWithOptions(const GURL& url, - const CookieOptions& options, - GetCookieListCallback callback); + void GetCookieListWithOptions( + const GURL& url, + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain, + GetCookieListCallback callback); void DeleteAllCreatedInTimeRange( const CookieDeletionInfo::TimeRange& creation_range, @@ -417,13 +442,24 @@ class NET_EXPORT CookieMonster : public CookieStore { // Checks for any duplicate cookies for CookieMap key |key| which lie between // |begin| and |end|. If any are found, all but the most recent are deleted. - void TrimDuplicateCookiesForKey(const std::string& key, - CookieMap::iterator begin, - CookieMap::iterator end); + // + // If |cookie_partition_it| is not nullopt, then this function trims cookies + // from the CookieMap in |partitioned_cookies_| at |cookie_partition_it| + // instead of trimming cookies from |cookies_|. + void TrimDuplicateCookiesForKey( + const std::string& key, + CookieMap::iterator begin, + CookieMap::iterator end, + absl::optional<PartitionedCookieMap::iterator> cookie_partition_it); void SetDefaultCookieableSchemes(); std::vector<CanonicalCookie*> FindCookiesForRegistryControlledHost( + const GURL& url, + CookieMap* cookie_map = nullptr); + + std::vector<CanonicalCookie*> FindPartitionedCookiesForRegistryControlledHost( + const CookiePartitionKey& cookie_partition_key, const GURL& url); void FilterCookiesWithOptions(const GURL url, @@ -455,6 +491,11 @@ class NET_EXPORT CookieMonster : public CookieStore { // the function. The function will update |*status| with exclusion reasons if // a secure cookie was skipped or an httponly cookie was skipped. // + // If |cookie_partition_it| is nullopt, it will search |cookies_| for + // duplicates of |cookie_being_set|. Otherwise, |cookie_partition_it|'s value + // is the iterator of the CookieMap in |partitioned_cookies_| we should search + // for duplicates. + // // NOTE: There should never be more than a single matching equivalent cookie. void MaybeDeleteEquivalentCookieAndUpdateStatus( const std::string& key, @@ -463,7 +504,8 @@ class NET_EXPORT CookieMonster : public CookieStore { bool skip_httponly, bool already_expired, base::Time* creation_date_to_inherit, - CookieInclusionStatus* status); + CookieInclusionStatus* status, + absl::optional<PartitionedCookieMap::iterator> cookie_partition_it); // Inserts `cc` into cookies_. Returns an iterator that points to the inserted // cookie in `cookies_`. Guarantee: all iterators to `cookies_` remain valid. @@ -476,6 +518,22 @@ class NET_EXPORT CookieMonster : public CookieStore { const CookieAccessResult& access_result, bool dispatch_change = true); + // Returns true if the cookie should be (or is already) synced to the store. + // Used for cookies during insertion and deletion into the in-memory store. + bool ShouldUpdatePersistentStore(CanonicalCookie* cc); + + void LogCookieTypeToUMA(CanonicalCookie* cc, + const CookieAccessResult& access_result); + + // Inserts `cc` into partitioned_cookies_. Should only be used when + // cc->IsPartitioned() is true. + PartitionedCookieMapIterators InternalInsertPartitionedCookie( + std::string key, + std::unique_ptr<CanonicalCookie> cc, + bool sync_to_store, + const CookieAccessResult& access_result, + bool dispatch_change = true); + // Sets all cookies from |list| after deleting any equivalent cookie. // For data gathering purposes, this routine is treated as if it is // restoring saved cookies; some statistics are not gathered in this case. @@ -491,14 +549,37 @@ class NET_EXPORT CookieMonster : public CookieStore { bool sync_to_store, DeletionCause deletion_cause); + // Deletes a Partitioned cookie. Returns true if the deletion operation + // resulted in the CookieMap the cookie was stored in was deleted. + // + // If the CookieMap which contains the deleted cookie only has one entry, then + // this function will also delete the CookieMap from PartitionedCookieMap. + // This may invalidate the |cookie_partition_it| argument. + void InternalDeletePartitionedCookie( + PartitionedCookieMap::iterator partition_it, + CookieMap::iterator cookie_it, + bool sync_to_store, + DeletionCause deletion_cause); + // If the number of cookies for CookieMap key |key|, or globally, are // over the preset maximums above, garbage collect, first for the host and // then globally. See comments above garbage collection threshold - // constants for details. + // constants for details. Also removes expired cookies. // // Returns the number of cookies deleted (useful for debugging). size_t GarbageCollect(const base::Time& current, const std::string& key); + // Run garbage collection for PartitionedCookieMap keys |cookie_partition_key| + // and |key|. + // + // Partitioned cookies are subject to different limits than unpartitioned + // cookies in order to prevent leaking entropy about user behavior across + // cookie partitions. + size_t GarbageCollectPartitionedCookies( + const base::Time& current, + const CookiePartitionKey& cookie_partition_key, + const std::string& key); + // Helper for GarbageCollect(). Deletes up to |purge_goal| cookies with a // priority less than or equal to |priority| from |cookies|, while ensuring // that at least the |to_protect| most-recent cookies are retained. @@ -523,6 +604,22 @@ class NET_EXPORT CookieMonster : public CookieStore { const CookieMapItPair& itpair, CookieItVector* cookie_its); + // Deletes all expired cookies in the double-keyed PartitionedCookie map in + // the CookieMap at |cookie_partition_it|. It deletes all cookies in that + // CookieMap in |itpair|. If |cookie_its| is non-NULL, all non-expired cookies + // from |itpair| are appended to |cookie_its|. + // + // Returns the number of cookies deleted. + size_t GarbageCollectExpiredPartitionedCookies( + const base::Time& current, + const PartitionedCookieMap::iterator& cookie_partition_it, + const CookieMapItPair& itpair, + CookieItVector* cookie_its); + + // Helper function to garbage collect all expired cookies in + // PartitionedCookieMap. + void GarbageCollectAllExpiredPartitionedCookies(const base::Time& current); + // Helper for GarbageCollect(). Deletes all cookies in the range specified by // [|it_begin|, |it_end|). Returns the number of cookies deleted. size_t GarbageCollectDeleteRange(const base::Time& current, @@ -595,6 +692,12 @@ class NET_EXPORT CookieMonster : public CookieStore { CookieMap cookies_; + PartitionedCookieMap partitioned_cookies_; + + // Number of distinct partitioned cookies globally. This is used to enforce a + // global maximum on the number of partitioned cookies. + size_t num_partitioned_cookies_; + CookieMonsterChangeDispatcher change_dispatcher_; // Indicates whether the cookie store has been initialized. @@ -653,8 +756,6 @@ class NET_EXPORT CookieMonster : public CookieStore { base::ThreadChecker thread_checker_; base::WeakPtrFactory<CookieMonster> weak_ptr_factory_{this}; - - DISALLOW_COPY_AND_ASSIGN(CookieMonster); }; typedef base::RefCountedThreadSafe<CookieMonster::PersistentCookieStore> @@ -667,6 +768,9 @@ class NET_EXPORT CookieMonster::PersistentCookieStore std::vector<std::unique_ptr<CanonicalCookie>>)> LoadedCallback; + PersistentCookieStore(const PersistentCookieStore&) = delete; + PersistentCookieStore& operator=(const PersistentCookieStore&) = delete; + // Initializes the store and retrieves the existing cookies. This will be // called only once at startup. The callback will return all the cookies // that are not yet returned to CookieMonster by previous priority loads. @@ -709,7 +813,6 @@ class NET_EXPORT CookieMonster::PersistentCookieStore private: friend class base::RefCountedThreadSafe<PersistentCookieStore>; - DISALLOW_COPY_AND_ASSIGN(PersistentCookieStore); }; } // namespace net diff --git a/chromium/net/cookies/cookie_monster_change_dispatcher.cc b/chromium/net/cookies/cookie_monster_change_dispatcher.cc index 43dffc1657d..6cac1881162 100644 --- a/chromium/net/cookies/cookie_monster_change_dispatcher.cc +++ b/chromium/net/cookies/cookie_monster_change_dispatcher.cc @@ -33,11 +33,13 @@ CookieMonsterChangeDispatcher::Subscription::Subscription( std::string domain_key, std::string name_key, GURL url, + absl::optional<CookiePartitionKey> cookie_partition_key, net::CookieChangeCallback callback) : change_dispatcher_(std::move(change_dispatcher)), domain_key_(std::move(domain_key)), name_key_(std::move(name_key)), url_(std::move(url)), + cookie_partition_key_(std::move(cookie_partition_key)), callback_(std::move(callback)), task_runner_(base::ThreadTaskRunnerHandle::Get()) { DCHECK(url_.is_valid() || url_.is_empty()); @@ -80,6 +82,11 @@ void CookieMonsterChangeDispatcher::Subscription::DispatchChange( } } + if (change.cookie.IsPartitioned() && + change.cookie.PartitionKey() != cookie_partition_key_) { + return; + } + // TODO(mmenke, pwnall): Run callbacks synchronously? task_runner_->PostTask( FROM_HERE, base::BindOnce(&Subscription::DoDispatchChange, @@ -130,12 +137,13 @@ std::unique_ptr<CookieChangeSubscription> CookieMonsterChangeDispatcher::AddCallbackForCookie( const GURL& url, const std::string& name, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>( weak_ptr_factory_.GetWeakPtr(), DomainKey(url), NameKey(name), url, - std::move(callback)); + cookie_partition_key, std::move(callback)); LinkSubscription(subscription.get()); return subscription; @@ -144,12 +152,14 @@ CookieMonsterChangeDispatcher::AddCallbackForCookie( std::unique_ptr<CookieChangeSubscription> CookieMonsterChangeDispatcher::AddCallbackForUrl( const GURL& url, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) { DCHECK_CALLED_ON_VALID_THREAD(thread_checker_); std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>( weak_ptr_factory_.GetWeakPtr(), DomainKey(url), - std::string(kGlobalNameKey), url, std::move(callback)); + std::string(kGlobalNameKey), url, cookie_partition_key, + std::move(callback)); LinkSubscription(subscription.get()); return subscription; @@ -162,7 +172,8 @@ CookieMonsterChangeDispatcher::AddCallbackForAllChanges( std::unique_ptr<Subscription> subscription = std::make_unique<Subscription>( weak_ptr_factory_.GetWeakPtr(), std::string(kGlobalDomainKey), - std::string(kGlobalNameKey), GURL(""), std::move(callback)); + std::string(kGlobalNameKey), GURL(""), CookiePartitionKey::Todo(), + std::move(callback)); LinkSubscription(subscription.get()); return subscription; diff --git a/chromium/net/cookies/cookie_monster_change_dispatcher.h b/chromium/net/cookies/cookie_monster_change_dispatcher.h index c769efe9b99..bf25388810f 100644 --- a/chromium/net/cookies/cookie_monster_change_dispatcher.h +++ b/chromium/net/cookies/cookie_monster_change_dispatcher.h @@ -33,6 +33,11 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { // Expects |cookie_monster| to outlive this. explicit CookieMonsterChangeDispatcher(const CookieMonster* cookie_monster); + + CookieMonsterChangeDispatcher(const CookieMonsterChangeDispatcher&) = delete; + CookieMonsterChangeDispatcher& operator=( + const CookieMonsterChangeDispatcher&) = delete; + ~CookieMonsterChangeDispatcher() override; // The key in CookieNameMap for a cookie name. @@ -48,9 +53,11 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { std::unique_ptr<CookieChangeSubscription> AddCallbackForCookie( const GURL& url, const std::string& name, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) override WARN_UNUSED_RESULT; std::unique_ptr<CookieChangeSubscription> AddCallbackForUrl( const GURL& url, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) override WARN_UNUSED_RESULT; std::unique_ptr<CookieChangeSubscription> AddCallbackForAllChanges( CookieChangeCallback callback) override WARN_UNUSED_RESULT; @@ -69,8 +76,12 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { std::string domain_key, std::string name_key, GURL url, + absl::optional<CookiePartitionKey> cookie_partition_key, net::CookieChangeCallback callback); + Subscription(const Subscription&) = delete; + Subscription& operator=(const Subscription&) = delete; + ~Subscription() override; // The lookup key used in the domain subscription map. @@ -91,6 +102,8 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { const std::string domain_key_; // kGlobalDomainKey means no filtering. const std::string name_key_; // kGlobalNameKey means no filtering. const GURL url_; // empty() means no URL-based filtering. + // nullopt means all Partitioned cookies will be ignored. + const absl::optional<CookiePartitionKey> cookie_partition_key_; const net::CookieChangeCallback callback_; void DoDispatchChange(const CookieChangeInfo& change) const; @@ -103,8 +116,6 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { // Used to cancel delayed calls to DoDispatchChange() when the subscription // gets destroyed. base::WeakPtrFactory<Subscription> weak_ptr_factory_{this}; - - DISALLOW_COPY_AND_ASSIGN(Subscription); }; // The last level of the subscription data structures. @@ -147,8 +158,6 @@ class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { // Vends weak pointers to subscriptions. base::WeakPtrFactory<CookieMonsterChangeDispatcher> weak_ptr_factory_{this}; - - DISALLOW_COPY_AND_ASSIGN(CookieMonsterChangeDispatcher); }; } // namespace net diff --git a/chromium/net/cookies/cookie_monster_perftest.cc b/chromium/net/cookies/cookie_monster_perftest.cc index 61bb68fcadc..3d9746c7093 100644 --- a/chromium/net/cookies/cookie_monster_perftest.cc +++ b/chromium/net/cookies/cookie_monster_perftest.cc @@ -117,7 +117,7 @@ class GetCookieListCallback : public CookieTestCallback { public: const CookieList& GetCookieList(CookieMonster* cm, const GURL& gurl) { cm->GetCookieListWithOptionsAsync( - gurl, options_, + gurl, options_, CookiePartitionKeychain(), base::BindOnce(&GetCookieListCallback::Run, base::Unretained(this))); WaitForCallback(); return cookie_list_; diff --git a/chromium/net/cookies/cookie_monster_store_test.cc b/chromium/net/cookies/cookie_monster_store_test.cc index 63e85b3e690..786039566d1 100644 --- a/chromium/net/cookies/cookie_monster_store_test.cc +++ b/chromium/net/cookies/cookie_monster_store_test.cc @@ -205,7 +205,7 @@ std::unique_ptr<CookieMonster> CreateMonsterFromStoreForGC( int num_old_non_secure_cookies, int days_old) { base::Time current(base::Time::Now()); - base::Time past_creation(base::Time::Now() - base::TimeDelta::FromDays(1000)); + base::Time past_creation(base::Time::Now() - base::Days(1000)); scoped_refptr<MockSimplePersistentCookieStore> store( new MockSimplePersistentCookieStore); int total_cookies = num_secure_cookies + num_non_secure_cookies; @@ -222,13 +222,11 @@ std::unique_ptr<CookieMonster> CreateMonsterFromStoreForGC( num_old_cookies = num_old_non_secure_cookies; secure = false; } - base::Time creation_time = - past_creation + base::TimeDelta::FromMicroseconds(i); - base::Time expiration_time = current + base::TimeDelta::FromDays(30); - base::Time last_access_time = - ((i - base) < num_old_cookies) - ? current - base::TimeDelta::FromDays(days_old) - : current; + base::Time creation_time = past_creation + base::Microseconds(i); + base::Time expiration_time = current + base::Days(30); + base::Time last_access_time = ((i - base) < num_old_cookies) + ? current - base::Days(days_old) + : current; // The URL must be HTTPS since |secure| can be true or false, and because // strict secure cookies are enforced, the cookie will fail to be created if diff --git a/chromium/net/cookies/cookie_monster_store_test.h b/chromium/net/cookies/cookie_monster_store_test.h index c925612720f..7244241f182 100644 --- a/chromium/net/cookies/cookie_monster_store_test.h +++ b/chromium/net/cookies/cookie_monster_store_test.h @@ -77,6 +77,10 @@ class MockPersistentCookieStore : public CookieMonster::PersistentCookieStore { MockPersistentCookieStore(); + MockPersistentCookieStore(const MockPersistentCookieStore&) = delete; + MockPersistentCookieStore& operator=(const MockPersistentCookieStore&) = + delete; + // When set, Load() and LoadCookiesForKey() calls are store in the command // list, rather than being automatically executed. Defaults to false. void set_store_load_commands(bool store_load_commands) { @@ -125,8 +129,6 @@ class MockPersistentCookieStore : public CookieMonster::PersistentCookieStore { // Indicates if the store has been fully loaded to avoid returning duplicate // cookies. bool loaded_; - - DISALLOW_COPY_AND_ASSIGN(MockPersistentCookieStore); }; // Helper to build a single CanonicalCookie. diff --git a/chromium/net/cookies/cookie_monster_unittest.cc b/chromium/net/cookies/cookie_monster_unittest.cc index 23c3f2e783a..f610c750570 100644 --- a/chromium/net/cookies/cookie_monster_unittest.cc +++ b/chromium/net/cookies/cookie_monster_unittest.cc @@ -62,7 +62,6 @@ namespace net { using base::Time; -using base::TimeDelta; using CookieDeletionInfo = net::CookieDeletionInfo; namespace { @@ -109,6 +108,7 @@ struct CookieMonsterTestTraits { static const bool has_exact_change_ordering = true; static const int creation_time_granularity_in_ms = 0; static const bool supports_cookie_access_semantics = true; + static const bool supports_partitioned_cookies = true; }; INSTANTIATE_TYPED_TEST_SUITE_P(CookieMonster, @@ -133,12 +133,16 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { using CookieStoreTest<T>::http_www_foo_; using CookieStoreTest<T>::https_www_foo_; - CookieList GetAllCookiesForURLWithOptions(CookieMonster* cm, - const GURL& url, - const CookieOptions& options) { + CookieList GetAllCookiesForURLWithOptions( + CookieMonster* cm, + const GURL& url, + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain = + CookiePartitionKeychain()) { DCHECK(cm); GetCookieListCallback callback; - cm->GetCookieListWithOptionsAsync(url, options, callback.MakeCallback()); + cm->GetCookieListWithOptionsAsync(url, options, cookie_partition_keychain, + callback.MakeCallback()); callback.WaitUntilDone(); return callback.cookies(); } @@ -146,10 +150,13 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { CookieAccessResultList GetExcludedCookiesForURLWithOptions( CookieMonster* cm, const GURL& url, - const CookieOptions& options) { + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain = + CookiePartitionKeychain()) { DCHECK(cm); GetCookieListCallback callback; - cm->GetCookieListWithOptionsAsync(url, options, callback.MakeCallback()); + cm->GetCookieListWithOptionsAsync(url, options, cookie_partition_keychain, + callback.MakeCallback()); callback.WaitUntilDone(); return callback.excluded_cookies(); } @@ -162,17 +169,19 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { return callback.result().status.IsInclude(); } - bool SetCookieWithCreationTime(CookieMonster* cm, - const GURL& url, - const std::string& cookie_line, - base::Time creation_time) { + bool SetCookieWithCreationTime( + CookieMonster* cm, + const GURL& url, + const std::string& cookie_line, + base::Time creation_time, + absl::optional<CookiePartitionKey> cookie_partition_key = absl::nullopt) { DCHECK(cm); DCHECK(!creation_time.is_null()); ResultSavingCookieCallback<CookieAccessResult> callback; cm->SetCanonicalCookieAsync( CanonicalCookie::Create(url, cookie_line, creation_time, absl::nullopt /* server_time */, - absl::nullopt /* cookie_partition_key */), + cookie_partition_key), url, CookieOptions::MakeAllInclusive(), callback.MakeCallback()); callback.WaitUntilDone(); return callback.result().status.IsInclude(); @@ -215,6 +224,8 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { std::string url_top_level_domain_plus_1(GURL(kTopLevelDomainPlus1).host()); std::string url_top_level_domain_plus_2(GURL(kTopLevelDomainPlus2).host()); std::string url_top_level_domain_plus_3(GURL(kTopLevelDomainPlus3).host()); + std::string url_top_level_domain_secure( + GURL(kTopLevelDomainPlus2Secure).host()); std::string url_other(GURL(kOtherDomain).host()); this->DeleteAll(cm); @@ -309,6 +320,18 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); + // Partitioned cookies + cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-pc_1", "A", url_top_level_domain_secure, "/", now, base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + CookiePriority::COOKIE_PRIORITY_DEFAULT, false, + CookiePartitionKey::FromURLForTesting(GURL(kTopLevelDomainPlus1)))); + cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-pc_2", "B", url_top_level_domain_secure, "/", now, base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + CookiePriority::COOKIE_PRIORITY_DEFAULT, false, + CookiePartitionKey::FromURLForTesting(GURL(kTopLevelDomainPlus1)))); + for (auto& cookie : cookies) { GURL source_url = cookie_util::SimulatedCookieSource( *cookie, cookie->IsSecure() ? "https" : "http"); @@ -317,7 +340,7 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { } EXPECT_EQ(cookies.size(), this->GetAllCookies(cm).size()); - return now + base::TimeDelta::FromMilliseconds(100); + return now + base::Milliseconds(100); } Time GetFirstCookieAccessDate(CookieMonster* cm) { @@ -878,6 +901,29 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { 70U); } + // Test enforcement of the per-partition domain limit on partitioned cookies. + void TestPartitionedCookiesGarbageCollectionHelper() { + DCHECK_EQ(10u, CookieMonster::kPerPartitionDomainMaxCookies); + int max_cookies = CookieMonster::kPerPartitionDomainMaxCookies; + auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); + + auto cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")); + for (int i = 0; i < max_cookies + 5; ++i) { + std::string cookie = base::StringPrintf("__Host-a%02d=b", i); + EXPECT_TRUE(SetCookie(cm.get(), https_www_foo_.url(), + cookie + "; secure; path=/; partitioned", + cookie_partition_key)); + std::string cookies = + this->GetCookies(cm.get(), https_www_foo_.url(), + CookiePartitionKeychain(cookie_partition_key)); + EXPECT_NE(cookies.find(cookie), std::string::npos); + EXPECT_LE(CountInString(cookies, '='), max_cookies); + } + // TODO(crbug.com/1225444): Test recording stats for deleting partitioned + // cookies. + } + // Function for creating a CM with a number of cookies in it, // no store (and hence no ability to affect access time). CookieMonster* CreateMonsterForGC(int num_cookies) { @@ -1006,12 +1052,12 @@ class DeferredCookieTaskTest : public CookieMonsterTest { TEST_F(DeferredCookieTaskTest, DeferredGetCookieList) { DeclareLoadedCookie(http_www_foo_.url(), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3)); + Time::Now() + base::Days(3)); GetCookieListCallback call1; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), - call1.MakeCallback()); + CookiePartitionKeychain(), call1.MakeCallback()); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(call1.was_run()); @@ -1024,7 +1070,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetCookieList) { GetCookieListCallback call2; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), - call2.MakeCallback()); + CookiePartitionKeychain(), call2.MakeCallback()); // Already ready, no need for second load. EXPECT_THAT(call2.cookies(), MatchesCookieLine("X=1")); EXPECT_EQ("", TakeCommandSummary()); @@ -1096,7 +1142,7 @@ TEST_F(DeferredCookieTaskTest, DeferredSetAllCookies) { TEST_F(DeferredCookieTaskTest, DeferredGetAllCookies) { DeclareLoadedCookie(http_www_foo_.url(), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3)); + Time::Now() + base::Days(3)); GetAllCookiesCallback call1; cookie_monster_->GetAllCookiesAsync(call1.MakeCallback()); @@ -1118,12 +1164,12 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllCookies) { TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) { DeclareLoadedCookie(http_www_foo_.url(), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3)); + Time::Now() + base::Days(3)); GetCookieListCallback call1; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), - call1.MakeCallback()); + CookiePartitionKeychain(), call1.MakeCallback()); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(call1.was_run()); @@ -1135,7 +1181,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) { GetCookieListCallback call2; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), - call2.MakeCallback()); + CookiePartitionKeychain(), call2.MakeCallback()); EXPECT_TRUE(call2.was_run()); EXPECT_THAT(call2.cookies(), MatchesCookieLine("X=1")); EXPECT_EQ("", TakeCommandSummary()); @@ -1144,12 +1190,12 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlCookies) { TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) { DeclareLoadedCookie(http_www_foo_.url(), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3)); + Time::Now() + base::Days(3)); GetCookieListCallback call1; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), - call1.MakeCallback()); + CookiePartitionKeychain(), call1.MakeCallback()); base::RunLoop().RunUntilIdle(); EXPECT_FALSE(call1.was_run()); @@ -1161,7 +1207,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) { GetCookieListCallback call2; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), - call2.MakeCallback()); + CookiePartitionKeychain(), call2.MakeCallback()); EXPECT_TRUE(call2.was_run()); EXPECT_THAT(call2.cookies(), MatchesCookieLine("X=1")); EXPECT_EQ("", TakeCommandSummary()); @@ -1170,7 +1216,7 @@ TEST_F(DeferredCookieTaskTest, DeferredGetAllForUrlWithOptionsCookies) { TEST_F(DeferredCookieTaskTest, DeferredDeleteAllCookies) { DeclareLoadedCookie(http_www_foo_.url(), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3)); + Time::Now() + base::Days(3)); ResultSavingCookieCallback<uint32_t> call1; cookie_monster_->DeleteAllAsync(call1.MakeCallback()); @@ -1307,7 +1353,7 @@ TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) { cookie_monster_->SetPersistSessionCookies(true); DeclareLoadedCookie(http_www_foo_.url(), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3)); + Time::Now() + base::Days(3)); bool get_cookie_list_callback_was_run = false; GetCookieListCallback get_cookie_list_callback_deferred; @@ -1315,6 +1361,7 @@ TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) { base::RunLoop run_loop; cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(), base::BindLambdaForTesting( [&](const CookieAccessResultList& cookies, const CookieAccessResultList& excluded_list) { @@ -1331,6 +1378,7 @@ TEST_F(DeferredCookieTaskTest, DeferredTaskOrder) { // before it. cookie_monster_->GetCookieListWithOptionsAsync( http_www_foo_.url(), CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(), get_cookie_list_callback_deferred.MakeCallback()); run_loop.Quit(); @@ -1389,6 +1437,19 @@ TEST_F(CookieMonsterTest, TestCookieDeleteAll) { EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type); EXPECT_EQ("", GetCookiesWithOptions(cm.get(), http_www_foo_.url(), options)); + + // Create a Partitioned cookie. + auto cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")); + EXPECT_TRUE(SetCookie( + cm.get(), https_www_foo_.url(), + "__Host-" + std::string(kValidCookieLine) + "; partitioned; secure", + cookie_partition_key)); + EXPECT_EQ(1u, DeleteAll(cm.get())); + EXPECT_EQ( + "", GetCookiesWithOptions(cm.get(), http_www_foo_.url(), options, + CookiePartitionKeychain(cookie_partition_key))); + EXPECT_EQ(2u, store->commands().size()); } TEST_F(CookieMonsterTest, TestCookieDeleteAllCreatedInTimeRangeTimestamps) { @@ -1397,39 +1458,79 @@ TEST_F(CookieMonsterTest, TestCookieDeleteAllCreatedInTimeRangeTimestamps) { Time now = Time::Now(); // Nothing has been added so nothing should be deleted. - EXPECT_EQ(0u, - DeleteAllCreatedInTimeRange( - cm.get(), TimeRange(now - TimeDelta::FromDays(99), Time()))); + EXPECT_EQ(0u, DeleteAllCreatedInTimeRange( + cm.get(), TimeRange(now - base::Days(99), Time()))); // Create 5 cookies with different creation dates. EXPECT_TRUE( SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), "T-0=Now", now)); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-1=Yesterday", - now - TimeDelta::FromDays(1))); + "T-1=Yesterday", now - base::Days(1))); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-2=DayBefore", - now - TimeDelta::FromDays(2))); + "T-2=DayBefore", now - base::Days(2))); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-3=ThreeDays", - now - TimeDelta::FromDays(3))); + "T-3=ThreeDays", now - base::Days(3))); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-7=LastWeek", - now - TimeDelta::FromDays(7))); + "T-7=LastWeek", now - base::Days(7))); // Try to delete threedays and the daybefore. - EXPECT_EQ(2u, DeleteAllCreatedInTimeRange( - cm.get(), TimeRange(now - TimeDelta::FromDays(3), - now - TimeDelta::FromDays(1)))); + EXPECT_EQ(2u, + DeleteAllCreatedInTimeRange( + cm.get(), TimeRange(now - base::Days(3), now - base::Days(1)))); // Try to delete yesterday, also make sure that delete_end is not // inclusive. EXPECT_EQ(1u, DeleteAllCreatedInTimeRange( - cm.get(), TimeRange(now - TimeDelta::FromDays(2), now))); + cm.get(), TimeRange(now - base::Days(2), now))); // Make sure the delete_begin is inclusive. EXPECT_EQ(1u, DeleteAllCreatedInTimeRange( - cm.get(), TimeRange(now - TimeDelta::FromDays(7), now))); + cm.get(), TimeRange(now - base::Days(7), now))); + + // Delete the last (now) item. + EXPECT_EQ(1u, DeleteAllCreatedInTimeRange(cm.get(), TimeRange())); + + // Really make sure everything is gone. + EXPECT_EQ(0u, DeleteAll(cm.get())); + + // Test the same deletion process with partitioned cookies. Partitioned + // cookies should behave the same way as unpartitioned cookies here, they are + // just stored in a different data structure internally. + + EXPECT_TRUE( + SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), "T-0=Now", now, + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite0.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), https_www_foo_.url(), "T-1=Yesterday", now - base::Days(1), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite1.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), http_www_foo_.url(), "T-2=DayBefore", now - base::Days(2), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite1.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), http_www_foo_.url(), "T-3=ThreeDays", now - base::Days(3), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite2.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), http_www_foo_.url(), "T-7=LastWeek", now - base::Days(7), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite3.com")))); + + // Try to delete threedays and the daybefore. + EXPECT_EQ(2u, + DeleteAllCreatedInTimeRange( + cm.get(), TimeRange(now - base::Days(3), now - base::Days(1)))); + + // Try to delete yesterday, also make sure that delete_end is not + // inclusive. + EXPECT_EQ(1u, DeleteAllCreatedInTimeRange( + cm.get(), TimeRange(now - base::Days(2), now))); + + // Make sure the delete_begin is inclusive. + EXPECT_EQ(1u, DeleteAllCreatedInTimeRange( + cm.get(), TimeRange(now - base::Days(7), now))); // Delete the last (now) item. EXPECT_EQ(1u, DeleteAllCreatedInTimeRange(cm.get(), TimeRange())); @@ -1447,41 +1548,78 @@ TEST_F(CookieMonsterTest, CanonicalCookie test_cookie; // Nothing has been added so nothing should be deleted. - EXPECT_EQ(0u, DeleteAllMatchingInfo( - cm.get(), - CookieDeletionInfo(now - TimeDelta::FromDays(99), Time()))); + EXPECT_EQ(0u, + DeleteAllMatchingInfo( + cm.get(), CookieDeletionInfo(now - base::Days(99), Time()))); // Create 5 cookies with different creation dates. EXPECT_TRUE( SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), "T-0=Now", now)); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-1=Yesterday", - now - TimeDelta::FromDays(1))); + "T-1=Yesterday", now - base::Days(1))); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-2=DayBefore", - now - TimeDelta::FromDays(2))); + "T-2=DayBefore", now - base::Days(2))); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-3=ThreeDays", - now - TimeDelta::FromDays(3))); + "T-3=ThreeDays", now - base::Days(3))); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), - "T-7=LastWeek", - now - TimeDelta::FromDays(7))); + "T-7=LastWeek", now - base::Days(7))); // Delete threedays and the daybefore. - EXPECT_EQ(2u, - DeleteAllMatchingInfo( - cm.get(), CookieDeletionInfo(now - TimeDelta::FromDays(3), - now - TimeDelta::FromDays(1)))); + EXPECT_EQ(2u, DeleteAllMatchingInfo(cm.get(), + CookieDeletionInfo(now - base::Days(3), + now - base::Days(1)))); // Delete yesterday, also make sure that delete_end is not inclusive. - EXPECT_EQ( - 1u, DeleteAllMatchingInfo( - cm.get(), CookieDeletionInfo(now - TimeDelta::FromDays(2), now))); + EXPECT_EQ(1u, DeleteAllMatchingInfo( + cm.get(), CookieDeletionInfo(now - base::Days(2), now))); // Make sure the delete_begin is inclusive. - EXPECT_EQ( - 1u, DeleteAllMatchingInfo( - cm.get(), CookieDeletionInfo(now - TimeDelta::FromDays(7), now))); + EXPECT_EQ(1u, DeleteAllMatchingInfo( + cm.get(), CookieDeletionInfo(now - base::Days(7), now))); + + // Delete the last (now) item. + EXPECT_EQ(1u, DeleteAllMatchingInfo(cm.get(), CookieDeletionInfo())); + + // Really make sure everything is gone. + EXPECT_EQ(0u, DeleteAll(cm.get())); + + // Test the same deletion process with partitioned cookies. Partitioned + // cookies should behave the same way as unpartitioned cookies here, they are + // just stored in a different data structure internally. + + EXPECT_TRUE( + SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), "T-0=Now", now, + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite0.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), https_www_foo_.url(), "T-1=Yesterday", now - base::Days(1), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite1.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), http_www_foo_.url(), "T-2=DayBefore", now - base::Days(2), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite1.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), http_www_foo_.url(), "T-3=ThreeDays", now - base::Days(3), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite2.com")))); + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), http_www_foo_.url(), "T-7=LastWeek", now - base::Days(7), + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite3.com")))); + + // Delete threedays and the daybefore. + EXPECT_EQ(2u, DeleteAllMatchingInfo(cm.get(), + CookieDeletionInfo(now - base::Days(3), + now - base::Days(1)))); + + // Delete yesterday, also make sure that delete_end is not inclusive. + EXPECT_EQ(1u, DeleteAllMatchingInfo( + cm.get(), CookieDeletionInfo(now - base::Days(2), now))); + + // Make sure the delete_begin is inclusive. + EXPECT_EQ(1u, DeleteAllMatchingInfo( + cm.get(), CookieDeletionInfo(now - base::Days(7), now))); // Delete the last (now) item. EXPECT_EQ(1u, DeleteAllMatchingInfo(cm.get(), CookieDeletionInfo())); @@ -1513,6 +1651,12 @@ TEST_F(CookieMonsterTest, TestCookieDeleteMatchingCookies) { EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), GURL("https://c.com"), "c1=1;Secure", now)); + // Set a partitioned cookie. + EXPECT_TRUE(SetCookieWithCreationTime( + cm.get(), GURL("https://d.com"), + "__Host-pc=123; path=/; secure; partitioned", now, + CookiePartitionKey::FromURLForTesting(GURL("https://e.com")))); + // Delete http cookies. EXPECT_EQ(2u, DeleteMatchingCookies( cm.get(), @@ -1522,7 +1666,8 @@ TEST_F(CookieMonsterTest, TestCookieDeleteMatchingCookies) { EXPECT_THAT(GetAllCookies(cm.get()), ElementsAre(MatchesCookieNameDomain("a1", "a.com"), MatchesCookieNameDomain("b1", "b.com"), - MatchesCookieNameDomain("c1", "c.com"))); + MatchesCookieNameDomain("c1", "c.com"), + MatchesCookieNameDomain("__Host-pc", "d.com"))); // Delete remaining cookie for a.com. EXPECT_EQ(1u, DeleteMatchingCookies( @@ -1532,7 +1677,15 @@ TEST_F(CookieMonsterTest, TestCookieDeleteMatchingCookies) { }))); EXPECT_THAT(GetAllCookies(cm.get()), ElementsAre(MatchesCookieNameDomain("b1", "b.com"), - MatchesCookieNameDomain("c1", "c.com"))); + MatchesCookieNameDomain("c1", "c.com"), + MatchesCookieNameDomain("__Host-pc", "d.com"))); + + // Delete the partitioned cookie. + EXPECT_EQ(1u, DeleteMatchingCookies( + cm.get(), + base::BindRepeating([](const net::CanonicalCookie& cookie) { + return cookie.IsPartitioned(); + }))); // Delete the last two item. EXPECT_EQ(2u, DeleteMatchingCookies( @@ -1545,10 +1698,9 @@ TEST_F(CookieMonsterTest, TestCookieDeleteMatchingCookies) { EXPECT_TRUE(GetAllCookies(cm.get()).empty()); } -static const base::TimeDelta kLastAccessThreshold = - base::TimeDelta::FromMilliseconds(200); +static const base::TimeDelta kLastAccessThreshold = base::Milliseconds(200); static const base::TimeDelta kAccessDelay = - kLastAccessThreshold + base::TimeDelta::FromMilliseconds(20); + kLastAccessThreshold + base::Milliseconds(20); TEST_F(CookieMonsterTest, TestLastAccess) { std::unique_ptr<CookieMonster> cm( @@ -1605,6 +1757,10 @@ TEST_F(CookieMonsterTest, TestPriorityAwareGarbageCollectionMixed) { TestPriorityAwareGarbageCollectHelperMixed(); } +TEST_F(CookieMonsterTest, TestPartitionedCookiesGarbageCollection) { + TestPartitionedCookiesGarbageCollectionHelper(); +} + TEST_F(CookieMonsterTest, SetCookieableSchemes) { auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); @@ -1692,6 +1848,23 @@ TEST_F(CookieMonsterTest, GetAllCookiesForURL) { cm.get(), https_www_foo_.url(), https_www_foo_.Format("I=J; domain=.%D; secure; sameparty"), options)); + // Create partitioned cookies for the same site with some partition key. + auto cookie_partition_key1 = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite1.com")); + auto cookie_partition_key2 = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite2.com")); + auto cookie_partition_key3 = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite3.com")); + EXPECT_TRUE(CreateAndSetCookie( + cm.get(), https_www_bar_.url(), "__Host-K=L; secure; path=/; partitioned", + options, absl::nullopt, absl::nullopt, cookie_partition_key1)); + EXPECT_TRUE(CreateAndSetCookie( + cm.get(), https_www_bar_.url(), "__Host-M=N; secure; path=/; partitioned", + options, absl::nullopt, absl::nullopt, cookie_partition_key2)); + EXPECT_TRUE(CreateAndSetCookie( + cm.get(), https_www_bar_.url(), "__Host-O=P; secure; path=/; partitioned", + options, absl::nullopt, absl::nullopt, cookie_partition_key3)); + base::HistogramTester histogram_tester; const Time last_access_date(GetFirstCookieAccessDate(cm.get())); @@ -1721,6 +1894,42 @@ TEST_F(CookieMonsterTest, GetAllCookiesForURL) { MatchesCookieNameDomain("E", http_www_foo_.Format(".%D")), MatchesCookieNameDomain("I", http_www_foo_.Format(".%D")))); + // Test reading partitioned cookies for a single partition. + EXPECT_THAT( + GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain(cookie_partition_key1)), + ElementsAre(MatchesCookieNameDomain("G", https_www_bar_.Format(".%D")), + MatchesCookieNameDomain("__Host-K", https_www_bar_.host()))); + EXPECT_THAT( + GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain(cookie_partition_key2)), + ElementsAre(MatchesCookieNameDomain("G", https_www_bar_.Format(".%D")), + MatchesCookieNameDomain("__Host-M", https_www_bar_.host()))); + + // Test reading partitioned cookies from multiple partitions. + EXPECT_THAT( + GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain( + {cookie_partition_key1, cookie_partition_key2})), + ElementsAre(MatchesCookieNameDomain("G", https_www_bar_.Format(".%D")), + MatchesCookieNameDomain("__Host-K", https_www_bar_.host()), + MatchesCookieNameDomain("__Host-M", https_www_bar_.host()))); + + // Test reading partitioned cookies from every partition. + EXPECT_THAT( + GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain::ContainsAll()), + ElementsAre(MatchesCookieNameDomain("G", https_www_bar_.Format(".%D")), + MatchesCookieNameDomain("__Host-K", https_www_bar_.host()), + MatchesCookieNameDomain("__Host-M", https_www_bar_.host()), + MatchesCookieNameDomain("__Host-O", https_www_bar_.host()))); + + // Test excluding partitioned cookies. + EXPECT_THAT( + GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain()), + ElementsAre(MatchesCookieNameDomain("G", https_www_bar_.Format(".%D")))); + EXPECT_THAT( histogram_tester.GetAllSamples("Cookie.SamePartyReadIncluded.IsHTTP"), testing::ElementsAre(base::Bucket(1 /* min */, 1 /* samples */))); @@ -1890,7 +2099,7 @@ TEST_F(CookieMonsterTest, CookieSorting) { "A=A1; path=/", "A=A2; path=/foo", "A=A3; path=/foo/bar"}) { EXPECT_TRUE(SetCookieWithSystemTime(cm.get(), http_www_foo_.url(), cookie_line, system_time)); - system_time += base::TimeDelta::FromMilliseconds(100); + system_time += base::Milliseconds(100); } // Re-set cookie which should not change sort order, as the creation date @@ -1911,8 +2120,7 @@ TEST_F(CookieMonsterTest, CookieSorting) { TEST_F(CookieMonsterTest, InheritCreationDate) { auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); - base::Time the_not_so_distant_past(base::Time::Now() - - base::TimeDelta::FromSeconds(1000)); + base::Time the_not_so_distant_past(base::Time::Now() - base::Seconds(1000)); EXPECT_TRUE(SetCookieWithCreationTime(cm.get(), http_www_foo_.url(), "Name=Value; path=/", the_not_so_distant_past)); @@ -1955,6 +2163,31 @@ TEST_F(CookieMonsterTest, DeleteExpiredCookiesOnGet) { cookies = GetAllCookiesForURL(cm.get(), http_www_foo_.url()); EXPECT_EQ(1u, cookies.size()); + + // Test partitioned cookies. They should exhibit the same behavior but are + // stored in a different data structure internally. + auto cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")); + + EXPECT_TRUE(SetCookie(cm.get(), https_www_bar_.url(), + "__Host-A=B; secure; path=/; partitioned", + cookie_partition_key)); + EXPECT_TRUE(SetCookie(cm.get(), https_www_bar_.url(), + "__Host-C=D; secure; path=/; partitioned", + cookie_partition_key)); + + cookies = GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain(cookie_partition_key)); + EXPECT_EQ(2u, cookies.size()); + + EXPECT_TRUE(SetCookie(cm.get(), https_www_bar_.url(), + "__Host-C=D; secure; path=/; partitioned; expires=Thu, " + "01-Jan-1970 00:00:00 GMT", + cookie_partition_key)); + + cookies = GetAllCookiesForURL(cm.get(), https_www_bar_.url(), + CookiePartitionKeychain(cookie_partition_key)); + EXPECT_EQ(1u, cookies.size()); } // Tests importing from a persistent cookie store that contains duplicate @@ -1978,16 +2211,16 @@ TEST_F(CookieMonsterTest, DontImportDuplicateCookies) { AddCookieToList(GURL("http://www.foo.com"), "X=1; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(3), &initial_cookies); + Time::Now() + base::Days(3), &initial_cookies); AddCookieToList(GURL("http://www.foo.com"), "X=2; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(1), &initial_cookies); + Time::Now() + base::Days(1), &initial_cookies); // ===> This one is the WINNER (biggest creation time). <==== AddCookieToList(GURL("http://www.foo.com"), "X=3; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(4), &initial_cookies); + Time::Now() + base::Days(4), &initial_cookies); AddCookieToList(GURL("http://www.foo.com"), "X=4; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", @@ -1999,16 +2232,16 @@ TEST_F(CookieMonsterTest, DontImportDuplicateCookies) { // ===> This one is the WINNER (biggest creation time). <==== AddCookieToList(GURL("http://www.foo.com"), "X=a1; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(9), &initial_cookies); + Time::Now() + base::Days(9), &initial_cookies); AddCookieToList(GURL("http://www.foo.com"), "X=a2; path=/2; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(2), &initial_cookies); + Time::Now() + base::Days(2), &initial_cookies); // Insert 1 cookie with name "Y" on path "/". AddCookieToList(GURL("http://www.foo.com"), "Y=a; path=/; expires=Mon, 18-Apr-22 22:50:14 GMT", - Time::Now() + TimeDelta::FromDays(10), &initial_cookies); + Time::Now() + base::Days(10), &initial_cookies); // Inject our initial cookies into the mock PersistentCookieStore. store->SetLoadExpectation(true, std::move(initial_cookies)); @@ -2032,6 +2265,47 @@ TEST_F(CookieMonsterTest, DontImportDuplicateCookies) { EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[3].type); } +TEST_F(CookieMonsterTest, DontImportDuplicateCookies_PartitionedCookies) { + std::vector<std::unique_ptr<CanonicalCookie>> initial_cookies; + + auto cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")); + GURL cookie_url("https://www.bar.com"); + + // Insert 3 partitioned cookies with same name, partition key, and path. + + // ===> This one is the WINNER (biggest creation time). <==== + auto cc = CanonicalCookie::Create( + cookie_url, "__Host-Z=a; Secure; Path=/; Partitioned; Max-Age=3456000", + Time::Now() + base::Days(2), absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + + cc = CanonicalCookie::Create( + cookie_url, "__Host-Z=b; Secure; Path=/; Partitioned; Max-Age=3456000", + Time::Now(), absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + + cc = CanonicalCookie::Create( + cookie_url, "__Host-Z=c; Secure; Path=/; Partitioned; Max-Age=3456000", + Time::Now() + base::Days(1), absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + + scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore); + std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_)); + + store->SetLoadExpectation(true, std::move(initial_cookies)); + + EXPECT_EQ("__Host-Z=a", + GetCookies(cm.get(), GURL("https://www.bar.com/"), + CookiePartitionKeychain(cookie_partition_key))); + + // Verify that the PersistentCookieStore was told to kill the 2 + // duplicates. + ASSERT_EQ(2u, store->commands().size()); + EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[0].type); + EXPECT_EQ(CookieStoreCommand::REMOVE, store->commands()[1].type); +} + // Tests importing from a persistent cookie store that contains cookies // with duplicate creation times. This is OK now, but it still interacts // with the de-duplication algorithm. @@ -2041,7 +2315,7 @@ TEST_F(CookieMonsterTest, ImportDuplicateCreationTimes) { scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore); Time now(Time::Now()); - Time earlier(now - TimeDelta::FromDays(1)); + Time earlier(now - base::Days(1)); // Insert 8 cookies, four with the current time as creation times, and // four with the earlier time as creation times. We should only get @@ -2081,6 +2355,63 @@ TEST_F(CookieMonsterTest, ImportDuplicateCreationTimes) { EXPECT_NE(name1, name2); } +TEST_F(CookieMonsterTest, ImportDuplicateCreationTimes_PartitionedCookies) { + scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore); + + Time now(Time::Now()); + Time earlier(now - base::Days(1)); + + GURL cookie_url("https://www.foo.com"); + auto cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://www.bar.com")); + + // Insert 6 cookies, four with the current time as creation times, and + // four with the earlier time as creation times. We should only get + // two cookies remaining, but which two (other than that there should + // be one from each set) will be random. + + std::vector<std::unique_ptr<CanonicalCookie>> initial_cookies; + auto cc = CanonicalCookie::Create( + cookie_url, "__Host-X=1; Secure; Path=/; Partitioned; Max-Age=3456000", + now, absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + cc = CanonicalCookie::Create( + cookie_url, "__Host-X=2; Secure; Path=/; Partitioned; Max-Age=3456000", + now, absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + cc = CanonicalCookie::Create( + cookie_url, "__Host-X=3; Secure; Path=/; Partitioned; Max-Age=3456000", + now, absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + + cc = CanonicalCookie::Create( + cookie_url, "__Host-Y=1; Secure; Path=/; Partitioned; Max-Age=3456000", + earlier, absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + cc = CanonicalCookie::Create( + cookie_url, "__Host-Y=2; Secure; Path=/; Partitioned; Max-Age=3456000", + earlier, absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + cc = CanonicalCookie::Create( + cookie_url, "__Host-Y=3; Secure; Path=/; Partitioned; Max-Age=3456000", + earlier, absl::nullopt, cookie_partition_key); + initial_cookies.push_back(std::move(cc)); + + // Inject our initial cookies into the mock PersistentCookieStore. + store->SetLoadExpectation(true, std::move(initial_cookies)); + + std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_)); + + CookieList list(GetAllCookies(cm.get())); + EXPECT_EQ(2U, list.size()); + // Confirm that we have one of each. + std::string name1(list[0].Name()); + std::string name2(list[1].Name()); + EXPECT_TRUE(name1 == "__Host-X" || name2 == "__Host-X"); + EXPECT_TRUE(name1 == "__Host-Y" || name2 == "__Host-Y"); + EXPECT_NE(name1, name2); +} + TEST_F(CookieMonsterTest, PredicateSeesAllCookies) { auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); @@ -2090,7 +2421,7 @@ TEST_F(CookieMonsterTest, PredicateSeesAllCookies) { CookieDeletionInfo delete_info(base::Time(), now); delete_info.value_for_testing = "A"; - EXPECT_EQ(8u, DeleteAllMatchingInfo(cm.get(), std::move(delete_info))); + EXPECT_EQ(9u, DeleteAllMatchingInfo(cm.get(), std::move(delete_info))); EXPECT_EQ("dom_2=B; dom_3=C; host_3=C", GetCookies(cm.get(), GURL(kTopLevelDomainPlus3))); @@ -2100,6 +2431,11 @@ TEST_F(CookieMonsterTest, PredicateSeesAllCookies) { EXPECT_EQ("dom_path_2=B; host_path_2=B; dom_2=B; host_2=B; sec_host=B", GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure + std::string("/dir1/dir2/xxx")))); + EXPECT_EQ( + "dom_2=B; host_2=B; sec_host=B; __Host-pc_2=B", + GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure), + CookiePartitionKeychain(CookiePartitionKey::FromURLForTesting( + GURL(kTopLevelDomainPlus1))))); } // Mainly a test of GetEffectiveDomain, or more specifically, of the @@ -2127,23 +2463,25 @@ TEST_F(CookieMonsterTest, GetKey) { } // Test that cookies transfer from/to the backing store correctly. +// TODO(crbug.com/1225444): Include partitioned cookies in this test when we +// start saving them in the persistent store. TEST_F(CookieMonsterTest, BackingStoreCommunication) { // Store details for cookies transforming through the backing store interface. base::Time current(base::Time::Now()); scoped_refptr<MockSimplePersistentCookieStore> store( new MockSimplePersistentCookieStore); - base::Time expires(base::Time::Now() + base::TimeDelta::FromSeconds(100)); + base::Time expires(base::Time::Now() + base::Seconds(100)); const CookiesInputInfo input_info[] = { {GURL("https://a.b.foo.com"), "a", "1", "a.b.foo.com", "/path/to/cookie", expires, true /* secure */, false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false}, {GURL("https://www.foo.com"), "b", "2", ".foo.com", "/path/from/cookie", - expires + TimeDelta::FromSeconds(10), true, true, - CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, true}, + expires + base::Seconds(10), true, true, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, true}, {GURL("https://foo.com"), "c", "3", "foo.com", "/another/path/to/cookie", - base::Time::Now() + base::TimeDelta::FromSeconds(100), false, false, + base::Time::Now() + base::Seconds(100), false, false, CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT, false}}; const int INPUT_DELETE = 1; @@ -2430,6 +2768,7 @@ TEST_F(CookieMonsterTest, WhileLoadingDeleteAllGetForURL) { GetCookieListCallback get_cookie_list_callback; cm->GetCookieListWithOptionsAsync(kUrl, CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(), get_cookie_list_callback.MakeCallback()); // Only the main load should have been queued. @@ -2618,13 +2957,18 @@ TEST_F(CookieMonsterTest, SetAllCookies) { base::Time(), base::Time(), false, false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false)); list.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( - "W", "X", "." + http_www_foo_.url().host(), "/bar", base::Time::Now(), + "C", "D", "." + http_www_foo_.url().host(), "/bar", base::Time::Now(), base::Time(), base::Time(), false, false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false)); list.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( - "Y", "Z", "." + http_www_foo_.url().host(), "/", base::Time::Now(), + "W", "X", "." + http_www_foo_.url().host(), "/", base::Time::Now(), base::Time(), base::Time(), false, false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false)); + list.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-Y", "Z", https_www_foo_.url().host(), "/", base::Time::Now(), + base::Time(), base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + CookiePriority::COOKIE_PRIORITY_DEFAULT, false, + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")))); // SetAllCookies must not flush. ASSERT_EQ(0, store->flush_count()); @@ -2632,13 +2976,13 @@ TEST_F(CookieMonsterTest, SetAllCookies) { EXPECT_EQ(0, store->flush_count()); CookieList cookies = GetAllCookies(cm.get()); - size_t expected_size = 3; // "A", "W" and "Y". "U" is gone. + size_t expected_size = 4; // "A", "W" and "Y". "U" is gone. EXPECT_EQ(expected_size, cookies.size()); auto it = cookies.begin(); ASSERT_TRUE(it != cookies.end()); - EXPECT_EQ("W", it->Name()); - EXPECT_EQ("X", it->Value()); + EXPECT_EQ("C", it->Name()); + EXPECT_EQ("D", it->Value()); EXPECT_EQ("/bar", it->Path()); // The path has been updated. ASSERT_TRUE(++it != cookies.end()); @@ -2646,7 +2990,11 @@ TEST_F(CookieMonsterTest, SetAllCookies) { EXPECT_EQ("B", it->Value()); ASSERT_TRUE(++it != cookies.end()); - EXPECT_EQ("Y", it->Name()); + EXPECT_EQ("W", it->Name()); + EXPECT_EQ("X", it->Value()); + + ASSERT_TRUE(++it != cookies.end()); + EXPECT_EQ("__Host-Y", it->Name()); EXPECT_EQ("Z", it->Value()); cm = nullptr; @@ -2706,8 +3054,8 @@ TEST_F(CookieMonsterTest, HistogramCheck) { expired_histogram->SnapshotSamples()); auto cookie = CanonicalCookie::CreateUnsafeCookieForTesting( "a", "b", "a.url", "/", base::Time(), - base::Time::Now() + base::TimeDelta::FromMinutes(59), base::Time(), true, - false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false); + base::Time::Now() + base::Minutes(59), base::Time(), true, false, + CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false); GURL source_url = cookie_util::SimulatedCookieSource(*cookie, "https"); ASSERT_TRUE(SetCanonicalCookie(cm.get(), std::move(cookie), source_url, true /*modify_httponly*/)); @@ -2816,11 +3164,11 @@ TEST_F(CookieMonsterTest, PersisentCookieStorageTest) { // See http://crbug.com/238041 for background. TEST_F(CookieMonsterTest, ControlCharacterPurge) { const Time now1(Time::Now()); - const Time now2(Time::Now() + TimeDelta::FromSeconds(1)); - const Time now3(Time::Now() + TimeDelta::FromSeconds(2)); - const Time now4(Time::Now() + TimeDelta::FromSeconds(3)); - const Time later(now1 + TimeDelta::FromDays(1)); - const GURL url("http://host/path"); + const Time now2(Time::Now() + base::Seconds(1)); + const Time now3(Time::Now() + base::Seconds(2)); + const Time now4(Time::Now() + base::Seconds(3)); + const Time later(now1 + base::Days(1)); + const GURL url("https://host/path"); const std::string domain("host"); const std::string path("/path"); @@ -2852,6 +3200,19 @@ TEST_F(CookieMonsterTest, ControlCharacterPurge) { COOKIE_PRIORITY_DEFAULT, false /* sameparty */); initial_cookies.push_back(std::move(cc2)); + // Partitioned cookies with control characters should not be loaded. + auto cookie_partition_key = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")); + std::unique_ptr<CanonicalCookie> cc3 = + CanonicalCookie::CreateUnsafeCookieForTesting( + "__Host-baz", + "\x7F" + "boo", + domain, "/", now3, later, base::Time(), true /* secure */, + false /* httponly */, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /* sameparty */, cookie_partition_key); + initial_cookies.push_back(std::move(cc3)); + AddCookieToList(url, "hello=world; path=" + path, now4, &initial_cookies); // Inject our initial cookies into the mock PersistentCookieStore. @@ -2859,7 +3220,9 @@ TEST_F(CookieMonsterTest, ControlCharacterPurge) { std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_)); - EXPECT_EQ("foo=bar; hello=world", GetCookies(cm.get(), url)); + EXPECT_EQ( + "foo=bar; hello=world", + GetCookies(cm.get(), url, CookiePartitionKeychain(cookie_partition_key))); } // Test that cookie source schemes are histogrammed correctly. @@ -3067,9 +3430,9 @@ TEST_F(CookieMonsterTest, SecureCookieLocalhost) { // status. { GetCookieListCallback callback; - cm->GetCookieListWithOptionsAsync(insecure_localhost, - CookieOptions::MakeAllInclusive(), - callback.MakeCallback()); + cm->GetCookieListWithOptionsAsync( + insecure_localhost, CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(), callback.MakeCallback()); callback.WaitUntilDone(); EXPECT_EQ(2u, callback.cookies_with_access_results().size()); for (const auto& cookie_item : callback.cookies_with_access_results()) { @@ -3085,9 +3448,9 @@ TEST_F(CookieMonsterTest, SecureCookieLocalhost) { // status. { GetCookieListCallback callback; - cm->GetCookieListWithOptionsAsync(secure_localhost, - CookieOptions::MakeAllInclusive(), - callback.MakeCallback()); + cm->GetCookieListWithOptionsAsync( + secure_localhost, CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(), callback.MakeCallback()); callback.WaitUntilDone(); EXPECT_EQ(2u, callback.cookies_with_access_results().size()); for (const auto& cookie_item : callback.cookies_with_access_results()) { @@ -3202,6 +3565,48 @@ TEST_F(CookieMonsterTest, MaybeDeleteEquivalentCookieAndUpdateStatus) { entries, 0, NetLogEventType::COOKIE_STORE_COOKIE_REJECTED_SECURE)); } +TEST_F(CookieMonsterTest, + MaybeDeleteEquivalentCookieAndUpdateStatus_PartitionedCookies) { + scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore); + std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_)); + + // Test adding two cookies with the same name, domain, and path but different + // partition keys. + auto cookie_partition_key1 = + CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite1.com")); + + auto preexisting_cookie = CanonicalCookie::Create( + https_www_foo_.url(), "__Host-A=B; Secure; Path=/; Partitioned; HttpOnly", + base::Time::Now(), absl::nullopt /* server_time */, + cookie_partition_key1 /* cookie_partition_key */); + CookieAccessResult access_result = SetCanonicalCookieReturnAccessResult( + cm.get(), std::move(preexisting_cookie), https_www_foo_.url(), + true /* can_modify_httponly */); + ASSERT_TRUE(access_result.status.IsInclude()); + + // Should be able to set a cookie with a different partition key. + EXPECT_TRUE(SetCookie(cm.get(), https_www_foo_.url(), + "__Host-A=C; Secure; Path=/; Partitioned", + CookiePartitionKey::FromURLForTesting( + GURL("https://toplevelsite2.com")))); + + // Should not overwrite HttpOnly cookie. + auto bad_cookie = CanonicalCookie::Create( + https_www_foo_.url(), "__Host-A=D; Secure; Path=/; Partitioned", + base::Time::Now(), absl::nullopt /* server_time */, + cookie_partition_key1); + access_result = SetCanonicalCookieReturnAccessResult( + cm.get(), std::move(bad_cookie), https_www_foo_.url(), + false /* can_modify_httponly */); + EXPECT_TRUE(access_result.status.HasExactlyExclusionReasonsForTesting( + {CookieInclusionStatus::EXCLUDE_OVERWRITE_HTTP_ONLY})); + EXPECT_THAT( + GetCookiesWithOptions(cm.get(), https_www_foo_.url(), + CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(cookie_partition_key1)), + ::testing::HasSubstr("A=B")); +} + // Test skipping a cookie in MaybeDeleteEquivalentCookieAndUpdateStatus for // multiple reasons (Secure and HttpOnly). TEST_F(CookieMonsterTest, SkipDontOverwriteForMultipleReasons) { @@ -3502,7 +3907,7 @@ TEST_F(CookieMonsterTest, LeaveSecureCookiesAlone_DomainMatch) { const char* kDomain = "b.a.foo.com"; const char* kSubdomain = "c.b.a.foo.com"; // This domain does not match any, aside from the registrable domain. - const char* kOtherDomain = "z.foo.com"; + const char* kAnotherDomain = "z.foo.com"; for (const char* preexisting_cookie_host : {kRegistrableDomain, kSuperdomain, kDomain, kSubdomain}) { @@ -3591,7 +3996,7 @@ TEST_F(CookieMonsterTest, LeaveSecureCookiesAlone_DomainMatch) { // Test non-domain-matching case. These sets should all be allowed because the // cookie is not equivalent. GURL nonmatching_https_url(base::StrCat( - {url::kHttpsScheme, url::kStandardSchemeSeparator, kOtherDomain})); + {url::kHttpsScheme, url::kStandardSchemeSeparator, kAnotherDomain})); for (const char* host : {kSuperdomain, kDomain, kSubdomain}) { GURL https_url( @@ -3605,7 +4010,7 @@ TEST_F(CookieMonsterTest, LeaveSecureCookiesAlone_DomainMatch) { .IsInclude()); EXPECT_TRUE(CreateAndSetCookieReturnStatus( cm.get(), nonmatching_https_url, - base::StrCat({"B=0; Secure; Domain=", kOtherDomain})) + base::StrCat({"B=0; Secure; Domain=", kAnotherDomain})) .IsInclude()); // New cookie from insecure URL is set. @@ -3932,9 +4337,9 @@ TEST_F(CookieMonsterTest, SetCanonicalCookieDoesNotBlockForLoadAll) { // Get cookies for a different URL. GetCookieListCallback callback_get; - cm.GetCookieListWithOptionsAsync(GURL("http://b.com/"), - CookieOptions::MakeAllInclusive(), - callback_get.MakeCallback()); + cm.GetCookieListWithOptionsAsync( + GURL("http://b.com/"), CookieOptions::MakeAllInclusive(), + CookiePartitionKeychain(), callback_get.MakeCallback()); // Now go through the store commands, and execute individual loads. const auto& commands = persistent_store->commands(); @@ -3999,7 +4404,7 @@ TEST_F(CookieMonsterTest, DeleteDuplicateCTime) { TEST_F(CookieMonsterTest, DeleteCookieWithInheritedTimestamps) { Time t1 = Time::Now(); - Time t2 = t1 + base::TimeDelta::FromSeconds(1); + Time t2 = t1 + base::Seconds(1); GURL url("http://www.example.com"); std::string cookie_line = "foo=bar"; CookieOptions options = CookieOptions::MakeAllInclusive(); @@ -4257,7 +4662,7 @@ TEST_F(CookieMonsterNotificationTest, NoNotificationOnLoad) { url, "Y=1; path=/", base::Time::Now(), absl::nullopt /* server_time */, absl::nullopt /* cookie_partition_key */)); initial_cookies.push_back(CanonicalCookie::Create( - url, "Y=2; path=/", base::Time::Now() + base::TimeDelta::FromDays(1), + url, "Y=2; path=/", base::Time::Now() + base::Days(1), absl::nullopt /* server_time */, absl::nullopt /* cookie_partition_key */)); diff --git a/chromium/net/cookies/cookie_partition_key.cc b/chromium/net/cookies/cookie_partition_key.cc index 055866802c3..e0542e20816 100644 --- a/chromium/net/cookies/cookie_partition_key.cc +++ b/chromium/net/cookies/cookie_partition_key.cc @@ -35,6 +35,10 @@ bool CookiePartitionKey::operator==(const CookiePartitionKey& other) const { return site_ == other.site_; } +bool CookiePartitionKey::operator!=(const CookiePartitionKey& other) const { + return site_ != other.site_; +} + bool CookiePartitionKey::operator<(const CookiePartitionKey& other) const { return site_ < other.site_; } @@ -46,6 +50,8 @@ bool CookiePartitionKey::Serialize(const absl::optional<CookiePartitionKey>& in, out = kEmptyCookiePartitionKey; return true; } + if (!base::FeatureList::IsEnabled(features::kPartitionedCookies)) + return false; if (in->site_.GetURL().SchemeIsFile()) { out = in->site_.SerializeFileSiteWithHost(); return true; @@ -63,6 +69,8 @@ bool CookiePartitionKey::Deserialize(const std::string& in, out = absl::nullopt; return true; } + if (!base::FeatureList::IsEnabled(features::kPartitionedCookies)) + return false; auto schemeful_site = SchemefulSite::Deserialize(in); // SchemfulSite is opaque if the input is invalid. if (schemeful_site.opaque()) diff --git a/chromium/net/cookies/cookie_partition_key.h b/chromium/net/cookies/cookie_partition_key.h index 5f8207f727e..5ce04f20f14 100644 --- a/chromium/net/cookies/cookie_partition_key.h +++ b/chromium/net/cookies/cookie_partition_key.h @@ -13,23 +13,11 @@ #include "third_party/abseil-cpp/absl/types/optional.h" #include "url/gurl.h" -namespace network { -namespace mojom { -class CookiePartitionKeyDataView; -} // namespace mojom -} // namespace network - -namespace mojo { -template <typename DataViewType, typename T> -struct StructTraits; -} // namespace mojo - namespace net { class NET_EXPORT CookiePartitionKey { public: CookiePartitionKey(); - explicit CookiePartitionKey(const SchemefulSite& site); CookiePartitionKey(const CookiePartitionKey& other); CookiePartitionKey(CookiePartitionKey&& other); CookiePartitionKey& operator=(const CookiePartitionKey& other); @@ -37,6 +25,7 @@ class NET_EXPORT CookiePartitionKey { ~CookiePartitionKey(); bool operator==(const CookiePartitionKey& other) const; + bool operator!=(const CookiePartitionKey& other) const; bool operator<(const CookiePartitionKey& other) const; // Methods for serializing and deserializing a partition key to/from a string. @@ -70,17 +59,23 @@ class NET_EXPORT CookiePartitionKey { static absl::optional<CookiePartitionKey> FromNetworkIsolationKey( const NetworkIsolationKey& network_isolation_key); + // Create a new CookiePartitionKey from the site of an existing + // CookiePartitionKey. This should only be used for sites of partition keys + // which were already created using Deserialize or FromNetworkIsolationKey. + static CookiePartitionKey FromWire(const SchemefulSite& site) { + return CookiePartitionKey(site); + } + // Temporary method, used to mark the places where we need to supply the // cookie partition key to CanonicalCookie::Create. static absl::optional<CookiePartitionKey> Todo() { return absl::nullopt; } + const SchemefulSite& site() const { return site_; } + private: + explicit CookiePartitionKey(const SchemefulSite& site); explicit CookiePartitionKey(const GURL& url); - // IPC needs access to internal site. - friend struct mojo::StructTraits<network::mojom::CookiePartitionKeyDataView, - CookiePartitionKey>; - SchemefulSite site_; }; diff --git a/chromium/net/cookies/cookie_partition_key_fuzzer.cc b/chromium/net/cookies/cookie_partition_key_fuzzer.cc index ddc089acd53..0770838160d 100644 --- a/chromium/net/cookies/cookie_partition_key_fuzzer.cc +++ b/chromium/net/cookies/cookie_partition_key_fuzzer.cc @@ -7,11 +7,17 @@ #include <fuzzer/FuzzedDataProvider.h> +#include "base/test/scoped_feature_list.h" +#include "net/base/features.h" #include "net/cookies/cookie_partition_key.h" +#include "url/origin.h" namespace net { extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { + base::test::ScopedFeatureList scoped_feature_list; + scoped_feature_list.InitAndEnableFeature(features::kPartitionedCookies); + FuzzedDataProvider data_provider(data, size); std::string url_str = data_provider.ConsumeRandomLengthString(800); @@ -19,22 +25,19 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { if (!url.is_valid()) return 0; - SchemefulSite site(url::Origin::Create(url)); absl::optional<CookiePartitionKey> partition_key = - absl::make_optional(CookiePartitionKey(site)); - - bool result = CookiePartitionKey::Deserialize(url_str, partition_key); - if (site.opaque()) - CHECK(!result); - else - CHECK(result); + absl::make_optional(CookiePartitionKey::FromURLForTesting(url)); + bool is_opaque = url::Origin::Create(url).opaque(); std::string tmp; - result = CookiePartitionKey::Serialize(partition_key, tmp); - if (site.opaque()) - CHECK(!result); - else - CHECK(result); + CHECK_NE(is_opaque, CookiePartitionKey::Serialize(partition_key, tmp)); + + CHECK_NE(is_opaque, CookiePartitionKey::Deserialize(url_str, partition_key)); + + if (!is_opaque) { + CHECK(absl::make_optional(CookiePartitionKey::FromURLForTesting(url)) == + partition_key); + } return 0; } diff --git a/chromium/net/cookies/cookie_partition_key_unittest.cc b/chromium/net/cookies/cookie_partition_key_unittest.cc index 7f83ba7ccb9..44ea201e88f 100644 --- a/chromium/net/cookies/cookie_partition_key_unittest.cc +++ b/chromium/net/cookies/cookie_partition_key_unittest.cc @@ -63,8 +63,15 @@ TEST_P(CookiePartitionKeyTest, Serialization) { for (const auto& tc : cases) { std::string got; - EXPECT_EQ(tc.expected_ret, CookiePartitionKey::Serialize(tc.input, got)); - EXPECT_EQ(tc.expected_output, got); + if (PartitionedCookiesEnabled()) { + EXPECT_EQ(tc.expected_ret, CookiePartitionKey::Serialize(tc.input, got)); + EXPECT_EQ(tc.expected_output, got); + } else { + // Serialize should only return true for unpartitioned cookies if the + // feature is disabled. + EXPECT_NE(tc.input.has_value(), + CookiePartitionKey::Serialize(tc.input, got)); + } } } @@ -83,12 +90,20 @@ TEST_P(CookiePartitionKeyTest, Deserialization) { for (const auto& tc : cases) { absl::optional<CookiePartitionKey> got; - EXPECT_EQ(tc.expected_ret, CookiePartitionKey::Deserialize(tc.input, got)); - if (tc.expected_output.has_value()) { - EXPECT_TRUE(got.has_value()); - EXPECT_EQ(tc.expected_output.value(), got.value()); + if (PartitionedCookiesEnabled()) { + EXPECT_EQ(tc.expected_ret, + CookiePartitionKey::Deserialize(tc.input, got)); + if (tc.expected_output.has_value()) { + EXPECT_TRUE(got.has_value()); + EXPECT_EQ(tc.expected_output.value(), got.value()); + } else { + EXPECT_FALSE(got.has_value()); + } } else { - EXPECT_FALSE(got.has_value()); + // Deserialize should only return true for unpartitioned cookies if the + // feature is disabled. + EXPECT_EQ(tc.input == kEmptyCookiePartitionKey, + CookiePartitionKey::Deserialize(tc.input, got)); } } } @@ -106,10 +121,17 @@ TEST_P(CookiePartitionKeyTest, FromNetworkIsolationKey) { bool partitioned_cookies_enabled = PartitionedCookiesEnabled(); EXPECT_EQ(partitioned_cookies_enabled, got.has_value()); if (partitioned_cookies_enabled) { - EXPECT_EQ(CookiePartitionKey(top_level_site), got.value()); + EXPECT_EQ(CookiePartitionKey::FromURLForTesting(top_level_site.GetURL()), + got.value()); } } +TEST_P(CookiePartitionKeyTest, FromWire) { + auto want = CookiePartitionKey::FromURLForTesting(GURL("https://foo.com")); + auto got = CookiePartitionKey::FromWire(want.site()); + EXPECT_EQ(want, got); +} + } // namespace net #endif // NET_COOKIES_COOKIE_PARTITION_KEY_UNITTEST_H_ diff --git a/chromium/net/cookies/cookie_partition_keychain.cc b/chromium/net/cookies/cookie_partition_keychain.cc new file mode 100644 index 00000000000..8eb83dc7048 --- /dev/null +++ b/chromium/net/cookies/cookie_partition_keychain.cc @@ -0,0 +1,37 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "net/cookies/cookie_partition_keychain.h" + +namespace net { + +CookiePartitionKeychain::CookiePartitionKeychain() = default; + +CookiePartitionKeychain::CookiePartitionKeychain( + const CookiePartitionKeychain& other) = default; + +CookiePartitionKeychain::CookiePartitionKeychain( + CookiePartitionKeychain&& other) = default; + +CookiePartitionKeychain::CookiePartitionKeychain( + const CookiePartitionKey& key) { + keys_.push_back(key); +} + +CookiePartitionKeychain::CookiePartitionKeychain( + const std::vector<CookiePartitionKey>& keys) + : keys_(keys) {} + +CookiePartitionKeychain::CookiePartitionKeychain(bool contains_all_keys_) + : contains_all_keys_(contains_all_keys_) {} + +CookiePartitionKeychain& CookiePartitionKeychain::operator=( + const CookiePartitionKeychain& other) = default; + +CookiePartitionKeychain& CookiePartitionKeychain::operator=( + CookiePartitionKeychain&& other) = default; + +CookiePartitionKeychain::~CookiePartitionKeychain() = default; + +} // namespace net diff --git a/chromium/net/cookies/cookie_partition_keychain.h b/chromium/net/cookies/cookie_partition_keychain.h new file mode 100644 index 00000000000..beef4aa322e --- /dev/null +++ b/chromium/net/cookies/cookie_partition_keychain.h @@ -0,0 +1,85 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_H_ +#define NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_H_ + +#include <set> +#include <vector> + +#include "net/base/net_export.h" +#include "net/cookies/cookie_partition_key.h" + +namespace net { + +// A data structure used to represent a collection of cookie partition keys. +// +// It can represent all possible cookie partition keys when +// `contains_all_keys_` is true. +// +// It can also represent a finite number of cookie partition keys, including +// zero. +// TODO(crbug.com/1225444): Consider changing the name of this class since the +// term "keychain" has a certain meaning for iOS and macOS. +class NET_EXPORT CookiePartitionKeychain { + public: + // Creates an empty keychain. + explicit CookiePartitionKeychain(); + CookiePartitionKeychain(const CookiePartitionKeychain& other); + CookiePartitionKeychain(CookiePartitionKeychain&& other); + // Creates a keychain with a single element. + explicit CookiePartitionKeychain(const CookiePartitionKey& key); + // Creates a set that contains each partition key in the vector. + explicit CookiePartitionKeychain(const std::vector<CookiePartitionKey>& keys); + + CookiePartitionKeychain& operator=(const CookiePartitionKeychain& other); + CookiePartitionKeychain& operator=(CookiePartitionKeychain&& other); + ~CookiePartitionKeychain(); + + static CookiePartitionKeychain ContainsAll() { + return CookiePartitionKeychain(true); + } + + static CookiePartitionKeychain FromOptional( + const absl::optional<CookiePartitionKey>& opt_key) { + return opt_key ? CookiePartitionKeychain(opt_key.value()) + : CookiePartitionKeychain(); + } + + // Temporary method used to record where we need to decide how to build the + // CookiePartitionKeychain. + // + // Returns an empty keychain, so no partitioned cookies will be returned at + // callsites this is used. + // + // TODO(crbug.com/1225444): Remove this method and update callsites to use + // appropriate constructor. + static CookiePartitionKeychain Todo() { return CookiePartitionKeychain(); } + + // CookieMonster can check if the keychain is empty to avoid searching the + // PartitionedCookieMap at all. + bool IsEmpty() const { return !contains_all_keys_ && keys_.empty(); } + + // Returns if the keychain contains every partition key. + bool ContainsAllKeys() const { return contains_all_keys_; } + + // Iterate over all keys in the keychain, do not call this method if + // `contains_all_keys` is true. + const std::vector<CookiePartitionKey>& PartitionKeys() const { + DCHECK(!contains_all_keys_); + return keys_; + } + + private: + explicit CookiePartitionKeychain(bool contains_all_keys_); + + bool contains_all_keys_ = false; + // If `contains_all_keys_` is true, `keys_` must be empty. + // If `keys_` is not empty, then `contains_all_keys_` must be false. + std::vector<CookiePartitionKey> keys_; +}; + +} // namespace net + +#endif // NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_H_ diff --git a/chromium/net/cookies/cookie_partition_keychain_unittest.cc b/chromium/net/cookies/cookie_partition_keychain_unittest.cc new file mode 100644 index 00000000000..770d8cdee8a --- /dev/null +++ b/chromium/net/cookies/cookie_partition_keychain_unittest.cc @@ -0,0 +1,75 @@ +// Copyright 2021 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_UNITTEST_H_ +#define NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_UNITTEST_H_ + +#include "net/cookies/cookie_partition_keychain.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace net { + +TEST(CookiePartitionKeychainTest, EmptySet) { + CookiePartitionKeychain keychain; + + EXPECT_TRUE(keychain.IsEmpty()); + EXPECT_FALSE(keychain.ContainsAllKeys()); + EXPECT_EQ(0u, keychain.PartitionKeys().size()); +} + +TEST(CookiePartitionKeychainTest, SingletonSet) { + CookiePartitionKeychain keychain( + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com"))); + + EXPECT_FALSE(keychain.IsEmpty()); + EXPECT_FALSE(keychain.ContainsAllKeys()); + EXPECT_THAT( + keychain.PartitionKeys(), + testing::UnorderedElementsAre( + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")))); +} + +TEST(CookiePartitionKeychainTest, MultipleElements) { + CookiePartitionKeychain keychain({ + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")), + CookiePartitionKey::FromURLForTesting(GURL("https://www.bar.com")), + }); + + EXPECT_FALSE(keychain.IsEmpty()); + EXPECT_FALSE(keychain.ContainsAllKeys()); + EXPECT_THAT( + keychain.PartitionKeys(), + testing::UnorderedElementsAre( + CookiePartitionKey::FromURLForTesting( + GURL("https://subdomain.foo.com")), + CookiePartitionKey::FromURLForTesting(GURL("https://www.bar.com")))); +} + +TEST(CookiePartitionKeychainTest, ContainsAll) { + CookiePartitionKeychain keychain = CookiePartitionKeychain::ContainsAll(); + EXPECT_FALSE(keychain.IsEmpty()); + EXPECT_TRUE(keychain.ContainsAllKeys()); +} + +TEST(CookiePartitionKeychainTest, FromOptional) { + CookiePartitionKeychain keychain = + CookiePartitionKeychain::FromOptional(absl::nullopt); + EXPECT_TRUE(keychain.IsEmpty()); + EXPECT_FALSE(keychain.ContainsAllKeys()); + + keychain = CookiePartitionKeychain::FromOptional( + absl::make_optional<CookiePartitionKey>( + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")))); + EXPECT_FALSE(keychain.IsEmpty()); + EXPECT_FALSE(keychain.ContainsAllKeys()); + EXPECT_THAT( + keychain.PartitionKeys(), + testing::UnorderedElementsAre( + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")))); +} + +} // namespace net + +#endif // NET_COOKIES_COOKIE_PARTITION_KEYCHAIN_UNITTEST_H_ diff --git a/chromium/net/cookies/cookie_store.cc b/chromium/net/cookies/cookie_store.cc index 6f7b1ca42e9..adc34fb0d41 100644 --- a/chromium/net/cookies/cookie_store.cc +++ b/chromium/net/cookies/cookie_store.cc @@ -45,8 +45,4 @@ void CookieStore::SetCookieAccessDelegate( cookie_access_delegate_ = std::move(delegate); } -void CookieStore::DumpMemoryStats( - base::trace_event::ProcessMemoryDump* pmd, - const std::string& parent_absolute_name) const {} - } // namespace net diff --git a/chromium/net/cookies/cookie_store.h b/chromium/net/cookies/cookie_store.h index 0642e632ded..3a6c7c10d26 100644 --- a/chromium/net/cookies/cookie_store.h +++ b/chromium/net/cookies/cookie_store.h @@ -20,15 +20,10 @@ #include "net/cookies/cookie_access_result.h" #include "net/cookies/cookie_deletion_info.h" #include "net/cookies/cookie_options.h" +#include "net/cookies/cookie_partition_keychain.h" class GURL; -namespace base { -namespace trace_event { -class ProcessMemoryDump; -} -} // namespace base - namespace net { class CookieChangeDispatcher; @@ -79,9 +74,13 @@ class NET_EXPORT CookieStore { // creation date. // To get all the cookies for a URL, use this method with an all-inclusive // |options|. + // If |cookie_partition_keychain| is not empty, then this function will return + // the partitioned cookies for that URL whose partition keys are in the + // keychain *in addition to* the unpartitioned cookies for that URL. virtual void GetCookieListWithOptionsAsync( const GURL& url, const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain, GetCookieListCallback callback) = 0; // Returns all the cookies, for use in management UI, etc. This does not mark @@ -155,10 +154,6 @@ class NET_EXPORT CookieStore { // Transfer ownership of a CookieAccessDelegate. void SetCookieAccessDelegate(std::unique_ptr<CookieAccessDelegate> delegate); - // Reports the estimate of dynamically allocated memory in bytes. - virtual void DumpMemoryStats(base::trace_event::ProcessMemoryDump* pmd, - const std::string& parent_absolute_name) const; - // This may be null if no delegate has been set yet, or the delegate has been // reset to null. const CookieAccessDelegate* cookie_access_delegate() const { diff --git a/chromium/net/cookies/cookie_store_change_unittest.h b/chromium/net/cookies/cookie_store_change_unittest.h index 5c742e776fc..47e5fb28521 100644 --- a/chromium/net/cookies/cookie_store_change_unittest.h +++ b/chromium/net/cookies/cookie_store_change_unittest.h @@ -715,7 +715,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, NoCookie) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -733,7 +733,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, InitialCookie) { this->DeliverChangeNotifications(); std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -749,7 +749,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, InsertOne) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -776,7 +776,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, InsertMany) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -820,7 +820,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, InsertFiltering) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->www_foo_foo_.url(), + this->www_foo_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -874,7 +874,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DeleteOne) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -904,7 +904,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DeleteTwo) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -953,7 +953,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DeleteFiltering) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->www_foo_foo_.url(), + this->www_foo_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1019,7 +1019,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, Overwrite) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1063,7 +1063,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, OverwriteFiltering) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->www_foo_foo_.url(), + this->www_foo_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1159,7 +1159,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, OverwriteWithHttpOnly) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->www_foo_foo_.url(), + this->www_foo_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1220,7 +1220,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, Deregister) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1256,13 +1256,13 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DeregisterMultiple) { std::vector<CookieChangeInfo> cookie_changes_1, cookie_changes_2; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -1313,7 +1313,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DispatchRace) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1340,7 +1340,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DeregisterRace) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1385,13 +1385,13 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DeregisterRaceMultiple) { std::vector<CookieChangeInfo> cookie_changes_1, cookie_changes_2; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -1446,13 +1446,13 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DifferentSubscriptionsDisjoint) { std::vector<CookieChangeInfo> cookie_changes_1, cookie_changes_2; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_bar_com_.url(), + this->http_bar_com_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -1490,13 +1490,13 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DifferentSubscriptionsDomains) { std::vector<CookieChangeInfo> cookie_changes_1, cookie_changes_2; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_bar_com_.url(), + this->http_bar_com_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -1534,13 +1534,13 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DifferentSubscriptionsPaths) { std::vector<CookieChangeInfo> cookie_changes_1, cookie_changes_2; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->www_foo_foo_.url(), + this->www_foo_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -1590,19 +1590,19 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, DifferentSubscriptionsFiltering) { std::vector<CookieChangeInfo> cookie_changes_3; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_bar_com_.url(), + this->http_bar_com_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); std::unique_ptr<CookieChangeSubscription> subscription3 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->www_foo_foo_.url(), + this->www_foo_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_3))); @@ -1667,13 +1667,13 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, MultipleSubscriptions) { std::vector<CookieChangeInfo> cookie_changes_1, cookie_changes_2; std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForUrl( - this->http_www_foo_.url(), + this->http_www_foo_.url(), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -1706,7 +1706,7 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, ChangeIncludesCookieAccessSemantics) { std::vector<CookieChangeInfo> cookie_changes; std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForUrl( - GURL("http://domain1.test"), + GURL("http://domain1.test"), absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1723,6 +1723,77 @@ TYPED_TEST_P(CookieStoreChangeUrlTest, ChangeIncludesCookieAccessSemantics) { cookie_changes[0].access_result.access_semantics)); } +TYPED_TEST_P(CookieStoreChangeUrlTest, PartitionedCookies) { + if (!TypeParam::supports_url_cookie_tracking || + !TypeParam::supports_partitioned_cookies) + return; + + CookieStore* cs = this->GetCookieStore(); + std::vector<CookieChangeInfo> cookie_changes; + std::unique_ptr<CookieChangeSubscription> subscription = + cs->GetChangeDispatcher().AddCallbackForUrl( + GURL("https://www.example.com/"), + absl::make_optional(CookiePartitionKey::FromURLForTesting( + GURL("https://www.foo.com"))), + base::BindRepeating( + &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, + base::Unretained(&cookie_changes))); + + // Unpartitioned cookie + this->CreateAndSetCookie(cs, GURL("https://www.example.com/"), + "__Host-a=1; Secure; Path=/", + CookieOptions::MakeAllInclusive()); + // Partitioned cookie with the same partition key + this->CreateAndSetCookie( + cs, GURL("https://www.example.com/"), + "__Host-b=2; Secure; Path=/; Partitioned", + CookieOptions::MakeAllInclusive(), absl::nullopt /* server_time */, + absl::nullopt /* system_time */, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://sub.foo.com")))); + // Partitioned cookie with a different partition key + this->CreateAndSetCookie( + cs, GURL("https://www.example.com"), + "__Host-c=3; Secure; Path=/; Partitioned", + CookieOptions::MakeAllInclusive(), absl::nullopt /* server_time */, + absl::nullopt /* system_time */, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://www.bar.com")))); + this->DeliverChangeNotifications(); + + ASSERT_EQ(2u, cookie_changes.size()); + EXPECT_FALSE(cookie_changes[0].cookie.IsPartitioned()); + EXPECT_EQ("__Host-a", cookie_changes[0].cookie.Name()); + EXPECT_TRUE(cookie_changes[1].cookie.IsPartitioned()); + EXPECT_EQ(CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")), + cookie_changes[1].cookie.PartitionKey().value()); + EXPECT_EQ("__Host-b", cookie_changes[1].cookie.Name()); + + // Test that when the partition key parameter is nullopt that all Partitioned + // cookies do not emit events. + + std::vector<CookieChangeInfo> other_cookie_changes; + std::unique_ptr<CookieChangeSubscription> other_subscription = + cs->GetChangeDispatcher().AddCallbackForUrl( + GURL("https://www.example.com/"), + absl::nullopt /* cookie_partition_key */, + base::BindRepeating( + &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, + base::Unretained(&other_cookie_changes))); + // Update Max-Age: None -> 7200 + this->CreateAndSetCookie( + cs, GURL("https://www.example.com"), + "__Host-b=2; Secure; Path=/; Partitioned; Max-Age=7200", + CookieOptions::MakeAllInclusive(), absl::nullopt /* server_time */, + absl::nullopt /* system_time */, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")))); + this->DeliverChangeNotifications(); + ASSERT_EQ(0u, other_cookie_changes.size()); + // Check that the other listener was invoked. + ASSERT_LT(2u, cookie_changes.size()); +} + TYPED_TEST_P(CookieStoreChangeNamedTest, NoCookie) { if (!TypeParam::supports_named_cookie_tracking) return; @@ -1732,6 +1803,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, NoCookie) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1750,6 +1822,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, InitialCookie) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1766,6 +1839,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, InsertOne) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1793,6 +1867,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, InsertTwo) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1834,6 +1909,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, InsertFiltering) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1893,6 +1969,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DeleteOne) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1923,6 +2000,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DeleteTwo) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -1967,6 +2045,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DeleteFiltering) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2037,6 +2116,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, Overwrite) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2081,6 +2161,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, OverwriteFiltering) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2184,6 +2265,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, OverwriteWithHttpOnly) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2246,6 +2328,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, Deregister) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2285,12 +2368,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DeregisterMultiple) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2348,6 +2433,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DispatchRace) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2377,6 +2463,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DeregisterRace) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2424,12 +2511,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DeregisterRaceMultiple) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2490,12 +2579,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DifferentSubscriptionsDisjoint) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_bar_com_.url(), "ghi", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2534,12 +2625,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DifferentSubscriptionsDomains) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_bar_com_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2578,12 +2671,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DifferentSubscriptionsNames) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "ghi", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2622,12 +2717,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DifferentSubscriptionsPaths) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2679,24 +2776,28 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, DifferentSubscriptionsFiltering) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "hij", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); std::unique_ptr<CookieChangeSubscription> subscription3 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_bar_com_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_3))); std::unique_ptr<CookieChangeSubscription> subscription4 = cs->GetChangeDispatcher().AddCallbackForCookie( this->www_foo_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_4))); @@ -2783,12 +2884,14 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, MultipleSubscriptions) { std::unique_ptr<CookieChangeSubscription> subscription1 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_1))); std::unique_ptr<CookieChangeSubscription> subscription2 = cs->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes_2))); @@ -2817,6 +2920,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, SubscriptionOutlivesStore) { std::unique_ptr<CookieChangeSubscription> subscription = this->GetCookieStore()->GetChangeDispatcher().AddCallbackForCookie( this->http_www_foo_.url(), "abc", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2842,6 +2946,7 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, ChangeIncludesCookieAccessSemantics) { std::unique_ptr<CookieChangeSubscription> subscription = cs->GetChangeDispatcher().AddCallbackForCookie( GURL("http://domain1.test"), "cookie", + absl::nullopt /* cookie_partition_key */, base::BindRepeating( &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, base::Unretained(&cookie_changes))); @@ -2858,6 +2963,76 @@ TYPED_TEST_P(CookieStoreChangeNamedTest, ChangeIncludesCookieAccessSemantics) { cookie_changes[0].access_result.access_semantics)); } +TYPED_TEST_P(CookieStoreChangeNamedTest, PartitionedCookies) { + if (!TypeParam::supports_named_cookie_tracking || + !TypeParam::supports_partitioned_cookies) + return; + + CookieStore* cs = this->GetCookieStore(); + std::vector<CookieChangeInfo> cookie_changes; + std::unique_ptr<CookieChangeSubscription> subscription = + cs->GetChangeDispatcher().AddCallbackForCookie( + GURL("https://www.example.com"), "__Host-a", + absl::make_optional(CookiePartitionKey::FromURLForTesting( + GURL("https://www.foo.com"))), + base::BindRepeating( + &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, + base::Unretained(&cookie_changes))); + + // Unpartitioned cookie + this->CreateAndSetCookie(cs, GURL("https://www.example.com"), + "__Host-a=1; Secure; Path=/", + CookieOptions::MakeAllInclusive()); + // Partitioned cookie with the same partition key + this->CreateAndSetCookie( + cs, GURL("https://www.example.com"), + "__Host-a=2; Secure; Path=/; Partitioned", + CookieOptions::MakeAllInclusive(), absl::nullopt /* server_time */, + absl::nullopt /* system_time */, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://sub.foo.com")))); + // Partitioned cookie with a different partition key + this->CreateAndSetCookie( + cs, GURL("https://www.example.com"), + "__Host-a=3; Secure; Path=/; Partitioned", + CookieOptions::MakeAllInclusive(), absl::nullopt /* server_time */, + absl::nullopt /* system_time */, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://www.bar.com")))); + this->DeliverChangeNotifications(); + + ASSERT_EQ(2u, cookie_changes.size()); + EXPECT_FALSE(cookie_changes[0].cookie.IsPartitioned()); + EXPECT_EQ("1", cookie_changes[0].cookie.Value()); + EXPECT_TRUE(cookie_changes[1].cookie.IsPartitioned()); + EXPECT_EQ(CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")), + cookie_changes[1].cookie.PartitionKey().value()); + EXPECT_EQ("2", cookie_changes[1].cookie.Value()); + + // Test that when the partition key parameter is nullopt that all Partitioned + // cookies do not emit events. + std::vector<CookieChangeInfo> other_cookie_changes; + std::unique_ptr<CookieChangeSubscription> other_subscription = + cs->GetChangeDispatcher().AddCallbackForCookie( + GURL("https://www.example.com"), "__Host-a", + absl::nullopt /* cookie_partition_key */, + base::BindRepeating( + &CookieStoreChangeTestBase<TypeParam>::OnCookieChange, + base::Unretained(&other_cookie_changes))); + // Update Max-Age: None -> 7200 + this->CreateAndSetCookie( + cs, GURL("https://www.example.com"), + "__Host-a=2; Secure; Path=/; Partitioned; Max-Age=7200", + CookieOptions::MakeAllInclusive(), absl::nullopt /* server_time */, + absl::nullopt /* system_time */, + absl::make_optional( + CookiePartitionKey::FromURLForTesting(GURL("https://www.foo.com")))); + this->DeliverChangeNotifications(); + ASSERT_EQ(0u, other_cookie_changes.size()); + // Check that the other listener was invoked. + ASSERT_LT(2u, cookie_changes.size()); +} + REGISTER_TYPED_TEST_SUITE_P(CookieStoreChangeGlobalTest, NoCookie, InitialCookie, @@ -2897,7 +3072,8 @@ REGISTER_TYPED_TEST_SUITE_P(CookieStoreChangeUrlTest, DifferentSubscriptionsPaths, DifferentSubscriptionsFiltering, MultipleSubscriptions, - ChangeIncludesCookieAccessSemantics); + ChangeIncludesCookieAccessSemantics, + PartitionedCookies); REGISTER_TYPED_TEST_SUITE_P(CookieStoreChangeNamedTest, NoCookie, @@ -2923,7 +3099,8 @@ REGISTER_TYPED_TEST_SUITE_P(CookieStoreChangeNamedTest, DifferentSubscriptionsFiltering, MultipleSubscriptions, SubscriptionOutlivesStore, - ChangeIncludesCookieAccessSemantics); + ChangeIncludesCookieAccessSemantics, + PartitionedCookies); } // namespace net diff --git a/chromium/net/cookies/cookie_store_test_helpers.cc b/chromium/net/cookies/cookie_store_test_helpers.cc index 0736a515ca5..31d038e9cd9 100644 --- a/chromium/net/cookies/cookie_store_test_helpers.cc +++ b/chromium/net/cookies/cookie_store_test_helpers.cc @@ -46,6 +46,7 @@ std::unique_ptr<CookieChangeSubscription> DelayedCookieMonsterChangeDispatcher::AddCallbackForCookie( const GURL& url, const std::string& name, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) { ADD_FAILURE(); return nullptr; @@ -53,6 +54,7 @@ DelayedCookieMonsterChangeDispatcher::AddCallbackForCookie( std::unique_ptr<CookieChangeSubscription> DelayedCookieMonsterChangeDispatcher::AddCallbackForUrl( const GURL& url, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) { ADD_FAILURE(); return nullptr; @@ -102,16 +104,17 @@ void DelayedCookieMonster::SetCanonicalCookieAsync( FROM_HERE, base::BindOnce(&DelayedCookieMonster::InvokeSetCookiesCallback, base::Unretained(this), std::move(callback)), - base::TimeDelta::FromMilliseconds(kDelayedTime)); + base::Milliseconds(kDelayedTime)); } void DelayedCookieMonster::GetCookieListWithOptionsAsync( const GURL& url, const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain, CookieMonster::GetCookieListCallback callback) { did_run_ = false; cookie_monster_->GetCookieListWithOptionsAsync( - url, options, + url, options, cookie_partition_keychain, base::BindOnce( &DelayedCookieMonster::GetCookieListWithOptionsInternalCallback, base::Unretained(this))); @@ -120,7 +123,7 @@ void DelayedCookieMonster::GetCookieListWithOptionsAsync( FROM_HERE, base::BindOnce(&DelayedCookieMonster::InvokeGetCookieListCallback, base::Unretained(this), std::move(callback)), - base::TimeDelta::FromMilliseconds(kDelayedTime)); + base::Milliseconds(kDelayedTime)); } void DelayedCookieMonster::GetAllCookiesAsync(GetAllCookiesCallback callback) { diff --git a/chromium/net/cookies/cookie_store_test_helpers.h b/chromium/net/cookies/cookie_store_test_helpers.h index 63182daf15e..d95a0578a86 100644 --- a/chromium/net/cookies/cookie_store_test_helpers.h +++ b/chromium/net/cookies/cookie_store_test_helpers.h @@ -24,27 +24,35 @@ namespace net { class DelayedCookieMonsterChangeDispatcher : public CookieChangeDispatcher { public: DelayedCookieMonsterChangeDispatcher(); + + DelayedCookieMonsterChangeDispatcher( + const DelayedCookieMonsterChangeDispatcher&) = delete; + DelayedCookieMonsterChangeDispatcher& operator=( + const DelayedCookieMonsterChangeDispatcher&) = delete; + ~DelayedCookieMonsterChangeDispatcher() override; // net::CookieChangeDispatcher std::unique_ptr<CookieChangeSubscription> AddCallbackForCookie( const GURL& url, const std::string& name, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) override WARN_UNUSED_RESULT; std::unique_ptr<CookieChangeSubscription> AddCallbackForUrl( const GURL& url, + const absl::optional<CookiePartitionKey>& cookie_partition_key, CookieChangeCallback callback) override WARN_UNUSED_RESULT; std::unique_ptr<CookieChangeSubscription> AddCallbackForAllChanges( CookieChangeCallback callback) override WARN_UNUSED_RESULT; - - private: - DISALLOW_COPY_AND_ASSIGN(DelayedCookieMonsterChangeDispatcher); }; class DelayedCookieMonster : public CookieStore { public: DelayedCookieMonster(); + DelayedCookieMonster(const DelayedCookieMonster&) = delete; + DelayedCookieMonster& operator=(const DelayedCookieMonster&) = delete; + ~DelayedCookieMonster() override; // Call the asynchronous CookieMonster function, expect it to immediately @@ -56,9 +64,11 @@ class DelayedCookieMonster : public CookieStore { const CookieOptions& options, SetCookiesCallback callback) override; - void GetCookieListWithOptionsAsync(const GURL& url, - const CookieOptions& options, - GetCookieListCallback callback) override; + void GetCookieListWithOptionsAsync( + const GURL& url, + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain, + GetCookieListCallback callback) override; void GetAllCookiesAsync(GetAllCookiesCallback callback) override; @@ -111,8 +121,6 @@ class DelayedCookieMonster : public CookieStore { std::string cookie_line_; CookieAccessResultList cookie_access_result_list_; CookieList cookie_list_; - - DISALLOW_COPY_AND_ASSIGN(DelayedCookieMonster); }; class CookieURLHelper { diff --git a/chromium/net/cookies/cookie_store_unittest.h b/chromium/net/cookies/cookie_store_unittest.h index 1631f1667dd..e0af8710105 100644 --- a/chromium/net/cookies/cookie_store_unittest.h +++ b/chromium/net/cookies/cookie_store_unittest.h @@ -126,7 +126,8 @@ class CookieStoreTest : public testing::Test { www_foo_foo_("http://www.foo.com/foo"), www_foo_bar_("http://www.foo.com/bar"), http_baz_com_("http://baz.com"), - http_bar_com_("http://bar.com") { + http_bar_com_("http://bar.com"), + https_www_bar_("https://www.bar.com") { // This test may be used outside of the net test suite, and thus may not // have a task environment. if (!base::CurrentThread::Get()) { @@ -140,36 +141,52 @@ class CookieStoreTest : public testing::Test { // finally returning the value. // TODO(chlily): Consolidate some of these. - std::string GetCookies(CookieStore* cs, const GURL& url) { + std::string GetCookies( + CookieStore* cs, + const GURL& url, + const CookiePartitionKeychain& cookie_partition_keychain = + CookiePartitionKeychain()) { DCHECK(cs); CookieOptions options; if (!CookieStoreTestTraits::supports_http_only) options.set_include_httponly(); options.set_same_site_cookie_context( net::CookieOptions::SameSiteCookieContext::MakeInclusive()); - return GetCookiesWithOptions(cs, url, options); + return GetCookiesWithOptions(cs, url, options, cookie_partition_keychain); } - std::string GetCookiesWithOptions(CookieStore* cs, - const GURL& url, - const CookieOptions& options) { + std::string GetCookiesWithOptions( + CookieStore* cs, + const GURL& url, + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain = + CookiePartitionKeychain()) { return CanonicalCookie::BuildCookieLine( - GetCookieListWithOptions(cs, url, options)); + GetCookieListWithOptions(cs, url, options, cookie_partition_keychain)); } - CookieList GetCookieListWithOptions(CookieStore* cs, - const GURL& url, - const CookieOptions& options) { + CookieList GetCookieListWithOptions( + CookieStore* cs, + const GURL& url, + const CookieOptions& options, + const CookiePartitionKeychain& cookie_partition_keychain = + CookiePartitionKeychain()) { DCHECK(cs); GetCookieListCallback callback; - cs->GetCookieListWithOptionsAsync(url, options, callback.MakeCallback()); + cs->GetCookieListWithOptionsAsync(url, options, cookie_partition_keychain, + callback.MakeCallback()); callback.WaitUntilDone(); return callback.cookies(); } // This does not update the access time on the cookies. - CookieList GetAllCookiesForURL(CookieStore* cs, const GURL& url) { - return GetCookieListWithOptions(cs, url, CookieOptions::MakeAllInclusive()); + CookieList GetAllCookiesForURL( + CookieStore* cs, + const GURL& url, + const CookiePartitionKeychain& cookie_partition_keychain = + CookiePartitionKeychain()) { + return GetCookieListWithOptions(cs, url, CookieOptions::MakeAllInclusive(), + cookie_partition_keychain); } // This does not update the access time on the cookies. @@ -179,7 +196,8 @@ class CookieStoreTest : public testing::Test { GetCookieListCallback callback; CookieOptions options = CookieOptions::MakeAllInclusive(); options.set_return_excluded_cookies(); - cs->GetCookieListWithOptionsAsync(url, options, callback.MakeCallback()); + cs->GetCookieListWithOptionsAsync( + url, options, CookiePartitionKeychain::Todo(), callback.MakeCallback()); callback.WaitUntilDone(); return callback.excluded_cookies(); } @@ -198,10 +216,11 @@ class CookieStoreTest : public testing::Test { const std::string& cookie_line, const CookieOptions& options, absl::optional<base::Time> server_time = absl::nullopt, - absl::optional<base::Time> system_time = absl::nullopt) { + absl::optional<base::Time> system_time = absl::nullopt, + absl::optional<CookiePartitionKey> cookie_partition_key = absl::nullopt) { auto cookie = CanonicalCookie::Create( url, cookie_line, system_time.value_or(base::Time::Now()), server_time, - absl::nullopt /* cookie_partition_key */); + cookie_partition_key); if (!cookie) return false; @@ -256,15 +275,18 @@ class CookieStoreTest : public testing::Test { absl::make_optional(server_time)); } - bool SetCookie(CookieStore* cs, - const GURL& url, - const std::string& cookie_line) { + bool SetCookie( + CookieStore* cs, + const GURL& url, + const std::string& cookie_line, + absl::optional<CookiePartitionKey> cookie_partition_key = absl::nullopt) { CookieOptions options; if (!CookieStoreTestTraits::supports_http_only) options.set_include_httponly(); options.set_same_site_cookie_context( net::CookieOptions::SameSiteCookieContext::MakeInclusive()); - return CreateAndSetCookie(cs, url, cookie_line, options); + return CreateAndSetCookie(cs, url, cookie_line, options, absl::nullopt, + absl::nullopt, cookie_partition_key); } CookieInclusionStatus CreateAndSetCookieReturnStatus( @@ -388,12 +410,13 @@ class CookieStoreTest : public testing::Test { const std::string& line) { std::string cookies = GetCookies(cs, url); bool matched = (TokenizeCookieLine(line) == TokenizeCookieLine(cookies)); - base::Time polling_end_date = base::Time::Now() + - base::TimeDelta::FromMilliseconds( + base::Time polling_end_date = + base::Time::Now() + + base::Milliseconds( CookieStoreTestTraits::creation_time_granularity_in_ms); while (!matched && base::Time::Now() <= polling_end_date) { - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds(10)); + base::PlatformThread::Sleep(base::Milliseconds(10)); cookies = GetCookies(cs, url); matched = (TokenizeCookieLine(line) == TokenizeCookieLine(cookies)); } @@ -413,6 +436,7 @@ class CookieStoreTest : public testing::Test { const CookieURLHelper www_foo_bar_; const CookieURLHelper http_baz_com_; const CookieURLHelper http_bar_com_; + const CookieURLHelper https_www_bar_; std::unique_ptr<base::test::SingleThreadTaskEnvironment> task_environment_; @@ -433,10 +457,9 @@ TYPED_TEST_SUITE_P(CookieStoreTest); TYPED_TEST_P(CookieStoreTest, FilterTest) { CookieStore* cs = this->GetCookieStore(); - base::Time two_hours_ago = base::Time::Now() - base::TimeDelta::FromHours(2); - base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1); - base::Time one_hour_from_now = - base::Time::Now() + base::TimeDelta::FromHours(1); + base::Time two_hours_ago = base::Time::Now() - base::Hours(2); + base::Time one_hour_ago = base::Time::Now() - base::Hours(1); + base::Time one_hour_from_now = base::Time::Now() + base::Hours(1); std::unique_ptr<CanonicalCookie> cc(CanonicalCookie::CreateSanitizedCookie( this->www_foo_foo_.url(), "A", "B", std::string(), "/foo", one_hour_ago, @@ -560,10 +583,9 @@ TYPED_TEST_P(CookieStoreTest, FilterTest) { TYPED_TEST_P(CookieStoreTest, SetCanonicalCookieTest) { CookieStore* cs = this->GetCookieStore(); - base::Time two_hours_ago = base::Time::Now() - base::TimeDelta::FromHours(2); - base::Time one_hour_ago = base::Time::Now() - base::TimeDelta::FromHours(1); - base::Time one_hour_from_now = - base::Time::Now() + base::TimeDelta::FromHours(1); + base::Time two_hours_ago = base::Time::Now() - base::Hours(2); + base::Time one_hour_ago = base::Time::Now() - base::Hours(1); + base::Time one_hour_from_now = base::Time::Now() + base::Hours(1); std::string foo_foo_host(this->www_foo_foo_.url().host()); std::string foo_bar_domain(this->www_foo_bar_.domain()); @@ -1276,12 +1298,12 @@ TYPED_TEST_P(CookieStoreTest, EmptyExpires) { this->GetCookiesWithOptions(cs, url, options)); absl::optional<base::Time> server_time = - absl::make_optional(base::Time::Now() - base::TimeDelta::FromHours(1)); + absl::make_optional(base::Time::Now() - base::Hours(1)); this->CreateAndSetCookie(cs, url, set_cookie_line, options, server_time); this->MatchCookieLines(cookie_line, this->GetCookiesWithOptions(cs, url, options)); - server_time = base::Time::Now() + base::TimeDelta::FromHours(1); + server_time = base::Time::Now() + base::Hours(1); this->CreateAndSetCookie(cs, url, set_cookie_line, options, server_time); this->MatchCookieLines(cookie_line, this->GetCookiesWithOptions(cs, url, options)); @@ -1432,14 +1454,10 @@ TYPED_TEST_P(CookieStoreTest, TestDeleteAll) { TYPED_TEST_P(CookieStoreTest, TestDeleteAllCreatedInTimeRange) { CookieStore* cs = this->GetCookieStore(); - const base::Time last_month = base::Time::Now() - - base::TimeDelta::FromDays(30); - const base::Time last_minute = base::Time::Now() - - base::TimeDelta::FromMinutes(1); - const base::Time next_minute = base::Time::Now() + - base::TimeDelta::FromMinutes(1); - const base::Time next_month = base::Time::Now() + - base::TimeDelta::FromDays(30); + const base::Time last_month = base::Time::Now() - base::Days(30); + const base::Time last_minute = base::Time::Now() - base::Minutes(1); + const base::Time next_minute = base::Time::Now() + base::Minutes(1); + const base::Time next_month = base::Time::Now() + base::Days(30); // Add a cookie. EXPECT_TRUE(this->SetCookie(cs, this->http_www_foo_.url(), "A=B")); @@ -1480,8 +1498,8 @@ TYPED_TEST_P(CookieStoreTest, TestDeleteAllCreatedInTimeRange) { TYPED_TEST_P(CookieStoreTest, TestDeleteAllWithInfo) { CookieStore* cs = this->GetCookieStore(); base::Time now = base::Time::Now(); - base::Time last_month = base::Time::Now() - base::TimeDelta::FromDays(30); - base::Time last_minute = base::Time::Now() - base::TimeDelta::FromMinutes(1); + base::Time last_month = base::Time::Now() - base::Days(30); + base::Time last_minute = base::Time::Now() - base::Minutes(1); // These 3 cookies match the time range and host. EXPECT_TRUE(this->SetCookie(cs, this->http_www_foo_.url(), "A=B")); @@ -1665,12 +1683,12 @@ TYPED_TEST_P(CookieStoreTest, CookieOrdering) { this->SetCookie(cs, GURL("http://d.c.b.a.foo.com/aa/x.html"), "c=1")); EXPECT_TRUE(this->SetCookie(cs, GURL("http://b.a.foo.com/aa/bb/cc/x.html"), "d=1; domain=b.a.foo.com")); - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds( - TypeParam::creation_time_granularity_in_ms)); + base::PlatformThread::Sleep( + base::Milliseconds(TypeParam::creation_time_granularity_in_ms)); EXPECT_TRUE(this->SetCookie(cs, GURL("http://b.a.foo.com/aa/bb/cc/x.html"), "a=4; domain=b.a.foo.com")); - base::PlatformThread::Sleep(base::TimeDelta::FromMilliseconds( - TypeParam::creation_time_granularity_in_ms)); + base::PlatformThread::Sleep( + base::Milliseconds(TypeParam::creation_time_granularity_in_ms)); EXPECT_TRUE(this->SetCookie(cs, GURL("http://c.b.a.foo.com/aa/bb/cc/x.html"), "e=1; domain=c.b.a.foo.com")); EXPECT_TRUE( diff --git a/chromium/net/cookies/cookie_util.cc b/chromium/net/cookies/cookie_util.cc index 8033226d032..89618a98f34 100644 --- a/chromium/net/cookies/cookie_util.cc +++ b/chromium/net/cookies/cookie_util.cc @@ -252,7 +252,7 @@ bool CookieWithAccessResultSorter(const CookieWithAccessResult& a, } // namespace void FireStorageAccessHistogram(StorageAccessResult result) { - UMA_HISTOGRAM_ENUMERATION("API.StorageAccess.AllowedRequests", result); + UMA_HISTOGRAM_ENUMERATION("API.StorageAccess.AllowedRequests2", result); } bool DomainIsHostOnly(const std::string& domain_string) { diff --git a/chromium/net/cookies/cookie_util_unittest.cc b/chromium/net/cookies/cookie_util_unittest.cc index 91d5558b289..2cedc332599 100644 --- a/chromium/net/cookies/cookie_util_unittest.cc +++ b/chromium/net/cookies/cookie_util_unittest.cc @@ -168,8 +168,7 @@ TEST(CookieUtilTest, ParseCookieExpirationTimeBeyond2038) { // It should either have an exact value, or be base::Time::Max(). For // simplicity just check that it is greater than an arbitray date. - base::Time almost_jan_2038 = - base::Time::UnixEpoch() + base::TimeDelta::FromDays(365 * 68); + base::Time almost_jan_2038 = base::Time::UnixEpoch() + base::Days(365 * 68); EXPECT_LT(almost_jan_2038, parsed_time); } } diff --git a/chromium/net/cookies/parse_cookie_line_fuzzer.cc b/chromium/net/cookies/parse_cookie_line_fuzzer.cc index 830a8bfc078..3bb8dd889b4 100644 --- a/chromium/net/cookies/parse_cookie_line_fuzzer.cc +++ b/chromium/net/cookies/parse_cookie_line_fuzzer.cc @@ -10,17 +10,27 @@ #include "base/check_op.h" #include "net/cookies/parsed_cookie.h" -const std::string GetArbitraryString(FuzzedDataProvider* data_provider) { - // Adding a fudge factor to kMaxCookieSize so that both branches of the bounds - // detection code will be tested. +const std::string GetArbitraryNameValueString( + FuzzedDataProvider* data_provider) { + // There's no longer an upper bound on the size of a cookie line, but + // in practice using double kMaxCookieNamePlusValueSize should allow + // the majority of interesting cases to be covered. return data_provider->ConsumeRandomLengthString( - net::ParsedCookie::kMaxCookieSize + 10); + net::ParsedCookie::kMaxCookieNamePlusValueSize * 2); +} + +const std::string GetArbitraryAttributeValueString( + FuzzedDataProvider* data_provider) { + // Adding a fudge factor to kMaxCookieAttributeValueSize so that both branches + // of the bounds detection code will be tested. + return data_provider->ConsumeRandomLengthString( + net::ParsedCookie::kMaxCookieAttributeValueSize + 10); } // Entry point for LibFuzzer. extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { FuzzedDataProvider data_provider(data, size); - const std::string cookie_line = GetArbitraryString(&data_provider); + const std::string cookie_line = GetArbitraryNameValueString(&data_provider); net::ParsedCookie parsed_cookie(cookie_line); // Call zero or one of ParsedCookie's mutator methods. Should not call @@ -28,10 +38,10 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { const uint8_t action = data_provider.ConsumeIntegralInRange(0, 12); switch (action) { case 1: - parsed_cookie.SetName(GetArbitraryString(&data_provider)); + parsed_cookie.SetName(GetArbitraryNameValueString(&data_provider)); break; case 2: - parsed_cookie.SetValue(GetArbitraryString(&data_provider)); + parsed_cookie.SetValue(GetArbitraryNameValueString(&data_provider)); break; } @@ -39,16 +49,20 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { switch (action) { case 3: if (parsed_cookie.IsValid()) - parsed_cookie.SetPath(GetArbitraryString(&data_provider)); + parsed_cookie.SetPath( + GetArbitraryAttributeValueString(&data_provider)); break; case 4: - parsed_cookie.SetDomain(GetArbitraryString(&data_provider)); + parsed_cookie.SetDomain( + GetArbitraryAttributeValueString(&data_provider)); break; case 5: - parsed_cookie.SetExpires(GetArbitraryString(&data_provider)); + parsed_cookie.SetExpires( + GetArbitraryAttributeValueString(&data_provider)); break; case 6: - parsed_cookie.SetMaxAge(GetArbitraryString(&data_provider)); + parsed_cookie.SetMaxAge( + GetArbitraryAttributeValueString(&data_provider)); break; case 7: parsed_cookie.SetIsSecure(data_provider.ConsumeBool()); @@ -57,10 +71,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { parsed_cookie.SetIsHttpOnly(data_provider.ConsumeBool()); break; case 9: - parsed_cookie.SetSameSite(GetArbitraryString(&data_provider)); + parsed_cookie.SetSameSite( + GetArbitraryAttributeValueString(&data_provider)); break; case 10: - parsed_cookie.SetPriority(GetArbitraryString(&data_provider)); + parsed_cookie.SetPriority( + GetArbitraryAttributeValueString(&data_provider)); break; case 11: parsed_cookie.SetIsSameParty(data_provider.ConsumeBool()); @@ -76,17 +92,8 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) { const std::string serialized = parsed_cookie.ToCookieLine(); net::ParsedCookie reparsed_cookie(serialized); const std::string reserialized = reparsed_cookie.ToCookieLine(); - - // RFC6265 requires semicolons to be followed by spaces. Because our parser - // permits this rule to be broken, but follows the rule in ToCookieLine(), - // it's possible to serialize a string that's longer than the original - // input. If the serialized string exceeds kMaxCookieSize, the parser will - // reject it. For this fuzzer, we are considering this situation a false - // positive. - if (serialized.size() <= net::ParsedCookie::kMaxCookieSize) { - CHECK(reparsed_cookie.IsValid()); - CHECK_EQ(serialized, reserialized); - } + CHECK(reparsed_cookie.IsValid()); + CHECK_EQ(serialized, reserialized); } return 0; diff --git a/chromium/net/cookies/parsed_cookie.cc b/chromium/net/cookies/parsed_cookie.cc index a8a449c1a99..56c6bff0c87 100644 --- a/chromium/net/cookies/parsed_cookie.cc +++ b/chromium/net/cookies/parsed_cookie.cc @@ -47,6 +47,7 @@ #include "base/logging.h" #include "base/metrics/histogram_macros.h" #include "base/strings/string_util.h" +#include "net/base/features.h" #include "net/cookies/cookie_constants.h" #include "net/cookies/cookie_inclusion_status.h" #include "net/http/http_util.h" @@ -118,7 +119,12 @@ inline bool SeekBackPast(std::string::const_iterator* it, // ; US-ASCII characters excluding CTLs, // ; whitespace DQUOTE, comma, semicolon, // ; and backslash -bool IsValidCookieValue(const std::string& value) { +// Note: This function is being replaced by +// ParsedCookie::IsValidCookieValue() but remains here while we test that +// removing this functionality doesn't break things. +// TODO(crbug.com/1243852) Remove this when kExtraCookieValidityChecks +// gets removed (assuming the associated changes cause no issues). +bool IsValidCookieValueLegacy(const std::string& value) { // Number of characters to skip in validation at beginning and end of string. size_t skip = 0; if (value.size() >= 2 && *value.begin() == '"' && *(value.end() - 1) == '"') @@ -160,12 +166,13 @@ ParsedCookie::ParsedCookie(const std::string& cookie_line, if (status_out == nullptr) { status_out = &blank_status; } + *status_out = CookieInclusionStatus(); - if (cookie_line.size() > kMaxCookieSize) { + if ((!base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) && + cookie_line.size() > kMaxCookieSize) { DVLOG(1) << "Not parsing cookie, too large: " << cookie_line.size(); - // TODO(crbug.com/1228815): Apply more specific exclusion reasons. status_out->AddExclusionReason( - CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + CookieInclusionStatus::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE); return; } @@ -210,30 +217,91 @@ CookiePriority ParsedCookie::Priority() const { } bool ParsedCookie::SetName(const std::string& name) { - if (!name.empty() && !HttpUtil::IsToken(name)) - return false; + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + const std::string& value = pairs_.empty() ? "" : pairs_[0].second; + + // Ensure there are no invalid characters in `name`. This should be done + // before calling ParseTokenString because we want terminating characters + // ('\r', '\n', and '\0') and '=' in `name` to cause a rejection instead of + // truncation. + // TODO(crbug.com/1233602) Once we change logic more broadly to reject + // cookies containing these characters, we should be able to simplify this + // logic since IsValidCookieNameValuePair() also calls IsValidCookieName(). + // Also, this check will currently fail if `name` has a tab character in the + // leading or trailing whitespace, which is inconsistent with what happens + // when parsing a cookie line in the constructor (but the old logic for + // SetName() behaved this way as well). + if (!IsValidCookieName(name)) { + return false; + } - // Fail if we'd be creating a cookie with an empty name and value. - if (name.empty() && (pairs_.empty() || pairs_[0].second.empty())) - return false; + // Use the same whitespace trimming code as the constructor. + const std::string& parsed_name = ParseTokenString(name); + + if (!IsValidCookieNameValuePair(parsed_name, value)) { + return false; + } + + if (pairs_.empty()) + pairs_.push_back(std::make_pair("", "")); + pairs_[0].first = parsed_name; + + } else { + if (!name.empty() && !HttpUtil::IsToken(name)) + return false; - if (pairs_.empty()) - pairs_.push_back(std::make_pair("", "")); - pairs_[0].first = name; + // Fail if we'd be creating a cookie with an empty name and value. + if (name.empty() && (pairs_.empty() || pairs_[0].second.empty())) + return false; + + if (pairs_.empty()) + pairs_.push_back(std::make_pair("", "")); + pairs_[0].first = name; + } return true; } bool ParsedCookie::SetValue(const std::string& value) { - if (!IsValidCookieValue(value)) - return false; + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + const std::string& name = pairs_.empty() ? "" : pairs_[0].first; + + // Ensure there are no invalid characters in `value`. This should be done + // before calling ParseValueString because we want terminating characters + // ('\r', '\n', and '\0') in `value` to cause a rejection instead of + // truncation. + // TODO(crbug.com/1233602) Once we change logic more broadly to reject + // cookies containing these characters, we should be able to simplify this + // logic since IsValidCookieNameValuePair() also calls IsValidCookieValue(). + // Also, this check will currently fail if `value` has a tab character in + // the leading or trailing whitespace, which is inconsistent with what + // happens when parsing a cookie line in the constructor (but the old logic + // for SetValue() behaved this way as well). + if (!IsValidCookieValue(value)) { + return false; + } - // Fail if we'd be creating a cookie with an empty name and value. - if (value.empty() && (pairs_.empty() || pairs_[0].first.empty())) - return false; + // Use the same whitespace trimming code as the constructor. + const std::string& parsed_value = ParseValueString(value); + + if (!IsValidCookieNameValuePair(name, parsed_value)) { + return false; + } + if (pairs_.empty()) + pairs_.push_back(std::make_pair("", "")); + pairs_[0].second = parsed_value; + + } else { + if (!IsValidCookieValueLegacy(value)) + return false; - if (pairs_.empty()) - pairs_.push_back(std::make_pair("", "")); - pairs_[0].second = value; + // Fail if we'd be creating a cookie with an empty name and value. + if (value.empty() && (pairs_.empty() || pairs_[0].first.empty())) + return false; + + if (pairs_.empty()) + pairs_.push_back(std::make_pair("", "")); + pairs_[0].second = value; + } return true; } @@ -400,16 +468,120 @@ bool ParsedCookie::ValueMatchesParsedValue(const std::string& value) { } // static -bool ParsedCookie::IsValidCookieAttributeValue(const std::string& value) { - // The greatest common denominator of cookie attribute values is - // <any CHAR except CTLs or ";"> according to RFC 6265. - for (std::string::const_iterator i = value.begin(); i != value.end(); ++i) { - if (HttpUtil::IsControlChar(*i) || *i == ';') +bool ParsedCookie::IsValidCookieName(const std::string& name) { + // IsValidCookieName() returns whether a string matches the following + // grammar: + // + // cookie-name = *cookie-name-octet + // cookie-name-octet = %x20-3A / %x3C / %x3E-7E / %x80-FF + // ; octets excluding CTLs, ";", and "=" + // + // This can be used to determine whether cookie names and cookie attribute + // names contain any invalid characters. + // + // Note that RFC6265bis section 4.1.1 suggests a stricter grammar for + // parsing cookie names, but we choose to allow a wider range of characters + // than what's allowed by that grammar (while still conforming to the + // requirements of the parsing algorithm defined in section 5.2). + // + // For reference, see: + // - https://crbug.com/238041 + for (char i : name) { + if (HttpUtil::IsControlChar(i) || i == ';' || i == '=') + return false; + } + return true; +} + +// static +bool ParsedCookie::IsValidCookieValue(const std::string& value) { + // IsValidCookieValue() returns whether a string matches the following + // grammar: + // + // cookie-value = *cookie-value-octet + // cookie-value-octet = %x20-3A / %x3C-7E / %x80-FF + // ; octets excluding CTLs and ";" + // + // This can be used to determine whether cookie values contain any invalid + // characters. + // + // Note that RFC6265bis section 4.1.1 suggests a stricter grammar for + // parsing cookie values, but we choose to allow a wider range of characters + // than what's allowed by that grammar (while still conforming to the + // requirements of the parsing algorithm defined in section 5.2). + // + // For reference, see: + // - https://crbug.com/238041 + for (char i : value) { + if (HttpUtil::IsControlChar(i) || i == ';') return false; } return true; } +// static +bool ParsedCookie::IsValidCookieAttributeValueLegacy(const std::string& value) { + // This legacy method only checks the character set, so use + // CookieAttributeValueHasValidCharSet() + return CookieAttributeValueHasValidCharSet(value); +} + +// static +bool ParsedCookie::CookieAttributeValueHasValidCharSet( + const std::string& value) { + // A cookie attribute value has the same character set restrictions as cookie + // values, so re-use the validation function for that. + return IsValidCookieValue(value); +} + +// static +bool ParsedCookie::CookieAttributeValueHasValidSize(const std::string& value) { + return (value.size() <= kMaxCookieAttributeValueSize); +} + +// static +bool ParsedCookie::IsValidCookieNameValuePair( + const std::string& name, + const std::string& value, + CookieInclusionStatus* status_out) { + // Ignore cookies with neither name nor value. + if (name.empty() && value.empty()) { + if (status_out != nullptr) { + // TODO(crbug.com/1228815): Apply more specific exclusion reasons. + status_out->AddExclusionReason( + CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + } + // TODO(crbug.com/1228815) Note - if the exclusion reasons change to no + // longer be the same, we'll need to not return right away and evaluate all + // of the checks. + return false; + } + + // Enforce a length limit for name + value per RFC6265bis. + base::CheckedNumeric<size_t> name_value_pair_size = name.size(); + name_value_pair_size += value.size(); + if (!name_value_pair_size.IsValid() || + (name_value_pair_size.ValueOrDie() > kMaxCookieNamePlusValueSize)) { + if (status_out != nullptr) { + status_out->AddExclusionReason( + CookieInclusionStatus::EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE); + } + return false; + } + + // Ignore Set-Cookie directives containing control characters. See + // http://crbug.com/238041. + if (!IsValidCookieName(name) || !IsValidCookieValue(value)) { + // TODO(crbug.com/1228815): Apply more specific exclusion reasons. + if (status_out != nullptr) { + status_out->AddExclusionReason( + CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + } + return false; + } + return true; +} + // Parse all token/value pairs and populate pairs_. void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line, CookieInclusionStatus& status_out) { @@ -424,6 +596,26 @@ void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line, // Then we can log any unexpected terminators. std::string::const_iterator end = FindFirstTerminator(cookie_line); + // For metrics on truncating character presence in the cookie line. + if (end < cookie_line.end()) { + switch (*end) { + case '\0': + truncating_char_in_cookie_string_type_ = + TruncatingCharacterInCookieStringType::kTruncatingCharNull; + break; + case '\r': + truncating_char_in_cookie_string_type_ = + TruncatingCharacterInCookieStringType::kTruncatingCharNewline; + break; + case '\n': + truncating_char_in_cookie_string_type_ = + TruncatingCharacterInCookieStringType::kTruncatingCharLineFeed; + break; + default: + NOTREACHED(); + } + } + // Exit early for an empty cookie string. if (it == end) { // TODO(crbug.com/1228815): Apply more specific exclusion reasons. @@ -475,31 +667,76 @@ void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line, // OK, we're finished with a Token/Value. pair.second = std::string(value_start, value_end); - // Ignore cookies with neither name nor value. - if (pair_num == 0 && (pair.first.empty() && pair.second.empty())) { - // TODO(crbug.com/1228815): Apply more specific exclusion reasons. - status_out.AddExclusionReason( - CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); - pairs_.clear(); - break; - } + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + bool ignore_pair = false; + if (pair_num == 0) { + if (!IsValidCookieNameValuePair(pair.first, pair.second, &status_out)) { + pairs_.clear(); + break; + } + } else { + // From RFC2109: "Attributes (names) (attr) are case-insensitive." + pair.first = base::ToLowerASCII(pair.first); + + // Attribute names have the same character set limitations as cookie + // names, but only a handful of values are allowed. We don't check that + // this attribute name is one of the allowed ones here, so just re-use + // the cookie name check. + if (!IsValidCookieName(pair.first)) { + // TODO(crbug.com/1228815): Apply more specific exclusion reasons. + status_out.AddExclusionReason( + CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + pairs_.clear(); + break; + } + + if (!CookieAttributeValueHasValidCharSet(pair.second)) { + // If the attribute value contains invalid characters, the whole + // cookie should be ignored. + status_out.AddExclusionReason( + CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + pairs_.clear(); + break; + } + + if (!CookieAttributeValueHasValidSize(pair.second)) { + // If the attribute value is too large, it should be ignored. + ignore_pair = true; + status_out.AddWarningReason( + CookieInclusionStatus::WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE); + } + } - // From RFC2109: "Attributes (names) (attr) are case-insensitive." - if (pair_num != 0) - pair.first = base::ToLowerASCII(pair.first); + if (!ignore_pair) { + pairs_.push_back(pair); + } + } else { + // Ignore cookies with neither name nor value. + if (pair_num == 0 && (pair.first.empty() && pair.second.empty())) { + // TODO(crbug.com/1228815): Apply more specific exclusion reasons. + status_out.AddExclusionReason( + CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + pairs_.clear(); + break; + } - // Ignore Set-Cookie directives contaning control characters. See - // http://crbug.com/238041. - if (!IsValidCookieAttributeValue(pair.first) || - !IsValidCookieAttributeValue(pair.second)) { - // TODO(crbug.com/1228815): Apply more specific exclusion reasons. - status_out.AddExclusionReason( - CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); - pairs_.clear(); - break; - } + // From RFC2109: "Attributes (names) (attr) are case-insensitive." + if (pair_num != 0) + pair.first = base::ToLowerASCII(pair.first); + + // Ignore Set-Cookie directives containing control characters. See + // http://crbug.com/238041. + if (!IsValidCookieAttributeValueLegacy(pair.first) || + !IsValidCookieAttributeValueLegacy(pair.second)) { + // TODO(crbug.com/1228815): Apply more specific exclusion reasons. + status_out.AddExclusionReason( + CookieInclusionStatus::EXCLUDE_FAILURE_TO_STORE); + pairs_.clear(); + break; + } - pairs_.push_back(pair); + pairs_.push_back(pair); + } // We've processed a token/value pair, we're either at the end of // the string or a ValueSeparator like ';', which we want to skip. @@ -509,14 +746,14 @@ void ParsedCookie::ParseTokenValuePairs(const std::string& cookie_line, // For metrics on name/value truncation. // - // If we stopped before the (real) end of the string and the leftovers include - // something other than terminating characters or whitespace then this cookie - // was truncated due to a terminating CTL. + // If we stopped before the (real) end of the string and the leftovers + // include something other than terminating characters or whitespace then + // this cookie was truncated due to a terminating CTL. // - // We only care about the name or value being truncated which means we only - // care about the first pair. If the pairs_.size() > 1 then that means the - // truncation happened after name/value parsing which we're not concerned - // about for metrics. + // We only care about the name or value being truncated which means we + // only care about the first pair. If the pairs_.size() > 1 then that + // means the truncation happened after name/value parsing which we're not + // concerned about for metrics. using std::string_literals::operator""s; if (pairs_.size() == 1 && cookie_line.find_first_not_of("\n\r\0 \t"s, end - start) != @@ -567,12 +804,26 @@ bool ParsedCookie::SetString(size_t* index, // produce a cookie with "path" attribute equal to "baz" (no spaces). We // should not produce cookie lines that parse to different key/value pairs! - // Inputs containing invalid characters should be ignored. - if (!IsValidCookieAttributeValue(untrusted_value)) - return false; + // Inputs containing invalid characters or attribute value strings that are + // too large should be ignored. Note that we check the attribute value size + // after removing leading and trailing whitespace. + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + if (!CookieAttributeValueHasValidCharSet(untrusted_value)) + return false; + + } else { + if (!IsValidCookieAttributeValueLegacy(untrusted_value)) + return false; + } // Use the same whitespace trimming code as the constructor. const std::string parsed_value = ParseValueString(untrusted_value); + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + if (!CookieAttributeValueHasValidSize(parsed_value)) + return false; + } + if (parsed_value.empty()) { ClearAttributePair(*index); return true; @@ -608,8 +859,8 @@ bool ParsedCookie::SetAttributePair(size_t* index, void ParsedCookie::ClearAttributePair(size_t index) { // The first pair (name/value of cookie at pairs_[0]) cannot be cleared. - // Cookie attributes that don't have a value at the moment, are represented - // with an index being equal to 0. + // Cookie attributes that don't have a value at the moment, are + // represented with an index being equal to 0. if (index == 0) return; diff --git a/chromium/net/cookies/parsed_cookie.h b/chromium/net/cookies/parsed_cookie.h index cd2897bf628..61377bfed70 100644 --- a/chromium/net/cookies/parsed_cookie.h +++ b/chromium/net/cookies/parsed_cookie.h @@ -23,11 +23,19 @@ class NET_EXPORT ParsedCookie { typedef std::pair<std::string, std::string> TokenValuePair; typedef std::vector<TokenValuePair> PairList; - // The maximum length of a cookie string we will try to parse + // The maximum length of a cookie string we will try to parse. + // TODO(crbug.com/1243852) Remove this when kExtraCookieValidityChecks + // gets removed (assuming the associated changes cause no issues). static const size_t kMaxCookieSize = 4096; + // The maximum length allowed for a cookie string's name/value pair. + static const size_t kMaxCookieNamePlusValueSize = 4096; + + // The maximum length allowed for each attribute value in a cookie string. + static const size_t kMaxCookieAttributeValueSize = 1024; + // Construct from a cookie string like "BLAH=1; path=/; domain=.google.com" - // Format is according to RFC 6265. Cookies with both name and value empty + // Format is according to RFC6265bis. Cookies with both name and value empty // will be considered invalid. // `status_out` is a nullable output param which will be populated with // informative exclusion reasons if the resulting ParsedCookie is invalid. @@ -35,6 +43,10 @@ class NET_EXPORT ParsedCookie { // is valid. explicit ParsedCookie(const std::string& cookie_line, CookieInclusionStatus* status_out = nullptr); + + ParsedCookie(const ParsedCookie&) = delete; + ParsedCookie& operator=(const ParsedCookie&) = delete; + ~ParsedCookie(); // You should not call any other methods except for SetName/SetValue on the @@ -46,13 +58,25 @@ class NET_EXPORT ParsedCookie { const std::string& Value() const { return pairs_[0].second; } bool HasPath() const { return path_index_ != 0; } - const std::string& Path() const { return pairs_[path_index_].second; } + const std::string& Path() const { + DCHECK(HasPath()); + return pairs_[path_index_].second; + } bool HasDomain() const { return domain_index_ != 0; } - const std::string& Domain() const { return pairs_[domain_index_].second; } + const std::string& Domain() const { + DCHECK(HasDomain()); + return pairs_[domain_index_].second; + } bool HasExpires() const { return expires_index_ != 0; } - const std::string& Expires() const { return pairs_[expires_index_].second; } + const std::string& Expires() const { + DCHECK(HasExpires()); + return pairs_[expires_index_].second; + } bool HasMaxAge() const { return maxage_index_ != 0; } - const std::string& MaxAge() const { return pairs_[maxage_index_].second; } + const std::string& MaxAge() const { + DCHECK(HasMaxAge()); + return pairs_[maxage_index_].second; + } bool IsSecure() const { return secure_index_ != 0; } bool IsHttpOnly() const { return httponly_index_ != 0; } // Also spits out an enum value representing the string given as the SameSite @@ -63,7 +87,10 @@ class NET_EXPORT ParsedCookie { bool IsSameParty() const { return same_party_index_ != 0; } bool IsPartitioned() const { return partitioned_index_ != 0; } bool HasTruncatedNameOrValue() const { return truncated_name_or_value_; } - + TruncatingCharacterInCookieStringType + GetTruncatingCharacterInCookieStringType() const { + return truncating_char_in_cookie_string_type_; + } // Returns the number of attributes, for example, returning 2 for: // "BLAH=hah; path=/; domain=.google.com" size_t NumberOfAttributes() const { return pairs_.size() - 1; } @@ -126,8 +153,30 @@ class NET_EXPORT ParsedCookie { // Returns |true| if the parsed version of |value| matches |value|. static bool ValueMatchesParsedValue(const std::string& value); - // Is the string valid as the value of a cookie attribute? - static bool IsValidCookieAttributeValue(const std::string& value); + // Is the string valid as the name of the cookie or as an attribute name? + static bool IsValidCookieName(const std::string& name); + + // Is the string valid as the value of the cookie? + static bool IsValidCookieValue(const std::string& value); + + // Is the string free of any characters not allowed in attribute values? + static bool CookieAttributeValueHasValidCharSet(const std::string& value); + + // Is the string less than the size limits set for attribute values? + static bool CookieAttributeValueHasValidSize(const std::string& value); + + // Is the string valid as a cookie attribute value? (only checks the character + // set - no length checks performed) + static bool IsValidCookieAttributeValueLegacy(const std::string& value); + + // Returns `true` if the name and value combination are valid. Calls + // IsValidCookieName() and IsValidCookieValue() on `name` and `value` + // respectively, in addition to checking that the sum of the two doesn't + // exceed size limits specified in RFC6265bis. + static bool IsValidCookieNameValuePair( + const std::string& name, + const std::string& value, + CookieInclusionStatus* status_out = nullptr); private: void ParseTokenValuePairs(const std::string& cookie_line, @@ -177,8 +226,8 @@ class NET_EXPORT ParsedCookie { // For metrics on cookie name/value truncation. See usage at the bottom of // `ParseTokenValuePairs()` for more details. bool truncated_name_or_value_ = false; - - DISALLOW_COPY_AND_ASSIGN(ParsedCookie); + TruncatingCharacterInCookieStringType truncating_char_in_cookie_string_type_ = + TruncatingCharacterInCookieStringType::kTruncatingCharNone; }; } // namespace net diff --git a/chromium/net/cookies/parsed_cookie_unittest.cc b/chromium/net/cookies/parsed_cookie_unittest.cc index c511afaab16..fe8ccbac49c 100644 --- a/chromium/net/cookies/parsed_cookie_unittest.cc +++ b/chromium/net/cookies/parsed_cookie_unittest.cc @@ -4,7 +4,10 @@ #include <string> +#include "base/test/scoped_feature_list.h" +#include "net/base/features.h" #include "net/cookies/cookie_constants.h" +#include "net/cookies/cookie_inclusion_status.h" #include "net/cookies/parsed_cookie.h" #include "testing/gtest/include/gtest/gtest.h" @@ -145,6 +148,12 @@ TEST(ParsedCookieTest, ParseValueStrings) { } TEST(ParsedCookieTest, TestQuoted) { + // Ensure that kExtraCookieValidityChecks is always enabled so that the + // additional test cases get run. + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState(features::kExtraCookieValidityChecks, + true); + // These are some quoting cases which the major browsers all // handle differently. I've tested Internet Explorer 6, Opera 9.6, // Firefox 3, and Safari Windows 3.2.1. We originally tried to match @@ -184,6 +193,11 @@ TEST(ParsedCookieTest, TestQuoted) { EXPECT_EQ("aBc", pc.Name()); EXPECT_EQ(test.expected, pc.Value()); + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + EXPECT_TRUE(pc.SetValue(pc.Value())); + EXPECT_EQ(test.expected, pc.Value()); + } + // If a path was quoted, the path attribute keeps the quotes. This will // make the cookie effectively useless, but path parameters aren't supposed // to be quoted. Bug 1261605. @@ -248,6 +262,26 @@ TEST(ParsedCookieTest, MissingName) { EXPECT_EQ("ABC", pc.Value()); EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority()); EXPECT_EQ(0U, pc.NumberOfAttributes()); + + // Ensure that a preceding equal sign is emitted in the cookie line. + + // Note that this goes against what's specified in RFC6265bis and differs from + // how CanonicalCookie produces cookie lines. As currently written (draft 9), + // the spec says that a cookie with an empty name should not prepend an '=' + // character when writing out the cookie line, but in the case where the value + // already contains an equal sign the cookie line will be parsed incorrectly + // on the receiving end. ParsedCookie.ToCookieLine is only used by the + // extensions API to feed modified cookies into a network request for + // reparsing, though, so here it's more important that the values always + // deserialize correctly than conform to the spec + ParsedCookie pc2("=ABC"); + EXPECT_EQ("=ABC", pc2.ToCookieLine()); + EXPECT_TRUE(pc2.SetValue("param=value")); + EXPECT_EQ("=param=value", pc2.ToCookieLine()); + ParsedCookie pc3("=param=value"); + EXPECT_EQ("", pc3.Name()); + EXPECT_EQ("param=value", pc3.Value()); + EXPECT_EQ("=param=value", pc3.ToCookieLine()); } TEST(ParsedCookieTest, MissingValue) { @@ -259,6 +293,10 @@ TEST(ParsedCookieTest, MissingValue) { EXPECT_EQ("/wee", pc.Path()); EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority()); EXPECT_EQ(1U, pc.NumberOfAttributes()); + + // Ensure that a trailing equal sign is emitted in the cookie line + ParsedCookie pc2("ABC="); + EXPECT_EQ("ABC=", pc2.ToCookieLine()); } TEST(ParsedCookieTest, Whitespace) { @@ -334,16 +372,229 @@ TEST(ParsedCookieTest, LotsOfPairs) { } } -// TODO(erikwright): some better test cases for invalid cookies. -TEST(ParsedCookieTest, InvalidTooLong) { +TEST(ParsedCookieTest, EnforceSizeConstraintsLegacy) { + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState(features::kExtraCookieValidityChecks, + false); + CookieInclusionStatus status; + std::string maxstr; maxstr.resize(ParsedCookie::kMaxCookieSize, 'a'); - ParsedCookie pc1(maxstr); + ParsedCookie pc1(maxstr, &status); EXPECT_TRUE(pc1.IsValid()); + EXPECT_TRUE(status.IsInclude()); - ParsedCookie pc2(maxstr + "A"); + ParsedCookie pc2(maxstr + "A", &status); EXPECT_FALSE(pc2.IsValid()); + EXPECT_TRUE(status.HasOnlyExclusionReason( + CookieInclusionStatus::ExclusionReason:: + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)); +} + +TEST(ParsedCookieTest, EnforceSizeConstraints) { + // Ensure that kExtraCookieValidityChecks is always enabled so that the + // additional test cases get run. + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState(features::kExtraCookieValidityChecks, + true); + CookieInclusionStatus status; + + // Create maximum size and one-less-than-maximum size name and value + // strings for testing. + std::string max_name(ParsedCookie::kMaxCookieNamePlusValueSize, 'a'); + std::string max_value(ParsedCookie::kMaxCookieNamePlusValueSize, 'b'); + std::string almost_max_name = max_name.substr(1, std::string::npos); + std::string almost_max_value = max_value.substr(1, std::string::npos); + + // Test name + value size limits enforced by the constructor. + ParsedCookie pc1(max_name + "="); + EXPECT_TRUE(pc1.IsValid()); + EXPECT_EQ(max_name, pc1.Name()); + + ParsedCookie pc2(max_name + "=; path=/foo;"); + EXPECT_TRUE(pc2.IsValid()); + EXPECT_EQ(max_name, pc2.Name()); + + ParsedCookie pc3(max_name + "X=", &status); + EXPECT_FALSE(pc3.IsValid()); + EXPECT_TRUE(status.HasOnlyExclusionReason( + CookieInclusionStatus::ExclusionReason:: + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)); + + ParsedCookie pc4("=" + max_value); + EXPECT_TRUE(pc4.IsValid()); + EXPECT_EQ(max_value, pc4.Value()); + + ParsedCookie pc5("=" + max_value + "; path=/foo;"); + EXPECT_TRUE(pc5.IsValid()); + EXPECT_EQ(max_value, pc5.Value()); + + ParsedCookie pc6("=" + max_value + "X", &status); + EXPECT_FALSE(pc6.IsValid()); + EXPECT_TRUE(status.HasOnlyExclusionReason( + CookieInclusionStatus::ExclusionReason:: + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)); + + ParsedCookie pc7(almost_max_name + "=x"); + EXPECT_TRUE(pc7.IsValid()); + EXPECT_EQ(almost_max_name, pc7.Name()); + EXPECT_EQ("x", pc7.Value()); + + ParsedCookie pc8(almost_max_name + "=x; path=/foo;"); + EXPECT_TRUE(pc8.IsValid()); + EXPECT_EQ(almost_max_name, pc8.Name()); + EXPECT_EQ("x", pc8.Value()); + + ParsedCookie pc9(almost_max_name + "=xX", &status); + EXPECT_FALSE(pc9.IsValid()); + EXPECT_TRUE(status.HasOnlyExclusionReason( + CookieInclusionStatus::ExclusionReason:: + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)); + + ParsedCookie pc10("x=" + almost_max_value); + EXPECT_TRUE(pc10.IsValid()); + EXPECT_EQ("x", pc10.Name()); + EXPECT_EQ(almost_max_value, pc10.Value()); + + ParsedCookie pc11("x=" + almost_max_value + "; path=/foo;"); + EXPECT_TRUE(pc11.IsValid()); + EXPECT_EQ("x", pc11.Name()); + EXPECT_EQ(almost_max_value, pc11.Value()); + + ParsedCookie pc12("xX=" + almost_max_value, &status); + EXPECT_FALSE(pc12.IsValid()); + EXPECT_TRUE(status.HasOnlyExclusionReason( + CookieInclusionStatus::ExclusionReason:: + EXCLUDE_NAME_VALUE_PAIR_EXCEEDS_MAX_SIZE)); + + // Test attribute value size limits enforced by the constructor. + std::string almost_max_path(ParsedCookie::kMaxCookieAttributeValueSize - 1, + 'c'); + std::string max_path = "/" + almost_max_path; + std::string too_long_path = "/X" + almost_max_path; + + ParsedCookie pc20("name=value; path=" + max_path); + EXPECT_TRUE(pc20.IsValid()); + EXPECT_TRUE(pc20.HasPath()); + EXPECT_EQ("/" + almost_max_path, pc20.Path()); + + ParsedCookie pc21("name=value; path=" + too_long_path, &status); + EXPECT_TRUE(pc21.IsValid()); + EXPECT_FALSE(pc21.HasPath()); + EXPECT_TRUE(status.HasWarningReason( + CookieInclusionStatus::WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE)); + + // NOTE: max_domain is based on the max attribute value as defined in + // RFC6525bis, but this is larger than what is recommended by RFC1123. + // In theory some browsers could restrict domains to that smaller size, + // but ParsedCookie doesn't. + std::string max_domain(ParsedCookie::kMaxCookieAttributeValueSize, 'd'); + max_domain.replace(ParsedCookie::kMaxCookieAttributeValueSize - 4, 4, ".com"); + std::string too_long_domain = "x" + max_domain; + + ParsedCookie pc30("name=value; domain=" + max_domain); + EXPECT_TRUE(pc30.IsValid()); + EXPECT_TRUE(pc30.HasDomain()); + EXPECT_EQ(max_domain, pc30.Domain()); + + ParsedCookie pc31("name=value; domain=" + too_long_domain); + EXPECT_TRUE(pc31.IsValid()); + EXPECT_FALSE(pc31.HasDomain()); + EXPECT_TRUE(status.HasWarningReason( + CookieInclusionStatus::WARN_ATTRIBUTE_VALUE_EXCEEDS_MAX_SIZE)); + + std::string pc40_suffix = "; domain=example.com"; + + ParsedCookie pc40("a=b" + pc40_suffix); + EXPECT_TRUE(pc40.IsValid()); + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + // Test name + value size limits enforced by SetName / SetValue + EXPECT_FALSE(pc40.SetName(max_name)); + EXPECT_EQ("a=b" + pc40_suffix, pc40.ToCookieLine()); + EXPECT_TRUE(pc40.IsValid()); + + EXPECT_FALSE(pc40.SetValue(max_value)); + EXPECT_EQ("a=b" + pc40_suffix, pc40.ToCookieLine()); + EXPECT_TRUE(pc40.IsValid()); + + EXPECT_TRUE(pc40.SetName(almost_max_name)); + EXPECT_EQ(almost_max_name + "=b" + pc40_suffix, pc40.ToCookieLine()); + EXPECT_TRUE(pc40.IsValid()); + + EXPECT_FALSE(pc40.SetValue("xX")); + EXPECT_EQ(almost_max_name + "=b" + pc40_suffix, pc40.ToCookieLine()); + EXPECT_TRUE(pc40.IsValid()); + + EXPECT_TRUE(pc40.SetName("a")); + EXPECT_TRUE(pc40.SetValue(almost_max_value)); + EXPECT_EQ("a=" + almost_max_value + pc40_suffix, pc40.ToCookieLine()); + EXPECT_TRUE(pc40.IsValid()); + + EXPECT_FALSE(pc40.SetName("xX")); + EXPECT_EQ("a=" + almost_max_value + pc40_suffix, pc40.ToCookieLine()); + EXPECT_TRUE(pc40.IsValid()); + } + + std::string lots_of_spaces(ParsedCookie::kMaxCookieNamePlusValueSize, ' '); + std::string test_str = "test"; + std::string padded_test_str = lots_of_spaces + test_str + lots_of_spaces; + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + // Ensure that leading/trailing whitespace gets stripped before the length + // calculations are enforced. + ParsedCookie pc41("name=value"); + EXPECT_TRUE(pc41.SetName(padded_test_str)); + EXPECT_TRUE(pc41.SetValue(padded_test_str)); + EXPECT_EQ(test_str, pc41.Name()); + EXPECT_EQ(test_str, pc41.Value()); + } + + std::string name_equals_value = "name=value"; + ParsedCookie pc50(name_equals_value); + + EXPECT_TRUE(pc50.SetPath(max_path)); + EXPECT_EQ(pc50.Path(), max_path); + EXPECT_EQ(name_equals_value + "; path=" + max_path, pc50.ToCookieLine()); + EXPECT_TRUE(pc50.IsValid()); + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + // Test attribute value size limits enforced by SetPath + EXPECT_FALSE(pc50.SetPath(too_long_path)); + EXPECT_EQ(pc50.Path(), max_path); + EXPECT_EQ(name_equals_value + "; path=" + max_path, pc50.ToCookieLine()); + EXPECT_TRUE(pc50.IsValid()); + } + + std::string test_path = "/test"; + std::string padded_test_path = lots_of_spaces + test_path + lots_of_spaces; + + EXPECT_TRUE(pc50.SetPath(padded_test_path)); + EXPECT_EQ(test_path, pc50.Path()); + + ParsedCookie pc51(name_equals_value); + + EXPECT_TRUE(pc51.SetDomain(max_domain)); + EXPECT_EQ(pc51.Domain(), max_domain); + EXPECT_EQ(name_equals_value + "; domain=" + max_domain, pc51.ToCookieLine()); + EXPECT_TRUE(pc51.IsValid()); + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + // Test attribute value size limits enforced by SetDomain + EXPECT_FALSE(pc51.SetDomain(too_long_domain)); + EXPECT_EQ(pc51.Domain(), max_domain); + EXPECT_EQ(name_equals_value + "; domain=" + max_domain, + pc51.ToCookieLine()); + EXPECT_TRUE(pc51.IsValid()); + } + + std::string test_domain = "example.com"; + std::string padded_test_domain = + lots_of_spaces + test_domain + lots_of_spaces; + + EXPECT_TRUE(pc51.SetDomain(padded_test_domain)); + EXPECT_EQ(test_domain, pc51.Domain()); } TEST(ParsedCookieTest, EmbeddedTerminator) { @@ -388,53 +639,139 @@ TEST(ParsedCookieTest, SerializeCookieLine) { } TEST(ParsedCookieTest, SetNameAndValue) { - ParsedCookie cookie("a=b"); - EXPECT_TRUE(cookie.IsValid()); - EXPECT_TRUE(cookie.SetDomain("foobar.com")); - EXPECT_TRUE(cookie.SetName("name")); - EXPECT_TRUE(cookie.SetValue("value")); - EXPECT_EQ("name=value; domain=foobar.com", cookie.ToCookieLine()); - EXPECT_TRUE(cookie.IsValid()); - - // We don't test - // ParsedCookie invalid("@foo=bar"); - // EXPECT_FALSE(invalid.IsValid()); - // here because we are slightly more tolerant to invalid cookie names and - // values that are set by webservers. We only enforce a correct name and - // value if set via SetName() and SetValue(). + for (const bool toggle : {false, true}) { + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState( + features::kExtraCookieValidityChecks, toggle); + + ParsedCookie cookie("a=b"); + EXPECT_TRUE(cookie.IsValid()); + EXPECT_TRUE(cookie.SetDomain("foobar.com")); + EXPECT_TRUE(cookie.SetName("name")); + EXPECT_TRUE(cookie.SetValue("value")); + EXPECT_EQ("name=value; domain=foobar.com", cookie.ToCookieLine()); + EXPECT_TRUE(cookie.IsValid()); + + ParsedCookie pc("name=value"); + EXPECT_TRUE(pc.IsValid()); - ParsedCookie pc("name=value"); - EXPECT_TRUE(pc.IsValid()); + // Set invalid name / value. + EXPECT_FALSE(pc.SetName("foo\nbar")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - // Set invalid name / value. - EXPECT_FALSE(pc.SetName("@foobar")); - EXPECT_EQ("name=value", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + EXPECT_FALSE(pc.SetName("foo\rbar")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - EXPECT_FALSE(pc.SetValue("foo bar")); - EXPECT_EQ("name=value", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + EXPECT_FALSE(pc.SetValue(std::string("foo\0bar", 7))); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - EXPECT_FALSE(pc.SetValue("\"foobar")); - EXPECT_EQ("name=value", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + // Set previously invalid name / value. + EXPECT_TRUE(pc.SetName("@foobar")); + EXPECT_EQ("@foobar=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_TRUE(pc.SetName("foo bar")); + EXPECT_EQ("foo bar=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_TRUE(pc.SetName("\"foobar")); + EXPECT_EQ("\"foobar=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_TRUE(pc.SetValue("foo bar")); + EXPECT_EQ("\"foobar=foo bar", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_TRUE(pc.SetValue("\"foobar")); + EXPECT_EQ("\"foobar=\"foobar", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_TRUE(pc.SetName(" foo bar ")); + EXPECT_EQ("foo bar=\"foobar", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_TRUE(pc.SetValue(" foo bar ")); + EXPECT_EQ("foo bar=foo bar", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + } else { + // Set invalid name / value. + EXPECT_FALSE(pc.SetName("@foobar")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetValue("foo bar")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetValue("\"foobar")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetName(" foo bar ")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetValue(" foo bar ")); + EXPECT_EQ("name=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + } + + // Set valid name / value. + EXPECT_TRUE(pc.SetValue("value")); + EXPECT_TRUE(pc.SetName(std::string())); + EXPECT_EQ("=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - // Set valid name / value - EXPECT_TRUE(pc.SetName(std::string())); - EXPECT_EQ("=value", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + EXPECT_TRUE(pc.SetName("test")); + EXPECT_EQ("test=value", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - EXPECT_TRUE(pc.SetName("test")); - EXPECT_EQ("test=value", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + EXPECT_TRUE(pc.SetValue("\"foobar\"")); + EXPECT_EQ("test=\"foobar\"", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - EXPECT_TRUE(pc.SetValue("\"foobar\"")); - EXPECT_EQ("test=\"foobar\"", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + EXPECT_TRUE(pc.SetValue(std::string())); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); - EXPECT_TRUE(pc.SetValue(std::string())); - EXPECT_EQ("test=", pc.ToCookieLine()); - EXPECT_TRUE(pc.IsValid()); + // Ensure that failure occurs when trying to set a name containing '='. + EXPECT_FALSE(pc.SetName("invalid=name")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + // Ensure that trying to set a name containing ';' fails. + EXPECT_FALSE(pc.SetName("invalid;name")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetValue("invalid;value")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + // Ensure tab characters are treated as control characters. + // TODO(crbug.com/1233602) Update this such that tab characters are allowed + // and are handled correctly. + EXPECT_FALSE(pc.SetName("\tinvalid\t")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetValue("\tinvalid\t")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetName("na\tme")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + + EXPECT_FALSE(pc.SetValue("val\tue")); + EXPECT_EQ("test=", pc.ToCookieLine()); + EXPECT_TRUE(pc.IsValid()); + } } TEST(ParsedCookieTest, SetAttributes) { @@ -893,41 +1230,84 @@ TEST(ParsedCookieTest, InvalidNonAlphanumericChars) { } TEST(ParsedCookieTest, ValidNonAlphanumericChars) { - // Note that some of these words are pasted backwords thanks to poor vim bidi - // support. This should not affect the tests, however. - const char pc1_literal[] = "name=العربية"; - const char pc2_literal[] = "name=普通話"; - const char pc3_literal[] = "name=ภาษาไทย"; - const char pc4_literal[] = "name=עִבְרִית"; - const char pc5_literal[] = "العربية=value"; - const char pc6_literal[] = "普通話=value"; - const char pc7_literal[] = "ภาษาไทย=value"; - const char pc8_literal[] = "עִבְרִית=value"; - ParsedCookie pc1(pc1_literal); - ParsedCookie pc2(pc2_literal); - ParsedCookie pc3(pc3_literal); - ParsedCookie pc4(pc4_literal); - ParsedCookie pc5(pc5_literal); - ParsedCookie pc6(pc6_literal); - ParsedCookie pc7(pc7_literal); - ParsedCookie pc8(pc8_literal); - - EXPECT_TRUE(pc1.IsValid()); - EXPECT_EQ(pc1_literal, pc1.ToCookieLine()); - EXPECT_TRUE(pc2.IsValid()); - EXPECT_EQ(pc2_literal, pc2.ToCookieLine()); - EXPECT_TRUE(pc3.IsValid()); - EXPECT_EQ(pc3_literal, pc3.ToCookieLine()); - EXPECT_TRUE(pc4.IsValid()); - EXPECT_EQ(pc4_literal, pc4.ToCookieLine()); - EXPECT_TRUE(pc5.IsValid()); - EXPECT_EQ(pc5_literal, pc5.ToCookieLine()); - EXPECT_TRUE(pc6.IsValid()); - EXPECT_EQ(pc6_literal, pc6.ToCookieLine()); - EXPECT_TRUE(pc7.IsValid()); - EXPECT_EQ(pc7_literal, pc7.ToCookieLine()); - EXPECT_TRUE(pc8.IsValid()); - EXPECT_EQ(pc8_literal, pc8.ToCookieLine()); + // Ensure that kExtraCookieValidityChecks is always enabled so that the + // additional test cases get run. + for (const bool toggle : {false, true}) { + base::test::ScopedFeatureList scope_feature_list; + scope_feature_list.InitWithFeatureState( + features::kExtraCookieValidityChecks, toggle); + + // Note that some of these words are pasted backwords thanks to poor vim + // bidi support. This should not affect the tests, however. + const char pc1_literal[] = "name=العربية"; + const char pc2_literal[] = "name=普通話"; + const char pc3_literal[] = "name=ภาษาไทย"; + const char pc4_literal[] = "name=עִבְרִית"; + const char pc5_literal[] = "العربية=value"; + const char pc6_literal[] = "普通話=value"; + const char pc7_literal[] = "ภาษาไทย=value"; + const char pc8_literal[] = "עִבְרִית=value"; + const char pc9_literal[] = "@foo=bar"; + + ParsedCookie pc1(pc1_literal); + ParsedCookie pc2(pc2_literal); + ParsedCookie pc3(pc3_literal); + ParsedCookie pc4(pc4_literal); + ParsedCookie pc5(pc5_literal); + ParsedCookie pc6(pc6_literal); + ParsedCookie pc7(pc7_literal); + ParsedCookie pc8(pc8_literal); + ParsedCookie pc9(pc9_literal); + + EXPECT_TRUE(pc1.IsValid()); + EXPECT_EQ(pc1_literal, pc1.ToCookieLine()); + EXPECT_TRUE(pc2.IsValid()); + EXPECT_EQ(pc2_literal, pc2.ToCookieLine()); + EXPECT_TRUE(pc3.IsValid()); + EXPECT_EQ(pc3_literal, pc3.ToCookieLine()); + EXPECT_TRUE(pc4.IsValid()); + EXPECT_EQ(pc4_literal, pc4.ToCookieLine()); + EXPECT_TRUE(pc5.IsValid()); + EXPECT_EQ(pc5_literal, pc5.ToCookieLine()); + EXPECT_TRUE(pc6.IsValid()); + EXPECT_EQ(pc6_literal, pc6.ToCookieLine()); + EXPECT_TRUE(pc7.IsValid()); + EXPECT_EQ(pc7_literal, pc7.ToCookieLine()); + EXPECT_TRUE(pc8.IsValid()); + EXPECT_EQ(pc8_literal, pc8.ToCookieLine()); + EXPECT_TRUE(pc9.IsValid()); + EXPECT_EQ(pc9_literal, pc9.ToCookieLine()); + + if (base::FeatureList::IsEnabled(features::kExtraCookieValidityChecks)) { + EXPECT_TRUE(pc1.SetValue(pc1.Value())); + EXPECT_EQ(pc1_literal, pc1.ToCookieLine()); + EXPECT_TRUE(pc1.IsValid()); + EXPECT_TRUE(pc2.SetValue(pc2.Value())); + EXPECT_EQ(pc2_literal, pc2.ToCookieLine()); + EXPECT_TRUE(pc2.IsValid()); + EXPECT_TRUE(pc3.SetValue(pc3.Value())); + EXPECT_EQ(pc3_literal, pc3.ToCookieLine()); + EXPECT_TRUE(pc3.IsValid()); + EXPECT_TRUE(pc4.SetValue(pc4.Value())); + EXPECT_EQ(pc4_literal, pc4.ToCookieLine()); + EXPECT_TRUE(pc4.IsValid()); + EXPECT_TRUE(pc5.SetName(pc5.Name())); + EXPECT_EQ(pc5_literal, pc5.ToCookieLine()); + EXPECT_TRUE(pc5.IsValid()); + EXPECT_TRUE(pc6.SetName(pc6.Name())); + EXPECT_EQ(pc6_literal, pc6.ToCookieLine()); + EXPECT_TRUE(pc6.IsValid()); + EXPECT_TRUE(pc7.SetName(pc7.Name())); + EXPECT_EQ(pc7_literal, pc7.ToCookieLine()); + EXPECT_TRUE(pc7.IsValid()); + EXPECT_TRUE(pc8.SetName(pc8.Name())); + EXPECT_EQ(pc8_literal, pc8.ToCookieLine()); + EXPECT_TRUE(pc8.IsValid()); + EXPECT_TRUE(pc9.SetName(pc9.Name())); + EXPECT_EQ(pc9_literal, pc9.ToCookieLine()); + EXPECT_TRUE(pc9.IsValid()); + } + } } TEST(ParsedCookieTest, TruncatedNameOrValue) { @@ -977,4 +1357,52 @@ TEST(ParsedCookieTest, TruncatedNameOrValue) { } } +TEST(ParsedCookieTest, TruncatingCharInCookieLine) { + using std::string_literals::operator""s; + + // Test scenarios where a control char may appear at start, middle and end of + // a cookie line. Control char array with NULL (\x0), CR (\xD), LF (xA), + // HT (\x9) and BS (\x1B). + const struct { + const char ctlChar; + const TruncatingCharacterInCookieStringType + expectedTruncatingCharInCookieStringType; + } kTests[] = { + {'\x0', TruncatingCharacterInCookieStringType::kTruncatingCharNull}, + {'\xD', TruncatingCharacterInCookieStringType::kTruncatingCharNewline}, + {'\xA', TruncatingCharacterInCookieStringType::kTruncatingCharLineFeed}, + {'\x9', TruncatingCharacterInCookieStringType::kTruncatingCharNone}, + {'\x1B', TruncatingCharacterInCookieStringType::kTruncatingCharNone}}; + + for (const auto& test : kTests) { + std::string ctl_string(1, test.ctlChar); + std::string ctl_at_start_cookie_string = ctl_string + "foo=bar"s; + ParsedCookie ctl_at_start_cookie(ctl_at_start_cookie_string); + EXPECT_EQ(ctl_at_start_cookie.GetTruncatingCharacterInCookieStringType(), + test.expectedTruncatingCharInCookieStringType); + + std::string ctl_at_middle_cookie_string = + "foo=bar;"s + ctl_string + "secure"s; + ParsedCookie ctl_at_middle_cookie(ctl_at_start_cookie_string); + EXPECT_EQ(ctl_at_middle_cookie.GetTruncatingCharacterInCookieStringType(), + test.expectedTruncatingCharInCookieStringType); + + std::string ctl_at_end_cookie_string = + "foo=bar;"s + "secure;"s + ctl_string; + ParsedCookie ctl_at_end_cookie(ctl_at_start_cookie_string); + EXPECT_EQ(ctl_at_end_cookie.GetTruncatingCharacterInCookieStringType(), + test.expectedTruncatingCharInCookieStringType); + } + // Test if there are multiple control characters that terminate. + std::string ctls_cookie_string = "foo=bar;\xA\xD"s; + ParsedCookie ctls_cookie(ctls_cookie_string); + EXPECT_EQ(ctls_cookie.GetTruncatingCharacterInCookieStringType(), + TruncatingCharacterInCookieStringType::kTruncatingCharLineFeed); + // Test with no control characters. + std::string cookie_string = "foo=bar;"s; + ParsedCookie cookie(cookie_string); + EXPECT_EQ(cookie.GetTruncatingCharacterInCookieStringType(), + TruncatingCharacterInCookieStringType::kTruncatingCharNone); +} + } // namespace net diff --git a/chromium/net/cookies/site_for_cookies.cc b/chromium/net/cookies/site_for_cookies.cc index 977a0b7c7af..5907339ec9b 100644 --- a/chromium/net/cookies/site_for_cookies.cc +++ b/chromium/net/cookies/site_for_cookies.cc @@ -4,6 +4,8 @@ #include "net/cookies/site_for_cookies.h" +#include <tuple> + #include "base/strings/strcat.h" #include "base/strings/string_util.h" #include "net/base/registry_controlled_domains/registry_controlled_domain.h" @@ -204,4 +206,22 @@ void SiteForCookies::MarkIfCrossScheme(const SchemefulSite& other) { schemefully_same_ = false; } +bool operator<(const SiteForCookies& lhs, const SiteForCookies& rhs) { + // Similar to IsEquivalent(), if they're both null then they're equivalent + // and therefore `lhs` is not < `rhs`. + if (lhs.IsNull() && rhs.IsNull()) + return false; + + // If only `lhs` is null then it's always < `rhs`. + if (lhs.IsNull()) + return true; + + // If only `rhs` is null then `lhs` is not < `rhs`. + if (rhs.IsNull()) + return false; + + // Otherwise neither are null and we need to compare the `site_`s. + return lhs.site_ < rhs.site_; +} + } // namespace net diff --git a/chromium/net/cookies/site_for_cookies.h b/chromium/net/cookies/site_for_cookies.h index d0379b23522..9db7b5cda80 100644 --- a/chromium/net/cookies/site_for_cookies.h +++ b/chromium/net/cookies/site_for_cookies.h @@ -169,6 +169,11 @@ class NET_EXPORT SiteForCookies { // this function to return true. bool IsNull() const; + // Allows SiteForCookies to be used as a key in STL (for example, a std::set + // or std::map). + NET_EXPORT friend bool operator<(const SiteForCookies& lhs, + const SiteForCookies& rhs); + private: FRIEND_TEST_ALL_PREFIXES(SiteForCookiesTest, SameScheme); FRIEND_TEST_ALL_PREFIXES(SiteForCookiesTest, SameSchemeOpaque); diff --git a/chromium/net/cookies/site_for_cookies_unittest.cc b/chromium/net/cookies/site_for_cookies_unittest.cc index 69526f6de35..bf20aefc876 100644 --- a/chromium/net/cookies/site_for_cookies_unittest.cc +++ b/chromium/net/cookies/site_for_cookies_unittest.cc @@ -602,4 +602,30 @@ TEST(SiteForCookiesTest, SameSchemeOpaque) { } } +// Quick correctness check that the less-than operator works as expected. +TEST(SiteForCookiesTest, LessThan) { + SiteForCookies first = SiteForCookies::FromUrl(GURL("https://example.com")); + SiteForCookies second = + SiteForCookies::FromUrl(GURL("https://examplelonger.com")); + SiteForCookies third = + SiteForCookies::FromUrl(GURL("https://examplelongerstill.com")); + + SiteForCookies null1 = SiteForCookies(); + SiteForCookies null2 = + SiteForCookies::FromUrl(GURL("https://examplelongerstillstill.com")); + null2.SetSchemefullySameForTesting(false); + + EXPECT_LT(first, second); + EXPECT_LT(second, third); + EXPECT_LT(first, third); + EXPECT_LT(null1, first); + EXPECT_LT(null2, first); + + EXPECT_FALSE(second < first); + EXPECT_FALSE(first < null1); + EXPECT_FALSE(first < null2); + EXPECT_FALSE(null1 < null2); + EXPECT_FALSE(null2 < null1); +} + } // namespace net diff --git a/chromium/net/cookies/static_cookie_policy.h b/chromium/net/cookies/static_cookie_policy.h index 7f3042cc0da..2fa191b2a04 100644 --- a/chromium/net/cookies/static_cookie_policy.h +++ b/chromium/net/cookies/static_cookie_policy.h @@ -33,6 +33,9 @@ class NET_EXPORT StaticCookiePolicy { explicit StaticCookiePolicy(Type type) : type_(type) {} + StaticCookiePolicy(const StaticCookiePolicy&) = delete; + StaticCookiePolicy& operator=(const StaticCookiePolicy&) = delete; + // Sets the current policy to enforce. This should be called when the user's // preferences change. void set_type(Type type) { type_ = type; } @@ -45,8 +48,6 @@ class NET_EXPORT StaticCookiePolicy { private: Type type_; - - DISALLOW_COPY_AND_ASSIGN(StaticCookiePolicy); }; } // namespace net diff --git a/chromium/net/cookies/test_cookie_access_delegate.h b/chromium/net/cookies/test_cookie_access_delegate.h index 8a0320ebd53..d04e9e6ac91 100644 --- a/chromium/net/cookies/test_cookie_access_delegate.h +++ b/chromium/net/cookies/test_cookie_access_delegate.h @@ -22,6 +22,10 @@ class SchemefulSite; class TestCookieAccessDelegate : public CookieAccessDelegate { public: TestCookieAccessDelegate(); + + TestCookieAccessDelegate(const TestCookieAccessDelegate&) = delete; + TestCookieAccessDelegate& operator=(const TestCookieAccessDelegate&) = delete; + ~TestCookieAccessDelegate() override; // CookieAccessDelegate implementation: @@ -68,8 +72,6 @@ class TestCookieAccessDelegate : public CookieAccessDelegate { std::map<std::string, bool> ignore_samesite_restrictions_schemes_; base::flat_map<net::SchemefulSite, std::set<net::SchemefulSite>> first_party_sets_; - - DISALLOW_COPY_AND_ASSIGN(TestCookieAccessDelegate); }; } // namespace net |