Commit 324657c3 authored by Lily Chen's avatar Lily Chen Committed by Commit Bot

Parsing cookie SameSite: add None & Extended options, change default

This CL changes the default net::CookieSameSite value to UNSPECIFIED
(it was previously NO_RESTRICTION). The default is used when the cookie
does not specify a SameSite attribute, or if the given SameSite
attribute does not specify a valid value.

Support is added for the new `SameSite=None` option, which parses as
CookieSameSite::NO_RESTRICTION. Support is also added for the new
`SameSite=Extended` option, which for now is identical to Lax mode.

Bug: 953306, 953995
Change-Id: I96ee07f5e626a2f99e65e9c27260a8f6e5b6b9f4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1573081
Commit-Queue: Lily Chen <chlily@chromium.org>
Reviewed-by: default avatarMaks Orlovich <morlovich@chromium.org>
Cr-Commit-Position: refs/heads/master@{#652529}
parent 6afa4142
...@@ -139,8 +139,8 @@ class NET_EXPORT CanonicalCookie { ...@@ -139,8 +139,8 @@ class NET_EXPORT CanonicalCookie {
// having been canonicalized (in // having been canonicalized (in
// GetCookieDomainWithString->CanonicalizeHost). // GetCookieDomainWithString->CanonicalizeHost).
bool IsEquivalent(const CanonicalCookie& ecc) const { bool IsEquivalent(const CanonicalCookie& ecc) const {
// It seems like it would make sense to take secure and httponly into // It seems like it would make sense to take secure, httponly, and samesite
// account, but the RFC doesn't specify this. // into account, but the RFC doesn't specify this.
// NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost(). // NOTE: Keep this logic in-sync with TrimDuplicateCookiesForHost().
return (name_ == ecc.Name() && domain_ == ecc.Domain() return (name_ == ecc.Name() && domain_ == ecc.Domain()
&& path_ == ecc.Path()); && path_ == ecc.Path());
......
...@@ -130,6 +130,18 @@ TEST(CanonicalCookieTest, Create) { ...@@ -130,6 +130,18 @@ TEST(CanonicalCookieTest, Create) {
same_site_options); same_site_options);
ASSERT_TRUE(cookie.get()); ASSERT_TRUE(cookie.get());
EXPECT_EQ(CookieSameSite::LAX_MODE, cookie->SameSite()); EXPECT_EQ(CookieSameSite::LAX_MODE, cookie->SameSite());
cookie = CanonicalCookie::Create(url, "A=2; SameSite=Extended", creation_time,
same_site_options);
ASSERT_TRUE(cookie.get());
EXPECT_EQ(CookieSameSite::EXTENDED_MODE, cookie->SameSite());
cookie = CanonicalCookie::Create(url, "A=2; SameSite=None", creation_time,
same_site_options);
ASSERT_TRUE(cookie.get());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite());
cookie =
CanonicalCookie::Create(url, "A=2", creation_time, same_site_options);
ASSERT_TRUE(cookie.get());
EXPECT_EQ(CookieSameSite::UNSPECIFIED, cookie->SameSite());
// Test the creating cookies using specific parameter instead of a cookie // Test the creating cookies using specific parameter instead of a cookie
// string. // string.
...@@ -171,12 +183,12 @@ TEST(CanonicalCookieTest, CreateNonStandardSameSite) { ...@@ -171,12 +183,12 @@ TEST(CanonicalCookieTest, CreateNonStandardSameSite) {
cookie = cookie =
CanonicalCookie::Create(url, "A=2; SameSite=NonStandard", now, options); CanonicalCookie::Create(url, "A=2; SameSite=NonStandard", now, options);
EXPECT_TRUE(cookie.get()); EXPECT_TRUE(cookie.get());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, cookie->SameSite());
// Omit value for the SameSite attribute. // Omit value for the SameSite attribute.
cookie = CanonicalCookie::Create(url, "A=2; SameSite", now, options); cookie = CanonicalCookie::Create(url, "A=2; SameSite", now, options);
EXPECT_TRUE(cookie.get()); EXPECT_TRUE(cookie.get());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, cookie->SameSite());
} }
TEST(CanonicalCookieTest, CreateInvalidHttpOnly) { TEST(CanonicalCookieTest, CreateInvalidHttpOnly) {
...@@ -568,10 +580,29 @@ TEST(CanonicalCookieTest, IncludeSameSiteForSameSiteURL) { ...@@ -568,10 +580,29 @@ TEST(CanonicalCookieTest, IncludeSameSiteForSameSiteURL) {
EXPECT_EQ(cookie->IncludeForRequestURL(url, options), EXPECT_EQ(cookie->IncludeForRequestURL(url, options),
CanonicalCookie::CookieInclusionStatus::INCLUDE); CanonicalCookie::CookieInclusionStatus::INCLUDE);
// TODO(chlily): Use SameSite=None once ParsedCookie supports it. // `SameSite=Extended` cookies are included for a URL only if the options'
// Cookies without a SameSite attribute are parsed as // SameSiteCookieMode is SAME_SITE_STRICT or SAME_SITE_LAX.
// CookieSameSite::NO_RESTRICTION, and are included for all URLs. // TODO(crbug.com/953995): Right now Extended behaves the same as Lax.
cookie = CanonicalCookie::Create(url, "A=2", creation_time, options); // Implement Extended.
cookie = CanonicalCookie::Create(url, "A=2; SameSite=Extended", creation_time,
options);
EXPECT_EQ(CookieSameSite::EXTENDED_MODE, cookie->SameSite());
options.set_same_site_cookie_context(
CookieOptions::SameSiteCookieContext::CROSS_SITE);
EXPECT_EQ(cookie->IncludeForRequestURL(url, options),
CanonicalCookie::CookieInclusionStatus::EXCLUDE_SAMESITE_LAX);
options.set_same_site_cookie_context(
CookieOptions::SameSiteCookieContext::SAME_SITE_LAX);
EXPECT_EQ(cookie->IncludeForRequestURL(url, options),
CanonicalCookie::CookieInclusionStatus::INCLUDE);
options.set_same_site_cookie_context(
CookieOptions::SameSiteCookieContext::SAME_SITE_STRICT);
EXPECT_EQ(cookie->IncludeForRequestURL(url, options),
CanonicalCookie::CookieInclusionStatus::INCLUDE);
// `SameSite=None` cookies are included for all URLs.
cookie = CanonicalCookie::Create(url, "A=2; SameSite=None", creation_time,
options);
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite()); EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie->SameSite());
options.set_same_site_cookie_context( options.set_same_site_cookie_context(
CookieOptions::SameSiteCookieContext::CROSS_SITE); CookieOptions::SameSiteCookieContext::CROSS_SITE);
...@@ -586,11 +617,9 @@ TEST(CanonicalCookieTest, IncludeSameSiteForSameSiteURL) { ...@@ -586,11 +617,9 @@ TEST(CanonicalCookieTest, IncludeSameSiteForSameSiteURL) {
EXPECT_EQ(cookie->IncludeForRequestURL(url, options), EXPECT_EQ(cookie->IncludeForRequestURL(url, options),
CanonicalCookie::CookieInclusionStatus::INCLUDE); CanonicalCookie::CookieInclusionStatus::INCLUDE);
// TODO(chlily): Use Create() once ParsedCookie supports UNSPECIFIED. // Cookies with no SameSite attribute are parsed as
// Cookies with CookieSameSite::UNSPECIFIED depend on the FeatureList. // CookieSameSite::UNSPECIFIED, and inclusion depends on the FeatureList.
cookie = std::make_unique<CanonicalCookie>( cookie = CanonicalCookie::Create(url, "A=2", creation_time, options);
"A", "2", "example.test", "/", creation_time, base::Time(), base::Time(),
false, false, CookieSameSite::UNSPECIFIED, COOKIE_PRIORITY_DEFAULT);
EXPECT_EQ(CookieSameSite::UNSPECIFIED, cookie->SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, cookie->SameSite());
{ {
base::test::ScopedFeatureList feature_list; base::test::ScopedFeatureList feature_list;
......
...@@ -67,11 +67,15 @@ std::string CookieSameSiteToString(CookieSameSite same_site) { ...@@ -67,11 +67,15 @@ std::string CookieSameSiteToString(CookieSameSite same_site) {
} }
CookieSameSite StringToCookieSameSite(const std::string& same_site) { CookieSameSite StringToCookieSameSite(const std::string& same_site) {
if (base::EqualsCaseInsensitiveASCII(same_site, kSameSiteNone))
return CookieSameSite::NO_RESTRICTION;
if (base::EqualsCaseInsensitiveASCII(same_site, kSameSiteLax)) if (base::EqualsCaseInsensitiveASCII(same_site, kSameSiteLax))
return CookieSameSite::LAX_MODE; return CookieSameSite::LAX_MODE;
if (base::EqualsCaseInsensitiveASCII(same_site, kSameSiteStrict)) if (base::EqualsCaseInsensitiveASCII(same_site, kSameSiteStrict))
return CookieSameSite::STRICT_MODE; return CookieSameSite::STRICT_MODE;
return CookieSameSite::NO_RESTRICTION; if (base::EqualsCaseInsensitiveASCII(same_site, kSameSiteExtended))
return CookieSameSite::EXTENDED_MODE;
return CookieSameSite::UNSPECIFIED;
} }
} // namespace net } // namespace net
...@@ -44,7 +44,7 @@ NET_EXPORT CookiePriority StringToCookiePriority(const std::string& priority); ...@@ -44,7 +44,7 @@ NET_EXPORT CookiePriority StringToCookiePriority(const std::string& priority);
NET_EXPORT std::string CookieSameSiteToString(CookieSameSite same_site); NET_EXPORT std::string CookieSameSiteToString(CookieSameSite same_site);
// Converts the Set-Cookie header SameSite token |same_site| to a // Converts the Set-Cookie header SameSite token |same_site| to a
// CookieSameSite. Defaults to CookieSameSite::NO_RESTRICTION for empty or // CookieSameSite. Defaults to CookieSameSite::UNSPECIFIED for empty or
// unrecognized strings. // unrecognized strings.
NET_EXPORT CookieSameSite StringToCookieSameSite(const std::string& same_site); NET_EXPORT CookieSameSite StringToCookieSameSite(const std::string& same_site);
......
...@@ -38,4 +38,27 @@ TEST(CookieConstantsTest, TestCookiePriority) { ...@@ -38,4 +38,27 @@ TEST(CookieConstantsTest, TestCookiePriority) {
} }
} }
TEST(CookieConstantsTest, TestCookieSameSite) {
// Test case insensitivity
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, StringToCookieSameSite("None"));
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, StringToCookieSameSite("none"));
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, StringToCookieSameSite("NONE"));
EXPECT_EQ(CookieSameSite::LAX_MODE, StringToCookieSameSite("Lax"));
EXPECT_EQ(CookieSameSite::LAX_MODE, StringToCookieSameSite("LAX"));
EXPECT_EQ(CookieSameSite::LAX_MODE, StringToCookieSameSite("lAx"));
EXPECT_EQ(CookieSameSite::STRICT_MODE, StringToCookieSameSite("Strict"));
EXPECT_EQ(CookieSameSite::STRICT_MODE, StringToCookieSameSite("STRICT"));
EXPECT_EQ(CookieSameSite::STRICT_MODE, StringToCookieSameSite("sTrIcT"));
EXPECT_EQ(CookieSameSite::EXTENDED_MODE, StringToCookieSameSite("extended"));
EXPECT_EQ(CookieSameSite::EXTENDED_MODE, StringToCookieSameSite("EXTENDED"));
EXPECT_EQ(CookieSameSite::EXTENDED_MODE, StringToCookieSameSite("ExtenDED"));
// Unrecognized tokens are interpreted as UNSPECIFIED.
const char* const bad_tokens[] = {"", "foo", "none ",
"strictest", " none", "0"};
for (const auto* bad_token : bad_tokens) {
EXPECT_EQ(CookieSameSite::UNSPECIFIED, StringToCookieSameSite(bad_token));
}
}
} // namespace net } // namespace net
...@@ -155,7 +155,7 @@ bool ParsedCookie::IsValid() const { ...@@ -155,7 +155,7 @@ bool ParsedCookie::IsValid() const {
CookieSameSite ParsedCookie::SameSite() const { CookieSameSite ParsedCookie::SameSite() const {
return (same_site_index_ == 0) return (same_site_index_ == 0)
? CookieSameSite::NO_RESTRICTION ? CookieSameSite::UNSPECIFIED
: StringToCookieSameSite(pairs_[same_site_index_].second); : StringToCookieSameSite(pairs_[same_site_index_].second);
} }
...@@ -207,8 +207,8 @@ bool ParsedCookie::SetIsHttpOnly(bool is_http_only) { ...@@ -207,8 +207,8 @@ bool ParsedCookie::SetIsHttpOnly(bool is_http_only) {
return SetBool(&httponly_index_, kHttpOnlyTokenName, is_http_only); return SetBool(&httponly_index_, kHttpOnlyTokenName, is_http_only);
} }
bool ParsedCookie::SetSameSite(const std::string& is_same_site) { bool ParsedCookie::SetSameSite(const std::string& same_site) {
return SetString(&same_site_index_, kSameSiteTokenName, is_same_site); return SetString(&same_site_index_, kSameSiteTokenName, same_site);
} }
bool ParsedCookie::SetPriority(const std::string& priority) { bool ParsedCookie::SetPriority(const std::string& priority) {
......
...@@ -181,7 +181,7 @@ TEST(ParsedCookieTest, MultipleEquals) { ...@@ -181,7 +181,7 @@ TEST(ParsedCookieTest, MultipleEquals) {
EXPECT_FALSE(pc.HasDomain()); EXPECT_FALSE(pc.HasDomain());
EXPECT_TRUE(pc.IsSecure()); EXPECT_TRUE(pc.IsSecure());
EXPECT_TRUE(pc.IsHttpOnly()); EXPECT_TRUE(pc.IsHttpOnly());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, pc.SameSite());
EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority()); EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority());
EXPECT_EQ(4U, pc.NumberOfAttributes()); EXPECT_EQ(4U, pc.NumberOfAttributes());
} }
...@@ -411,7 +411,7 @@ TEST(ParsedCookieTest, SetAttributes) { ...@@ -411,7 +411,7 @@ TEST(ParsedCookieTest, SetAttributes) {
EXPECT_FALSE(pc.HasMaxAge()); EXPECT_FALSE(pc.HasMaxAge());
EXPECT_FALSE(pc.IsSecure()); EXPECT_FALSE(pc.IsSecure());
EXPECT_FALSE(pc.IsHttpOnly()); EXPECT_FALSE(pc.IsHttpOnly());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, pc.SameSite());
EXPECT_EQ("name2=value2", pc.ToCookieLine()); EXPECT_EQ("name2=value2", pc.ToCookieLine());
} }
...@@ -465,7 +465,7 @@ TEST(ParsedCookieTest, SetSameSite) { ...@@ -465,7 +465,7 @@ TEST(ParsedCookieTest, SetSameSite) {
EXPECT_TRUE(pc.IsValid()); EXPECT_TRUE(pc.IsValid());
EXPECT_EQ("name=value", pc.ToCookieLine()); EXPECT_EQ("name=value", pc.ToCookieLine());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, pc.SameSite());
// Test each samesite directive, expect case-insensitive compare. // Test each samesite directive, expect case-insensitive compare.
EXPECT_TRUE(pc.SetSameSite("strict")); EXPECT_TRUE(pc.SetSameSite("strict"));
...@@ -483,15 +483,25 @@ TEST(ParsedCookieTest, SetSameSite) { ...@@ -483,15 +483,25 @@ TEST(ParsedCookieTest, SetSameSite) {
EXPECT_EQ(CookieSameSite::LAX_MODE, pc.SameSite()); EXPECT_EQ(CookieSameSite::LAX_MODE, pc.SameSite());
EXPECT_TRUE(pc.IsValid()); EXPECT_TRUE(pc.IsValid());
EXPECT_TRUE(pc.SetSameSite("None"));
EXPECT_EQ("name=value; samesite=None", pc.ToCookieLine());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite());
EXPECT_TRUE(pc.IsValid());
EXPECT_TRUE(pc.SetSameSite("NONE"));
EXPECT_EQ("name=value; samesite=NONE", pc.ToCookieLine());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite());
EXPECT_TRUE(pc.IsValid());
// Remove the SameSite attribute. // Remove the SameSite attribute.
EXPECT_TRUE(pc.SetSameSite("")); EXPECT_TRUE(pc.SetSameSite(""));
EXPECT_EQ("name=value", pc.ToCookieLine()); EXPECT_EQ("name=value", pc.ToCookieLine());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, pc.SameSite());
EXPECT_TRUE(pc.IsValid()); EXPECT_TRUE(pc.IsValid());
EXPECT_TRUE(pc.SetSameSite("Blah")); EXPECT_TRUE(pc.SetSameSite("Blah"));
EXPECT_EQ("name=value; samesite=Blah", pc.ToCookieLine()); EXPECT_EQ("name=value; samesite=Blah", pc.ToCookieLine());
EXPECT_EQ(CookieSameSite::NO_RESTRICTION, pc.SameSite()); EXPECT_EQ(CookieSameSite::UNSPECIFIED, pc.SameSite());
EXPECT_TRUE(pc.IsValid()); EXPECT_TRUE(pc.IsValid());
} }
...@@ -502,9 +512,10 @@ TEST(ParsedCookieTest, SameSiteValues) { ...@@ -502,9 +512,10 @@ TEST(ParsedCookieTest, SameSiteValues) {
CookieSameSite mode; CookieSameSite mode;
} cases[]{{"n=v; samesite=strict", true, CookieSameSite::STRICT_MODE}, } cases[]{{"n=v; samesite=strict", true, CookieSameSite::STRICT_MODE},
{"n=v; samesite=lax", true, CookieSameSite::LAX_MODE}, {"n=v; samesite=lax", true, CookieSameSite::LAX_MODE},
{"n=v; samesite=boo", true, CookieSameSite::NO_RESTRICTION}, {"n=v; samesite=none", true, CookieSameSite::NO_RESTRICTION},
{"n=v; samesite", true, CookieSameSite::NO_RESTRICTION}, {"n=v; samesite=boo", true, CookieSameSite::UNSPECIFIED},
{"n=v", true, CookieSameSite::NO_RESTRICTION}}; {"n=v; samesite", true, CookieSameSite::UNSPECIFIED},
{"n=v", true, CookieSameSite::UNSPECIFIED}};
for (const auto& test : cases) { for (const auto& test : cases) {
SCOPED_TRACE(test.cookie); SCOPED_TRACE(test.cookie);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment