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 | |
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}
23 files changed, 461 insertions, 299 deletions
diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java b/chrome/android/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java index 951a636..8657e51 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/cookies/CanonicalCookie.java @@ -22,20 +22,13 @@ class CanonicalCookie { private final long mLastAccess; private final boolean mSecure; private final boolean mHttpOnly; + private final boolean mFirstPartyOnly; private final int mPriority; /** Constructs a CanonicalCookie */ - CanonicalCookie(String url, - String name, - String value, - String domain, - String path, - long creation, - long expiration, - long lastAccess, - boolean secure, - boolean httpOnly, - int priority) { + CanonicalCookie(String url, String name, String value, String domain, String path, + long creation, long expiration, long lastAccess, boolean secure, boolean httpOnly, + boolean firstPartyOnly, int priority) { mUrl = url; mName = name; mValue = value; @@ -46,6 +39,7 @@ class CanonicalCookie { mLastAccess = lastAccess; mSecure = secure; mHttpOnly = httpOnly; + mFirstPartyOnly = firstPartyOnly; mPriority = priority; } @@ -59,6 +53,11 @@ class CanonicalCookie { return mHttpOnly; } + /** @return True if the cookie is First-Party only. */ + boolean isFirstPartyOnly() { + return mFirstPartyOnly; + } + /** @return True if the cookie is secure. */ boolean isSecure() { return mSecure; @@ -121,6 +120,7 @@ class CanonicalCookie { out.writeLong(mLastAccess); out.writeBoolean(mSecure); out.writeBoolean(mHttpOnly); + out.writeBoolean(mFirstPartyOnly); out.writeInt(mPriority); } @@ -134,6 +134,6 @@ class CanonicalCookie { throws IOException { return new CanonicalCookie(in.readUTF(), in.readUTF(), in.readUTF(), in.readUTF(), in.readUTF(), in.readLong(), in.readLong(), in.readLong(), in.readBoolean(), - in.readBoolean(), in.readInt()); + in.readBoolean(), in.readBoolean(), in.readInt()); } } diff --git a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java b/chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java index ef54545..bd6ceee 100644 --- a/chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java +++ b/chrome/android/java/src/org/chromium/chrome/browser/cookies/CookiesFetcher.java @@ -162,19 +162,11 @@ public class CookiesFetcher { protected void onPostExecute(List<CanonicalCookie> cookies) { // We can only access cookies and profiles on the UI thread. for (CanonicalCookie cookie : cookies) { - nativeRestoreCookies(mNativeCookiesFetcher, - cookie.getUrl(), - cookie.getName(), - cookie.getValue(), - cookie.getDomain(), - cookie.getPath(), - cookie.getCreationDate(), - cookie.getExpirationDate(), - cookie.getLastAccessDate(), - cookie.isSecure(), - cookie.isHttpOnly(), - cookie.getPriority() - ); + nativeRestoreCookies(mNativeCookiesFetcher, cookie.getUrl(), cookie.getName(), + cookie.getValue(), cookie.getDomain(), cookie.getPath(), + cookie.getCreationDate(), cookie.getExpirationDate(), + cookie.getLastAccessDate(), cookie.isSecure(), cookie.isHttpOnly(), + cookie.isFirstPartyOnly(), cookie.getPriority()); } } }.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR); @@ -201,9 +193,9 @@ public class CookiesFetcher { @CalledByNative private CanonicalCookie createCookie(String url, String name, String value, String domain, String path, long creation, long expiration, long lastAccess, boolean secure, - boolean httpOnly, int priority) { + boolean httpOnly, boolean firstPartyOnly, int priority) { return new CanonicalCookie(url, name, value, domain, path, creation, expiration, lastAccess, - secure, httpOnly, priority); + secure, httpOnly, firstPartyOnly, priority); } @CalledByNative @@ -292,8 +284,8 @@ public class CookiesFetcher { private native long nativeInit(); private static native void nativeDestroy(long nativeCookiesFetcher); private native void nativePersistCookies(long nativeCookiesFetcher); - private native void nativeRestoreCookies(long nativeCookiesFetcher, - String url, String name, String value, String domain, String path, - long creation, long expiration, long lastAccess, boolean secure, - boolean httpOnly, int priority); + private native void nativeRestoreCookies(long nativeCookiesFetcher, String url, String name, + String value, String domain, String path, long creation, long expiration, + long lastAccess, boolean secure, boolean httpOnly, boolean firstPartyOnly, + int priority); } diff --git a/chrome/browser/android/cookies/cookies_fetcher.cc b/chrome/browser/android/cookies/cookies_fetcher.cc index 16cfbaf..25f55e7 100644 --- a/chrome/browser/android/cookies/cookies_fetcher.cc +++ b/chrome/browser/android/cookies/cookies_fetcher.cc @@ -76,20 +76,16 @@ void CookiesFetcher::OnCookiesFetchFinished(const net::CookieList& cookies) { int index = 0; for (net::CookieList::const_iterator i = cookies.begin(); i != cookies.end(); ++i) { - ScopedJavaLocalRef<jobject> java_cookie = - Java_CookiesFetcher_createCookie( - env, jobject_.obj(), - base::android::ConvertUTF8ToJavaString(env, i->Source()).obj(), - base::android::ConvertUTF8ToJavaString(env, i->Name()).obj(), - base::android::ConvertUTF8ToJavaString(env, i->Value()).obj(), - base::android::ConvertUTF8ToJavaString(env, i->Domain()).obj(), - base::android::ConvertUTF8ToJavaString(env, i->Path()).obj(), - i->CreationDate().ToInternalValue(), - i->ExpiryDate().ToInternalValue(), - i->LastAccessDate().ToInternalValue(), - i->IsSecure(), - i->IsHttpOnly(), - i->Priority()); + ScopedJavaLocalRef<jobject> java_cookie = Java_CookiesFetcher_createCookie( + env, jobject_.obj(), + base::android::ConvertUTF8ToJavaString(env, i->Source()).obj(), + base::android::ConvertUTF8ToJavaString(env, i->Name()).obj(), + base::android::ConvertUTF8ToJavaString(env, i->Value()).obj(), + base::android::ConvertUTF8ToJavaString(env, i->Domain()).obj(), + base::android::ConvertUTF8ToJavaString(env, i->Path()).obj(), + i->CreationDate().ToInternalValue(), i->ExpiryDate().ToInternalValue(), + i->LastAccessDate().ToInternalValue(), i->IsSecure(), i->IsHttpOnly(), + i->IsFirstPartyOnly(), i->Priority()); env->SetObjectArrayElement(joa.obj(), index++, java_cookie.obj()); } @@ -111,6 +107,7 @@ void CookiesFetcher::RestoreCookies(JNIEnv* env, int64 last_access, bool secure, bool httponly, + bool firstpartyonly, int priority) { Profile* profile = ProfileManager::GetPrimaryUserProfile(); if (!profile->HasOffTheRecordProfile()) { @@ -129,8 +126,8 @@ void CookiesFetcher::RestoreCookies(JNIEnv* env, base::android::ConvertJavaStringToUTF8(env, path), base::Time::FromInternalValue(creation), base::Time::FromInternalValue(expiration), - base::Time::FromInternalValue(last_access), - secure, httponly, static_cast<net::CookiePriority>(priority)); + base::Time::FromInternalValue(last_access), secure, httponly, + firstpartyonly, static_cast<net::CookiePriority>(priority)); // The rest must be done from the IO thread. content::BrowserThread::PostTask( @@ -158,17 +155,9 @@ void CookiesFetcher::RestoreToCookieJarInternal( base::Callback<void(bool success)> cb; monster->SetCookieWithDetailsAsync( - GURL(cookie.Source()), - cookie.Name(), - cookie.Value(), - cookie.Domain(), - cookie.Path(), - cookie.ExpiryDate(), - cookie.IsSecure(), - cookie.IsHttpOnly(), - cookie.Priority(), - cb - ); + GURL(cookie.Source()), cookie.Name(), cookie.Value(), cookie.Domain(), + cookie.Path(), cookie.ExpiryDate(), cookie.IsSecure(), + cookie.IsHttpOnly(), cookie.IsFirstPartyOnly(), cookie.Priority(), cb); } // JNI functions diff --git a/chrome/browser/android/cookies/cookies_fetcher.h b/chrome/browser/android/cookies/cookies_fetcher.h index e8462cc..2601670 100644 --- a/chrome/browser/android/cookies/cookies_fetcher.h +++ b/chrome/browser/android/cookies/cookies_fetcher.h @@ -47,6 +47,7 @@ class CookiesFetcher { int64 last_access, bool secure, bool httponly, + bool firstpartyonly, int priority); private: diff --git a/chrome/browser/chromeos/login/profile_auth_data_unittest.cc b/chrome/browser/chromeos/login/profile_auth_data_unittest.cc index 6d7d88a..bf6bc33 100644 --- a/chrome/browser/chromeos/login/profile_auth_data_unittest.cc +++ b/chrome/browser/chromeos/login/profile_auth_data_unittest.cc @@ -223,28 +223,14 @@ void ProfileAuthDataTest::PopulateBrowserContext( run_loop_->Run(); net::CookieList cookie_list; - cookie_list.push_back(net::CanonicalCookie(GURL(kGAIACookieURL), - kCookieName, - cookie_value, - kGAIACookieDomain, - std::string(), - base::Time(), - base::Time(), - base::Time(), - true, - false, - net::COOKIE_PRIORITY_DEFAULT)); - cookie_list.push_back(net::CanonicalCookie(GURL(kSAMLIdPCookieURL), - kCookieName, - cookie_value, - kSAMLIdPCookieDomain, - std::string(), - base::Time(), - base::Time(), - base::Time(), - true, - false, - net::COOKIE_PRIORITY_DEFAULT)); + cookie_list.push_back(net::CanonicalCookie( + GURL(kGAIACookieURL), kCookieName, cookie_value, kGAIACookieDomain, + std::string(), base::Time(), base::Time(), base::Time(), true, false, + false, net::COOKIE_PRIORITY_DEFAULT)); + cookie_list.push_back(net::CanonicalCookie( + GURL(kSAMLIdPCookieURL), kCookieName, cookie_value, kSAMLIdPCookieDomain, + std::string(), base::Time(), base::Time(), base::Time(), true, false, + false, net::COOKIE_PRIORITY_DEFAULT)); cookies->ImportCookies(cookie_list); GetChannelIDs(browser_context)->SetChannelID(kChannelIDServerIdentifier, diff --git a/chrome/browser/extensions/api/cookies/cookies_api.cc b/chrome/browser/extensions/api/cookies/cookies_api.cc index d059071..d3bc6e7 100644 --- a/chrome/browser/extensions/api/cookies/cookies_api.cc +++ b/chrome/browser/extensions/api/cookies/cookies_api.cc @@ -399,6 +399,10 @@ void CookiesSetFunction::SetCookieOnIOThread() { : false, parsed_args_->details.http_only.get() ? *parsed_args_->details.http_only : false, + // TODO(mkwst): If we decide to ship First-party-only cookies, we'll need + // to extend the extension API to support them. For the moment, we'll set + // all cookies as non-First-party-only. + false, net::COOKIE_PRIORITY_DEFAULT, base::Bind(&CookiesSetFunction::PullCookie, this)); } diff --git a/chrome/browser/extensions/api/cookies/cookies_unittest.cc b/chrome/browser/extensions/api/cookies/cookies_unittest.cc index e578df2..cbb4cc2 100644 --- a/chrome/browser/extensions/api/cookies/cookies_unittest.cc +++ b/chrome/browser/extensions/api/cookies/cookies_unittest.cc @@ -81,9 +81,8 @@ TEST_F(ExtensionCookiesTest, StoreIdProfileConversion) { TEST_F(ExtensionCookiesTest, ExtensionTypeCreation) { net::CanonicalCookie canonical_cookie1( - GURL(), "ABC", "DEF", "www.foobar.com", "/", - base::Time(), base::Time(), base::Time(), - false, false, net::COOKIE_PRIORITY_DEFAULT); + GURL(), "ABC", "DEF", "www.foobar.com", "/", base::Time(), base::Time(), + base::Time(), false, false, false, net::COOKIE_PRIORITY_DEFAULT); scoped_ptr<Cookie> cookie1( cookies_helpers::CreateCookie( canonical_cookie1, "some cookie store")); @@ -99,9 +98,9 @@ TEST_F(ExtensionCookiesTest, ExtensionTypeCreation) { EXPECT_EQ("some cookie store", cookie1->store_id); net::CanonicalCookie canonical_cookie2( - GURL(), "ABC", "DEF", ".foobar.com", "/", - base::Time(), base::Time::FromDoubleT(10000), base::Time(), - false, false, net::COOKIE_PRIORITY_DEFAULT); + GURL(), "ABC", "DEF", ".foobar.com", "/", base::Time(), + base::Time::FromDoubleT(10000), base::Time(), false, false, false, + net::COOKIE_PRIORITY_DEFAULT); scoped_ptr<Cookie> cookie2( cookies_helpers::CreateCookie( canonical_cookie2, "some cookie store")); @@ -120,16 +119,16 @@ TEST_F(ExtensionCookiesTest, ExtensionTypeCreation) { } TEST_F(ExtensionCookiesTest, GetURLFromCanonicalCookie) { - net::CanonicalCookie cookie1( - GURL(), "ABC", "DEF", "www.foobar.com", "/", base::Time(), base::Time(), - base::Time(), false, false, net::COOKIE_PRIORITY_DEFAULT); + net::CanonicalCookie cookie1(GURL(), "ABC", "DEF", "www.foobar.com", "/", + base::Time(), base::Time(), base::Time(), false, + false, false, net::COOKIE_PRIORITY_DEFAULT); EXPECT_EQ("http://www.foobar.com/", cookies_helpers::GetURLFromCanonicalCookie( cookie1).spec()); - net::CanonicalCookie cookie2( - GURL(), "ABC", "DEF", ".helloworld.com", "/", base::Time(), base::Time(), - base::Time(), true, false, net::COOKIE_PRIORITY_DEFAULT); + net::CanonicalCookie cookie2(GURL(), "ABC", "DEF", ".helloworld.com", "/", + base::Time(), base::Time(), base::Time(), true, + false, false, net::COOKIE_PRIORITY_DEFAULT); EXPECT_EQ("https://helloworld.com/", cookies_helpers::GetURLFromCanonicalCookie( cookie2).spec()); @@ -165,33 +164,19 @@ TEST_F(ExtensionCookiesTest, DomainMatching) { scoped_ptr<GetAll::Params> params(GetAll::Params::Create(args)); cookies_helpers::MatchFilter filter(¶ms->details); - net::CanonicalCookie cookie(GURL(), - std::string(), - std::string(), - tests[i].domain, - std::string(), - base::Time(), - base::Time(), - base::Time(), - false, - false, + net::CanonicalCookie cookie(GURL(), std::string(), std::string(), + tests[i].domain, std::string(), base::Time(), + base::Time(), base::Time(), false, false, false, net::COOKIE_PRIORITY_DEFAULT); EXPECT_EQ(tests[i].matches, filter.MatchesCookie(cookie)); } } TEST_F(ExtensionCookiesTest, DecodeUTF8WithErrorHandling) { - net::CanonicalCookie canonical_cookie(GURL(), - std::string(), - "011Q255bNX_1!yd\203e+", - "test.com", - "/path\203", - base::Time(), - base::Time(), - base::Time(), - false, - false, - net::COOKIE_PRIORITY_DEFAULT); + net::CanonicalCookie canonical_cookie( + GURL(), std::string(), "011Q255bNX_1!yd\203e+", "test.com", "/path\203", + base::Time(), base::Time(), base::Time(), false, false, false, + net::COOKIE_PRIORITY_DEFAULT); scoped_ptr<Cookie> cookie( cookies_helpers::CreateCookie( canonical_cookie, "some cookie store")); diff --git a/content/browser/net/sqlite_persistent_cookie_store.cc b/content/browser/net/sqlite_persistent_cookie_store.cc index cb2f98e..75c89fd 100644 --- a/content/browser/net/sqlite_persistent_cookie_store.cc +++ b/content/browser/net/sqlite_persistent_cookie_store.cc @@ -310,6 +310,8 @@ namespace { // Version number of the database. // +// Version 8 adds "first-party only" cookies. +// // Version 7 adds encrypted values. Old values will continue to be used but // all new values written will be encrypted on selected operating systems. New // records read by old clients will simply get an empty cookie value while old @@ -333,7 +335,7 @@ namespace { // Version 3 updated the database to include the last access time, so we can // expire them in decreasing order of use when we've reached the maximum // number of cookies. -const int kCurrentVersionNumber = 7; +const int kCurrentVersionNumber = 8; const int kCompatibleVersionNumber = 5; // Possible values for the 'priority' column. @@ -399,19 +401,20 @@ bool InitTable(sql::Connection* db) { if (!db->DoesTableExist("cookies")) { std::string stmt(base::StringPrintf( "CREATE TABLE cookies (" - "creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY," - "host_key TEXT NOT NULL," - "name TEXT NOT NULL," - "value TEXT NOT NULL," - "path TEXT NOT NULL," - "expires_utc INTEGER NOT NULL," - "secure INTEGER NOT NULL," - "httponly INTEGER NOT NULL," - "last_access_utc INTEGER NOT NULL, " - "has_expires INTEGER NOT NULL DEFAULT 1, " - "persistent INTEGER NOT NULL DEFAULT 1," - "priority INTEGER NOT NULL DEFAULT %d," - "encrypted_value BLOB DEFAULT '')", + "creation_utc INTEGER NOT NULL UNIQUE PRIMARY KEY," + "host_key TEXT NOT NULL," + "name TEXT NOT NULL," + "value TEXT NOT NULL," + "path TEXT NOT NULL," + "expires_utc INTEGER NOT NULL," + "secure INTEGER NOT NULL," + "httponly INTEGER NOT NULL," + "last_access_utc INTEGER NOT NULL, " + "has_expires INTEGER NOT NULL DEFAULT 1, " + "persistent INTEGER NOT NULL DEFAULT 1," + "priority INTEGER NOT NULL DEFAULT %d," + "encrypted_value BLOB DEFAULT ''," + "firstpartyonly INTEGER NOT NULL DEFAULT 0)", CookiePriorityToDBCookiePriority(net::COOKIE_PRIORITY_DEFAULT))); if (!db->Execute(stmt.c_str())) return false; @@ -723,14 +726,14 @@ bool SQLitePersistentCookieStore::Backend::LoadCookiesForDomains( smt.Assign(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT creation_utc, host_key, name, value, encrypted_value, path, " - "expires_utc, secure, httponly, last_access_utc, has_expires, " - "persistent, priority FROM cookies WHERE host_key = ?")); + "expires_utc, secure, httponly, firstpartyonly, last_access_utc, " + "has_expires, persistent, priority FROM cookies WHERE host_key = ?")); } else { smt.Assign(db_->GetCachedStatement( SQL_FROM_HERE, "SELECT creation_utc, host_key, name, value, encrypted_value, path, " - "expires_utc, secure, httponly, last_access_utc, has_expires, " - "persistent, priority FROM cookies WHERE host_key = ? " + "expires_utc, secure, httponly, firstpartyonly, last_access_utc, " + "has_expires, persistent, priority FROM cookies WHERE host_key = ? " "AND persistent = 1")); } if (!smt.is_valid()) { @@ -769,18 +772,19 @@ void SQLitePersistentCookieStore::Backend::MakeCookiesFromSQLStatement( } scoped_ptr<net::CanonicalCookie> cc(new net::CanonicalCookie( // The "source" URL is not used with persisted cookies. - GURL(), // Source - smt.ColumnString(2), // name - value, // value - smt.ColumnString(1), // domain - smt.ColumnString(5), // path - Time::FromInternalValue(smt.ColumnInt64(0)), // creation_utc - Time::FromInternalValue(smt.ColumnInt64(6)), // expires_utc - Time::FromInternalValue(smt.ColumnInt64(9)), // last_access_utc - smt.ColumnInt(7) != 0, // secure - smt.ColumnInt(8) != 0, // httponly + GURL(), // Source + smt.ColumnString(2), // name + value, // value + smt.ColumnString(1), // domain + smt.ColumnString(5), // path + Time::FromInternalValue(smt.ColumnInt64(0)), // creation_utc + Time::FromInternalValue(smt.ColumnInt64(6)), // expires_utc + Time::FromInternalValue(smt.ColumnInt64(10)), // last_access_utc + smt.ColumnInt(7) != 0, // secure + smt.ColumnInt(8) != 0, // httponly + smt.ColumnInt(9) != 0, // firstpartyonly DBCookiePriorityToCookiePriority( - static_cast<DBCookiePriority>(smt.ColumnInt(12))))); // priority + static_cast<DBCookiePriority>(smt.ColumnInt(13))))); // priority DLOG_IF(WARNING, cc->CreationDate() > Time::Now()) << L"CreationDate too recent"; cookies_per_origin_[CookieOrigin(cc->Domain(), cc->IsSecure())]++; @@ -916,6 +920,27 @@ bool SQLitePersistentCookieStore::Backend::EnsureDatabaseVersion() { base::TimeTicks::Now() - start_time); } + if (cur_version == 7) { + const base::TimeTicks start_time = base::TimeTicks::Now(); + sql::Transaction transaction(db_.get()); + if (!transaction.Begin()) + return false; + // Alter the table to add a 'firstpartyonly' column. + if (!db_->Execute( + "ALTER TABLE cookies " + "ADD COLUMN firstpartyonly INTEGER DEFAULT 0")) { + LOG(WARNING) << "Unable to update cookie database to version 8."; + return false; + } + ++cur_version; + meta_table_.SetVersionNumber(cur_version); + meta_table_.SetCompatibleVersionNumber( + std::min(cur_version, kCompatibleVersionNumber)); + transaction.Commit(); + UMA_HISTOGRAM_TIMES("Cookie.TimeDatabaseMigrationToV8", + base::TimeTicks::Now() - start_time); + } + // Put future migration cases here. if (cur_version < kCurrentVersionNumber) { @@ -999,11 +1024,12 @@ void SQLitePersistentCookieStore::Backend::Commit() { if (!db_.get() || ops.empty()) return; - sql::Statement add_smt(db_->GetCachedStatement(SQL_FROM_HERE, + sql::Statement add_smt(db_->GetCachedStatement( + SQL_FROM_HERE, "INSERT INTO cookies (creation_utc, host_key, name, value, " - "encrypted_value, path, expires_utc, secure, httponly, last_access_utc, " - "has_expires, persistent, priority) " - "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)")); + "encrypted_value, path, expires_utc, secure, httponly, firstpartyonly, " + "last_access_utc, has_expires, persistent, priority) " + "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)")); if (!add_smt.is_valid()) return; @@ -1048,11 +1074,12 @@ void SQLitePersistentCookieStore::Backend::Commit() { add_smt.BindInt64(6, po->cc().ExpiryDate().ToInternalValue()); add_smt.BindInt(7, po->cc().IsSecure()); add_smt.BindInt(8, po->cc().IsHttpOnly()); - add_smt.BindInt64(9, po->cc().LastAccessDate().ToInternalValue()); - add_smt.BindInt(10, po->cc().IsPersistent()); + add_smt.BindInt(9, po->cc().IsFirstPartyOnly()); + add_smt.BindInt64(10, po->cc().LastAccessDate().ToInternalValue()); add_smt.BindInt(11, po->cc().IsPersistent()); - add_smt.BindInt( - 12, CookiePriorityToDBCookiePriority(po->cc().Priority())); + add_smt.BindInt(12, po->cc().IsPersistent()); + add_smt.BindInt(13, + CookiePriorityToDBCookiePriority(po->cc().Priority())); if (!add_smt.Run()) NOTREACHED() << "Could not add a cookie to the DB."; break; diff --git a/content/browser/net/sqlite_persistent_cookie_store_perftest.cc b/content/browser/net/sqlite_persistent_cookie_store_perftest.cc index 21f3c85..2a708b42 100644 --- a/content/browser/net/sqlite_persistent_cookie_store_perftest.cc +++ b/content/browser/net/sqlite_persistent_cookie_store_perftest.cc @@ -78,11 +78,9 @@ class SQLitePersistentCookieStorePerfTest : public testing::Test { GURL gurl("www" + domain_name); for (int cookie_num = 0; cookie_num < 50; ++cookie_num) { t += base::TimeDelta::FromInternalValue(10); - store_->AddCookie( - net::CanonicalCookie(gurl, - base::StringPrintf("Cookie_%d", cookie_num), "1", - domain_name, "/", t, t, t, false, false, - net::COOKIE_PRIORITY_DEFAULT)); + store_->AddCookie(net::CanonicalCookie( + gurl, base::StringPrintf("Cookie_%d", cookie_num), "1", domain_name, + "/", t, t, t, false, false, false, net::COOKIE_PRIORITY_DEFAULT)); } } // Replace the store effectively destroying the current one and forcing it diff --git a/content/browser/net/sqlite_persistent_cookie_store_unittest.cc b/content/browser/net/sqlite_persistent_cookie_store_unittest.cc index 47590d7..ab177a9 100644 --- a/content/browser/net/sqlite_persistent_cookie_store_unittest.cc +++ b/content/browser/net/sqlite_persistent_cookie_store_unittest.cc @@ -159,10 +159,9 @@ class SQLitePersistentCookieStoreTest : public testing::Test { const std::string& domain, const std::string& path, const base::Time& creation) { - store_->AddCookie( - net::CanonicalCookie(GURL(), name, value, domain, path, creation, - creation, creation, false, false, - net::COOKIE_PRIORITY_DEFAULT)); + store_->AddCookie(net::CanonicalCookie( + GURL(), name, value, domain, path, creation, creation, creation, false, + false, false, net::COOKIE_PRIORITY_DEFAULT)); } std::string ReadRawDBContents() { @@ -361,11 +360,10 @@ TEST_F(SQLitePersistentCookieStoreTest, TestLoadOldSessionCookies) { InitializeStore(false, true); // Add a session cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), "C", "D", "sessioncookie.com", "/", base::Time::Now(), - base::Time(), base::Time::Now(), false, false, - net::COOKIE_PRIORITY_DEFAULT)); + store_->AddCookie(net::CanonicalCookie(GURL(), "C", "D", "sessioncookie.com", + "/", base::Time::Now(), base::Time(), + base::Time::Now(), false, false, false, + net::COOKIE_PRIORITY_DEFAULT)); // Force the store to write its data to the disk. DestroyStore(); @@ -389,11 +387,10 @@ TEST_F(SQLitePersistentCookieStoreTest, TestDontLoadOldSessionCookies) { InitializeStore(false, true); // Add a session cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), "C", "D", "sessioncookie.com", "/", base::Time::Now(), - base::Time(), base::Time::Now(), false, false, - net::COOKIE_PRIORITY_DEFAULT)); + store_->AddCookie(net::CanonicalCookie(GURL(), "C", "D", "sessioncookie.com", + "/", base::Time::Now(), base::Time(), + base::Time::Now(), false, false, false, + net::COOKIE_PRIORITY_DEFAULT)); // Force the store to write its data to the disk. DestroyStore(); @@ -420,19 +417,16 @@ TEST_F(SQLitePersistentCookieStoreTest, PersistIsPersistent) { static const char kPersistentName[] = "persistent"; // Add a session cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), kSessionName, "val", "sessioncookie.com", "/", - base::Time::Now(), base::Time(), base::Time::Now(), false, false, - net::COOKIE_PRIORITY_DEFAULT)); + store_->AddCookie(net::CanonicalCookie( + GURL(), kSessionName, "val", "sessioncookie.com", "/", base::Time::Now(), + base::Time(), base::Time::Now(), false, false, false, + net::COOKIE_PRIORITY_DEFAULT)); // Add a persistent cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), kPersistentName, "val", "sessioncookie.com", "/", - base::Time::Now() - base::TimeDelta::FromDays(1), - base::Time::Now() + base::TimeDelta::FromDays(1), - base::Time::Now(), false, false, - net::COOKIE_PRIORITY_DEFAULT)); + store_->AddCookie(net::CanonicalCookie( + GURL(), kPersistentName, "val", "sessioncookie.com", "/", + base::Time::Now() - base::TimeDelta::FromDays(1), + base::Time::Now() + base::TimeDelta::FromDays(1), base::Time::Now(), + false, false, false, net::COOKIE_PRIORITY_DEFAULT)); // Force the store to write its data to the disk. DestroyStore(); @@ -473,31 +467,25 @@ TEST_F(SQLitePersistentCookieStoreTest, PriorityIsPersistent) { InitializeStore(false, true); // Add a low-priority persistent cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), kLowName, kCookieValue, kCookieDomain, kCookiePath, - base::Time::Now() - base::TimeDelta::FromMinutes(1), - base::Time::Now() + base::TimeDelta::FromDays(1), - base::Time::Now(), false, false, - net::COOKIE_PRIORITY_LOW)); + store_->AddCookie(net::CanonicalCookie( + GURL(), kLowName, kCookieValue, kCookieDomain, kCookiePath, + base::Time::Now() - base::TimeDelta::FromMinutes(1), + base::Time::Now() + base::TimeDelta::FromDays(1), base::Time::Now(), + false, false, false, net::COOKIE_PRIORITY_LOW)); // Add a medium-priority persistent cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), kMediumName, kCookieValue, kCookieDomain, kCookiePath, - base::Time::Now() - base::TimeDelta::FromMinutes(2), - base::Time::Now() + base::TimeDelta::FromDays(1), - base::Time::Now(), false, false, - net::COOKIE_PRIORITY_MEDIUM)); + store_->AddCookie(net::CanonicalCookie( + GURL(), kMediumName, kCookieValue, kCookieDomain, kCookiePath, + base::Time::Now() - base::TimeDelta::FromMinutes(2), + base::Time::Now() + base::TimeDelta::FromDays(1), base::Time::Now(), + false, false, false, net::COOKIE_PRIORITY_MEDIUM)); // Add a high-priority peristent cookie. - store_->AddCookie( - net::CanonicalCookie( - GURL(), kHighName, kCookieValue, kCookieDomain, kCookiePath, - base::Time::Now() - base::TimeDelta::FromMinutes(3), - base::Time::Now() + base::TimeDelta::FromDays(1), - base::Time::Now(), false, false, - net::COOKIE_PRIORITY_HIGH)); + store_->AddCookie(net::CanonicalCookie( + GURL(), kHighName, kCookieValue, kCookieDomain, kCookiePath, + base::Time::Now() - base::TimeDelta::FromMinutes(3), + base::Time::Now() + base::TimeDelta::FromDays(1), base::Time::Now(), + false, false, false, net::COOKIE_PRIORITY_HIGH)); // Force the store to write its data to the disk. DestroyStore(); 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, diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml index 1ed23b8..6173e3d 100644 --- a/tools/metrics/histograms/histograms.xml +++ b/tools/metrics/histograms/histograms.xml @@ -3542,15 +3542,25 @@ Therefore, the affected-histogram name has to have at least one dot in it. </histogram> <histogram name="Cookie.TimeDatabaseMigrationToV5" units="ms"> - <owner>Please list the metric's owners. Add more owner tags as needed.</owner> + <owner>erikwright@chromium.org</owner> <summary>The amount of time (ms) to migrate a v4 database to v5.</summary> </histogram> <histogram name="Cookie.TimeDatabaseMigrationToV6" units="ms"> - <owner>Please list the metric's owners. Add more owner tags as needed.</owner> + <owner>erikwright@chromium.org</owner> <summary>The amount of time (ms) to migrate a v5 database to v6.</summary> </histogram> +<histogram name="Cookie.TimeDatabaseMigrationToV7" units="ms"> + <owner>erikwright@chromium.org</owner> + <summary>The amount of time (ms) to migrate a v6 database to v7.</summary> +</histogram> + +<histogram name="Cookie.TimeDatabaseMigrationToV8" units="ms"> + <owner>erikwright@chromium.org</owner> + <summary>The amount of time (ms) to migrate a v7 database to v8.</summary> +</histogram> + <histogram name="Cookie.TimeGet" units="ms"> <obsolete> Deprecated as of 11/2014. |