diff options
author | mkwst <mkwst@chromium.org> | 2015-02-22 21:10:31 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-02-23 05:11:25 +0000 |
commit | ae819bb3096b63a11b8c1ff47dd3b69f85ea241b (patch) | |
tree | ec97dfaf204412c66e4246c597fe00cbdf64083e /net | |
parent | d726d218c92888278509ef9b4a9e639cf9fce659 (diff) | |
download | chromium_src-ae819bb3096b63a11b8c1ff47dd3b69f85ea241b.zip chromium_src-ae819bb3096b63a11b8c1ff47dd3b69f85ea241b.tar.gz chromium_src-ae819bb3096b63a11b8c1ff47dd3b69f85ea241b.tar.bz2 |
Implement the "First-Party-Only" cookie attribute.
First-party-only cookies allow servers to mitigate the risk of cross-site
request forgery and related information leakage attacks by asserting that a
particular cookie should only be sent in a "first-party" context.
This patch adds support for the 'First-Party-Only' attribute to the
CookieMonster and CookieStore, but does not yet wire up requests such that
the flag has any effect. https://codereview.chromium.org/940373002 will do so
by correctly setting the first-party URL on the CookieOptions object used to
load cookies for a request.
Spec: https://tools.ietf.org/html/draft-west-first-party-cookies
Intent to Implement: https://groups.google.com/a/chromium.org/d/msg/blink-dev/vT98riFhhT0/3Q-lADqsh0UJ
BUG=459154
TBR=dpolukhin@chromium.org
Review URL: https://codereview.chromium.org/876973003
Cr-Commit-Position: refs/heads/master@{#317544}
Diffstat (limited to 'net')
-rw-r--r-- | net/cookies/canonical_cookie.cc | 43 | ||||
-rw-r--r-- | net/cookies/canonical_cookie.h | 4 | ||||
-rw-r--r-- | net/cookies/canonical_cookie_unittest.cc | 118 | ||||
-rw-r--r-- | net/cookies/cookie_monster.cc | 17 | ||||
-rw-r--r-- | net/cookies/cookie_monster.h | 2 | ||||
-rw-r--r-- | net/cookies/cookie_monster_store_test.cc | 6 | ||||
-rw-r--r-- | net/cookies/cookie_monster_unittest.cc | 111 | ||||
-rw-r--r-- | net/cookies/cookie_options.h | 27 | ||||
-rw-r--r-- | net/cookies/parsed_cookie.cc | 13 | ||||
-rw-r--r-- | net/cookies/parsed_cookie.h | 3 | ||||
-rw-r--r-- | net/cookies/parsed_cookie_unittest.cc | 22 | ||||
-rw-r--r-- | net/url_request/url_request_http_job.cc | 6 |
12 files changed, 277 insertions, 95 deletions
diff --git a/net/cookies/canonical_cookie.cc b/net/cookies/canonical_cookie.cc index f0b3b3f..9d7ccf2 100644 --- a/net/cookies/canonical_cookie.cc +++ b/net/cookies/canonical_cookie.cc @@ -109,12 +109,18 @@ CanonicalCookie::CanonicalCookie() httponly_(false) { } -CanonicalCookie::CanonicalCookie( - const GURL& url, const std::string& name, const std::string& value, - const std::string& domain, const std::string& path, - const base::Time& creation, const base::Time& expiration, - const base::Time& last_access, bool secure, bool httponly, - CookiePriority priority) +CanonicalCookie::CanonicalCookie(const GURL& url, + const std::string& name, + const std::string& value, + const std::string& domain, + const std::string& path, + const base::Time& creation, + const base::Time& expiration, + const base::Time& last_access, + bool secure, + bool httponly, + bool firstpartyonly, + CookiePriority priority) : source_(GetCookieSourceFromURL(url)), name_(name), value_(value), @@ -125,6 +131,7 @@ CanonicalCookie::CanonicalCookie( last_access_date_(last_access), secure_(secure), httponly_(httponly), + first_party_only_(firstpartyonly), priority_(priority) { } @@ -137,6 +144,7 @@ CanonicalCookie::CanonicalCookie(const GURL& url, const ParsedCookie& pc) last_access_date_(Time()), secure_(pc.IsSecure()), httponly_(pc.IsHttpOnly()), + first_party_only_(pc.IsFirstPartyOnly()), priority_(pc.Priority()) { if (pc.HasExpires()) expiry_date_ = CanonExpiration(pc, creation_date_, creation_date_); @@ -238,12 +246,11 @@ CanonicalCookie* CanonicalCookie::Create(const GURL& url, creation_time, server_time); - return new CanonicalCookie(url, parsed_cookie.Name(), parsed_cookie.Value(), - cookie_domain, cookie_path, creation_time, - cookie_expires, creation_time, - parsed_cookie.IsSecure(), - parsed_cookie.IsHttpOnly(), - parsed_cookie.Priority()); + return new CanonicalCookie( + url, parsed_cookie.Name(), parsed_cookie.Value(), cookie_domain, + cookie_path, creation_time, cookie_expires, creation_time, + parsed_cookie.IsSecure(), parsed_cookie.IsHttpOnly(), + parsed_cookie.IsFirstPartyOnly(), parsed_cookie.Priority()); } CanonicalCookie* CanonicalCookie::Create(const GURL& url, @@ -255,6 +262,7 @@ CanonicalCookie* CanonicalCookie::Create(const GURL& url, const base::Time& expiration, bool secure, bool http_only, + bool first_party_only, CookiePriority priority) { // Expect valid attribute tokens and values, as defined by the ParsedCookie // logic, otherwise don't create the cookie. @@ -293,7 +301,7 @@ CanonicalCookie* CanonicalCookie::Create(const GURL& url, return new CanonicalCookie(url, parsed_name, parsed_value, cookie_domain, cookie_path, creation, expiration, creation, - secure, http_only, priority); + secure, http_only, first_party_only, priority); } bool CanonicalCookie::IsOnPath(const std::string& url_path) const { @@ -382,6 +390,14 @@ bool CanonicalCookie::IncludeForRequestURL(const GURL& url, if (!IsOnPath(url.path())) return false; + // Include first-party-only cookies iff |options| tells us to include all of + // them, or if a first-party URL is set and its origin matches the origin of + // |url|. + if (IsFirstPartyOnly() && !options.include_first_party_only() && + options.first_party_url().GetOrigin() != url.GetOrigin()) { + return false; + } + return true; } @@ -406,6 +422,7 @@ CanonicalCookie* CanonicalCookie::Duplicate() const { cc->last_access_date_ = last_access_date_; cc->secure_ = secure_; cc->httponly_ = httponly_; + cc->first_party_only_ = first_party_only_; cc->priority_ = priority_; return cc; } diff --git a/net/cookies/canonical_cookie.h b/net/cookies/canonical_cookie.h index 19ed388..f54fb6d 100644 --- a/net/cookies/canonical_cookie.h +++ b/net/cookies/canonical_cookie.h @@ -37,6 +37,7 @@ class NET_EXPORT CanonicalCookie { const base::Time& last_access, bool secure, bool httponly, + bool firstpartyonly, CookiePriority priority); // This constructor does canonicalization but not validation. @@ -68,6 +69,7 @@ class NET_EXPORT CanonicalCookie { const base::Time& expiration, bool secure, bool http_only, + bool first_party_only, CookiePriority priority); const std::string& Source() const { return source_; } @@ -81,6 +83,7 @@ class NET_EXPORT CanonicalCookie { const base::Time& ExpiryDate() const { return expiry_date_; } bool IsSecure() const { return secure_; } bool IsHttpOnly() const { return httponly_; } + bool IsFirstPartyOnly() const { return first_party_only_; } CookiePriority Priority() const { return priority_; } bool IsDomainCookie() const { return !domain_.empty() && domain_[0] == '.'; } @@ -158,6 +161,7 @@ class NET_EXPORT CanonicalCookie { base::Time last_access_date_; bool secure_; bool httponly_; + bool first_party_only_; CookiePriority priority_; // NOTE: When any new members are added above this comment, the // implementation of Duplicate() must be updated to copy the new member diff --git a/net/cookies/canonical_cookie_unittest.cc b/net/cookies/canonical_cookie_unittest.cc index 46d730b..78af2e0 100644 --- a/net/cookies/canonical_cookie_unittest.cc +++ b/net/cookies/canonical_cookie_unittest.cc @@ -39,23 +39,27 @@ TEST(CanonicalCookieTest, Constructor) { CanonicalCookie cookie(url, "A", "2", "www.example.com", "/test", current_time, base::Time(), current_time, false, false, - COOKIE_PRIORITY_DEFAULT); + false, COOKIE_PRIORITY_DEFAULT); EXPECT_EQ(url.GetOrigin().spec(), cookie.Source()); EXPECT_EQ("A", cookie.Name()); EXPECT_EQ("2", cookie.Value()); EXPECT_EQ("www.example.com", cookie.Domain()); EXPECT_EQ("/test", cookie.Path()); EXPECT_FALSE(cookie.IsSecure()); + EXPECT_FALSE(cookie.IsHttpOnly()); + EXPECT_FALSE(cookie.IsFirstPartyOnly()); CanonicalCookie cookie2(url, "A", "2", std::string(), std::string(), current_time, base::Time(), current_time, false, - false, COOKIE_PRIORITY_DEFAULT); + false, false, COOKIE_PRIORITY_DEFAULT); EXPECT_EQ(url.GetOrigin().spec(), cookie.Source()); EXPECT_EQ("A", cookie2.Name()); EXPECT_EQ("2", cookie2.Value()); EXPECT_EQ("", cookie2.Domain()); EXPECT_EQ("", cookie2.Path()); EXPECT_FALSE(cookie2.IsSecure()); + EXPECT_FALSE(cookie2.IsHttpOnly()); + EXPECT_FALSE(cookie2.IsFirstPartyOnly()); } TEST(CanonicalCookieTest, Create) { @@ -99,27 +103,39 @@ TEST(CanonicalCookieTest, Create) { httponly_options)); EXPECT_TRUE(cookie->IsHttpOnly()); + // Test creating http only cookies. + CookieOptions first_party_options; + first_party_options.set_first_party_url(url); + cookie.reset(CanonicalCookie::Create(url, "A=2; First-Party-Only", + creation_time, httponly_options)); + EXPECT_TRUE(cookie.get()); + EXPECT_TRUE(cookie->IsFirstPartyOnly()); + // Test the creating cookies using specific parameter instead of a cookie // string. - cookie.reset(CanonicalCookie::Create(url, "A", "2", "www.example.com", - "/test", creation_time, base::Time(), - false, false, COOKIE_PRIORITY_DEFAULT)); + cookie.reset(CanonicalCookie::Create( + url, "A", "2", "www.example.com", "/test", creation_time, base::Time(), + false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_EQ(url.GetOrigin().spec(), cookie->Source()); EXPECT_EQ("A", cookie->Name()); EXPECT_EQ("2", cookie->Value()); EXPECT_EQ(".www.example.com", cookie->Domain()); EXPECT_EQ("/test", cookie->Path()); EXPECT_FALSE(cookie->IsSecure()); + EXPECT_FALSE(cookie->IsHttpOnly()); + EXPECT_FALSE(cookie->IsFirstPartyOnly()); - cookie.reset(CanonicalCookie::Create(url, "A", "2", ".www.example.com", - "/test", creation_time, base::Time(), - false, false, COOKIE_PRIORITY_DEFAULT)); + cookie.reset(CanonicalCookie::Create( + url, "A", "2", ".www.example.com", "/test", creation_time, base::Time(), + false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_EQ(url.GetOrigin().spec(), cookie->Source()); EXPECT_EQ("A", cookie->Name()); EXPECT_EQ("2", cookie->Value()); EXPECT_EQ(".www.example.com", cookie->Domain()); EXPECT_EQ("/test", cookie->Path()); EXPECT_FALSE(cookie->IsSecure()); + EXPECT_FALSE(cookie->IsHttpOnly()); + EXPECT_FALSE(cookie->IsFirstPartyOnly()); } TEST(CanonicalCookieTest, EmptyExpiry) { @@ -166,18 +182,19 @@ TEST(CanonicalCookieTest, IsEquivalent) { base::Time expiration_time = creation_time + base::TimeDelta::FromDays(2); bool secure(false); bool httponly(false); + bool firstparty(false); // Test that a cookie is equivalent to itself. scoped_ptr<CanonicalCookie> cookie(new CanonicalCookie( url, cookie_name, cookie_value, cookie_domain, cookie_path, creation_time, - expiration_time, last_access_time, secure, httponly, + expiration_time, last_access_time, secure, httponly, firstparty, COOKIE_PRIORITY_MEDIUM)); EXPECT_TRUE(cookie->IsEquivalent(*cookie)); // Test that two identical cookies are equivalent. scoped_ptr<CanonicalCookie> other_cookie(new CanonicalCookie( url, cookie_name, cookie_value, cookie_domain, cookie_path, creation_time, - expiration_time, last_access_time, secure, httponly, + expiration_time, last_access_time, secure, httponly, firstparty, COOKIE_PRIORITY_MEDIUM)); EXPECT_TRUE(cookie->IsEquivalent(*other_cookie)); @@ -186,34 +203,47 @@ TEST(CanonicalCookieTest, IsEquivalent) { other_cookie.reset( new CanonicalCookie(url, cookie_name, "2", cookie_domain, cookie_path, creation_time, expiration_time, last_access_time, - secure, httponly, COOKIE_PRIORITY_HIGH)); + secure, httponly, firstparty, COOKIE_PRIORITY_HIGH)); EXPECT_TRUE(cookie->IsEquivalent(*other_cookie)); base::Time other_creation_time = creation_time + base::TimeDelta::FromMinutes(2); other_cookie.reset(new CanonicalCookie( url, cookie_name, "2", cookie_domain, cookie_path, other_creation_time, - expiration_time, last_access_time, secure, httponly, + expiration_time, last_access_time, secure, httponly, firstparty, COOKIE_PRIORITY_MEDIUM)); EXPECT_TRUE(cookie->IsEquivalent(*other_cookie)); other_cookie.reset(new CanonicalCookie( url, cookie_name, cookie_name, cookie_domain, cookie_path, creation_time, - expiration_time, last_access_time, true, httponly, COOKIE_PRIORITY_LOW)); + expiration_time, last_access_time, true, httponly, firstparty, + COOKIE_PRIORITY_LOW)); + EXPECT_TRUE(cookie->IsEquivalent(*other_cookie)); + + other_cookie.reset(new CanonicalCookie( + url, cookie_name, cookie_name, cookie_domain, cookie_path, creation_time, + expiration_time, last_access_time, secure, true, firstparty, + COOKIE_PRIORITY_LOW)); + EXPECT_TRUE(cookie->IsEquivalent(*other_cookie)); + + other_cookie.reset(new CanonicalCookie( + url, cookie_name, cookie_name, cookie_domain, cookie_path, creation_time, + expiration_time, last_access_time, secure, httponly, true, + COOKIE_PRIORITY_LOW)); EXPECT_TRUE(cookie->IsEquivalent(*other_cookie)); // Tests that use different variations of attribute values that // DO affect cookie equivalence. - other_cookie.reset( - new CanonicalCookie(url, "B", cookie_value, cookie_domain, cookie_path, - creation_time, expiration_time, last_access_time, - secure, httponly, COOKIE_PRIORITY_MEDIUM)); + other_cookie.reset(new CanonicalCookie( + url, "B", cookie_value, cookie_domain, cookie_path, creation_time, + expiration_time, last_access_time, secure, httponly, firstparty, + COOKIE_PRIORITY_MEDIUM)); EXPECT_FALSE(cookie->IsEquivalent(*other_cookie)); other_cookie.reset(new CanonicalCookie( url, cookie_name, cookie_value, "www.example.com", cookie_path, creation_time, expiration_time, last_access_time, secure, httponly, - COOKIE_PRIORITY_MEDIUM)); + firstparty, COOKIE_PRIORITY_MEDIUM)); EXPECT_TRUE(cookie->IsDomainCookie()); EXPECT_FALSE(other_cookie->IsDomainCookie()); EXPECT_FALSE(cookie->IsEquivalent(*other_cookie)); @@ -221,12 +251,12 @@ TEST(CanonicalCookieTest, IsEquivalent) { other_cookie.reset(new CanonicalCookie( url, cookie_name, cookie_value, ".example.com", cookie_path, creation_time, expiration_time, last_access_time, secure, httponly, - COOKIE_PRIORITY_MEDIUM)); + firstparty, COOKIE_PRIORITY_MEDIUM)); EXPECT_FALSE(cookie->IsEquivalent(*other_cookie)); other_cookie.reset(new CanonicalCookie( url, cookie_name, cookie_value, cookie_domain, "/test/0", creation_time, - expiration_time, last_access_time, secure, httponly, + expiration_time, last_access_time, secure, httponly, firstparty, COOKIE_PRIORITY_MEDIUM)); EXPECT_FALSE(cookie->IsEquivalent(*other_cookie)); } @@ -329,4 +359,52 @@ TEST(CanonicalCookieTest, IncludeForRequestURL) { EXPECT_FALSE(cookie->IncludeForRequestURL(url, options)); } +TEST(CanonicalCookieTest, IncludeFirstPartyForFirstPartyURL) { + GURL insecure_url("http://example.test"); + GURL secure_url("https://example.test"); + GURL secure_url_with_path("https://example.test/foo/bar/index.html"); + GURL third_party_url("https://not-example.test"); + base::Time creation_time = base::Time::Now(); + CookieOptions options; + scoped_ptr<CanonicalCookie> cookie; + + // First-party-only cookies are not inlcuded if a top-level URL is unset. + cookie.reset(CanonicalCookie::Create(secure_url, "A=2; First-Party-Only", + creation_time, options)); + EXPECT_TRUE(cookie->IsFirstPartyOnly()); + options.set_first_party_url(GURL()); + EXPECT_FALSE(cookie->IncludeForRequestURL(secure_url, options)); + + // First-party-only cookies are included only if the cookie's origin matches + // the + // first-party origin. + options.set_first_party_url(secure_url); + EXPECT_TRUE(cookie->IncludeForRequestURL(secure_url, options)); + options.set_first_party_url(insecure_url); + EXPECT_FALSE(cookie->IncludeForRequestURL(secure_url, options)); + options.set_first_party_url(third_party_url); + EXPECT_FALSE(cookie->IncludeForRequestURL(secure_url, options)); + + // "First-Party-Only" doesn't override the 'secure' flag. + cookie.reset(CanonicalCookie::Create( + secure_url, "A=2; Secure; First-Party-Only", creation_time, options)); + options.set_first_party_url(secure_url); + EXPECT_TRUE(cookie->IncludeForRequestURL(secure_url, options)); + EXPECT_FALSE(cookie->IncludeForRequestURL(insecure_url, options)); + options.set_first_party_url(insecure_url); + EXPECT_FALSE(cookie->IncludeForRequestURL(secure_url, options)); + EXPECT_FALSE(cookie->IncludeForRequestURL(insecure_url, options)); + + // "First-Party-Only" doesn't override the 'path' flag. + cookie.reset(CanonicalCookie::Create(secure_url_with_path, + "A=2; First-Party-Only; path=/foo/bar", + creation_time, options)); + options.set_first_party_url(secure_url_with_path); + EXPECT_TRUE(cookie->IncludeForRequestURL(secure_url_with_path, options)); + EXPECT_FALSE(cookie->IncludeForRequestURL(secure_url, options)); + options.set_first_party_url(secure_url); + EXPECT_TRUE(cookie->IncludeForRequestURL(secure_url_with_path, options)); + EXPECT_FALSE(cookie->IncludeForRequestURL(secure_url, options)); +} + } // namespace net diff --git a/net/cookies/cookie_monster.cc b/net/cookies/cookie_monster.cc index bcf0fd0..63da860 100644 --- a/net/cookies/cookie_monster.cc +++ b/net/cookies/cookie_monster.cc @@ -414,6 +414,7 @@ class CookieMonster::SetCookieWithDetailsTask : public CookieMonsterTask { const base::Time& expiration_time, bool secure, bool http_only, + bool first_party_only, CookiePriority priority, const SetCookiesCallback& callback) : CookieMonsterTask(cookie_monster), @@ -425,6 +426,7 @@ class CookieMonster::SetCookieWithDetailsTask : public CookieMonsterTask { expiration_time_(expiration_time), secure_(secure), http_only_(http_only), + first_party_only_(first_party_only), priority_(priority), callback_(callback) {} @@ -443,6 +445,7 @@ class CookieMonster::SetCookieWithDetailsTask : public CookieMonsterTask { base::Time expiration_time_; bool secure_; bool http_only_; + bool first_party_only_; CookiePriority priority_; SetCookiesCallback callback_; @@ -456,7 +459,7 @@ void CookieMonster::SetCookieWithDetailsTask::Run() { "456373 CookieMonster::SetCookieWithDetailsTask::Run")); bool success = this->cookie_monster()->SetCookieWithDetails( url_, name_, value_, domain_, path_, expiration_time_, secure_, - http_only_, priority_); + http_only_, first_party_only_, priority_); if (!callback_.is_null()) { this->InvokeCallback(base::Bind(&SetCookiesCallback::Run, base::Unretained(&callback_), success)); @@ -912,11 +915,12 @@ void CookieMonster::SetCookieWithDetailsAsync( const Time& expiration_time, bool secure, bool http_only, + bool first_party_only, CookiePriority priority, const SetCookiesCallback& callback) { scoped_refptr<SetCookieWithDetailsTask> task = new SetCookieWithDetailsTask( this, url, name, value, domain, path, expiration_time, secure, http_only, - priority, callback); + first_party_only, priority, callback); DoCookieTaskForURL(task, url); } @@ -941,6 +945,7 @@ void CookieMonster::GetAllCookiesForURLAsync( const GetCookieListCallback& callback) { CookieOptions options; options.set_include_httponly(); + options.set_include_first_party_only(); scoped_refptr<GetAllCookiesForURLWithOptionsTask> task = new GetAllCookiesForURLWithOptionsTask(this, url, options, callback); @@ -1093,6 +1098,7 @@ bool CookieMonster::SetCookieWithDetails(const GURL& url, const base::Time& expiration_time, bool secure, bool http_only, + bool first_party_only, CookiePriority priority) { base::AutoLock autolock(lock_); @@ -1105,13 +1111,14 @@ bool CookieMonster::SetCookieWithDetails(const GURL& url, scoped_ptr<CanonicalCookie> cc; cc.reset(CanonicalCookie::Create(url, name, value, domain, path, creation_time, expiration_time, secure, - http_only, priority)); + http_only, first_party_only, priority)); if (!cc.get()) return false; CookieOptions options; options.set_include_httponly(); + options.set_include_first_party_only(); return SetCanonicalCookie(&cc, creation_time, options); } @@ -1123,6 +1130,7 @@ bool CookieMonster::ImportCookies(const CookieList& list) { scoped_ptr<CanonicalCookie> cookie(new CanonicalCookie(*iter)); net::CookieOptions options; options.set_include_httponly(); + options.set_include_first_party_only(); if (!SetCanonicalCookie(&cookie, cookie->CreationDate(), options)) return false; } @@ -1181,6 +1189,7 @@ CookieList CookieMonster::GetAllCookiesForURLWithOptions( CookieList CookieMonster::GetAllCookiesForURL(const GURL& url) { CookieOptions options; options.set_include_httponly(); + options.set_first_party_url(url); return GetAllCookiesForURLWithOptions(url, options); } @@ -1347,6 +1356,7 @@ void CookieMonster::DeleteCookie(const GURL& url, CookieOptions options; options.set_include_httponly(); + options.set_include_first_party_only(); // Get the cookies for this host and its domain(s). std::vector<CanonicalCookie*> cookies; FindCookiesForHostAndDomain(url, options, true, &cookies); @@ -2321,6 +2331,7 @@ void CookieMonster::RunCallbacks(const CanonicalCookie& cookie, bool removed) { lock_.AssertAcquired(); CookieOptions opts; opts.set_include_httponly(); + opts.set_include_first_party_only(); // Note that the callbacks in hook_map_ are wrapped with MakeAsync(), so they // are guaranteed to not take long - they just post a RunAsync task back to // the appropriate thread's message loop and return. It is important that this diff --git a/net/cookies/cookie_monster.h b/net/cookies/cookie_monster.h index 75c6397..f0b3fd4 100644 --- a/net/cookies/cookie_monster.h +++ b/net/cookies/cookie_monster.h @@ -168,6 +168,7 @@ class NET_EXPORT CookieMonster : public CookieStore { const base::Time& expiration_time, bool secure, bool http_only, + bool first_party, CookiePriority priority, const SetCookiesCallback& callback); @@ -421,6 +422,7 @@ class NET_EXPORT CookieMonster : public CookieStore { const base::Time& expiration_time, bool secure, bool http_only, + bool first_party, CookiePriority priority); CookieList GetAllCookies(); diff --git a/net/cookies/cookie_monster_store_test.cc b/net/cookies/cookie_monster_store_test.cc index dc371ed..68abc19 100644 --- a/net/cookies/cookie_monster_store_test.cc +++ b/net/cookies/cookie_monster_store_test.cc @@ -121,7 +121,8 @@ CanonicalCookie BuildCanonicalCookie(const std::string& key, return CanonicalCookie(GURL(), pc.Name(), pc.Value(), key, cookie_path, creation_time, cookie_expires, creation_time, - pc.IsSecure(), pc.IsHttpOnly(), pc.Priority()); + pc.IsSecure(), pc.IsHttpOnly(), pc.IsFirstPartyOnly(), + pc.Priority()); } void AddCookieToList(const std::string& key, @@ -214,7 +215,8 @@ CookieMonster* CreateMonsterFromStoreForGC(int num_cookies, CanonicalCookie cc(GURL(), "a", "1", base::StringPrintf("h%05d.izzle", i), "/path", creation_time, expiration_time, - last_access_time, false, false, COOKIE_PRIORITY_DEFAULT); + last_access_time, false, false, false, + COOKIE_PRIORITY_DEFAULT); store->AddCookie(cc); } diff --git a/net/cookies/cookie_monster_unittest.cc b/net/cookies/cookie_monster_unittest.cc index 51724a9..7ca99a3 100644 --- a/net/cookies/cookie_monster_unittest.cc +++ b/net/cookies/cookie_monster_unittest.cc @@ -152,13 +152,15 @@ class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> { const base::Time& expiration_time, bool secure, bool http_only, + bool first_party_only, CookiePriority priority) { DCHECK(cm); ResultSavingCookieCallback<bool> callback; cm->SetCookieWithDetailsAsync( url, name, value, domain, path, expiration_time, secure, http_only, - priority, base::Bind(&ResultSavingCookieCallback<bool>::Run, - base::Unretained(&callback))); + first_party_only, priority, + base::Bind(&ResultSavingCookieCallback<bool>::Run, + base::Unretained(&callback))); RunFor(kTimeout); EXPECT_TRUE(callback.did_run()); return callback.result(); @@ -240,6 +242,7 @@ class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> { // * Three levels of domain cookie (.b.a, .c.b.a, .d.c.b.a) // * Three levels of host cookie (w.b.a, w.c.b.a, w.d.c.b.a) // * http_only cookie (w.c.b.a) + // * first-party cookie (w.c.b.a) // * Two secure cookies (.c.b.a, w.c.b.a) // * Two domain path cookies (.c.b.a/dir1, .c.b.a/dir1/dir2) // * Two host path cookies (w.c.b.a/dir1, w.c.b.a/dir1/dir2) @@ -247,64 +250,70 @@ class CookieMonsterTest : public CookieStoreTest<CookieMonsterTestTraits> { // Domain cookies EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_1, "dom_1", "X", ".harvard.edu", - "/", base::Time(), false, false, COOKIE_PRIORITY_DEFAULT)); + "/", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2, "dom_2", "X", - ".math.harvard.edu", "/", base::Time(), false, false, + ".math.harvard.edu", "/", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_3, "dom_3", "X", - ".bourbaki.math.harvard.edu", "/", base::Time(), false, false, + ".bourbaki.math.harvard.edu", "/", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); // Host cookies EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_1, "host_1", "X", std::string(), - "/", base::Time(), false, false, COOKIE_PRIORITY_DEFAULT)); + "/", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2, "host_2", "X", std::string(), - "/", base::Time(), false, false, COOKIE_PRIORITY_DEFAULT)); + "/", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_3, "host_3", "X", std::string(), - "/", base::Time(), false, false, COOKIE_PRIORITY_DEFAULT)); + "/", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); + + // http_only cookie + EXPECT_TRUE(this->SetCookieWithDetails( + cm.get(), url_top_level_domain_plus_2, "httpo_check", "x", + std::string(), "/", base::Time(), false, true, false, + COOKIE_PRIORITY_DEFAULT)); - // Http_only cookie + // first-party cookie EXPECT_TRUE(this->SetCookieWithDetails( - cm.get(), url_top_level_domain_plus_2, "httpo_check", "X", - std::string(), "/", base::Time(), false, true, + cm.get(), url_top_level_domain_plus_2, "firstp_check", "x", + std::string(), "/", base::Time(), false, false, true, COOKIE_PRIORITY_DEFAULT)); // Secure cookies EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2_secure, "sec_dom", "X", - ".math.harvard.edu", "/", base::Time(), true, false, + ".math.harvard.edu", "/", base::Time(), true, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2_secure, "sec_host", "X", - std::string(), "/", base::Time(), true, false, + std::string(), "/", base::Time(), true, false, false, COOKIE_PRIORITY_DEFAULT)); // Domain path cookies EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2, "dom_path_1", "X", - ".math.harvard.edu", "/dir1", base::Time(), false, false, + ".math.harvard.edu", "/dir1", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2, "dom_path_2", "X", - ".math.harvard.edu", "/dir1/dir2", base::Time(), false, false, + ".math.harvard.edu", "/dir1/dir2", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); // Host path cookies EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2, "host_path_1", "X", - std::string(), "/dir1", base::Time(), false, false, + std::string(), "/dir1", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(this->SetCookieWithDetails( cm.get(), url_top_level_domain_plus_2, "host_path_2", "X", - std::string(), "/dir1/dir2", base::Time(), false, false, + std::string(), "/dir1/dir2", base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); - EXPECT_EQ(13U, this->GetAllCookies(cm.get()).size()); + EXPECT_EQ(14U, this->GetAllCookies(cm.get()).size()); } Time GetFirstCookieAccessDate(CookieMonster* cm) { @@ -594,6 +603,7 @@ struct CookiesInputInfo { const base::Time expiration_time; bool secure; bool http_only; + bool first_party_only; CookiePriority priority; }; @@ -626,7 +636,8 @@ ACTION_P4(DeleteAllCreatedBetweenAction, ACTION_P3(SetCookieWithDetailsAction, cookie_monster, cc, callback) { cookie_monster->SetCookieWithDetailsAsync( cc.url, cc.name, cc.value, cc.domain, cc.path, cc.expiration_time, - cc.secure, cc.http_only, cc.priority, callback->AsCallback()); + cc.secure, cc.http_only, cc.first_party_only, cc.priority, + callback->AsCallback()); } ACTION_P2(GetAllCookiesAction, cookie_monster, callback) { @@ -842,6 +853,7 @@ TEST_F(DeferredCookieTaskTest, DeferredSetCookieWithDetails) { base::Time(), false, false, + false, COOKIE_PRIORITY_DEFAULT}; BeginWithForDomainKey( "google.izzle", SetCookieWithDetailsAction(&cookie_monster(), cookie_info, @@ -857,6 +869,7 @@ TEST_F(DeferredCookieTaskTest, DeferredSetCookieWithDetails) { base::Time(), false, false, + false, COOKIE_PRIORITY_DEFAULT}; EXPECT_CALL(set_cookies_callback, Invoke(true)) .WillOnce(SetCookieWithDetailsAction(&cookie_monster(), cookie_info_exp, @@ -1537,30 +1550,30 @@ TEST_F(CookieMonsterTest, SetCookieWithDetails) { EXPECT_TRUE(SetCookieWithDetails(cm.get(), url_google_foo_, "A", "B", std::string(), "/foo", base::Time(), false, - false, COOKIE_PRIORITY_DEFAULT)); + false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_TRUE(SetCookieWithDetails(cm.get(), url_google_bar_, "C", "D", "google.izzle", "/bar", base::Time(), false, - true, COOKIE_PRIORITY_DEFAULT)); - EXPECT_TRUE(SetCookieWithDetails(cm.get(), url_google_, "E", "F", - std::string(), std::string(), base::Time(), true, false, COOKIE_PRIORITY_DEFAULT)); + EXPECT_TRUE(SetCookieWithDetails( + cm.get(), url_google_, "E", "F", std::string(), std::string(), + base::Time(), true, false, false, COOKIE_PRIORITY_DEFAULT)); // Test that malformed attributes fail to set the cookie. EXPECT_FALSE(SetCookieWithDetails(cm.get(), url_google_foo_, " A", "B", std::string(), "/foo", base::Time(), false, - false, COOKIE_PRIORITY_DEFAULT)); + false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_FALSE(SetCookieWithDetails(cm.get(), url_google_foo_, "A;", "B", std::string(), "/foo", base::Time(), false, - false, COOKIE_PRIORITY_DEFAULT)); + false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_FALSE(SetCookieWithDetails(cm.get(), url_google_foo_, "A=", "B", std::string(), "/foo", base::Time(), false, - false, COOKIE_PRIORITY_DEFAULT)); - EXPECT_FALSE(SetCookieWithDetails(cm.get(), url_google_foo_, "A", "B", - "google.ozzzzzzle", "foo", base::Time(), false, false, COOKIE_PRIORITY_DEFAULT)); + EXPECT_FALSE(SetCookieWithDetails( + cm.get(), url_google_foo_, "A", "B", "google.ozzzzzzle", "foo", + base::Time(), false, false, false, COOKIE_PRIORITY_DEFAULT)); EXPECT_FALSE(SetCookieWithDetails(cm.get(), url_google_foo_, "A=", "B", std::string(), "foo", base::Time(), false, - false, COOKIE_PRIORITY_DEFAULT)); + false, false, COOKIE_PRIORITY_DEFAULT)); CookieList cookies = GetAllCookiesForURL(cm.get(), url_google_foo_); CookieList::iterator it = cookies.begin(); @@ -1627,7 +1640,7 @@ TEST_F(CookieMonsterTest, DeleteAllForHost) { GetCookies(cm.get(), GURL(kTopLevelDomainPlus2Secure + std::string("/dir1/dir2/xxx")))); - EXPECT_EQ(5, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2))); + EXPECT_EQ(6, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2))); EXPECT_EQ(8U, GetAllCookies(cm.get()).size()); EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X", @@ -1641,7 +1654,7 @@ TEST_F(CookieMonsterTest, DeleteAllForHost) { std::string("/dir1/dir2/xxx")))); PopulateCmForDeleteAllForHost(cm); - EXPECT_EQ(5, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2Secure))); + EXPECT_EQ(6, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2Secure))); EXPECT_EQ(8U, GetAllCookies(cm.get()).size()); EXPECT_EQ("dom_1=X; dom_2=X; dom_3=X; host_3=X", @@ -1655,7 +1668,7 @@ TEST_F(CookieMonsterTest, DeleteAllForHost) { std::string("/dir1/dir2/xxx")))); PopulateCmForDeleteAllForHost(cm); - EXPECT_EQ(5, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2Secure + + EXPECT_EQ(6, DeleteAllForHost(cm.get(), GURL(kTopLevelDomainPlus2Secure + std::string("/dir1/xxx")))); EXPECT_EQ(8U, GetAllCookies(cm.get()).size()); @@ -1696,13 +1709,13 @@ TEST_F(CookieMonsterTest, UniqueCreationTime) { options); SetCookieWithDetails(cm.get(), url_google_, "setCookieWithDetails1", "A", - ".google.com", "/", Time(), false, false, + ".google.com", "/", Time(), false, false, false, COOKIE_PRIORITY_DEFAULT); SetCookieWithDetails(cm.get(), url_google_, "setCookieWithDetails2", "A", - ".google.com", "/", Time(), false, false, + ".google.com", "/", Time(), false, false, false, COOKIE_PRIORITY_DEFAULT); SetCookieWithDetails(cm.get(), url_google_, "setCookieWithDetails3", "A", - ".google.com", "/", Time(), false, false, + ".google.com", "/", Time(), false, false, false, COOKIE_PRIORITY_DEFAULT); // Now we check @@ -1770,6 +1783,7 @@ TEST_F(CookieMonsterTest, BackingStoreCommunication) { expires, false, false, + false, COOKIE_PRIORITY_DEFAULT}, {GURL("https://www.google.com"), "b", @@ -1779,6 +1793,7 @@ TEST_F(CookieMonsterTest, BackingStoreCommunication) { expires + TimeDelta::FromSeconds(10), true, true, + false, COOKIE_PRIORITY_DEFAULT}, {GURL("https://google.com"), "c", @@ -1788,6 +1803,7 @@ TEST_F(CookieMonsterTest, BackingStoreCommunication) { base::Time::Now() + base::TimeDelta::FromSeconds(100), true, false, + true, COOKIE_PRIORITY_DEFAULT}}; const int INPUT_DELETE = 1; @@ -1798,7 +1814,8 @@ TEST_F(CookieMonsterTest, BackingStoreCommunication) { p < &input_info[arraysize(input_info)]; p++) { EXPECT_TRUE(SetCookieWithDetails(cmout.get(), p->url, p->name, p->value, p->domain, p->path, p->expiration_time, - p->secure, p->http_only, p->priority)); + p->secure, p->http_only, + p->first_party_only, p->priority)); } GURL del_url(input_info[INPUT_DELETE] .url.Resolve(input_info[INPUT_DELETE].path) @@ -1827,6 +1844,7 @@ TEST_F(CookieMonsterTest, BackingStoreCommunication) { output->CreationDate().ToInternalValue()); EXPECT_EQ(input->secure, output->IsSecure()); EXPECT_EQ(input->http_only, output->IsHttpOnly()); + EXPECT_EQ(input->first_party_only, output->IsFirstPartyOnly()); EXPECT_TRUE(output->IsPersistent()); EXPECT_EQ(input->expiration_time.ToInternalValue(), output->ExpiryDate().ToInternalValue()); @@ -2101,7 +2119,7 @@ TEST_F(CookieMonsterTest, HistogramCheck) { expired_histogram->SnapshotSamples()); ASSERT_TRUE(SetCookieWithDetails( cm.get(), GURL("http://fake.a.url"), "a", "b", "a.url", "/", - base::Time::Now() + base::TimeDelta::FromMinutes(59), false, false, + base::Time::Now() + base::TimeDelta::FromMinutes(59), false, false, false, COOKIE_PRIORITY_DEFAULT)); scoped_ptr<base::HistogramSamples> samples2( @@ -2158,11 +2176,13 @@ class MultiThreadedCookieMonsterTest : public CookieMonsterTest { base::Time expiration_time = base::Time(); bool secure = false; bool http_only = false; + bool first_party_only = false; CookiePriority priority = COOKIE_PRIORITY_DEFAULT; cm->SetCookieWithDetailsAsync( url, name, value, domain, path, expiration_time, secure, http_only, - priority, base::Bind(&ResultSavingCookieCallback<bool>::Run, - base::Unretained(callback))); + first_party_only, priority, + base::Bind(&ResultSavingCookieCallback<bool>::Run, + base::Unretained(callback))); } void DeleteAllCreatedBetweenTask(CookieMonster* cm, @@ -2288,7 +2308,7 @@ TEST_F(MultiThreadedCookieMonsterTest, ThreadCheckSetCookieWithDetails) { scoped_refptr<CookieMonster> cm(new CookieMonster(NULL, NULL)); EXPECT_TRUE(SetCookieWithDetails(cm.get(), url_google_foo_, "A", "B", std::string(), "/foo", base::Time(), false, - false, COOKIE_PRIORITY_DEFAULT)); + false, false, COOKIE_PRIORITY_DEFAULT)); ResultSavingCookieCallback<bool> callback(&other_thread_); base::Closure task = base::Bind(&net::MultiThreadedCookieMonsterTest::SetCookieWithDetailsTask, @@ -2555,11 +2575,12 @@ TEST_F(CookieMonsterTest, ControlCharacterPurge) { // We have to manually build this cookie because it contains a control // character, and our cookie line parser rejects control characters. - CanonicalCookie* cc = new CanonicalCookie( - url, "baz", - "\x05" - "boo", - domain, path, now2, later, now2, false, false, COOKIE_PRIORITY_DEFAULT); + CanonicalCookie* cc = + new CanonicalCookie(url, "baz", + "\x05" + "boo", + domain, path, now2, later, now2, false, false, false, + COOKIE_PRIORITY_DEFAULT); initial_cookies.push_back(cc); AddCookieToList(domain, "hello=world; path=" + path, now3, &initial_cookies); diff --git a/net/cookies/cookie_options.h b/net/cookies/cookie_options.h index 975b8c8..a7ed5a9 100644 --- a/net/cookies/cookie_options.h +++ b/net/cookies/cookie_options.h @@ -7,19 +7,35 @@ #ifndef NET_COOKIES_COOKIE_OPTIONS_H_ #define NET_COOKIES_COOKIE_OPTIONS_H_ +#include "url/gurl.h" + namespace net { class CookieOptions { public: - // Default is to exclude httponly, which means: - // - reading operations will not return httponly cookies. - // - writing operations will not write httponly cookies. - CookieOptions() : exclude_httponly_(true), server_time_() {} + // Default is to exclude httponly completely, and exclude first-party from + // being read, which means: + // - reading operations will not return httponly or first-party cookies. + // - writing operations will not write httponly cookies (first-party will be + // written). + // + // If a first-party URL is set, then first-party cookies which match that URL + // will be returned. + CookieOptions() + : exclude_httponly_(true), + include_first_party_only_(false), + server_time_() {} void set_exclude_httponly() { exclude_httponly_ = true; } void set_include_httponly() { exclude_httponly_ = false; } bool exclude_httponly() const { return exclude_httponly_; } + void set_include_first_party_only() { include_first_party_only_ = true; } + bool include_first_party_only() const { return include_first_party_only_; } + + void set_first_party_url(const GURL& url) { first_party_url_ = url; } + GURL first_party_url() const { return first_party_url_; } + // |server_time| indicates what the server sending us the Cookie thought the // current time was when the cookie was produced. This is used to adjust for // clock skew between server and host. @@ -31,8 +47,11 @@ class CookieOptions { private: bool exclude_httponly_; + bool include_first_party_only_; + GURL first_party_url_; base::Time server_time_; }; + } // namespace net #endif // NET_COOKIES_COOKIE_OPTIONS_H_ diff --git a/net/cookies/parsed_cookie.cc b/net/cookies/parsed_cookie.cc index 8cbca5c..30afe10 100644 --- a/net/cookies/parsed_cookie.cc +++ b/net/cookies/parsed_cookie.cc @@ -55,6 +55,7 @@ const char kExpiresTokenName[] = "expires"; const char kMaxAgeTokenName[] = "max-age"; const char kSecureTokenName[] = "secure"; const char kHttpOnlyTokenName[] = "httponly"; +const char kFirstPartyOnlyTokenName[] = "first-party-only"; const char kPriorityTokenName[] = "priority"; const char kTerminator[] = "\n\r\0"; @@ -162,6 +163,7 @@ ParsedCookie::ParsedCookie(const std::string& cookie_line) maxage_index_(0), secure_index_(0), httponly_index_(0), + firstpartyonly_index_(0), priority_index_(0) { if (cookie_line.size() > kMaxCookieSize) { VLOG(1) << "Not parsing cookie, too large: " << cookie_line.size(); @@ -228,6 +230,11 @@ bool ParsedCookie::SetIsHttpOnly(bool is_http_only) { return SetBool(&httponly_index_, kHttpOnlyTokenName, is_http_only); } +bool ParsedCookie::SetIsFirstPartyOnly(bool is_first_party_only) { + return SetBool(&firstpartyonly_index_, kFirstPartyOnlyTokenName, + is_first_party_only); +} + bool ParsedCookie::SetPriority(const std::string& priority) { return SetString(&priority_index_, kPriorityTokenName, priority); } @@ -238,7 +245,8 @@ std::string ParsedCookie::ToCookieLine() const { if (!out.empty()) out.append("; "); out.append(it->first); - if (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName) { + if (it->first != kSecureTokenName && it->first != kHttpOnlyTokenName && + it->first != kFirstPartyOnlyTokenName) { out.append("="); out.append(it->second); } @@ -429,6 +437,8 @@ void ParsedCookie::SetupAttributes() { secure_index_ = i; } else if (pairs_[i].first == kHttpOnlyTokenName) { httponly_index_ = i; + } else if (pairs_[i].first == kFirstPartyOnlyTokenName) { + firstpartyonly_index_ = i; } else if (pairs_[i].first == kPriorityTokenName) { priority_index_ = i; } else { @@ -486,6 +496,7 @@ void ParsedCookie::ClearAttributePair(size_t index) { &maxage_index_, &secure_index_, &httponly_index_, + &firstpartyonly_index_, &priority_index_}; for (size_t i = 0; i < arraysize(indexes); ++i) { if (*indexes[i] == index) diff --git a/net/cookies/parsed_cookie.h b/net/cookies/parsed_cookie.h index 971cf37..029728d 100644 --- a/net/cookies/parsed_cookie.h +++ b/net/cookies/parsed_cookie.h @@ -48,6 +48,7 @@ class NET_EXPORT ParsedCookie { const std::string& MaxAge() const { return pairs_[maxage_index_].second; } bool IsSecure() const { return secure_index_ != 0; } bool IsHttpOnly() const { return httponly_index_ != 0; } + bool IsFirstPartyOnly() const { return firstpartyonly_index_ != 0; } CookiePriority Priority() const; // Returns the number of attributes, for example, returning 2 for: @@ -67,6 +68,7 @@ class NET_EXPORT ParsedCookie { bool SetMaxAge(const std::string& maxage); bool SetIsSecure(bool is_secure); bool SetIsHttpOnly(bool is_http_only); + bool SetIsFirstPartyOnly(bool is_first_party_only); bool SetPriority(const std::string& priority); // Returns the cookie description as it appears in a HTML response header. @@ -136,6 +138,7 @@ class NET_EXPORT ParsedCookie { size_t maxage_index_; size_t secure_index_; size_t httponly_index_; + size_t firstpartyonly_index_; size_t priority_index_; DISALLOW_COPY_AND_ASSIGN(ParsedCookie); diff --git a/net/cookies/parsed_cookie_unittest.cc b/net/cookies/parsed_cookie_unittest.cc index 9f231e8..61a6167 100644 --- a/net/cookies/parsed_cookie_unittest.cc +++ b/net/cookies/parsed_cookie_unittest.cc @@ -92,16 +92,18 @@ TEST(ParsedCookieTest, TestNameless) { } TEST(ParsedCookieTest, TestAttributeCase) { - ParsedCookie pc("BLAHHH; Path=/; sECuRe; httpONLY; pRIoRitY=hIgH"); + ParsedCookie pc( + "BLAHHH; Path=/; sECuRe; httpONLY; first-PaRty-only; pRIoRitY=hIgH"); EXPECT_TRUE(pc.IsValid()); EXPECT_TRUE(pc.IsSecure()); EXPECT_TRUE(pc.IsHttpOnly()); + EXPECT_TRUE(pc.IsFirstPartyOnly()); EXPECT_TRUE(pc.HasPath()); EXPECT_EQ("/", pc.Path()); EXPECT_EQ("", pc.Name()); EXPECT_EQ("BLAHHH", pc.Value()); EXPECT_EQ(COOKIE_PRIORITY_HIGH, pc.Priority()); - EXPECT_EQ(4U, pc.NumberOfAttributes()); + EXPECT_EQ(5U, pc.NumberOfAttributes()); } TEST(ParsedCookieTest, TestDoubleQuotedNameless) { @@ -146,14 +148,15 @@ TEST(ParsedCookieTest, MissingValue) { } TEST(ParsedCookieTest, Whitespace) { - ParsedCookie pc(" A = BC ;secure;;; httponly"); + ParsedCookie pc(" A = BC ;secure;;; first-party-only "); EXPECT_TRUE(pc.IsValid()); EXPECT_EQ("A", pc.Name()); EXPECT_EQ("BC", pc.Value()); EXPECT_FALSE(pc.HasPath()); EXPECT_FALSE(pc.HasDomain()); EXPECT_TRUE(pc.IsSecure()); - EXPECT_TRUE(pc.IsHttpOnly()); + EXPECT_FALSE(pc.IsHttpOnly()); + EXPECT_TRUE(pc.IsFirstPartyOnly()); EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority()); // We parse anything between ; as attributes, so we end up with two // attributes with an empty string name and value. @@ -168,6 +171,7 @@ TEST(ParsedCookieTest, MultipleEquals) { EXPECT_FALSE(pc.HasDomain()); EXPECT_TRUE(pc.IsSecure()); EXPECT_TRUE(pc.IsHttpOnly()); + EXPECT_FALSE(pc.IsFirstPartyOnly()); EXPECT_EQ(COOKIE_PRIORITY_DEFAULT, pc.Priority()); EXPECT_EQ(4U, pc.NumberOfAttributes()); } @@ -353,11 +357,12 @@ TEST(ParsedCookieTest, SetAttributes) { EXPECT_TRUE(pc.SetIsSecure(true)); EXPECT_TRUE(pc.SetIsHttpOnly(true)); EXPECT_TRUE(pc.SetIsHttpOnly(true)); + EXPECT_TRUE(pc.SetIsFirstPartyOnly(true)); EXPECT_TRUE(pc.SetPriority("HIGH")); EXPECT_EQ( "name=value; domain=domain.com; path=/; " "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; " - "httponly; priority=HIGH", + "httponly; first-party-only; priority=HIGH", pc.ToCookieLine()); EXPECT_TRUE(pc.HasDomain()); EXPECT_TRUE(pc.HasPath()); @@ -365,6 +370,7 @@ TEST(ParsedCookieTest, SetAttributes) { EXPECT_TRUE(pc.HasMaxAge()); EXPECT_TRUE(pc.IsSecure()); EXPECT_TRUE(pc.IsHttpOnly()); + EXPECT_TRUE(pc.IsFirstPartyOnly()); EXPECT_EQ(COOKIE_PRIORITY_HIGH, pc.Priority()); // Clear one attribute from the middle. @@ -377,7 +383,7 @@ TEST(ParsedCookieTest, SetAttributes) { EXPECT_EQ( "name=value; domain=domain.com; path=/foo; " "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; " - "httponly; priority=HIGH", + "httponly; first-party-only; priority=HIGH", pc.ToCookieLine()); // Set priority to medium. @@ -385,7 +391,7 @@ TEST(ParsedCookieTest, SetAttributes) { EXPECT_EQ( "name=value; domain=domain.com; path=/foo; " "expires=Sun, 18-Apr-2027 21:06:29 GMT; max-age=12345; secure; " - "httponly; priority=medium", + "httponly; first-party-only; priority=medium", pc.ToCookieLine()); // Clear the rest and change the name and value. @@ -395,6 +401,7 @@ TEST(ParsedCookieTest, SetAttributes) { EXPECT_TRUE(pc.SetMaxAge(std::string())); EXPECT_TRUE(pc.SetIsSecure(false)); EXPECT_TRUE(pc.SetIsHttpOnly(false)); + EXPECT_TRUE(pc.SetIsFirstPartyOnly(false)); EXPECT_TRUE(pc.SetName("name2")); EXPECT_TRUE(pc.SetValue("value2")); EXPECT_TRUE(pc.SetPriority(std::string())); @@ -404,6 +411,7 @@ TEST(ParsedCookieTest, SetAttributes) { EXPECT_FALSE(pc.HasMaxAge()); EXPECT_FALSE(pc.IsSecure()); EXPECT_FALSE(pc.IsHttpOnly()); + EXPECT_FALSE(pc.IsFirstPartyOnly()); EXPECT_EQ("name2=value2", pc.ToCookieLine()); } diff --git a/net/url_request/url_request_http_job.cc b/net/url_request/url_request_http_job.cc index c763a05..958ef82 100644 --- a/net/url_request/url_request_http_job.cc +++ b/net/url_request/url_request_http_job.cc @@ -629,6 +629,12 @@ void URLRequestHttpJob::AddCookieHeaderAndStart() { void URLRequestHttpJob::DoLoadCookies() { CookieOptions options; options.set_include_httponly(); + options.set_include_first_party_only(); + + // TODO(mkwst): Pipe a switch down here to allow us to decide whether we + // should enforce "first-party only" cookies or not (by setting |options|'s + // first-party-url to the first-party-for-cookies value. crbug.com/459154 + GetCookieStore()->GetCookiesWithOptionsAsync( request_->url(), options, base::Bind(&URLRequestHttpJob::OnCookiesLoaded, |