diff options
author | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2021-09-01 11:08:40 +0200 |
---|---|---|
committer | Allan Sandfeld Jensen <allan.jensen@qt.io> | 2021-10-01 12:16:21 +0000 |
commit | 03c549e0392f92c02536d3f86d5e1d8dfa3435ac (patch) | |
tree | fe49d170a929b34ba82cd10db1a0bd8e3760fa4b /chromium/net/cookies | |
parent | 5d013f5804a0d91fcf6c626b2d6fb6eca5c845b0 (diff) | |
download | qtwebengine-chromium-03c549e0392f92c02536d3f86d5e1d8dfa3435ac.tar.gz |
BASELINE: Update Chromium to 91.0.4472.160
Change-Id: I0def1f08a2412aeed79a9ab95dd50eb5c3f65f31
Reviewed-by: Allan Sandfeld Jensen <allan.jensen@qt.io>
Diffstat (limited to 'chromium/net/cookies')
21 files changed, 961 insertions, 315 deletions
diff --git a/chromium/net/cookies/README.md b/chromium/net/cookies/README.md index 73cd8392244..8b43af9a67e 100644 --- a/chromium/net/cookies/README.md +++ b/chromium/net/cookies/README.md @@ -76,9 +76,10 @@ name and value. The `ParsedCookie` is then converted into a `CanonicalCookie`. This is the main data type representing cookies. Any cookie consumer that does not deal directly with HTTP headers operates on `CanonicalCookie`s. A `CanonicalCookie` has some -additional guarantees of validity over a `ParsedCookie`, such as sane expiration -times, valid domain and path attributes, etc. Once a `CanonicalCookie` is -created, you will almost never see a `ParsedCookie` used for anything else. +additional guarantees of validity over a `ParsedCookie`, such as valid +expiration times, valid domain and path attributes, etc. Once a +`CanonicalCookie` is created, you will almost never see a `ParsedCookie` used +for anything else. If a valid `CanonicalCookie` could not be created (due to some illegal syntax, inconsistent attribute values, or other circumstances preventing parsing), then diff --git a/chromium/net/cookies/canonical_cookie.cc b/chromium/net/cookies/canonical_cookie.cc index 9168c32f3c1..8af2c1b14b4 100644 --- a/chromium/net/cookies/canonical_cookie.cc +++ b/chromium/net/cookies/canonical_cookie.cc @@ -44,6 +44,7 @@ #include "net/cookies/canonical_cookie.h" +#include <limits> #include <sstream> #include <utility> @@ -54,6 +55,7 @@ #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/strings/strcat.h" +#include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "net/base/features.h" @@ -334,15 +336,30 @@ Time CanonicalCookie::CanonExpiration(const ParsedCookie& pc, const Time& current, const Time& server_time) { // First, try the Max-Age attribute. - uint64_t max_age = 0; - if (pc.HasMaxAge() && -#ifdef COMPILER_MSVC - sscanf_s( -#else - sscanf( -#endif - pc.MaxAge().c_str(), " %" PRIu64, &max_age) == 1) { - return current + TimeDelta::FromSeconds(max_age); + if (pc.HasMaxAge()) { + int64_t max_age = 0; + // Use the output if StringToInt64 returns true ("perfect" conversion). This + // case excludes overflow/underflow, leading/trailing whitespace, non-number + // strings, and empty string. (ParsedCookie trims whitespace.) + if (base::StringToInt64(pc.MaxAge(), &max_age)) { + // RFC 6265bis algorithm for parsing Max-Age: + // "If delta-seconds is less than or equal to zero (0), let expiry- + // time be the earliest representable date and time. ... " + if (max_age <= 0) + return Time::Min(); + // "... Otherwise, let the expiry-time be the current date and time plus + // delta-seconds seconds." + return current + TimeDelta::FromSeconds(max_age); + } else { + // If the conversion wasn't perfect, but the best-effort conversion + // resulted in an overflow/underflow, use the min/max representable time. + // (This is alluded to in the spec, which says the user agent MAY clip an + // Expires attribute to a saturated time. We'll do the same for Max-Age.) + if (max_age == std::numeric_limits<int64_t>::min()) + return Time::Min(); + if (max_age == std::numeric_limits<int64_t>::max()) + return Time::Max(); + } } // Try the Expires attribute. diff --git a/chromium/net/cookies/canonical_cookie_unittest.cc b/chromium/net/cookies/canonical_cookie_unittest.cc index 921eb2260f0..d42132030c8 100644 --- a/chromium/net/cookies/canonical_cookie_unittest.cc +++ b/chromium/net/cookies/canonical_cookie_unittest.cc @@ -452,6 +452,89 @@ TEST(CanonicalCookieTest, CreateSameParty) { {CookieInclusionStatus::EXCLUDE_INVALID_SAMEPARTY})); } +TEST(CanonicalCookieTest, CreateWithMaxAge) { + GURL url("http://www.example.com/test/foo.html"); + base::Time creation_time = base::Time::Now(); + base::Optional<base::Time> server_time = base::nullopt; + + // Max-age with positive integer. + std::unique_ptr<CanonicalCookie> cookie = CanonicalCookie::Create( + url, "A=1; max-age=60", creation_time, server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_FALSE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::TimeDelta::FromSeconds(60) + creation_time, + cookie->ExpiryDate()); + + // Max-age with expires (max-age should take precedence). + cookie = CanonicalCookie::Create( + url, "A=1; expires=01-Jan-1970, 00:00:00 GMT; max-age=60", creation_time, + server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_FALSE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::TimeDelta::FromSeconds(60) + creation_time, + cookie->ExpiryDate()); + + // Max-age=0 should create an expired cookie with expiry equal to the earliest + // representable time. + cookie = CanonicalCookie::Create(url, "A=1; max-age=0", creation_time, + server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_TRUE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::Time::Min(), cookie->ExpiryDate()); + + // Negative max-age should create an expired cookie with expiry equal to the + // earliest representable time. + cookie = CanonicalCookie::Create(url, "A=1; max-age=-1", creation_time, + server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_TRUE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::Time::Min(), cookie->ExpiryDate()); + + // Max-age with whitespace (should be trimmed out). + cookie = CanonicalCookie::Create(url, "A=1; max-age = 60 ; Secure", + creation_time, server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_FALSE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::TimeDelta::FromSeconds(60) + creation_time, + cookie->ExpiryDate()); + + // Max-age with non-integer should be ignored. + cookie = CanonicalCookie::Create(url, "A=1; max-age=abcd", creation_time, + server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_FALSE(cookie->IsPersistent()); + EXPECT_FALSE(cookie->IsExpired(creation_time)); + + // Overflow max-age should be clipped. + cookie = CanonicalCookie::Create(url, + "A=1; " + "max-age=" + "9999999999999999999999999999999999999999999" + "999999999999999999999999999999999999999999", + creation_time, server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_FALSE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::Time::Max(), cookie->ExpiryDate()); + + // Underflow max-age should be clipped. + cookie = CanonicalCookie::Create(url, + "A=1; " + "max-age=-" + "9999999999999999999999999999999999999999999" + "999999999999999999999999999999999999999999", + creation_time, server_time); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsPersistent()); + EXPECT_TRUE(cookie->IsExpired(creation_time)); + EXPECT_EQ(base::Time::Min(), cookie->ExpiryDate()); +} + TEST(CanonicalCookieTest, EmptyExpiry) { GURL url("http://www7.ipdl.inpit.go.jp/Tokujitu/tjkta.ipdl?N0000=108"); base::Time creation_time = base::Time::Now(); diff --git a/chromium/net/cookies/cookie_access_delegate.h b/chromium/net/cookies/cookie_access_delegate.h index f1866725621..f9ecd912c3e 100644 --- a/chromium/net/cookies/cookie_access_delegate.h +++ b/chromium/net/cookies/cookie_access_delegate.h @@ -39,10 +39,11 @@ class NET_EXPORT CookieAccessDelegate { const SiteForCookies& site_for_cookies) const = 0; // Returns whether `site` is same-party with `party_context` and - // `top_frame_site`. + // `top_frame_site`. If `top_frame_site` is nullopt, then `site` will be + // checked only against `party_context`. virtual bool IsContextSamePartyWithSite( const net::SchemefulSite& site, - const net::SchemefulSite& top_frame_site, + const base::Optional<net::SchemefulSite>& top_frame_site, const std::set<net::SchemefulSite>& party_context) const = 0; // Returns whether `site` belongs to a non-singleton First-Party Set. diff --git a/chromium/net/cookies/cookie_constants.cc b/chromium/net/cookies/cookie_constants.cc index 117fe0c90f5..e6c5c429eb4 100644 --- a/chromium/net/cookies/cookie_constants.cc +++ b/chromium/net/cookies/cookie_constants.cc @@ -8,6 +8,7 @@ #include "base/metrics/histogram_macros.h" #include "base/notreached.h" #include "base/strings/string_util.h" +#include "url/url_constants.h" namespace net { @@ -294,4 +295,58 @@ CookiePort ReducePortRangeForCookieHistogram(const int port) { } } +CookieSourceSchemeName GetSchemeNameEnum(const GURL& url) { + // The most likely schemes are first, to improve performance. + if (url.SchemeIs(url::kHttpsScheme)) { + return CookieSourceSchemeName::kHttpsScheme; + } else if (url.SchemeIs(url::kHttpScheme)) { + return CookieSourceSchemeName::kHttpScheme; + } else if (url.SchemeIs(url::kWssScheme)) { + return CookieSourceSchemeName::kWssScheme; + } else if (url.SchemeIs(url::kWsScheme)) { + return CookieSourceSchemeName::kWsScheme; + } else if (url.SchemeIs("chrome-extension")) { + return CookieSourceSchemeName::kChromeExtensionScheme; + } else if (url.SchemeIs(url::kFileScheme)) { + return CookieSourceSchemeName::kFileScheme; + } + // These all aren't marked as cookieable and so are much less likely to + // occur. + else if (url.SchemeIs(url::kAboutBlankURL)) { + return CookieSourceSchemeName::kAboutBlankURL; + } else if (url.SchemeIs(url::kAboutSrcdocURL)) { + return CookieSourceSchemeName::kAboutSrcdocURL; + } else if (url.SchemeIs(url::kAboutBlankPath)) { + return CookieSourceSchemeName::kAboutBlankPath; + } else if (url.SchemeIs(url::kAboutSrcdocPath)) { + return CookieSourceSchemeName::kAboutSrcdocPath; + } else if (url.SchemeIs(url::kAboutScheme)) { + return CookieSourceSchemeName::kAboutScheme; + } else if (url.SchemeIs(url::kBlobScheme)) { + return CookieSourceSchemeName::kBlobScheme; + } else if (url.SchemeIs(url::kContentScheme)) { + return CookieSourceSchemeName::kContentScheme; + } else if (url.SchemeIs(url::kContentIDScheme)) { + return CookieSourceSchemeName::kContentIDScheme; + } else if (url.SchemeIs(url::kDataScheme)) { + return CookieSourceSchemeName::kDataScheme; + } else if (url.SchemeIs(url::kFileSystemScheme)) { + return CookieSourceSchemeName::kFileSystemScheme; + } else if (url.SchemeIs(url::kFtpScheme)) { + return CookieSourceSchemeName::kFtpScheme; + } else if (url.SchemeIs(url::kJavaScriptScheme)) { + return CookieSourceSchemeName::kJavaScriptScheme; + } else if (url.SchemeIs(url::kMailToScheme)) { + return CookieSourceSchemeName::kMailToScheme; + } else if (url.SchemeIs(url::kQuicTransportScheme)) { + return CookieSourceSchemeName::kQuicTransportScheme; + } else if (url.SchemeIs(url::kTelScheme)) { + return CookieSourceSchemeName::kTelScheme; + } else if (url.SchemeIs(url::kUrnScheme)) { + return CookieSourceSchemeName::kUrnScheme; + } + + return CookieSourceSchemeName::kOther; +} + } // namespace net diff --git a/chromium/net/cookies/cookie_constants.h b/chromium/net/cookies/cookie_constants.h index 04b1656eb37..729d2b1418e 100644 --- a/chromium/net/cookies/cookie_constants.h +++ b/chromium/net/cookies/cookie_constants.h @@ -9,6 +9,7 @@ #include "base/time/time.h" #include "net/base/net_export.h" +#include "url/gurl.h" namespace net { @@ -241,6 +242,38 @@ enum class CookieAccessScheme { kMaxValue = kTrustworthy // Keep as the last value. }; +// Used to populate a histogram that measures which schemes are used to set +// cookies and how frequently. Many of these probably won't/can't be used, +// but we know about them and there's no harm in including them. +// +// Do not reorder or renumber. Used for metrics. +enum class CookieSourceSchemeName { + kOther = 0, // Catch all for any other schemes that may be used. + kAboutBlankURL = 1, + kAboutSrcdocURL = 2, + kAboutBlankPath = 3, + kAboutSrcdocPath = 4, + kAboutScheme = 5, + kBlobScheme = 6, + kContentScheme = 7, + kContentIDScheme = 8, + kDataScheme = 9, + kFileScheme = 10, + kFileSystemScheme = 11, + kFtpScheme = 12, + kHttpScheme = 13, + kHttpsScheme = 14, + kJavaScriptScheme = 15, + kMailToScheme = 16, + kQuicTransportScheme = 17, + kTelScheme = 18, + kUrnScheme = 19, + kWsScheme = 20, + kWssScheme = 21, + kChromeExtensionScheme = 22, + kMaxValue = kChromeExtensionScheme +}; + // Returns the Set-Cookie header priority token corresponding to |priority|. NET_EXPORT std::string CookiePriorityToString(CookiePriority priority); @@ -270,6 +303,9 @@ NET_EXPORT void RecordCookieSameSiteAttributeValueHistogram( // is because UMA cannot handle the full range. NET_EXPORT CookiePort ReducePortRangeForCookieHistogram(const int port); +// Returns the appropriate enum value for the scheme of the given GURL. +CookieSourceSchemeName GetSchemeNameEnum(const GURL& url); + } // namespace net #endif // NET_COOKIES_COOKIE_CONSTANTS_H_ diff --git a/chromium/net/cookies/cookie_monster.cc b/chromium/net/cookies/cookie_monster.cc index e8e5c326c79..dd1ff2e34ab 100644 --- a/chromium/net/cookies/cookie_monster.cc +++ b/chromium/net/cookies/cookie_monster.cc @@ -55,7 +55,6 @@ #include "base/logging.h" #include "base/macros.h" #include "base/metrics/field_trial.h" -#include "base/metrics/histogram.h" #include "base/metrics/histogram_functions.h" #include "base/metrics/histogram_macros.h" #include "base/ranges/algorithm.h" @@ -76,6 +75,7 @@ #include "net/cookies/cookie_monster_netlog_params.h" #include "net/cookies/cookie_util.h" #include "net/cookies/parsed_cookie.h" +#include "net/http/http_util.h" #include "net/log/net_log.h" #include "net/log/net_log_values.h" #include "url/origin.h" @@ -165,7 +165,7 @@ const int CookieMonster::kSafeFromGlobalPurgeDays = 30; namespace { bool ContainsControlCharacter(const std::string& s) { - return base::ranges::any_of(s, [](char c) { return c >= 0 && c <= 31; }); + return base::ranges::any_of(s, &HttpUtil::IsControlChar); } typedef std::vector<CanonicalCookie*> CanonicalCookieVector; @@ -304,6 +304,27 @@ size_t CountCookiesForPossibleDeletion( return cookies_count; } +// Records minutes until the expiration date of a cookie to the appropriate +// histogram. Only histograms cookies that have an expiration date (i.e. are +// persistent). +void HistogramExpirationDuration(const CanonicalCookie& cookie, + base::Time creation_time) { + if (!cookie.IsPersistent()) + return; + + int expiration_duration_minutes = + (cookie.ExpiryDate() - creation_time).InMinutes(); + if (cookie.IsSecure()) { + UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ExpirationDurationMinutesSecure", + expiration_duration_minutes, 1, + kMinutesInTenYears, 50); + } else { + UMA_HISTOGRAM_CUSTOM_COUNTS("Cookie.ExpirationDurationMinutesNonSecure", + expiration_duration_minutes, 1, + kMinutesInTenYears, 50); + } +} + } // namespace CookieMonster::CookieMonster(scoped_refptr<PersistentCookieStore> store, @@ -327,7 +348,6 @@ CookieMonster::CookieMonster(scoped_refptr<PersistentCookieStore> store, last_access_threshold_(last_access_threshold), last_statistic_record_time_(base::Time::Now()), persist_session_cookies_(false) { - InitializeHistograms(); cookieable_schemes_.insert( cookieable_schemes_.begin(), kDefaultCookieableSchemes, kDefaultCookieableSchemes + kDefaultCookieableSchemesCount); @@ -724,7 +744,9 @@ void CookieMonster::OnLoaded( std::vector<std::unique_ptr<CanonicalCookie>> cookies) { DCHECK(thread_checker_.CalledOnValidThread()); StoreLoadedCookies(std::move(cookies)); - histogram_time_blocked_on_load_->AddTime(TimeTicks::Now() - beginning_time); + base::UmaHistogramCustomTimes( + "Cookie.TimeBlockedOnLoad", base::TimeTicks::Now() - beginning_time, + TimeDelta::FromMilliseconds(1), TimeDelta::FromMinutes(1), 50); // Invoke the task queue of cookie request. InvokeQueue(); @@ -1163,14 +1185,14 @@ CookieMonster::CookieMap::iterator CookieMonster::InternalInsertCookie( } auto inserted = cookies_.insert(CookieMap::value_type(key, std::move(cc))); - // See InitializeHistograms() for details. 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; - histogram_cookie_type_->Add(type_sample); + UMA_HISTOGRAM_EXACT_LINEAR("Cookie.Type", type_sample, + (1 << COOKIE_TYPE_LAST_ENTRY)); DCHECK(access_result.status.IsInclude()); if (dispatch_change) { @@ -1265,16 +1287,7 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, // 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) { - // See InitializeHistograms() for details. - if (cc->IsPersistent()) { - if (cc->IsSecure()) { - histogram_expiration_duration_minutes_secure_->Add( - (cc->ExpiryDate() - creation_date).InMinutes()); - } else { - histogram_expiration_duration_minutes_non_secure_->Add( - (cc->ExpiryDate() - creation_date).InMinutes()); - } - } + HistogramExpirationDuration(*cc, creation_date); // Histogram the type of scheme used on URLs that set cookies. This // intentionally includes cookies that are set or overwritten by @@ -1291,7 +1304,8 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, : (cc->IsSecure() ? COOKIE_SOURCE_SECURE_COOKIE_NONCRYPTOGRAPHIC_SCHEME : COOKIE_SOURCE_NONSECURE_COOKIE_NONCRYPTOGRAPHIC_SCHEME)); - histogram_cookie_source_scheme_->Add(cookie_source_sample); + UMA_HISTOGRAM_ENUMERATION("Cookie.CookieSourceScheme", + cookie_source_sample); UMA_HISTOGRAM_BOOLEAN("Cookie.DomainSet", cc->IsDomainCookie()); @@ -1321,6 +1335,9 @@ void CookieMonster::SetCanonicalCookie(std::unique_ptr<CanonicalCookie> cc, "Cookie.Port.Set.RemoteHost", ReducePortRangeForCookieHistogram(source_url.EffectiveIntPort())); } + + UMA_HISTOGRAM_ENUMERATION("Cookie.CookieSourceSchemeName", + GetSchemeNameEnum(source_url)); } else { UMA_HISTOGRAM_BOOLEAN( "Cookie.SameParty.SetExclusionDecidedBySameParty", @@ -1349,15 +1366,7 @@ void CookieMonster::SetAllCookies(CookieList list, if (cookie.IsExpired(creation_time)) continue; - if (cookie.IsPersistent()) { - if (cookie.IsSecure()) { - histogram_expiration_duration_minutes_secure_->Add( - (cookie.ExpiryDate() - creation_time).InMinutes()); - } else { - histogram_expiration_duration_minutes_non_secure_->Add( - (cookie.ExpiryDate() - creation_time).InMinutes()); - } - } + HistogramExpirationDuration(cookie, creation_time); CookieAccessResult access_result; access_result.access_semantics = GetAccessSemanticsForCookie(cookie); @@ -1489,10 +1498,10 @@ size_t CookieMonster::GarbageCollect(const Time& current, // 4. High-priority non-secure cookies. // 5. Medium-priority secure cookies. // 6. High-priority secure cookies. - const static struct { + constexpr struct { CookiePriority priority; bool protect_secure_cookies; - } purge_rounds[] = { + } kPurgeRounds[] = { // 1. Low-priority non-secure cookies. {COOKIE_PRIORITY_LOW, true}, // 2. Low-priority secure cookies. @@ -1508,7 +1517,7 @@ size_t CookieMonster::GarbageCollect(const Time& current, }; size_t quota = 0; - for (const auto& purge_round : purge_rounds) { + for (const auto& purge_round : kPurgeRounds) { // Adjust quota according to the priority of cookies. Each round should // protect certain number of cookies in order to avoid starvation. // For example, when each round starts to remove cookies, the number of @@ -1636,7 +1645,7 @@ size_t CookieMonster::PurgeLeastRecentMatches(CookieItVector* cookies, secure_cookies = CountCookiesForPossibleDeletion( priority, cookies, protect_secure_cookies /* count secure cookies */); cookies_count_possibly_to_be_deleted -= - std::max(secure_cookies, to_protect - secure_cookies); + std::max(secure_cookies, to_protect); } else { cookies_count_possibly_to_be_deleted -= to_protect; } @@ -1811,8 +1820,7 @@ bool CookieMonster::DoRecordPeriodicStats() { if (started_fetching_all_cookies_ && !finished_fetching_all_cookies_) return false; - // See InitializeHistograms() for details. - histogram_count_->Add(cookies_.size()); + base::UmaHistogramCounts100000("Cookie.Count2", cookies_.size()); if (cookie_access_delegate()) { for (const auto& set : cookie_access_delegate()->RetrieveFirstPartySets()) { @@ -1837,43 +1845,6 @@ bool CookieMonster::DoRecordPeriodicStats() { return true; } -// Initialize all histogram counter variables used in this class. -// -// TODO(https://crbug.com/1087445): remove this in favor of histogram_macros.h -// or histogram_functions.h usage, since both are now threadsafe. -void CookieMonster::InitializeHistograms() { - DCHECK(thread_checker_.CalledOnValidThread()); - - // From UMA_HISTOGRAM_CUSTOM_COUNTS - histogram_expiration_duration_minutes_secure_ = base::Histogram::FactoryGet( - "Cookie.ExpirationDurationMinutesSecure", 1, kMinutesInTenYears, 50, - base::Histogram::kUmaTargetedHistogramFlag); - histogram_expiration_duration_minutes_non_secure_ = - base::Histogram::FactoryGet("Cookie.ExpirationDurationMinutesNonSecure", - 1, kMinutesInTenYears, 50, - base::Histogram::kUmaTargetedHistogramFlag); - - // From UMA_HISTOGRAM_COUNTS_100000 - // This replaces Cookie.Count which only counted up to 4000 cookies. - histogram_count_ = - base::Histogram::FactoryGet("Cookie.Count2", 1, 100000, 50, - base::Histogram::kUmaTargetedHistogramFlag); - - // From UMA_HISTOGRAM_ENUMERATION - histogram_cookie_type_ = base::LinearHistogram::FactoryGet( - "Cookie.Type", 1, (1 << COOKIE_TYPE_LAST_ENTRY) - 1, - 1 << COOKIE_TYPE_LAST_ENTRY, base::Histogram::kUmaTargetedHistogramFlag); - histogram_cookie_source_scheme_ = base::LinearHistogram::FactoryGet( - "Cookie.CookieSourceScheme", 1, COOKIE_SOURCE_LAST_ENTRY - 1, - COOKIE_SOURCE_LAST_ENTRY, base::Histogram::kUmaTargetedHistogramFlag); - - // From UMA_HISTOGRAM_{CUSTOM_,}TIMES - histogram_time_blocked_on_load_ = base::Histogram::FactoryTimeGet( - "Cookie.TimeBlockedOnLoad", base::TimeDelta::FromMilliseconds(1), - base::TimeDelta::FromMinutes(1), 50, - base::Histogram::kUmaTargetedHistogramFlag); -} - void CookieMonster::DoCookieCallback(base::OnceClosure callback) { DCHECK(thread_checker_.CalledOnValidThread()); diff --git a/chromium/net/cookies/cookie_monster.h b/chromium/net/cookies/cookie_monster.h index 42650759651..00446acaabc 100644 --- a/chromium/net/cookies/cookie_monster.h +++ b/chromium/net/cookies/cookie_monster.h @@ -37,10 +37,6 @@ #include "net/log/net_log_with_source.h" #include "url/gurl.h" -namespace base { -class HistogramBase; -} // namespace base - namespace net { class CookieChangeDispatcher; @@ -284,6 +280,7 @@ class NET_EXPORT CookieMonster : public CookieStore { // of stored cookies. Please do not reorder the list when adding new entries. // New items MUST be added at the end of the list, just before // COOKIE_TYPE_LAST_ENTRY; + // There will be 2^COOKIE_TYPE_LAST_ENTRY buckets in the linear histogram. enum CookieType { COOKIE_TYPE_SAME_SITE = 0, COOKIE_TYPE_HTTPONLY, @@ -295,8 +292,8 @@ class NET_EXPORT CookieMonster : public CookieStore { // sources of Secure and non-Secure cookies: that is, whether such // cookies are set by origins with cryptographic or non-cryptographic // schemes. Please do not reorder the list when adding new - // entries. New items MUST be added at the end of the list, just - // before COOKIE_SOURCE_LAST_ENTRY. + // entries. New items MUST be added at the end of the list, and kMaxValue + // should be updated to the last value. // // COOKIE_SOURCE_(NON)SECURE_COOKIE_(NON)CRYPTOGRAPHIC_SCHEME means // that a cookie was set or overwritten from a URL with the given type @@ -308,7 +305,7 @@ class NET_EXPORT CookieMonster : public CookieStore { COOKIE_SOURCE_SECURE_COOKIE_NONCRYPTOGRAPHIC_SCHEME, COOKIE_SOURCE_NONSECURE_COOKIE_CRYPTOGRAPHIC_SCHEME, COOKIE_SOURCE_NONSECURE_COOKIE_NONCRYPTOGRAPHIC_SCHEME, - COOKIE_SOURCE_LAST_ENTRY + kMaxValue = COOKIE_SOURCE_NONSECURE_COOKIE_NONCRYPTOGRAPHIC_SCHEME }; // Enum for collecting metrics on how frequently a cookie is sent to the same @@ -555,10 +552,6 @@ class NET_EXPORT CookieMonster : public CookieStore { // cookies. Returns whether stats were recorded. bool DoRecordPeriodicStats(); - // Initialize the histogram_* variables below; should only be called from - // the constructor. - void InitializeHistograms(); - // Defers the callback until the full coookie database has been loaded. If // it's already been loaded, runs the callback synchronously. void DoCookieCallback(base::OnceClosure callback); @@ -583,15 +576,6 @@ class NET_EXPORT CookieMonster : public CookieStore { int source_port, CookieSourceScheme source_scheme); - // Histogram variables; see CookieMonster::InitializeHistograms() in - // cookie_monster.cc for details. - base::HistogramBase* histogram_expiration_duration_minutes_secure_; - base::HistogramBase* histogram_expiration_duration_minutes_non_secure_; - base::HistogramBase* histogram_count_; - base::HistogramBase* histogram_cookie_type_; - base::HistogramBase* histogram_cookie_source_scheme_; - base::HistogramBase* histogram_time_blocked_on_load_; - // Set of keys (eTLD+1's) for which non-expired cookies have // been evicted for hitting the per-domain max. The size of this set is // histogrammed periodically. The size is limited to |kMaxDomainPurgedKeys|. diff --git a/chromium/net/cookies/cookie_monster_change_dispatcher.h b/chromium/net/cookies/cookie_monster_change_dispatcher.h index 29a2b1ab6f9..c769efe9b99 100644 --- a/chromium/net/cookies/cookie_monster_change_dispatcher.h +++ b/chromium/net/cookies/cookie_monster_change_dispatcher.h @@ -29,7 +29,7 @@ class CookieMonster; class CookieMonsterChangeDispatcher : public CookieChangeDispatcher { public: using CookieChangeCallbackList = - base::CallbackList<void(const CookieChangeInfo&)>; + base::RepeatingCallbackList<void(const CookieChangeInfo&)>; // Expects |cookie_monster| to outlive this. explicit CookieMonsterChangeDispatcher(const CookieMonster* cookie_monster); diff --git a/chromium/net/cookies/cookie_monster_unittest.cc b/chromium/net/cookies/cookie_monster_unittest.cc index eb883c1f524..db92d8cd2d8 100644 --- a/chromium/net/cookies/cookie_monster_unittest.cc +++ b/chromium/net/cookies/cookie_monster_unittest.cc @@ -7,6 +7,7 @@ #include <stdint.h> #include <algorithm> +#include <memory> #include <string> #include <vector> @@ -41,6 +42,7 @@ #include "net/cookies/cookie_inclusion_status.h" #include "net/cookies/cookie_monster_store_test.h" // For CookieStore mock #include "net/cookies/cookie_store_change_unittest.h" +#include "net/cookies/cookie_store_test_callbacks.h" #include "net/cookies/cookie_store_test_helpers.h" #include "net/cookies/cookie_store_unittest.h" #include "net/cookies/cookie_util.h" @@ -197,8 +199,9 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { } // Helper for PredicateSeesAllCookies test; repopulates CM with same layout - // each time. - void PopulateCmForPredicateCheck(CookieMonster* cm) { + // each time. Returns the time which is strictly greater than any creation + // time which was passed to created cookies. + base::Time PopulateCmForPredicateCheck(CookieMonster* cm) { 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()); @@ -217,83 +220,84 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { // * Two host path cookies (w.c.b.a/dir1, w.c.b.a/dir1/dir2) std::vector<std::unique_ptr<CanonicalCookie>> cookies; + const base::Time now = base::Time::Now(); // Domain cookies cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "dom_1", "A", ".harvard.edu", "/", base::Time(), base::Time(), - base::Time(), false, false, CookieSameSite::LAX_MODE, - COOKIE_PRIORITY_DEFAULT, false)); + "dom_1", "A", ".harvard.edu", "/", now, base::Time(), base::Time(), + false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, + false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "dom_2", "B", ".math.harvard.edu", "/", base::Time(), base::Time(), - base::Time(), false, false, CookieSameSite::LAX_MODE, - COOKIE_PRIORITY_DEFAULT, false)); + "dom_2", "B", ".math.harvard.edu", "/", now, base::Time(), base::Time(), + false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, + false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "dom_3", "C", ".bourbaki.math.harvard.edu", "/", base::Time(), - base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + "dom_3", "C", ".bourbaki.math.harvard.edu", "/", now, base::Time(), + base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); // Host cookies cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "host_1", "A", url_top_level_domain_plus_1, "/", base::Time(), - base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + "host_1", "A", url_top_level_domain_plus_1, "/", now, base::Time(), + base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "host_2", "B", url_top_level_domain_plus_2, "/", base::Time(), - base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + "host_2", "B", url_top_level_domain_plus_2, "/", now, base::Time(), + base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "host_3", "C", url_top_level_domain_plus_3, "/", base::Time(), - base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + "host_3", "C", url_top_level_domain_plus_3, "/", now, base::Time(), + base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); // http_only cookie cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "httpo_check", "A", url_top_level_domain_plus_2, "/", base::Time(), - base::Time(), base::Time(), false, true, CookieSameSite::LAX_MODE, + "httpo_check", "A", url_top_level_domain_plus_2, "/", now, base::Time(), + base::Time(), false, true, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); // same-site cookie cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "same_site_check", "A", url_top_level_domain_plus_2, "/", base::Time(), + "same_site_check", "A", url_top_level_domain_plus_2, "/", now, base::Time(), base::Time(), false, false, CookieSameSite::STRICT_MODE, COOKIE_PRIORITY_DEFAULT, false)); // same-party cookie cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "same_party_check", "A", url_top_level_domain_plus_2, "/", base::Time(), + "same_party_check", "A", url_top_level_domain_plus_2, "/", now, base::Time(), base::Time(), true /* secure */, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, true /* same_party */)); // Secure cookies cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "sec_dom", "A", ".math.harvard.edu", "/", base::Time(), base::Time(), + "sec_dom", "A", ".math.harvard.edu", "/", now, base::Time(), base::Time(), true, false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "sec_host", "B", url_top_level_domain_plus_2, "/", base::Time(), - base::Time(), base::Time(), true, false, CookieSameSite::NO_RESTRICTION, + "sec_host", "B", url_top_level_domain_plus_2, "/", now, base::Time(), + base::Time(), true, false, CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false)); // Domain path cookies cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "dom_path_1", "A", ".math.harvard.edu", "/dir1", base::Time(), - base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + "dom_path_1", "A", ".math.harvard.edu", "/dir1", now, base::Time(), + base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "dom_path_2", "B", ".math.harvard.edu", "/dir1/dir2", base::Time(), - base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + "dom_path_2", "B", ".math.harvard.edu", "/dir1/dir2", now, base::Time(), + base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); // Host path cookies cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "host_path_1", "A", url_top_level_domain_plus_2, "/dir1", base::Time(), + "host_path_1", "A", url_top_level_domain_plus_2, "/dir1", now, base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); cookies.push_back(CanonicalCookie::CreateUnsafeCookieForTesting( - "host_path_2", "B", url_top_level_domain_plus_2, "/dir1/dir2", - base::Time(), base::Time(), base::Time(), false, false, - CookieSameSite::LAX_MODE, COOKIE_PRIORITY_DEFAULT, false)); + "host_path_2", "B", url_top_level_domain_plus_2, "/dir1/dir2", now, + base::Time(), base::Time(), false, false, CookieSameSite::LAX_MODE, + COOKIE_PRIORITY_DEFAULT, false)); for (auto& cookie : cookies) { GURL source_url = cookie_util::SimulatedCookieSource( @@ -303,6 +307,7 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { } EXPECT_EQ(cookies.size(), this->GetAllCookies(cm).size()); + return now + base::TimeDelta::FromMilliseconds(100); } Time GetFirstCookieAccessDate(CookieMonster* cm) { @@ -574,7 +579,7 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { std::unique_ptr<CookieMonster> cm; if (alt_host_entries == nullptr) { - cm.reset(new CookieMonster(nullptr, &net_log_)); + cm = std::make_unique<CookieMonster>(nullptr, &net_log_); } else { // When generating all of these cookies on alternate hosts, they need to // be all older than the max "safe" date for GC, which is currently 30 @@ -620,6 +625,9 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { CookieMonster::kDomainPurgeCookies); auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); + // Key: + // Round 1 => LN; round 2 => LS; round 3 => MN. + // Round 4 => HN; round 5 => MS; round 6 => HS // Each test case adds 181 cookies, so 31 cookies are evicted. // Cookie same priority, repeated for each priority. @@ -683,6 +691,9 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { CookieMonster::kDomainPurgeCookies); auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); + // Key: + // Round 1 => LN; round 2 => LS; round 3 => MN. + // Round 4 => HN; round 5 => MS; round 6 => HS // Each test case adds 181 cookies, so 31 cookies are evicted. // Cookie same priority, repeated for each priority. @@ -740,6 +751,9 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { CookieMonster::kDomainPurgeCookies); auto cm = std::make_unique<CookieMonster>(nullptr, &net_log_); + // Key: + // Round 1 => LN; round 2 => LS; round 3 => MN. + // Round 4 => HN; round 5 => MS; round 6 => HS // Each test case adds 180 secure cookies, and some non-secure cookie. The // secure cookies take priority, so the non-secure cookie is removed, along @@ -764,6 +778,18 @@ class CookieMonsterTestBase : public CookieStoreTest<T> { // Round 4 => 1HN; round 5 => none; round 6 => 30HS. TestPriorityCookieCase(cm.get(), "180HS 1HN", 0U, 0U, 150U, 0U, 150U); + // Quotas should be correctly maintained when a given priority has both + // secure and non-secure cookies. + // + // Round 1 => 10LN; round 2 => none; round 3 => none. + // Round 4 => 21HN; round 5 => none; round 6 => none. + TestPriorityCookieCase(cm.get(), "39LN 1LS 141HN", 30U, 0U, 120U, 149U, + 1U); + // Round 1 => none; round 2 => none; round 3 => 10MN. + // Round 4 => none; round 5 => none; round 6 => 21HS. + TestPriorityCookieCase(cm.get(), "29LN 1LS 59MN 1MS 91HS", 30U, 50U, 70U, + 78U, 72U); + // Low-priority secure cookies are removed before higher priority non-secure // cookies. // Round 1 => none; round 2 => 31LS; round 3 => none. @@ -1752,17 +1778,19 @@ TEST_F(CookieMonsterTest, GetExcludedCookiesForURLPathMatching) { TEST_F(CookieMonsterTest, CookieSorting) { std::unique_ptr<CookieMonster> cm(new CookieMonster(nullptr, &net_log_)); - - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "B=B1; path=/")); - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "B=B2; path=/foo")); - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "B=B3; path=/foo/bar")); - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "A=A1; path=/")); - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "A=A2; path=/foo")); - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "A=A3; path=/foo/bar")); + base::Time system_time = base::Time::Now(); + for (const char* cookie_line : + {"B=B1; path=/", "B=B2; path=/foo", "B=B3; path=/foo/bar", + "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); + } // Re-set cookie which should not change sort order, as the creation date // will be retained, as per RFC 6265 5.3.11.3. - EXPECT_TRUE(SetCookie(cm.get(), http_www_foo_.url(), "B=B3; path=/foo/bar")); + EXPECT_TRUE(SetCookieWithSystemTime(cm.get(), http_www_foo_.url(), + "B=B3; path=/foo/bar", system_time)); CookieList cookies = GetAllCookies(cm.get()); ASSERT_EQ(6u, cookies.size()); @@ -1949,10 +1977,10 @@ TEST_F(CookieMonsterTest, ImportDuplicateCreationTimes) { TEST_F(CookieMonsterTest, PredicateSeesAllCookies) { std::unique_ptr<CookieMonster> cm(new CookieMonster(nullptr, &net_log_)); - PopulateCmForPredicateCheck(cm.get()); + const base::Time now = PopulateCmForPredicateCheck(cm.get()); // We test that we can see all cookies with |delete_info|. This includes // host, http_only, host secure, and all domain cookies. - CookieDeletionInfo delete_info(base::Time(), base::Time::Now()); + CookieDeletionInfo delete_info(base::Time(), now); delete_info.value_for_testing = "A"; EXPECT_EQ(8u, DeleteAllMatchingInfo(cm.get(), std::move(delete_info))); @@ -2450,7 +2478,7 @@ TEST_F(CookieMonsterTest, FlushStore) { ASSERT_EQ(2, counter->callback_count()); // If there's no backing store, FlushStore() is always a safe no-op. - cm.reset(new CookieMonster(nullptr, &net_log_)); + cm = std::make_unique<CookieMonster>(nullptr, &net_log_); GetAllCookies(cm.get()); // Force init. cm->FlushStore(base::DoNothing()); base::RunLoop().RunUntilIdle(); @@ -2523,8 +2551,7 @@ TEST_F(CookieMonsterTest, SetAllCookies) { NetLogEventPhase::END); } -// Check that DeleteAll does flush (as a sanity check that flush_count() -// works). +// Check that DeleteAll does flush (as a quick check that flush_count() works). TEST_F(CookieMonsterTest, DeleteAll) { scoped_refptr<FlushablePersistentStore> store(new FlushablePersistentStore()); auto cm = std::make_unique<CookieMonster>(store.get(), &net_log_); @@ -2677,6 +2704,7 @@ 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 std::string domain("host"); @@ -2688,18 +2716,29 @@ TEST_F(CookieMonsterTest, ControlCharacterPurge) { AddCookieToList(url, "foo=bar; path=" + path, now1, &initial_cookies); - // We have to manually build this cookie because it contains a control - // character, and our cookie line parser rejects control characters. + // We have to manually build these cookies because they contain control + // characters, and our cookie line parser rejects control characters. std::unique_ptr<CanonicalCookie> cc = CanonicalCookie::CreateUnsafeCookieForTesting( "baz", "\x05" "boo", - "." + domain, path, now2, later, base::Time(), false, false, - CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT, false); + "." + domain, path, now2, later, base::Time(), true /* secure */, + false /* httponly */, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /* sameparty */); initial_cookies.push_back(std::move(cc)); - AddCookieToList(url, "hello=world; path=" + path, now3, &initial_cookies); + std::unique_ptr<CanonicalCookie> cc2 = + CanonicalCookie::CreateUnsafeCookieForTesting( + "baz", + "\x7F" + "boo", + "." + domain, path, now3, later, base::Time(), true /* secure */, + false /* httponly */, CookieSameSite::NO_RESTRICTION, + COOKIE_PRIORITY_DEFAULT, false /* sameparty */); + initial_cookies.push_back(std::move(cc2)); + + AddCookieToList(url, "hello=world; path=" + path, now4, &initial_cookies); // Inject our initial cookies into the mock PersistentCookieStore. store->SetLoadExpectation(true, std::move(initial_cookies)); @@ -4666,6 +4705,68 @@ TEST_F(CookieMonsterTest, CookiePortReadDiffersFromSetHistogram) { CookieMonster::CookieSentToSamePort::kYes, 2); } +TEST_F(CookieMonsterTest, CookieSourceSchemeNameHistogram) { + base::HistogramTester histograms; + const char kHistogramName[] = "Cookie.CookieSourceSchemeName"; + + scoped_refptr<MockPersistentCookieStore> store(new MockPersistentCookieStore); + std::unique_ptr<CookieMonster> cm(new CookieMonster(store.get(), &net_log_)); + + histograms.ExpectTotalCount(kHistogramName, 0); + + struct TestCase { + CookieSourceSchemeName enum_value; + std::string scheme; + }; + + // Test the usual and a smattering of some other types including a kOther. + // It doesn't matter if we add this to the scheme registry or not because we + // don't actually need the whole url to parse, we just need GURL to pick up on + // the scheme correctly (which it does). What the rest of the cookie code does + // with the oddly formed GURL is out of scope of this test (i.e. we don't + // care). + const TestCase kTestCases[] = { + {CookieSourceSchemeName::kHttpsScheme, url::kHttpsScheme}, + {CookieSourceSchemeName::kHttpScheme, url::kHttpScheme}, + {CookieSourceSchemeName::kWssScheme, url::kWssScheme}, + {CookieSourceSchemeName::kWsScheme, url::kWsScheme}, + {CookieSourceSchemeName::kChromeExtensionScheme, "chrome-extension"}, + {CookieSourceSchemeName::kFileScheme, url::kFileScheme}, + {CookieSourceSchemeName::kQuicTransportScheme, url::kQuicTransportScheme}, + {CookieSourceSchemeName::kOther, "abcd1234"}}; + + // Make sure all the schemes are considered cookieable. + std::vector<std::string> schemes; + for (auto test_case : kTestCases) { + schemes.push_back(test_case.scheme); + } + ResultSavingCookieCallback<bool> cookie_scheme_callback; + cm->SetCookieableSchemes(schemes, cookie_scheme_callback.MakeCallback()); + cookie_scheme_callback.WaitUntilDone(); + ASSERT_TRUE(cookie_scheme_callback.result()); + + const char kUrl[] = "://www.foo.com"; + int count = 0; + + // Test all the cases. + for (auto test_case : kTestCases) { + histograms.ExpectBucketCount(kHistogramName, test_case.enum_value, 0); + + EXPECT_TRUE(SetCookie(cm.get(), GURL(test_case.scheme + kUrl), "A=B")); + + histograms.ExpectBucketCount(kHistogramName, test_case.enum_value, 1); + histograms.ExpectTotalCount(kHistogramName, ++count); + } + + // This metric is only for cookies that are actually set. Make sure the + // histogram doesn't increment for cookies that fail to set. + + // Try to set an invalid cookie, for instance: a non-cookieable scheme will be + // rejected. + EXPECT_FALSE(SetCookie(cm.get(), GURL("invalidscheme://foo.com"), "A=B")); + histograms.ExpectTotalCount(kHistogramName, count); +} + class FirstPartySetEnabledCookieMonsterTest : public CookieMonsterTest { public: FirstPartySetEnabledCookieMonsterTest() diff --git a/chromium/net/cookies/cookie_store_unittest.h b/chromium/net/cookies/cookie_store_unittest.h index 26857ee51b5..31f702c2d25 100644 --- a/chromium/net/cookies/cookie_store_unittest.h +++ b/chromium/net/cookies/cookie_store_unittest.h @@ -195,9 +195,11 @@ class CookieStoreTest : public testing::Test { const GURL& url, const std::string& cookie_line, const CookieOptions& options, - base::Optional<base::Time> server_time = base::nullopt) { - auto cookie = CanonicalCookie::Create(url, cookie_line, base::Time::Now(), - server_time); + base::Optional<base::Time> server_time = base::nullopt, + base::Optional<base::Time> system_time = base::nullopt) { + auto cookie = CanonicalCookie::Create( + url, cookie_line, system_time.value_or(base::Time::Now()), server_time); + if (!cookie) return false; DCHECK(cs); @@ -225,6 +227,19 @@ class CookieStoreTest : public testing::Test { return callback.result().status.IsInclude(); } + bool SetCookieWithSystemTime(CookieStore* cs, + const GURL& url, + const std::string& cookie_line, + const base::Time& system_time) { + 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, base::nullopt, + base::make_optional(system_time)); + } + bool SetCookieWithServerTime(CookieStore* cs, const GURL& url, const std::string& cookie_line, diff --git a/chromium/net/cookies/cookie_util.cc b/chromium/net/cookies/cookie_util.cc index ddcccc2a531..16c3c5e8f95 100644 --- a/chromium/net/cookies/cookie_util.cc +++ b/chromium/net/cookies/cookie_util.cc @@ -94,17 +94,30 @@ bool SaturatedTimeFromUTCExploded(const base::Time::Exploded& exploded, // schemeful_context, i.e. whether scheme should be considered when comparing // two sites. // +// The bool in the return value is whether the ContextType return value was +// affected by bugfix 1166211, used for CookieInclusionStatus warnings and +// histogramming. +// TODO(crbug.com/1166211): Remove when no longer needed. +// // See documentation of `ComputeSameSiteContextForRequest` for explanations of // other parameters. std::pair<ContextType, bool> ComputeSameSiteContext( - const GURL& url, + const std::vector<GURL>& url_chain, const SiteForCookies& site_for_cookies, const base::Optional<url::Origin>& initiator, bool is_http, bool is_main_frame_navigation, bool compute_schemefully) { + DCHECK(!url_chain.empty()); + const GURL& request_url = url_chain.back(); + const auto is_same_site_with_site_for_cookies = + [&site_for_cookies, compute_schemefully](const GURL& url) { + return site_for_cookies.IsFirstPartyWithSchemefulMode( + url, compute_schemefully); + }; + bool site_for_cookies_is_same_site = - site_for_cookies.IsFirstPartyWithSchemefulMode(url, compute_schemefully); + is_same_site_with_site_for_cookies(request_url); // If the request is a main frame navigation, site_for_cookies must either be // null (for opaque origins, e.g., data: origins) or same-site with the @@ -112,38 +125,50 @@ std::pair<ContextType, bool> ComputeSameSiteContext( // ws/wss (these schemes are not navigable). DCHECK(!is_main_frame_navigation || site_for_cookies_is_same_site || site_for_cookies.IsNull()); - DCHECK(!is_main_frame_navigation || !url.SchemeIsWSOrWSS()); - - bool affected_by_bugfix_1166211 = false; - if (site_for_cookies_is_same_site) { - // Create a SiteForCookies object from the initiator so that we can reuse - // IsFirstPartyWithSchemefulMode(). - if (!initiator || - SiteForCookies::FromOrigin(initiator.value()) - .IsFirstPartyWithSchemefulMode(url, compute_schemefully)) { - return {ContextType::SAME_SITE_STRICT, false}; - } + DCHECK(!is_main_frame_navigation || !request_url.SchemeIsWSOrWSS()); + + if (!site_for_cookies_is_same_site) + return {ContextType::CROSS_SITE, false}; + + // Create a SiteForCookies object from the initiator so that we can reuse + // IsFirstPartyWithSchemefulMode(). + bool same_site_initiator = + !initiator || + SiteForCookies::FromOrigin(initiator.value()) + .IsFirstPartyWithSchemefulMode(request_url, compute_schemefully); + + // Check that the URLs in the redirect chain are all same-site with the + // site_for_cookies and hence (by transitivity) same-site with the request + // URL. (If the URL chain only has one member, it's the request_url and we've + // already checked it previously.) + bool same_site_redirect_chain = + url_chain.size() == 1u || + base::ranges::all_of(url_chain, is_same_site_with_site_for_cookies); + + if (same_site_initiator && + (!base::FeatureList::IsEnabled( + features::kCookieSameSiteConsidersRedirectChain) || + same_site_redirect_chain)) { + return {ContextType::SAME_SITE_STRICT, false}; + } - if (is_http) { - base::UmaHistogramBoolean("Cookie.SameSiteContextAffectedByBugfix1166211", - !is_main_frame_navigation); - } + if (is_http) { + base::UmaHistogramBoolean("Cookie.SameSiteContextAffectedByBugfix1166211", + !is_main_frame_navigation); + } - // Preserve old behavior if the bugfix is disabled. - if (!base::FeatureList::IsEnabled(features::kSameSiteCookiesBugfix1166211)) - return {ContextType::SAME_SITE_LAX, false}; + // Preserve old behavior if the bugfix is disabled. + if (!base::FeatureList::IsEnabled(features::kSameSiteCookiesBugfix1166211)) + return {ContextType::SAME_SITE_LAX, false}; - if (!is_http || is_main_frame_navigation) { - return {ContextType::SAME_SITE_LAX, false}; - } else if (is_http) { - affected_by_bugfix_1166211 = true; - } - } - return {ContextType::CROSS_SITE, affected_by_bugfix_1166211}; + if (!is_http || is_main_frame_navigation) + return {ContextType::SAME_SITE_LAX, false}; + + return {ContextType::CROSS_SITE, true}; } CookieOptions::SameSiteCookieContext ComputeSameSiteContextForSet( - const GURL& url, + const std::vector<GURL>& url_chain, const SiteForCookies& site_for_cookies, const base::Optional<url::Origin>& initiator, bool is_http, @@ -151,10 +176,10 @@ CookieOptions::SameSiteCookieContext ComputeSameSiteContextForSet( CookieOptions::SameSiteCookieContext same_site_context; same_site_context.set_context(ComputeSameSiteContext( - url, site_for_cookies, initiator, is_http, is_main_frame_navigation, + url_chain, site_for_cookies, initiator, is_http, is_main_frame_navigation, false /* compute_schemefully */)); same_site_context.set_schemeful_context(ComputeSameSiteContext( - url, site_for_cookies, initiator, is_http, is_main_frame_navigation, + url_chain, site_for_cookies, initiator, is_http, is_main_frame_navigation, true /* compute_schemefully */)); // Setting any SameSite={Strict,Lax} cookie only requires a LAX context, so @@ -510,7 +535,7 @@ std::string SerializeRequestCookieLine( CookieOptions::SameSiteCookieContext ComputeSameSiteContextForRequest( const std::string& http_method, - const GURL& url, + const std::vector<GURL>& url_chain, const SiteForCookies& site_for_cookies, const base::Optional<url::Origin>& initiator, bool is_main_frame_navigation, @@ -547,10 +572,10 @@ CookieOptions::SameSiteCookieContext ComputeSameSiteContextForRequest( CookieOptions::SameSiteCookieContext same_site_context; same_site_context.set_context(ComputeSameSiteContext( - url, site_for_cookies, initiator, true /* is_http */, + url_chain, site_for_cookies, initiator, true /* is_http */, is_main_frame_navigation, false /* compute_schemefully */)); same_site_context.set_schemeful_context(ComputeSameSiteContext( - url, site_for_cookies, initiator, true /* is_http */, + url_chain, site_for_cookies, initiator, true /* is_http */, is_main_frame_navigation, true /* compute_schemefully */)); // If the method is safe, the context is Lax. Otherwise, make a note that @@ -578,18 +603,20 @@ ComputeSameSiteContextForScriptGet(const GURL& url, CookieOptions::SameSiteCookieContext same_site_context; + // We don't check the redirect chain for script access to cookies (only the + // URL itself). same_site_context.set_context(ComputeSameSiteContext( - url, site_for_cookies, initiator, false /* is_http */, + {url}, site_for_cookies, initiator, false /* is_http */, false /* is_main_frame_navigation */, false /* compute_schemefully */)); same_site_context.set_schemeful_context(ComputeSameSiteContext( - url, site_for_cookies, initiator, false /* is_http */, + {url}, site_for_cookies, initiator, false /* is_http */, false /* is_main_frame_navigation */, true /* compute_schemefully */)); return same_site_context; } CookieOptions::SameSiteCookieContext ComputeSameSiteContextForResponse( - const GURL& url, + const std::vector<GURL>& url_chain, const SiteForCookies& site_for_cookies, const base::Optional<url::Origin>& initiator, bool is_main_frame_navigation, @@ -597,17 +624,19 @@ CookieOptions::SameSiteCookieContext ComputeSameSiteContextForResponse( if (force_ignore_site_for_cookies) return CookieOptions::SameSiteCookieContext::MakeInclusiveForSet(); + DCHECK(!url_chain.empty()); if (is_main_frame_navigation && !site_for_cookies.IsNull()) { // If the request is a main frame navigation, site_for_cookies must either // be null (for opaque origins, e.g., data: origins) or same-site with the // request URL (both schemefully and schemelessly), and the URL cannot be // ws/wss (these schemes are not navigable). - DCHECK(site_for_cookies.IsFirstPartyWithSchemefulMode(url, true)); - DCHECK(!url.SchemeIsWSOrWSS()); + DCHECK( + site_for_cookies.IsFirstPartyWithSchemefulMode(url_chain.back(), true)); + DCHECK(!url_chain.back().SchemeIsWSOrWSS()); return CookieOptions::SameSiteCookieContext::MakeInclusiveForSet(); } - return ComputeSameSiteContextForSet(url, site_for_cookies, initiator, + return ComputeSameSiteContextForSet(url_chain, site_for_cookies, initiator, true /* is_http */, is_main_frame_navigation); } @@ -621,10 +650,11 @@ CookieOptions::SameSiteCookieContext ComputeSameSiteContextForScriptSet( // It doesn't matter what initiator origin we pass here. Either way, the // context will be considered same-site iff the site_for_cookies is same-site - // with the url. + // with the url. We don't check the redirect chain for script access to + // cookies (only the URL itself). return ComputeSameSiteContextForSet( - url, site_for_cookies, base::nullopt /* initiator */, false /* is_http */, - false /* is_main_frame_navigation */); + {url}, site_for_cookies, base::nullopt /* initiator */, + false /* is_http */, false /* is_main_frame_navigation */); } CookieOptions::SameSiteCookieContext ComputeSameSiteContextForSubresource( @@ -675,17 +705,23 @@ bool IsFirstPartySetsEnabled() { // 2) `isolation_info.party_context` is null. // 3) `cookie_access_delegate.IsContextSamePartyWithSite` returns false. CookieOptions::SamePartyCookieContextType ComputeSamePartyContext( - const net::SchemefulSite& request_site, + const SchemefulSite& request_site, const IsolationInfo& isolation_info, - const CookieAccessDelegate* cookie_access_delegate) { + const CookieAccessDelegate* cookie_access_delegate, + bool force_ignore_top_frame_party) { if (!isolation_info.IsEmpty() && isolation_info.party_context().has_value() && cookie_access_delegate && cookie_access_delegate->IsContextSamePartyWithSite( request_site, - isolation_info.network_isolation_key().GetTopFrameSite().value(), + force_ignore_top_frame_party + ? base::nullopt + : base::make_optional(isolation_info.network_isolation_key() + .GetTopFrameSite() + .value()), isolation_info.party_context().value())) { return CookieOptions::SamePartyCookieContextType::kSameParty; } + return CookieOptions::SamePartyCookieContextType::kCrossParty; } diff --git a/chromium/net/cookies/cookie_util.h b/chromium/net/cookies/cookie_util.h index 4ffa877c4ff..cfb57b4bf93 100644 --- a/chromium/net/cookies/cookie_util.h +++ b/chromium/net/cookies/cookie_util.h @@ -140,11 +140,17 @@ NET_EXPORT void ParseRequestCookieLine(const std::string& header_value, NET_EXPORT std::string SerializeRequestCookieLine( const ParsedRequestCookies& parsed_cookies); -// Determines which of the cookies for `url` can be accessed, with respect to -// the SameSite attribute. This applies to looking up existing cookies for HTTP -// requests. For looking up cookies for non-HTTP APIs (i.e., JavaScript), see -// ComputeSameSiteContextForScriptGet. For setting new cookies, see -// ComputeSameSiteContextForResponse and ComputeSameSiteContextForScriptSet. +// Determines which of the cookies for the request URL can be accessed, with +// respect to the SameSite attribute. This applies to looking up existing +// cookies for HTTP requests. For looking up cookies for non-HTTP APIs (i.e., +// JavaScript), see ComputeSameSiteContextForScriptGet. For setting new cookies, +// see ComputeSameSiteContextForResponse and ComputeSameSiteContextForScriptSet. +// +// `url_chain` is a non-empty vector of URLs, the last of which is the current +// request URL. It represents the redirect chain of the current request. The +// redirect chain is used to calculate whether there has been a cross-site +// redirect. In order for a context to be deemed strictly same-site, there must +// not have been any cross-site redirects. // // `site_for_cookies` is the currently navigated to site that should be // considered "first-party" for cookies. @@ -175,7 +181,7 @@ NET_EXPORT std::string SerializeRequestCookieLine( // when the method is "safe" in the RFC7231 section 4.2.1 sense. NET_EXPORT CookieOptions::SameSiteCookieContext ComputeSameSiteContextForRequest(const std::string& http_method, - const GURL& url, + const std::vector<GURL>& url_chain, const SiteForCookies& site_for_cookies, const base::Optional<url::Origin>& initiator, bool is_main_frame_navigation, @@ -190,16 +196,20 @@ ComputeSameSiteContextForScriptGet(const GURL& url, const base::Optional<url::Origin>& initiator, bool force_ignore_site_for_cookies); -// Determines which of the cookies for `url` can be set from a network response, -// with respect to the SameSite attribute. This will only return CROSS_SITE or -// SAME_SITE_LAX (cookie sets of SameSite=strict cookies are permitted in same -// contexts that sets of SameSite=lax cookies are). +// Determines which of the cookies for the request URL can be set from a network +// response, with respect to the SameSite attribute. This will only return +// CROSS_SITE or SAME_SITE_LAX (cookie sets of SameSite=strict cookies are +// permitted in same contexts that sets of SameSite=lax cookies are). +// `url_chain` is a non-empty vector of URLs, the last of which is the current +// request URL. It represents the redirect chain of the current request. The +// redirect chain is used to calculate whether there has been a cross-site +// redirect. // `is_main_frame_navigation` is whether the request was for a navigation that // targets the main frame or top-level browsing context. Both SameSite=Lax and // SameSite=Strict cookies may be set by any main frame navigation. // If `force_ignore_site_for_cookies` is true, this returns SAME_SITE_LAX. NET_EXPORT CookieOptions::SameSiteCookieContext -ComputeSameSiteContextForResponse(const GURL& url, +ComputeSameSiteContextForResponse(const std::vector<GURL>& url_chain, const SiteForCookies& site_for_cookies, const base::Optional<url::Origin>& initiator, bool is_main_frame_navigation, @@ -231,15 +241,19 @@ NET_EXPORT bool IsSchemefulSameSiteEnabled(); NET_EXPORT bool IsFirstPartySetsEnabled(); -// Compute SameParty context, determines which of the cookies for `request_url` +// Compute SameParty context, determines which of the cookies for `request_site` // can be accessed. Returns either kCrossParty or kSameParty. `isolation_info` // must be fully populated. In Chrome, all requests with credentials enabled // have a fully populated IsolationInfo. But that might not be true for other -// embedders yet (including cast, WebView, etc). Also not sure about iOS. +// embedders yet (including cast, WebView, etc). Also not sure about iOS. If +// `force_ignore_top_frame_party` is true, the top frame from `isolation_info` +// will be assumed to be same-party with `request_site`, regardless of what it +// is. NET_EXPORT CookieOptions::SamePartyCookieContextType ComputeSamePartyContext( - const net::SchemefulSite& request_url, + const SchemefulSite& request_site, const IsolationInfo& isolation_info, - const CookieAccessDelegate* cookie_access_delegate); + const CookieAccessDelegate* cookie_access_delegate, + bool force_ignore_top_frame_party); // Get the SameParty inclusion status. If the cookie is not SameParty, returns // kNoSamePartyEnforcement; if the cookie is SameParty but does not have a diff --git a/chromium/net/cookies/cookie_util_unittest.cc b/chromium/net/cookies/cookie_util_unittest.cc index 6ab7ddfbc7c..c4ff622fc99 100644 --- a/chromium/net/cookies/cookie_util_unittest.cc +++ b/chromium/net/cookies/cookie_util_unittest.cc @@ -9,6 +9,7 @@ #include "base/callback.h" #include "base/containers/contains.h" #include "base/optional.h" +#include "base/strings/strcat.h" #include "base/strings/string_split.h" #include "base/test/bind.h" #include "base/test/scoped_feature_list.h" @@ -351,20 +352,36 @@ MATCHER_P2(ContextTypeIsWithSchemefulMode, context_type, schemeful, "") { return context_type == (schemeful ? arg.schemeful_context() : arg.context()); } +std::string UrlChainToString(const std::vector<GURL>& url_chain) { + std::string s; + for (const GURL& url : url_chain) { + base::StrAppend(&s, {" ", url.spec()}); + } + return s; +} + // Tests for the various ComputeSameSiteContextFor*() functions. The first // boolean test param is whether the results of the computations are evaluated // schemefully. The second boolean test param is whether the fix for -// crbug.com/1166211 is enabled. +// crbug.com/1166211 is enabled. The third boolean param is whether SameSite +// considers redirect chains. class CookieUtilComputeSameSiteContextTest - : public ::testing::TestWithParam<std::tuple<bool, bool>> { + : public ::testing::TestWithParam<std::tuple<bool, bool, bool>> { public: CookieUtilComputeSameSiteContextTest() { - // No need to explicitly enable the feature because it is enabled by - // default. - if (!IsBugfix1166211Enabled()) { - feature_list_.InitAndDisableFeature( - features::kSameSiteCookiesBugfix1166211); + std::vector<base::Feature> enabled_features; + std::vector<base::Feature> disabled_features; + // No need to explicitly enable the Bugfix1166211 feature because it + // is enabled by default. + if (!IsBugfix1166211Enabled()) + disabled_features.push_back(features::kSameSiteCookiesBugfix1166211); + // No need to explicitly disable the redirect chain feature because it + // is disabled by default. + if (DoesSameSiteConsiderRedirectChain()) { + enabled_features.push_back( + features::kCookieSameSiteConsidersRedirectChain); } + feature_list_.InitWithFeatures(enabled_features, disabled_features); } ~CookieUtilComputeSameSiteContextTest() override = default; @@ -375,13 +392,17 @@ class CookieUtilComputeSameSiteContextTest bool IsBugfix1166211Enabled() const { return std::get<1>(GetParam()); } + bool DoesSameSiteConsiderRedirectChain() const { + return std::get<2>(GetParam()); + } + // Returns the proper gtest matcher to use for the schemeless/schemeful mode. auto ContextTypeIs(ContextType context_type) const { return ContextTypeIsWithSchemefulMode(context_type, IsSchemeful()); } - // The following methods return the sets of URLs/SiteForCookies/initiators - // that are same-site or cross-site with respect to kSiteUrl. + // The following methods return the sets of URLs/SiteForCookies/initiators/URL + // chains that are same-site or cross-site with respect to kSiteUrl. std::vector<GURL> GetAllUrls() const { return {kSiteUrl, @@ -409,6 +430,16 @@ class CookieUtilComputeSameSiteContextTest return same_site_urls; } + std::vector<GURL> GetCrossSiteUrls() const { + std::vector<GURL> cross_site_urls; + std::vector<GURL> same_site_urls = GetSameSiteUrls(); + for (const GURL& url : GetAllUrls()) { + if (!base::Contains(same_site_urls, url)) + cross_site_urls.push_back(url); + } + return cross_site_urls; + } + std::vector<SiteForCookies> GetAllSitesForCookies() const { return {kNullSiteForCookies, kSiteForCookies, kSecureSiteForCookies, kCrossSiteForCookies, kSecureCrossSiteForCookies}; @@ -467,6 +498,36 @@ class CookieUtilComputeSameSiteContextTest return cross_site_initiators; } + // Returns an assortment of redirect chains that end in `url` as the + // current request URL, and are completely same-site. `url` is expected to be + // same-site to kSiteUrl. + std::vector<std::vector<GURL>> GetSameSiteUrlChains(const GURL& url) const { + std::vector<std::vector<GURL>> same_site_url_chains; + for (const GURL& same_site_url : GetSameSiteUrls()) { + same_site_url_chains.push_back({same_site_url, url}); + for (const GURL& other_same_site_url : GetSameSiteUrls()) { + same_site_url_chains.push_back( + {other_same_site_url, same_site_url, url}); + } + } + return same_site_url_chains; + } + + // Returns an assortment of redirect chains that end in `url` as the + // current request URL, and are cross-site. `url` is expected to be same-site + // to kSiteUrl. + std::vector<std::vector<GURL>> GetCrossSiteUrlChains(const GURL& url) const { + std::vector<std::vector<GURL>> cross_site_url_chains; + for (const GURL& cross_site_url : GetCrossSiteUrls()) { + cross_site_url_chains.push_back({cross_site_url, url}); + for (const GURL& same_site_url : GetSameSiteUrls()) { + cross_site_url_chains.push_back({cross_site_url, same_site_url, url}); + cross_site_url_chains.push_back({same_site_url, cross_site_url, url}); + } + } + return cross_site_url_chains; + } + // Computes possible values of is_main_frame_navigation that are consistent // with the DCHECKs. bool CanBeMainFrameNavigation(const GURL& url, @@ -545,13 +606,28 @@ TEST_P(CookieUtilComputeSameSiteContextTest, UrlAndSiteForCookiesCrossSite) { for (bool is_main_frame_navigation : IsMainFrameNavigationPossibleValues(url, site_for_cookies)) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, + is_main_frame_navigation, + false /* force_ignore_site_for_cookies */), + ContextTypeIs(ContextType::CROSS_SITE)); + EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( + {url}, site_for_cookies, initiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::CROSS_SITE)); + // If the current request URL is cross-site to the site-for-cookies, + // the request context is always cross-site even if the URL chain + // contains members that are same-site to the site-for-cookies. + EXPECT_THAT( + cookie_util::ComputeSameSiteContextForRequest( + method, {site_for_cookies.RepresentativeUrl(), url}, + site_for_cookies, initiator, is_main_frame_navigation, + false /* force_ignore_site_for_cookies */), + ContextTypeIs(ContextType::CROSS_SITE)); EXPECT_THAT( cookie_util::ComputeSameSiteContextForResponse( - url, site_for_cookies, initiator, is_main_frame_navigation, + {site_for_cookies.RepresentativeUrl(), url}, + site_for_cookies, initiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::CROSS_SITE)); } @@ -592,12 +668,12 @@ TEST_P(CookieUtilComputeSameSiteContextTest, SiteForCookiesNotSchemefullySame) { // If the site-for-cookies isn't schemefully_same, this cannot be a // main frame navigation. EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::CROSS_SITE)); EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( - url, site_for_cookies, initiator, + {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::CROSS_SITE)); @@ -615,7 +691,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, SiteForCookiesNotSchemefullySame) { TEST_P(CookieUtilComputeSameSiteContextTest, ForScriptGet) { for (const GURL& url : GetSameSiteUrls()) { // Same-site site-for-cookies. - // (Cross-site cases covered above in UrlAndSiteForCookiesCrosSite test.) + // (Cross-site cases covered above in UrlAndSiteForCookiesCrossSite test.) for (const SiteForCookies& site_for_cookies : GetSameSiteSitesForCookies()) { // Cross-site initiator -> it's same-site lax. @@ -685,10 +761,11 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForScriptGet_WebSocketSchemes) { ContextTypeIs(ContextType::SAME_SITE_LAX)); } +// Test cases where the URL chain has 1 member (i.e. no redirects). TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { for (const GURL& url : GetSameSiteUrls()) { // Same-site site-for-cookies. - // (Cross-site cases covered above in UrlAndSiteForCookiesCrosSite test.) + // (Cross-site cases covered above in UrlAndSiteForCookiesCrossSite test.) for (const SiteForCookies& site_for_cookies : GetSameSiteSitesForCookies()) { // Same-Site initiator -> it's same-site strict. @@ -698,7 +775,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { for (bool is_main_frame_navigation : IsMainFrameNavigationPossibleValues(url, site_for_cookies)) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_STRICT)); @@ -714,7 +791,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { if (!CanBeMainFrameNavigation(url, site_for_cookies)) break; EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, true /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX)); @@ -723,7 +800,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { if (!CanBeMainFrameNavigation(url, site_for_cookies)) break; EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, true /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX_METHOD_UNSAFE)); @@ -735,7 +812,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { if (IsBugfix1166211Enabled()) { for (const std::string& method : {"GET", "POST", "PUT", "HEAD"}) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::CROSS_SITE)); @@ -743,7 +820,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { } else { for (const std::string& method : {"GET", "HEAD"}) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX)); @@ -751,7 +828,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest) { for (const std::string& method : {"POST", "PUT"}) { EXPECT_THAT( cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX_METHOD_UNSAFE)); @@ -774,13 +851,13 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest_SchemefulDowngrade) { EXPECT_EQ(SameSiteCookieContext(ContextType::SAME_SITE_STRICT, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForRequest( - method, kSecureSiteUrl, kSiteForCookies, kSiteInitiator, + method, {kSecureSiteUrl}, kSiteForCookies, kSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); EXPECT_EQ(SameSiteCookieContext(ContextType::SAME_SITE_STRICT, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForRequest( - method, kSiteUrl, kSecureSiteForCookies, kSiteInitiator, + method, {kSiteUrl}, kSecureSiteForCookies, kSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); } @@ -800,25 +877,25 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest_SchemefulDowngrade) { EXPECT_EQ( SameSiteCookieContext(ContextType::SAME_SITE_STRICT, lax_if_main_frame), cookie_util::ComputeSameSiteContextForRequest( - "GET", kSecureSiteUrl, kSecureSiteForCookies, kSiteInitiator, + "GET", {kSecureSiteUrl}, kSecureSiteForCookies, kSiteInitiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */)); EXPECT_EQ( SameSiteCookieContext(ContextType::SAME_SITE_STRICT, lax_if_main_frame), cookie_util::ComputeSameSiteContextForRequest( - "GET", kSiteUrl, kSiteForCookies, kSecureSiteInitiator, + "GET", {kSiteUrl}, kSiteForCookies, kSecureSiteInitiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */)); EXPECT_EQ(SameSiteCookieContext(ContextType::SAME_SITE_STRICT, lax_unsafe_if_main_frame), cookie_util::ComputeSameSiteContextForRequest( - "POST", kSecureSiteUrl, kSecureSiteForCookies, kSiteInitiator, - is_main_frame_navigation, + "POST", {kSecureSiteUrl}, kSecureSiteForCookies, + kSiteInitiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */)); EXPECT_EQ(SameSiteCookieContext(ContextType::SAME_SITE_STRICT, lax_unsafe_if_main_frame), cookie_util::ComputeSameSiteContextForRequest( - "POST", kSiteUrl, kSiteForCookies, kSecureSiteInitiator, + "POST", {kSiteUrl}, kSiteForCookies, kSecureSiteInitiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */)); } @@ -834,24 +911,24 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest_SchemefulDowngrade) { : ContextType::SAME_SITE_LAX_METHOD_UNSAFE; EXPECT_EQ(SameSiteCookieContext(incorrectly_lax, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForRequest( - "GET", kSiteUrl, kSecureSiteForCookies, kCrossSiteInitiator, + "GET", {kSiteUrl}, kSecureSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); EXPECT_EQ(SameSiteCookieContext(incorrectly_lax, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForRequest( - "GET", kSecureSiteUrl, kSiteForCookies, kCrossSiteInitiator, + "GET", {kSecureSiteUrl}, kSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); EXPECT_EQ( SameSiteCookieContext(incorrectly_lax_unsafe, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForRequest( - "POST", kSiteUrl, kSecureSiteForCookies, kCrossSiteInitiator, + "POST", {kSiteUrl}, kSecureSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); EXPECT_EQ( SameSiteCookieContext(incorrectly_lax_unsafe, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForRequest( - "POST", kSecureSiteUrl, kSiteForCookies, kCrossSiteInitiator, + "POST", {kSecureSiteUrl}, kSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); } @@ -864,35 +941,159 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest_WebSocketSchemes) { : ContextType::SAME_SITE_LAX; EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - "GET", kWssUrl, kSecureSiteForCookies, kSecureSiteInitiator, + "GET", {kWssUrl}, kSecureSiteForCookies, kSecureSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_STRICT)); EXPECT_THAT( cookie_util::ComputeSameSiteContextForRequest( - "GET", kWssUrl, kSecureSiteForCookies, kSecureCrossSiteInitiator, + "GET", {kWssUrl}, kSecureSiteForCookies, kSecureCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(incorrectly_lax)); EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - "GET", kWsUrl, kSiteForCookies, kSiteInitiator, + "GET", {kWsUrl}, kSiteForCookies, kSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_STRICT)); EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - "GET", kWsUrl, kSiteForCookies, kCrossSiteInitiator, + "GET", {kWsUrl}, kSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(incorrectly_lax)); } +// Test cases where the URL chain contains multiple members, where the last +// member (current request URL) is same-site to kSiteUrl. (Everything is listed +// as same-site or cross-site relative to kSiteUrl.) +TEST_P(CookieUtilComputeSameSiteContextTest, ForRequest_Redirect) { + ContextType incorrectly_lax = IsBugfix1166211Enabled() + ? ContextType::CROSS_SITE + : ContextType::SAME_SITE_LAX; + ContextType incorrectly_lax_unsafe = + IsBugfix1166211Enabled() ? ContextType::CROSS_SITE + : ContextType::SAME_SITE_LAX_METHOD_UNSAFE; + struct { + std::string method; + bool url_chain_is_same_site; + bool site_for_cookies_is_same_site; + bool initiator_is_same_site; + // These are the expected context types considering redirect chains: + ContextType expected_context_type; // for non-main-frame-nav requests. + ContextType expected_context_type_for_main_frame_navigation; + // These are the expected context types not considering redirect chains: + ContextType expected_context_type_without_chain; + ContextType expected_context_type_for_main_frame_navigation_without_chain; + } kTestCases[] = { + // If the url chain is same-site, then the result is the same with or + // without considering the redirect chain. + {"GET", true, true, true, ContextType::SAME_SITE_STRICT, + ContextType::SAME_SITE_STRICT, ContextType::SAME_SITE_STRICT, + ContextType::SAME_SITE_STRICT}, + {"GET", true, true, false, incorrectly_lax, ContextType::SAME_SITE_LAX, + incorrectly_lax, ContextType::SAME_SITE_LAX}, + {"GET", true, false, true, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE}, + {"GET", true, false, false, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE}, + // If the url chain is cross-site, then the result will differ depending + // on whether the redirect chain is considered, when the site-for-cookies + // and initiator are both same-site. + {"GET", false, true, true, incorrectly_lax, ContextType::SAME_SITE_LAX, + ContextType::SAME_SITE_STRICT, ContextType::SAME_SITE_STRICT}, + {"GET", false, true, false, incorrectly_lax, ContextType::SAME_SITE_LAX, + incorrectly_lax, ContextType::SAME_SITE_LAX}, + {"GET", false, false, true, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE}, + {"GET", false, false, false, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE}, + // If the url chain is same-site, then the result is the same with or + // without considering the redirect chain. + {"POST", true, true, true, ContextType::SAME_SITE_STRICT, + ContextType::SAME_SITE_STRICT, ContextType::SAME_SITE_STRICT, + ContextType::SAME_SITE_STRICT}, + {"POST", true, true, false, incorrectly_lax_unsafe, + ContextType::SAME_SITE_LAX_METHOD_UNSAFE, incorrectly_lax_unsafe, + ContextType::SAME_SITE_LAX_METHOD_UNSAFE}, + {"POST", true, false, true, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE}, + {"POST", true, false, false, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE}, + // If the url chain is cross-site, then the result will differ depending + // on whether the redirect chain is considered, when the site-for-cookies + // and initiator are both same-site. + {"POST", false, true, true, incorrectly_lax_unsafe, + ContextType::SAME_SITE_LAX_METHOD_UNSAFE, ContextType::SAME_SITE_STRICT, + ContextType::SAME_SITE_STRICT}, + {"POST", false, true, false, incorrectly_lax_unsafe, + ContextType::SAME_SITE_LAX_METHOD_UNSAFE, incorrectly_lax_unsafe, + ContextType::SAME_SITE_LAX_METHOD_UNSAFE}, + {"POST", false, false, true, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE}, + {"POST", false, false, false, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE}, + }; + + for (const auto& test_case : kTestCases) { + std::vector<std::vector<GURL>> url_chains = + test_case.url_chain_is_same_site ? GetSameSiteUrlChains(kSiteUrl) + : GetCrossSiteUrlChains(kSiteUrl); + std::vector<SiteForCookies> sites_for_cookies = + test_case.site_for_cookies_is_same_site ? GetSameSiteSitesForCookies() + : GetCrossSiteSitesForCookies(); + std::vector<base::Optional<url::Origin>> initiators = + test_case.initiator_is_same_site ? GetSameSiteInitiators() + : GetCrossSiteInitiators(); + ContextType expected_context_type = + DoesSameSiteConsiderRedirectChain() + ? test_case.expected_context_type + : test_case.expected_context_type_without_chain; + ContextType expected_context_type_for_main_frame_navigation = + DoesSameSiteConsiderRedirectChain() + ? test_case.expected_context_type_for_main_frame_navigation + : test_case + .expected_context_type_for_main_frame_navigation_without_chain; + for (const std::vector<GURL>& url_chain : url_chains) { + for (const SiteForCookies& site_for_cookies : sites_for_cookies) { + for (const base::Optional<url::Origin>& initiator : initiators) { + EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( + test_case.method, url_chain, site_for_cookies, + initiator, false /* is_main_frame_navigation */, + false /* force_ignore_site_for_cookies */), + ContextTypeIs(expected_context_type)) + << UrlChainToString(url_chain) << " " + << site_for_cookies.ToDebugString() << " " + << (initiator ? initiator->Serialize() : "nullopt"); + if (!CanBeMainFrameNavigation(url_chain.back(), site_for_cookies)) + continue; + EXPECT_THAT( + cookie_util::ComputeSameSiteContextForRequest( + test_case.method, url_chain, site_for_cookies, initiator, + true /* is_main_frame_navigation */, + false /* force_ignore_site_for_cookies */), + ContextTypeIs(expected_context_type_for_main_frame_navigation)) + << UrlChainToString(url_chain) << " " + << site_for_cookies.ToDebugString() << " " + << (initiator ? initiator->Serialize() : "nullopt"); + } + } + } + } +} + TEST_P(CookieUtilComputeSameSiteContextTest, ForScriptSet) { for (const GURL& url : GetSameSiteUrls()) { for (const SiteForCookies& site_for_cookies : GetSameSiteSitesForCookies()) { // Same-site site-for-cookies -> it's same-site lax. - // (Cross-site cases covered above in UrlAndSiteForCookiesCrosSite test.) + // (Cross-site cases covered above in UrlAndSiteForCookiesCrossSite test.) EXPECT_THAT( cookie_util::ComputeSameSiteContextForScriptSet( url, site_for_cookies, false /* force_ignore_site_for_cookies */), @@ -928,10 +1129,11 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForScriptSet_WebSocketSchemes) { ContextTypeIs(ContextType::SAME_SITE_LAX)); } +// Test cases where the URL chain has 1 member (i.e. no redirects). TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse) { for (const GURL& url : GetSameSiteUrls()) { // Same-site site-for-cookies. - // (Cross-site cases covered above in UrlAndSiteForCookiesCrosSite test.) + // (Cross-site cases covered above in UrlAndSiteForCookiesCrossSite test.) for (const SiteForCookies& site_for_cookies : GetSameSiteSitesForCookies()) { // For main frame navigations, setting all SameSite cookies is allowed @@ -940,7 +1142,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse) { if (!CanBeMainFrameNavigation(url, site_for_cookies)) break; EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( - url, site_for_cookies, initiator, + {url}, site_for_cookies, initiator, true /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX)); @@ -952,7 +1154,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse) { for (const base::Optional<url::Origin>& initiator : GetSameSiteInitiators()) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( - url, site_for_cookies, initiator, + {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX)); @@ -963,7 +1165,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse) { ? ContextType::CROSS_SITE : ContextType::SAME_SITE_LAX; EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( - url, site_for_cookies, initiator, + {url}, site_for_cookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(incorrectly_lax)); @@ -983,13 +1185,13 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse_SchemefulDowngrade) { EXPECT_EQ(SameSiteCookieContext(ContextType::SAME_SITE_LAX, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForResponse( - kSiteUrl, kSecureSiteForCookies, kSiteInitiator, + {kSiteUrl}, kSecureSiteForCookies, kSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); EXPECT_EQ(SameSiteCookieContext(ContextType::SAME_SITE_LAX, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForResponse( - kSecureSiteUrl, kSiteForCookies, kSecureSiteInitiator, + {kSecureSiteUrl}, kSiteForCookies, kSecureSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); // With cross-site initiator: @@ -998,12 +1200,12 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse_SchemefulDowngrade) { : ContextType::SAME_SITE_LAX; EXPECT_EQ(SameSiteCookieContext(incorrectly_lax, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForResponse( - kSiteUrl, kSecureSiteForCookies, kCrossSiteInitiator, + {kSiteUrl}, kSecureSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); EXPECT_EQ(SameSiteCookieContext(incorrectly_lax, ContextType::CROSS_SITE), cookie_util::ComputeSameSiteContextForResponse( - kSecureSiteUrl, kSiteForCookies, kCrossSiteInitiator, + {kSecureSiteUrl}, kSiteForCookies, kCrossSiteInitiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */)); @@ -1017,13 +1219,13 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse_SchemefulDowngrade) { EXPECT_EQ( SameSiteCookieContext(ContextType::SAME_SITE_LAX, lax_if_main_frame), cookie_util::ComputeSameSiteContextForResponse( - kSiteUrl, kSiteForCookies, kSecureSiteInitiator, + {kSiteUrl}, kSiteForCookies, kSecureSiteInitiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */)); EXPECT_EQ( SameSiteCookieContext(ContextType::SAME_SITE_LAX, lax_if_main_frame), cookie_util::ComputeSameSiteContextForResponse( - kSecureSiteUrl, kSecureSiteForCookies, kSiteInitiator, + {kSecureSiteUrl}, kSecureSiteForCookies, kSiteInitiator, is_main_frame_navigation, false /* force_ignore_site_for_cookies */)); } @@ -1036,7 +1238,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse_WebSocketSchemes) { // Same-site initiators. for (const base::Optional<url::Origin>& initiator : GetSameSiteInitiators()) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( - kWsUrl, kSiteForCookies, initiator, + {kWsUrl}, kSiteForCookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX)); @@ -1048,17 +1250,104 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse_WebSocketSchemes) { ? ContextType::CROSS_SITE : ContextType::SAME_SITE_LAX; EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( - kWsUrl, kSiteForCookies, initiator, + {kWsUrl}, kSiteForCookies, initiator, false /* is_main_frame_navigation */, false /* force_ignore_site_for_cookies */), ContextTypeIs(incorrectly_lax)); } } +// Test cases where the URL chain contains multiple members, where the last +// member (current request URL) is same-site to kSiteUrl. (Everything is listed +// as same-site or cross-site relative to kSiteUrl.) +TEST_P(CookieUtilComputeSameSiteContextTest, ForResponse_Redirect) { + ContextType incorrectly_lax = IsBugfix1166211Enabled() + ? ContextType::CROSS_SITE + : ContextType::SAME_SITE_LAX; + struct { + bool url_chain_is_same_site; + bool site_for_cookies_is_same_site; + bool initiator_is_same_site; + // These are the expected context types considering redirect chains: + ContextType expected_context_type; // for non-main-frame-nav requests. + ContextType expected_context_type_for_main_frame_navigation; + // These are the expected context types not considering redirect chains: + ContextType expected_context_type_without_chain; + ContextType expected_context_type_for_main_frame_navigation_without_chain; + } kTestCases[] = { + // If the url chain is same-site, then the result is the same with or + // without considering the redirect chain. + {true, true, true, ContextType::SAME_SITE_LAX, ContextType::SAME_SITE_LAX, + ContextType::SAME_SITE_LAX, ContextType::SAME_SITE_LAX}, + {true, true, false, incorrectly_lax, ContextType::SAME_SITE_LAX, + incorrectly_lax, ContextType::SAME_SITE_LAX}, + {true, false, true, ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE}, + {true, false, false, ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE}, + // If the url chain is cross-site, then the result will differ depending + // on whether the redirect chain is considered, when the site-for-cookies + // and initiator are both same-site. + {false, true, true, incorrectly_lax, ContextType::SAME_SITE_LAX, + ContextType::SAME_SITE_LAX, ContextType::SAME_SITE_LAX}, + {false, true, false, incorrectly_lax, ContextType::SAME_SITE_LAX, + incorrectly_lax, ContextType::SAME_SITE_LAX}, + {false, false, true, ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE}, + {false, false, false, ContextType::CROSS_SITE, ContextType::CROSS_SITE, + ContextType::CROSS_SITE, ContextType::CROSS_SITE}, + }; + for (const auto& test_case : kTestCases) { + std::vector<std::vector<GURL>> url_chains = + test_case.url_chain_is_same_site ? GetSameSiteUrlChains(kSiteUrl) + : GetCrossSiteUrlChains(kSiteUrl); + std::vector<SiteForCookies> sites_for_cookies = + test_case.site_for_cookies_is_same_site ? GetSameSiteSitesForCookies() + : GetCrossSiteSitesForCookies(); + std::vector<base::Optional<url::Origin>> initiators = + test_case.initiator_is_same_site ? GetSameSiteInitiators() + : GetCrossSiteInitiators(); + ContextType expected_context_type = + DoesSameSiteConsiderRedirectChain() + ? test_case.expected_context_type + : test_case.expected_context_type_without_chain; + ContextType expected_context_type_for_main_frame_navigation = + DoesSameSiteConsiderRedirectChain() + ? test_case.expected_context_type_for_main_frame_navigation + : test_case + .expected_context_type_for_main_frame_navigation_without_chain; + for (const std::vector<GURL>& url_chain : url_chains) { + for (const SiteForCookies& site_for_cookies : sites_for_cookies) { + for (const base::Optional<url::Origin>& initiator : initiators) { + EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( + url_chain, site_for_cookies, initiator, + false /* is_main_frame_navigation */, + false /* force_ignore_site_for_cookies */), + ContextTypeIs(expected_context_type)) + << UrlChainToString(url_chain) << " " + << site_for_cookies.ToDebugString() << " " + << (initiator ? initiator->Serialize() : "nullopt"); + if (!CanBeMainFrameNavigation(url_chain.back(), site_for_cookies)) + continue; + EXPECT_THAT( + cookie_util::ComputeSameSiteContextForResponse( + url_chain, site_for_cookies, initiator, + true /* is_main_frame_navigation */, + false /* force_ignore_site_for_cookies */), + ContextTypeIs(expected_context_type_for_main_frame_navigation)) + << UrlChainToString(url_chain) << " " + << site_for_cookies.ToDebugString() << " " + << (initiator ? initiator->Serialize() : "nullopt"); + } + } + } + } +} + TEST_P(CookieUtilComputeSameSiteContextTest, ForSubresource) { for (const GURL& url : GetSameSiteUrls()) { // Same-site site-for-cookies. - // (Cross-site cases covered above in UrlAndSiteForCookiesCrosSite test.) + // (Cross-site cases covered above in UrlAndSiteForCookiesCrossSite test.) for (const SiteForCookies& site_for_cookies : GetSameSiteSitesForCookies()) { EXPECT_THAT( @@ -1115,13 +1404,25 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForceIgnoreSiteForCookies) { for (bool is_main_frame_navigation : IsMainFrameNavigationPossibleValues(url, site_for_cookies)) { EXPECT_THAT(cookie_util::ComputeSameSiteContextForRequest( - method, url, site_for_cookies, initiator, + method, {url}, site_for_cookies, initiator, is_main_frame_navigation, true /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_STRICT)); + EXPECT_THAT(cookie_util::ComputeSameSiteContextForResponse( + {url}, site_for_cookies, initiator, + is_main_frame_navigation, + true /* force_ignore_site_for_cookies */), + ContextTypeIs(ContextType::SAME_SITE_LAX)); + EXPECT_THAT( + cookie_util::ComputeSameSiteContextForRequest( + method, {site_for_cookies.RepresentativeUrl(), url}, + site_for_cookies, initiator, is_main_frame_navigation, + true /* force_ignore_site_for_cookies */), + ContextTypeIs(ContextType::SAME_SITE_STRICT)); EXPECT_THAT( cookie_util::ComputeSameSiteContextForResponse( - url, site_for_cookies, initiator, is_main_frame_navigation, + {site_for_cookies.RepresentativeUrl(), url}, + site_for_cookies, initiator, is_main_frame_navigation, true /* force_ignore_site_for_cookies */), ContextTypeIs(ContextType::SAME_SITE_LAX)); } @@ -1138,6 +1439,7 @@ TEST_P(CookieUtilComputeSameSiteContextTest, ForceIgnoreSiteForCookies) { INSTANTIATE_TEST_SUITE_P(/* no label */, CookieUtilComputeSameSiteContextTest, ::testing::Combine(::testing::Bool(), + ::testing::Bool(), ::testing::Bool())); TEST(CookieUtilTest, AdaptCookieAccessResultToBool) { diff --git a/chromium/net/cookies/parsed_cookie.cc b/chromium/net/cookies/parsed_cookie.cc index f6c1cd4326f..5ef07e07213 100644 --- a/chromium/net/cookies/parsed_cookie.cc +++ b/chromium/net/cookies/parsed_cookie.cc @@ -122,12 +122,6 @@ bool IsValidCookieValue(const std::string& value) { return true; } -// RFC 5234 states that control characters are considered to be: -// CTL = %x00-1F / %x7F -bool IsControlCharacter(unsigned char c) { - return c <= 0x1F || c == 0x7F; -} - } // namespace namespace net { @@ -352,7 +346,7 @@ 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 (IsControlCharacter(*i) || *i == ';') + if (HttpUtil::IsControlChar(*i) || *i == ';') return false; } return true; diff --git a/chromium/net/cookies/site_for_cookies.cc b/chromium/net/cookies/site_for_cookies.cc index 71e58a04a81..977a0b7c7af 100644 --- a/chromium/net/cookies/site_for_cookies.cc +++ b/chromium/net/cookies/site_for_cookies.cc @@ -73,8 +73,11 @@ bool SiteForCookies::IsFirstPartyWithSchemefulMode( } bool SiteForCookies::IsEquivalent(const SiteForCookies& other) const { - if (IsNull()) - return other.IsNull(); + if (IsNull() || other.IsNull()) { + // We need to check if `other.IsNull()` explicitly in order to catch if + // `other.schemefully_same_` is false when "Schemeful Same-Site" is enabled. + return IsNull() && other.IsNull(); + } // In the case where the site has no registrable domain or host, the scheme // cannot be ws(s) or http(s), so equality of sites implies actual equality of diff --git a/chromium/net/cookies/site_for_cookies.h b/chromium/net/cookies/site_for_cookies.h index 031e765770d..d0379b23522 100644 --- a/chromium/net/cookies/site_for_cookies.h +++ b/chromium/net/cookies/site_for_cookies.h @@ -19,17 +19,21 @@ namespace net { // Represents which origins are to be considered same-site for a given // context (e.g. frame). There may be none. // -// The currently implemented policy ("schemeless") is that two valid URLs would -// be considered the same site if either: -// 1) They both have non-empty and equal registrable domains or hostnames/IPs. -// 2) They both have empty hostnames and equal schemes. -// -// With the SchemefulSameSite feature enabled the policy ("schemeful") is that +// The currently implemented policy ("schemeful") is that // two valid URLs would be considered same-site if either: // 1) They both have compatible schemes along with non-empty and equal // registrable domains or hostnames/IPs. Two schemes are compatible if they // are either equal, or are both in {http, ws}, or are both in {https, wss}. -// 2) They both have empty hostnames and exactly equal schemes. +// E.x. "https://example.com" and "wss://example.com" +// 2) They both have empty hostnames and exactly equal schemes. E.x. "file://" +// and "file://" +// +// With the SchemefulSameSite feature disabled the policy ("schemeless") is that +// two valid URLs would be considered the same site if either: 1) They both have +// non-empty and equal registrable domains or hostnames/IPs. E.x. "example.com" +// and "example.com" 2) They both have empty hostnames and equal schemes. E.x. +// "file://" and "file://" +// // // Invalid URLs are never same-site to anything. class NET_EXPORT SiteForCookies { @@ -59,8 +63,8 @@ class NET_EXPORT SiteForCookies { // If the origin is opaque, returns SiteForCookies that matches nothing. // - // If it's not, returns one that matches URLs which are considered to be - // same-party as URLs from `origin`. + // If it's not opaque, returns one that matches URLs which are considered to + // be same-party as URLs from `origin`. static SiteForCookies FromOrigin(const url::Origin& origin); // Equivalent to FromOrigin(url::Origin::Create(url)). @@ -90,7 +94,9 @@ class NET_EXPORT SiteForCookies { bool compute_schemefully) const; // Returns true if `other.IsFirstParty()` is true for exactly the same URLs - // as `this->IsFirstParty` (potentially none). + // as `this->IsFirstParty` (potentially none). Two SFCs are also considered + // equivalent if neither ever returns true for `IsFirstParty()`. I.e., both + // are null. bool IsEquivalent(const SiteForCookies& other) const; // Compares a "candidate" SFC, `this`, with an origin (represented as a @@ -123,7 +129,7 @@ class NET_EXPORT SiteForCookies { // Same-Site" is disabled.) bool CompareWithFrameTreeSiteAndRevise(const SchemefulSite& other); - // Overload which converts an Origin into a SchemefulSite and then calls + // Converts an Origin into a SchemefulSite and then calls //`CompareWithFrameTreeSiteAndRevise(SchemefulSite)` // // If possible, prefer `CompareWithFrameTreeSiteAndRevise(SchemefulSite)` @@ -151,7 +157,7 @@ class NET_EXPORT SiteForCookies { } // Used for serialization/deserialization. This value is irrelevant if - // IsNull() is true. + // site().opaque() is true. bool schemefully_same() const { return schemefully_same_; } void SetSchemefullySameForTesting(bool schemefully_same) { diff --git a/chromium/net/cookies/site_for_cookies_unittest.cc b/chromium/net/cookies/site_for_cookies_unittest.cc index 546813386e0..69526f6de35 100644 --- a/chromium/net/cookies/site_for_cookies_unittest.cc +++ b/chromium/net/cookies/site_for_cookies_unittest.cc @@ -18,10 +18,10 @@ namespace net { namespace { -class SchemefulSiteForCookiesTest : public ::testing::Test { +class SchemelessSiteForCookiesTest : public ::testing::Test { public: - SchemefulSiteForCookiesTest() { - scope_feature_list_.InitAndEnableFeature(features::kSchemefulSameSite); + SchemelessSiteForCookiesTest() { + scope_feature_list_.InitAndDisableFeature(features::kSchemefulSameSite); } protected: @@ -93,7 +93,7 @@ TEST(SiteForCookiesTest, Default) { should_match_none.ToDebugString()); } -TEST(SiteForCookiesTest, Basic) { +TEST_F(SchemelessSiteForCookiesTest, Basic) { std::vector<GURL> equivalent = { GURL("https://example.com"), GURL("http://sub1.example.com:42/something"), @@ -108,8 +108,9 @@ TEST(SiteForCookiesTest, Basic) { TestEquivalentAndDistinct(equivalent, distinct, "example.com"); } -// Similar to SiteForCookiesTest_Basic with a focus on testing secure SFCs. -TEST_F(SchemefulSiteForCookiesTest, BasicSecure) { +// Similar to SchemelessSiteForCookiesTest_Basic with a focus on testing secure +// SFCs. +TEST(SiteForCookiesTest, BasicSecure) { std::vector<GURL> equivalent = {GURL("https://example.com"), GURL("wss://example.com"), GURL("https://sub1.example.com:42/something"), @@ -124,8 +125,9 @@ TEST_F(SchemefulSiteForCookiesTest, BasicSecure) { TestEquivalentAndDistinct(equivalent, distinct, "example.com"); } -// Similar to SiteForCookiesTest_Basic with a focus on testing insecure SFCs. -TEST_F(SchemefulSiteForCookiesTest, BasicInsecure) { +// Similar to SchemelessSiteForCookiesTest_Basic with a focus on testing +// insecure SFCs. +TEST(SiteForCookiesTest, BasicInsecure) { std::vector<GURL> equivalent = {GURL("http://example.com"), GURL("ws://example.com"), GURL("http://sub1.example.com:42/something"), @@ -149,7 +151,7 @@ TEST(SiteForCookiesTest, File) { TestEquivalentAndDistinct(equivalent, distinct, ""); } -TEST(SiteForCookiesTest, Extension) { +TEST_F(SchemelessSiteForCookiesTest, Extension) { url::ScopedSchemeRegistryForTests scoped_registry; url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST); std::vector<GURL> equivalent = {GURL("chrome-extension://abc/"), @@ -163,9 +165,9 @@ TEST(SiteForCookiesTest, Extension) { TestEquivalentAndDistinct(equivalent, distinct, "abc"); } -// Similar to SiteForCookiesTest_Extension with a focus on ensuring that http(s) -// schemes are distinct. -TEST_F(SchemefulSiteForCookiesTest, Extension) { +// Similar to SchemelessSiteForCookiesTest_Extension with a focus on ensuring +// that http(s) schemes are distinct. +TEST(SiteForCookiesTest, Extension) { url::ScopedSchemeRegistryForTests scoped_registry; url::AddStandardScheme("chrome-extension", url::SCHEME_WITH_HOST); std::vector<GURL> equivalent = { @@ -192,7 +194,7 @@ TEST(SiteForCookiesTest, NonStandard) { TestEquivalentAndDistinct(equivalent, distinct, ""); } -TEST(SiteForCookiesTest, Blob) { +TEST_F(SchemelessSiteForCookiesTest, Blob) { // This case isn't really well-specified and is inconsistent between // different user agents; the behavior chosen here was to be more // consistent between url and origin handling. @@ -210,8 +212,8 @@ TEST(SiteForCookiesTest, Blob) { SiteForCookies::FromUrl(GURL("http://www.example.org:631")))); } -// Similar to SiteForCookiesTest_Blob with a focus on a secure blob. -TEST_F(SchemefulSiteForCookiesTest, SecureBlob) { +// Similar to SchemelessSiteForCookiesTest_Blob with a focus on a secure blob. +TEST(SiteForCookiesTest, SecureBlob) { SiteForCookies from_blob = SiteForCookies::FromUrl( GURL("blob:https://example.org/9115d58c-bcda-ff47-86e5-083e9a2153041")); @@ -227,8 +229,9 @@ TEST_F(SchemefulSiteForCookiesTest, SecureBlob) { SiteForCookies::FromUrl(GURL("http://www.example.org:631")))); } -// Similar to SiteForCookiesTest_Blob with a focus on an insecure blob. -TEST_F(SchemefulSiteForCookiesTest, InsecureBlob) { +// Similar to SchemelessSiteForCookiesTest_Blob with a focus on an insecure +// blob. +TEST(SiteForCookiesTest, InsecureBlob) { SiteForCookies from_blob = SiteForCookies::FromUrl( GURL("blob:http://example.org/9115d58c-bcda-ff47-86e5-083e9a2153041")); @@ -245,7 +248,7 @@ TEST_F(SchemefulSiteForCookiesTest, InsecureBlob) { SiteForCookies::FromUrl(GURL("https://www.example.org:631")))); } -TEST(SiteForCookiesTest, Wire) { +TEST_F(SchemelessSiteForCookiesTest, Wire) { SiteForCookies out; // Empty one. @@ -295,9 +298,9 @@ TEST(SiteForCookiesTest, Wire) { out.ToDebugString()); } -// Similar to SiteForCookiesTest_Wire except that schemefully_same has an -// effect (makes IsNull() return true if schemefully_same is false). -TEST_F(SchemefulSiteForCookiesTest, Wire) { +// Similar to SchemelessSiteForCookiesTest_Wire except that schemefully_same has +// an effect (makes IsNull() return true if schemefully_same is false). +TEST(SiteForCookiesTest, Wire) { SiteForCookies out; // Empty one. @@ -356,7 +359,7 @@ TEST_F(SchemefulSiteForCookiesTest, Wire) { out.ToDebugString()); } -TEST_F(SchemefulSiteForCookiesTest, SchemefulSite) { +TEST(SiteForCookiesTest, SchemefulSite) { const char* kTestCases[] = {"opaque.com", "http://a.com", "https://sub1.example.com:42/something", @@ -499,6 +502,29 @@ TEST(SiteForCookiesTest, CompareWithFrameTreeSiteAndReviseOpaque) { EXPECT_EQ(candidate4.site(), opaque_site1); } +TEST(SiteForCookiesTest, NotSchemefullySameEquivalent) { + SiteForCookies first = + SiteForCookies::FromUrl(GURL("https://www.example.com")); + SiteForCookies second = + SiteForCookies::FromUrl(GURL("https://www.example.com")); + // Smoke check that two SFCs should match when they're the same. + EXPECT_TRUE(first.IsEquivalent(second)); + EXPECT_TRUE(second.IsEquivalent(first)); + + // Two SFC should not be equivalent to each other when one of their + // schemefully_same_ flags is false, even if they're otherwise the same, when + // Schemeful Same-Site is enabled. + second.SetSchemefullySameForTesting(false); + EXPECT_FALSE(first.IsEquivalent(second)); + EXPECT_FALSE(second.IsEquivalent(first)); + + // However, they should match if both their schemefully_same_ flags are false. + // Because they're both considered null at that point. + first.SetSchemefullySameForTesting(false); + EXPECT_TRUE(first.IsEquivalent(second)); + EXPECT_TRUE(second.IsEquivalent(first)); +} + } // namespace TEST(SiteForCookiesTest, SameScheme) { diff --git a/chromium/net/cookies/static_cookie_policy_unittest.cc b/chromium/net/cookies/static_cookie_policy_unittest.cc index 958de262d90..3c57a57cc92 100644 --- a/chromium/net/cookies/static_cookie_policy_unittest.cc +++ b/chromium/net/cookies/static_cookie_policy_unittest.cc @@ -57,8 +57,9 @@ TEST_F(StaticCookiePolicyTest, BlockAllThirdPartyCookiesTest) { SetPolicyType(StaticCookiePolicy::BLOCK_ALL_THIRD_PARTY_COOKIES); EXPECT_THAT(CanAccessCookies(url_google_, url_google_), IsOk()); - EXPECT_THAT(CanAccessCookies(url_google_, url_google_secure_), IsOk()); EXPECT_THAT(CanAccessCookies(url_google_, url_google_mail_), IsOk()); + EXPECT_NE(OK, CanAccessCookies(url_google_, url_google_secure_)); + EXPECT_NE(OK, CanAccessCookies(url_google_secure_, url_google_)); EXPECT_NE(OK, CanAccessCookies(url_google_, url_google_analytics_)); EXPECT_NE(OK, CanAccessCookies(url_google_, GURL())); } diff --git a/chromium/net/cookies/test_cookie_access_delegate.cc b/chromium/net/cookies/test_cookie_access_delegate.cc index d1307806a60..d98eda75297 100644 --- a/chromium/net/cookies/test_cookie_access_delegate.cc +++ b/chromium/net/cookies/test_cookie_access_delegate.cc @@ -35,7 +35,7 @@ bool TestCookieAccessDelegate::ShouldIgnoreSameSiteRestrictions( bool TestCookieAccessDelegate::IsContextSamePartyWithSite( const net::SchemefulSite& site, - const net::SchemefulSite& top_frame_site, + const base::Optional<net::SchemefulSite>& top_frame_site, const std::set<net::SchemefulSite>& party_context) const { return false; } diff --git a/chromium/net/cookies/test_cookie_access_delegate.h b/chromium/net/cookies/test_cookie_access_delegate.h index 722f6f45948..8a9851dfd7b 100644 --- a/chromium/net/cookies/test_cookie_access_delegate.h +++ b/chromium/net/cookies/test_cookie_access_delegate.h @@ -30,7 +30,7 @@ class TestCookieAccessDelegate : public CookieAccessDelegate { const SiteForCookies& site_for_cookies) const override; bool IsContextSamePartyWithSite( const net::SchemefulSite& site, - const net::SchemefulSite& top_frame_site, + const base::Optional<net::SchemefulSite>& top_frame_site, const std::set<net::SchemefulSite>& party_context) const override; bool IsInNontrivialFirstPartySet( const net::SchemefulSite& site) const override; |