| // Copyright 2012 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "net/extras/sqlite/sqlite_persistent_cookie_store.h" |
| |
| #include <stdint.h> |
| |
| #include <map> |
| #include <memory> |
| #include <optional> |
| #include <set> |
| #include <utility> |
| #include <vector> |
| |
| #include "base/containers/span.h" |
| #include "base/files/file.h" |
| #include "base/files/file_util.h" |
| #include "base/files/scoped_temp_dir.h" |
| #include "base/functional/bind.h" |
| #include "base/functional/callback.h" |
| #include "base/location.h" |
| #include "base/memory/ref_counted.h" |
| #include "base/memory/weak_ptr.h" |
| #include "base/run_loop.h" |
| #include "base/sequence_checker.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/synchronization/waitable_event.h" |
| #include "base/task/sequenced_task_runner.h" |
| #include "base/task/single_thread_task_runner.h" |
| #include "base/task/thread_pool.h" |
| #include "base/test/bind.h" |
| #include "base/test/metrics/histogram_tester.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/threading/thread_restrictions.h" |
| #include "base/time/time.h" |
| #include "crypto/aes_cbc.h" |
| #include "net/base/features.h" |
| #include "net/base/test_completion_callback.h" |
| #include "net/cookies/canonical_cookie.h" |
| #include "net/cookies/cookie_constants.h" |
| #include "net/cookies/cookie_inclusion_status.h" |
| #include "net/cookies/cookie_store_test_callbacks.h" |
| #include "net/extras/sqlite/cookie_crypto_delegate.h" |
| #include "net/log/net_log_capture_mode.h" |
| #include "net/log/test_net_log.h" |
| #include "net/log/test_net_log_util.h" |
| #include "net/test/test_with_task_environment.h" |
| #include "sql/database.h" |
| #include "sql/meta_table.h" |
| #include "sql/statement.h" |
| #include "sql/test/test_helpers.h" |
| #include "sql/transaction.h" |
| #include "testing/gmock/include/gmock/gmock-matchers.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/gurl.h" |
| #include "url/third_party/mozilla/url_parse.h" |
| |
| namespace net { |
| |
| namespace { |
| |
| const base::FilePath::CharType kCookieFilename[] = FILE_PATH_LITERAL("Cookies"); |
| |
| class CookieCryptor : public CookieCryptoDelegate { |
| public: |
| CookieCryptor(); |
| |
| // net::CookieCryptoDelegate implementation. |
| void Init(base::OnceClosure callback) override; |
| bool EncryptString(const std::string& plaintext, |
| std::string* ciphertext) override; |
| bool DecryptString(const std::string& ciphertext, |
| std::string* plaintext) override; |
| |
| // Obtain a closure that can be called to trigger an initialize. If this |
| // instance has already been destructed then the returned base::OnceClosure |
| // does nothing. This allows tests to pass ownership to the CookieCryptor |
| // while still retaining a weak reference to the Init function. |
| base::OnceClosure GetInitClosure(base::OnceClosure callback); |
| |
| private: |
| void InitComplete(); |
| bool init_ GUARDED_BY_CONTEXT(sequence_checker_) = false; |
| bool initing_ GUARDED_BY_CONTEXT(sequence_checker_) = false; |
| base::OnceClosureList callbacks_ GUARDED_BY_CONTEXT(sequence_checker_); |
| SEQUENCE_CHECKER(sequence_checker_); |
| |
| base::WeakPtrFactory<CookieCryptor> weak_ptr_factory_{this}; |
| }; |
| |
| constexpr std::array<uint8_t, 32> kFixedKey{ |
| 'c', 'o', 'o', 'k', 'i', 'e', 'c', 'r', 'y', 'p', 't', |
| 'o', 'r', 'i', 's', 'a', 'u', 's', 'e', 'f', 'u', 'l', |
| 't', 'e', 's', 't', 'c', 'l', 'a', 's', 's', '!', |
| }; |
| constexpr std::array<uint8_t, 16> kFixedIv{ |
| 't', 'h', 'e', ' ', 'i', 'v', ':', ' ', |
| '1', '6', ' ', 'b', 'y', 't', 'e', 's', |
| }; |
| |
| CookieCryptor::CookieCryptor() { |
| DETACH_FROM_SEQUENCE(sequence_checker_); |
| } |
| |
| base::OnceClosure CookieCryptor::GetInitClosure(base::OnceClosure callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| return base::BindOnce(&CookieCryptor::Init, weak_ptr_factory_.GetWeakPtr(), |
| std::move(callback)); |
| } |
| |
| void CookieCryptor::Init(base::OnceClosure callback) { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| if (init_) { |
| std::move(callback).Run(); |
| return; |
| } |
| |
| // Callbacks here are owned by test fixtures that outlive the CookieCryptor. |
| callbacks_.AddUnsafe(std::move(callback)); |
| |
| if (initing_) { |
| return; |
| } |
| |
| initing_ = true; |
| base::SingleThreadTaskRunner::GetCurrentDefault()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&CookieCryptor::InitComplete, |
| weak_ptr_factory_.GetWeakPtr()), |
| base::Milliseconds(100)); |
| } |
| |
| bool CookieCryptor::EncryptString(const std::string& plaintext, |
| std::string* ciphertext) { |
| // SQLite crypto uses OSCrypt Async Encryptor and the behavior for empty |
| // plaintext is to return empty ciphertext. See |
| // os_crypt_async::Encryptor::EncryptString. This matches this behavior, |
| // without adding a dependency from net into components. |
| if (plaintext.empty()) { |
| ciphertext->clear(); |
| return true; |
| } |
| auto result = crypto::aes_cbc::Encrypt(kFixedKey, kFixedIv, |
| base::as_byte_span(plaintext)); |
| ciphertext->assign(result.begin(), result.end()); |
| return true; |
| } |
| |
| bool CookieCryptor::DecryptString(const std::string& ciphertext, |
| std::string* plaintext) { |
| // SQLite crypto uses OSCrypt Async Encryptor and the behavior for empty |
| // ciphertext is to return empty plaintext. See |
| // os_crypt_async::Encryptor::DecryptString. This matches this behavior, |
| // without adding a dependency from net into components. |
| if (ciphertext.empty()) { |
| plaintext->clear(); |
| return true; |
| } |
| auto result = crypto::aes_cbc::Decrypt(kFixedKey, kFixedIv, |
| base::as_byte_span(ciphertext)); |
| if (result.has_value()) { |
| plaintext->assign(result->begin(), result->end()); |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void CookieCryptor::InitComplete() { |
| DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| init_ = true; |
| callbacks_.Notify(); |
| } |
| |
| // Matches the CanonicalCookie's strictly_unique_key and last_access_date |
| // against a unique_ptr<CanonicalCookie>. |
| MATCHER_P2(MatchesCookieKeyAndLastAccessDate, |
| StrictlyUniqueKey, |
| last_access_date, |
| "") { |
| if (!arg) { |
| return false; |
| } |
| const CanonicalCookie& list_cookie = *arg; |
| |
| return testing::ExplainMatchResult(StrictlyUniqueKey, |
| list_cookie.StrictlyUniqueKey(), |
| result_listener) && |
| testing::ExplainMatchResult( |
| last_access_date, list_cookie.LastAccessDate(), result_listener); |
| } |
| |
| // Matches every field of a CanonicalCookie against a |
| // unique_ptr<CanonicalCookie>. |
| MATCHER_P(MatchesEveryCookieField, cookie, "") { |
| if (!arg) { |
| return false; |
| } |
| const CanonicalCookie& list_cookie = *arg; |
| return cookie.HasEquivalentDataMembers(list_cookie); |
| } |
| |
| } // namespace |
| |
| typedef std::vector<std::unique_ptr<CanonicalCookie>> CanonicalCookieVector; |
| |
| class SQLitePersistentCookieStoreTest : public TestWithTaskEnvironment { |
| public: |
| SQLitePersistentCookieStoreTest() |
| : loaded_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED), |
| db_thread_event_(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED) {} |
| |
| void SignalLoadedEvent() { loaded_event_.Signal(); } |
| |
| void OnLoaded(base::OnceClosure closure, CanonicalCookieVector cookies) { |
| cookies_.swap(cookies); |
| std::move(closure).Run(); |
| } |
| |
| CanonicalCookieVector Load() { |
| base::RunLoop run_loop; |
| CanonicalCookieVector cookies; |
| store_->Load( |
| base::BindLambdaForTesting([&](CanonicalCookieVector obtained_cookies) { |
| cookies.swap(obtained_cookies); |
| run_loop.Quit(); |
| }), |
| NetLogWithSource::Make(NetLogSourceType::NONE)); |
| run_loop.Run(); |
| return cookies; |
| } |
| |
| void LoadAsyncAndSignalEvent() { |
| store_->Load( |
| base::BindOnce( |
| &SQLitePersistentCookieStoreTest::OnLoaded, base::Unretained(this), |
| base::BindOnce(&SQLitePersistentCookieStoreTest::SignalLoadedEvent, |
| base::Unretained(this))), |
| NetLogWithSource::Make(NetLogSourceType::NONE)); |
| } |
| |
| void Flush() { |
| base::RunLoop run_loop; |
| store_->Flush(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| |
| void DestroyStore() { |
| store_ = nullptr; |
| // Make sure we wait until the destructor has run by running all |
| // TaskEnvironment tasks. |
| RunUntilIdle(); |
| } |
| |
| void Create(bool crypt_cookies, |
| bool restore_old_session_cookies, |
| bool use_current_thread, |
| bool enable_exclusive_access) { |
| store_ = base::MakeRefCounted<SQLitePersistentCookieStore>( |
| temp_dir_.GetPath().Append(kCookieFilename), |
| use_current_thread ? base::SingleThreadTaskRunner::GetCurrentDefault() |
| : client_task_runner_, |
| background_task_runner_, restore_old_session_cookies, |
| crypt_cookies ? std::make_unique<CookieCryptor>() : nullptr, |
| enable_exclusive_access); |
| } |
| |
| CanonicalCookieVector CreateAndLoad(bool crypt_cookies, |
| bool restore_old_session_cookies) { |
| Create(crypt_cookies, restore_old_session_cookies, |
| /*use_current_thread=*/false, /*enable_exclusive_access=*/false); |
| return Load(); |
| } |
| |
| void InitializeStore(bool crypt, bool restore_old_session_cookies) { |
| EXPECT_EQ(0U, CreateAndLoad(crypt, restore_old_session_cookies).size()); |
| } |
| |
| void WaitOnDBEvent() { |
| base::ScopedAllowBaseSyncPrimitivesForTesting allow_base_sync_primitives; |
| db_thread_event_.Wait(); |
| } |
| |
| // Adds a persistent cookie to store_. |
| void AddCookie(const std::string& name, |
| const std::string& value, |
| const std::string& domain, |
| const std::string& path, |
| const base::Time& creation) { |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| name, value, domain, path, creation, /*expiration=*/creation, |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, |
| /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_DEFAULT)); |
| } |
| |
| void AddCookieWithExpiration(const std::string& name, |
| const std::string& value, |
| const std::string& domain, |
| const std::string& path, |
| const base::Time& creation, |
| const base::Time& expiration) { |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| name, value, domain, path, creation, expiration, |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, |
| /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_DEFAULT)); |
| } |
| |
| std::string ReadRawDBContents() { |
| std::string contents; |
| if (!base::ReadFileToString(temp_dir_.GetPath().Append(kCookieFilename), |
| &contents)) |
| return std::string(); |
| return contents; |
| } |
| |
| void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); } |
| |
| void TearDown() override { |
| if (!expect_init_errors_) { |
| EXPECT_THAT(histograms_.GetAllSamples("Cookie.ErrorInitializeDB"), |
| ::testing::IsEmpty()); |
| } |
| DestroyStore(); |
| } |
| |
| protected: |
| const scoped_refptr<base::SequencedTaskRunner> background_task_runner_ = |
| base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}); |
| const scoped_refptr<base::SequencedTaskRunner> client_task_runner_ = |
| base::ThreadPool::CreateSequencedTaskRunner({base::MayBlock()}); |
| base::WaitableEvent loaded_event_; |
| base::WaitableEvent db_thread_event_; |
| CanonicalCookieVector cookies_; |
| base::ScopedTempDir temp_dir_; |
| scoped_refptr<SQLitePersistentCookieStore> store_; |
| std::unique_ptr<CookieCryptor> cookie_crypto_delegate_; |
| base::HistogramTester histograms_; |
| bool expect_init_errors_ = false; |
| }; |
| |
| TEST_F(SQLitePersistentCookieStoreTest, TestInvalidVersionRecovery) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| AddCookie("A", "B", "foo.bar", "/", base::Time::Now()); |
| DestroyStore(); |
| |
| // Load up the store and verify that it has good data in it. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(1U, cookies.size()); |
| ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str()); |
| ASSERT_STREQ("A", cookies[0]->Name().c_str()); |
| ASSERT_STREQ("B", cookies[0]->Value().c_str()); |
| DestroyStore(); |
| cookies.clear(); |
| |
| // Now make the version too old to initialize from. |
| { |
| sql::Database db(sql::test::kTestTag); |
| ASSERT_TRUE(db.Open(temp_dir_.GetPath().Append(kCookieFilename))); |
| sql::MetaTable meta_table; |
| ASSERT_TRUE(meta_table.Init(&db, 1, 1)); |
| // Keep in sync with latest unsupported version from: |
| // net/extras/sqlite/sqlite_persistent_cookie_store.cc |
| ASSERT_TRUE(meta_table.SetVersionNumber(17)); |
| } |
| |
| // Upon loading, the database should be reset to a good, blank state. |
| cookies = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(0U, cookies.size()); |
| |
| // Verify that, after, recovery, the database persists properly. |
| AddCookie("X", "Y", "foo.bar", "/", base::Time::Now()); |
| DestroyStore(); |
| cookies = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(1U, cookies.size()); |
| ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str()); |
| ASSERT_STREQ("X", cookies[0]->Name().c_str()); |
| ASSERT_STREQ("Y", cookies[0]->Value().c_str()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, TestInvalidMetaTableRecovery) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| AddCookie("A", "B", "foo.bar", "/", base::Time::Now()); |
| DestroyStore(); |
| |
| // Load up the store and verify that it has good data in it. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(1U, cookies.size()); |
| ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str()); |
| ASSERT_STREQ("A", cookies[0]->Name().c_str()); |
| ASSERT_STREQ("B", cookies[0]->Value().c_str()); |
| DestroyStore(); |
| cookies.clear(); |
| |
| // Now corrupt the meta table. |
| { |
| sql::Database db(sql::test::kTestTag); |
| ASSERT_TRUE(db.Open(temp_dir_.GetPath().Append(kCookieFilename))); |
| sql::MetaTable meta_table; |
| ASSERT_TRUE(meta_table.Init(&db, 1, 1)); |
| ASSERT_TRUE(db.Execute("DELETE FROM meta")); |
| } |
| |
| // Upon loading, the database should be reset to a good, blank state. |
| cookies = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(0U, cookies.size()); |
| |
| // Verify that, after, recovery, the database persists properly. |
| AddCookie("X", "Y", "foo.bar", "/", base::Time::Now()); |
| DestroyStore(); |
| cookies = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(1U, cookies.size()); |
| ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str()); |
| ASSERT_STREQ("X", cookies[0]->Name().c_str()); |
| ASSERT_STREQ("Y", cookies[0]->Value().c_str()); |
| } |
| |
| // Test if data is stored as expected in the SQLite database. |
| TEST_F(SQLitePersistentCookieStoreTest, TestPersistance) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| AddCookie("A", "B", "foo.bar", "/", base::Time::Now()); |
| // Replace the store effectively destroying the current one and forcing it |
| // to write its data to disk. Then we can see if after loading it again it |
| // is still there. |
| DestroyStore(); |
| // Reload and test for persistence |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(1U, cookies.size()); |
| ASSERT_STREQ("foo.bar", cookies[0]->Domain().c_str()); |
| ASSERT_STREQ("A", cookies[0]->Name().c_str()); |
| ASSERT_STREQ("B", cookies[0]->Value().c_str()); |
| |
| // Now delete the cookie and check persistence again. |
| store_->DeleteCookie(*cookies[0]); |
| DestroyStore(); |
| cookies.clear(); |
| |
| // Reload and check if the cookie has been removed. |
| cookies = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(0U, cookies.size()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, TestSessionCookiesDeletedOnStartup) { |
| // Initialize the cookie store with 3 persistent cookies, 5 transient |
| // cookies. |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| |
| // Add persistent cookies. |
| base::Time t = base::Time::Now(); |
| AddCookie("A", "B", "a1.com", "/", t); |
| t += base::Microseconds(10); |
| AddCookie("A", "B", "a2.com", "/", t); |
| t += base::Microseconds(10); |
| AddCookie("A", "B", "a3.com", "/", t); |
| |
| // Add transient cookies. |
| t += base::Microseconds(10); |
| AddCookieWithExpiration("A", "B", "b1.com", "/", t, base::Time()); |
| t += base::Microseconds(10); |
| AddCookieWithExpiration("A", "B", "b2.com", "/", t, base::Time()); |
| t += base::Microseconds(10); |
| AddCookieWithExpiration("A", "B", "b3.com", "/", t, base::Time()); |
| t += base::Microseconds(10); |
| AddCookieWithExpiration("A", "B", "b4.com", "/", t, base::Time()); |
| t += base::Microseconds(10); |
| AddCookieWithExpiration("A", "B", "b5.com", "/", t, base::Time()); |
| DestroyStore(); |
| |
| // Load the store a second time. Before the store finishes loading, add a |
| // transient cookie and flush it to disk. |
| store_ = base::MakeRefCounted<SQLitePersistentCookieStore>( |
| temp_dir_.GetPath().Append(kCookieFilename), client_task_runner_, |
| background_task_runner_, false, nullptr, false); |
| |
| // Posting a blocking task to db_thread_ makes sure that the DB thread waits |
| // until both Load and Flush have been posted to its task queue. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, |
| base::Unretained(this))); |
| LoadAsyncAndSignalEvent(); |
| t += base::Microseconds(10); |
| AddCookieWithExpiration("A", "B", "c.com", "/", t, base::Time()); |
| base::WaitableEvent event(base::WaitableEvent::ResetPolicy::AUTOMATIC, |
| base::WaitableEvent::InitialState::NOT_SIGNALED); |
| store_->Flush( |
| base::BindOnce(&base::WaitableEvent::Signal, base::Unretained(&event))); |
| |
| // Now the DB-thread queue contains: |
| // (active:) |
| // 1. Wait (on db_event) |
| // (pending:) |
| // 2. "Init And Chain-Load First Domain" |
| // 3. Add Cookie (c.com) |
| // 4. Flush Cookie (c.com) |
| db_thread_event_.Signal(); |
| event.Wait(); |
| loaded_event_.Wait(); |
| cookies_.clear(); |
| DestroyStore(); |
| |
| // Load the store a third time, this time restoring session cookies. The |
| // store should contain exactly 4 cookies: the 3 persistent, and "c.com", |
| // which was added during the second cookie store load. |
| store_ = base::MakeRefCounted<SQLitePersistentCookieStore>( |
| temp_dir_.GetPath().Append(kCookieFilename), client_task_runner_, |
| background_task_runner_, true, nullptr, false); |
| LoadAsyncAndSignalEvent(); |
| loaded_event_.Wait(); |
| ASSERT_EQ(4u, cookies_.size()); |
| } |
| |
| // Test that priority load of cookies for a specific domain key could be |
| // completed before the entire store is loaded. |
| TEST_F(SQLitePersistentCookieStoreTest, TestLoadCookiesForKey) { |
| InitializeStore(/*crypt=*/true, /*restore_old_session_cookies=*/false); |
| base::Time t = base::Time::Now(); |
| AddCookie("A", "B", "foo.bar", "/", t); |
| t += base::Microseconds(10); |
| AddCookie("A", "B", "www.aaa.com", "/", t); |
| t += base::Microseconds(10); |
| AddCookie("A", "B", "travel.aaa.com", "/", t); |
| t += base::Microseconds(10); |
| AddCookie("A", "B", "www.bbb.com", "/", t); |
| DestroyStore(); |
| |
| auto cookie_crypto_delegate = std::make_unique<CookieCryptor>(); |
| base::RunLoop cookie_crypto_loop; |
| auto init_closure = |
| cookie_crypto_delegate->GetInitClosure(cookie_crypto_loop.QuitClosure()); |
| |
| // base::test::TaskEnvironment runs |background_task_runner_| and |
| // |client_task_runner_| on the same thread. Therefore, when a |
| // |background_task_runner_| task is blocked, |client_task_runner_| tasks |
| // can't run. To allow precise control of |background_task_runner_| without |
| // preventing client tasks to run, use |
| // base::SingleThreadTaskRunner::GetCurrentDefault() instead of |
| // |client_task_runner_| for this test. |
| store_ = base::MakeRefCounted<SQLitePersistentCookieStore>( |
| temp_dir_.GetPath().Append(kCookieFilename), |
| base::SingleThreadTaskRunner::GetCurrentDefault(), |
| background_task_runner_, |
| /*restore_old_session_cookies=*/false, std::move(cookie_crypto_delegate), |
| /*enable_exclusive_access=*/false); |
| |
| // Posting a blocking task to db_thread_ makes sure that the DB thread waits |
| // until both Load and LoadCookiesForKey have been posted to its task queue. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, |
| base::Unretained(this))); |
| RecordingNetLogObserver net_log_observer; |
| LoadAsyncAndSignalEvent(); |
| base::RunLoop run_loop; |
| net_log_observer.SetObserverCaptureMode(NetLogCaptureMode::kDefault); |
| store_->LoadCookiesForKey( |
| "aaa.com", |
| base::BindOnce(&SQLitePersistentCookieStoreTest::OnLoaded, |
| base::Unretained(this), run_loop.QuitClosure())); |
| |
| // Complete the initialization of the cookie crypto delegate. This ensures |
| // that any background tasks from the Load or the LoadCookiesForKey are posted |
| // to the background_task_runner_. |
| std::move(init_closure).Run(); |
| cookie_crypto_loop.Run(); |
| |
| // Post a final blocking task to the background_task_runner_ to ensure no |
| // other cookie loads take place during the test. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, |
| base::Unretained(this))); |
| |
| // Now the DB-thread queue contains: |
| // (active:) |
| // 1. Wait (on db_event) |
| // (pending:) |
| // 2. "Init And Chain-Load First Domain" |
| // 3. Priority Load (aaa.com) |
| // 4. Wait (on db_event) |
| db_thread_event_.Signal(); |
| |
| // Wait until the OnKeyLoaded callback has run. |
| run_loop.Run(); |
| EXPECT_FALSE(loaded_event_.IsSignaled()); |
| |
| std::set<std::string> cookies_loaded; |
| for (CanonicalCookieVector::const_iterator it = cookies_.begin(); |
| it != cookies_.end(); ++it) { |
| cookies_loaded.insert((*it)->Domain().c_str()); |
| } |
| cookies_.clear(); |
| ASSERT_GT(4U, cookies_loaded.size()); |
| ASSERT_EQ(true, cookies_loaded.find("www.aaa.com") != cookies_loaded.end()); |
| ASSERT_EQ(true, |
| cookies_loaded.find("travel.aaa.com") != cookies_loaded.end()); |
| |
| db_thread_event_.Signal(); |
| |
| RunUntilIdle(); |
| EXPECT_TRUE(loaded_event_.IsSignaled()); |
| |
| for (CanonicalCookieVector::const_iterator it = cookies_.begin(); |
| it != cookies_.end(); ++it) { |
| cookies_loaded.insert((*it)->Domain().c_str()); |
| } |
| ASSERT_EQ(4U, cookies_loaded.size()); |
| ASSERT_EQ(cookies_loaded.find("foo.bar") != cookies_loaded.end(), true); |
| ASSERT_EQ(cookies_loaded.find("www.bbb.com") != cookies_loaded.end(), true); |
| cookies_.clear(); |
| |
| store_ = nullptr; |
| auto entries = net_log_observer.GetEntries(); |
| size_t pos = ExpectLogContainsSomewhere( |
| entries, 0, NetLogEventType::COOKIE_PERSISTENT_STORE_LOAD, |
| NetLogEventPhase::BEGIN); |
| pos = ExpectLogContainsSomewhere( |
| entries, pos, NetLogEventType::COOKIE_PERSISTENT_STORE_LOAD, |
| NetLogEventPhase::END); |
| pos = ExpectLogContainsSomewhere( |
| entries, 0, NetLogEventType::COOKIE_PERSISTENT_STORE_LOAD, |
| NetLogEventPhase::BEGIN); |
| pos = ExpectLogContainsSomewhere( |
| entries, pos, NetLogEventType::COOKIE_PERSISTENT_STORE_KEY_LOAD_STARTED, |
| NetLogEventPhase::NONE); |
| EXPECT_FALSE(GetOptionalStringValueFromParams(entries[pos], "key")); |
| pos = ExpectLogContainsSomewhere( |
| entries, pos, NetLogEventType::COOKIE_PERSISTENT_STORE_KEY_LOAD_COMPLETED, |
| NetLogEventPhase::NONE); |
| pos = ExpectLogContainsSomewhere( |
| entries, pos, NetLogEventType::COOKIE_PERSISTENT_STORE_LOAD, |
| NetLogEventPhase::END); |
| ExpectLogContainsSomewhere(entries, pos, |
| NetLogEventType::COOKIE_PERSISTENT_STORE_CLOSED, |
| NetLogEventPhase::NONE); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, TestBeforeCommitCallback) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| |
| struct Counter { |
| int count = 0; |
| void increment() { count++; } |
| }; |
| |
| Counter counter; |
| store_->SetBeforeCommitCallback( |
| base::BindRepeating(&Counter::increment, base::Unretained(&counter))); |
| |
| // The implementation of SQLitePersistentCookieStore::Backend flushes changes |
| // after 30s or 512 pending operations. Add 512 cookies to the store to test |
| // that the callback gets called when SQLitePersistentCookieStore internally |
| // flushes its store. |
| for (int i = 0; i < 512; i++) { |
| // Each cookie needs a unique timestamp for creation_utc (see DB schema). |
| base::Time t = base::Time::Now() + base::Microseconds(i); |
| AddCookie(base::StringPrintf("%d", i), "foo", "example.com", "/", t); |
| } |
| |
| RunUntilIdle(); |
| EXPECT_GT(counter.count, 0); |
| |
| DestroyStore(); |
| } |
| |
| // Test that we can force the database to be written by calling Flush(). |
| TEST_F(SQLitePersistentCookieStoreTest, TestFlush) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| // File timestamps don't work well on all platforms, so we'll determine |
| // whether the DB file has been modified by checking its size. |
| base::FilePath path = temp_dir_.GetPath().Append(kCookieFilename); |
| base::File::Info info; |
| ASSERT_TRUE(base::GetFileInfo(path, &info)); |
| int64_t base_size = info.size; |
| |
| // Write some large cookies, so the DB will have to expand by several KB. |
| for (char c = 'a'; c < 'z'; ++c) { |
| // Each cookie needs a unique timestamp for creation_utc (see DB schema). |
| base::Time t = base::Time::Now() + base::Microseconds(c); |
| std::string name(1, c); |
| std::string value(1000, c); |
| AddCookie(name, value, "foo.bar", "/", t); |
| } |
| |
| Flush(); |
| |
| // We forced a write, so now the file will be bigger. |
| ASSERT_TRUE(base::GetFileInfo(path, &info)); |
| ASSERT_GT(info.size, base_size); |
| } |
| |
| // Test loading old session cookies from the disk. |
| TEST_F(SQLitePersistentCookieStoreTest, TestLoadOldSessionCookies) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| |
| // Add a session cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "C", "D", "sessioncookie.com", "/", /*creation=*/base::Time::Now(), |
| /*expiration=*/base::Time(), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Create a store that loads session cookies and test that the session cookie |
| // was loaded. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| |
| ASSERT_EQ(1U, cookies.size()); |
| ASSERT_STREQ("sessioncookie.com", cookies[0]->Domain().c_str()); |
| ASSERT_STREQ("C", cookies[0]->Name().c_str()); |
| ASSERT_STREQ("D", cookies[0]->Value().c_str()); |
| ASSERT_EQ(COOKIE_PRIORITY_DEFAULT, cookies[0]->Priority()); |
| } |
| |
| // Test refusing to load old session cookies from the disk. |
| TEST_F(SQLitePersistentCookieStoreTest, TestDontLoadOldSessionCookies) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| |
| // Add a session cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "C", "D", "sessioncookie.com", "/", /*creation=*/base::Time::Now(), |
| /*expiration=*/base::Time(), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Create a store that doesn't load old session cookies and test that the |
| // session cookie was not loaded. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(0U, cookies.size()); |
| |
| // The store should also delete the session cookie. Wait until that has been |
| // done. |
| DestroyStore(); |
| |
| // Create a store that loads old session cookies and test that the session |
| // cookie is gone. |
| cookies = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/true); |
| ASSERT_EQ(0U, cookies.size()); |
| } |
| |
| // Confirm bad cookies on disk don't get looaded, and that we also remove them |
| // from the database. |
| TEST_F(SQLitePersistentCookieStoreTest, FilterBadCookiesAndFixupDb) { |
| // Create an on-disk store. |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| DestroyStore(); |
| |
| // Add some cookies in by hand. |
| base::FilePath store_name(temp_dir_.GetPath().Append(kCookieFilename)); |
| std::unique_ptr<sql::Database> db( |
| std::make_unique<sql::Database>(sql::test::kTestTag)); |
| ASSERT_TRUE(db->Open(store_name)); |
| sql::Statement stmt(db->GetUniqueStatement( |
| "INSERT INTO cookies (creation_utc, host_key, top_frame_site_key, name, " |
| "value, encrypted_value, path, expires_utc, is_secure, is_httponly, " |
| "samesite, last_access_utc, has_expires, is_persistent, priority, " |
| "source_scheme, source_port, last_update_utc, source_type, " |
| "has_cross_site_ancestor) " |
| "VALUES (?,?,?,?,?,'',?,0,0,0,0,0,1,1,0,?,?,?,0,0)")); |
| ASSERT_TRUE(stmt.is_valid()); |
| |
| struct CookieInfo { |
| const char* domain; |
| const char* name; |
| const char* value; |
| const char* path; |
| } cookies_info[] = {// A couple non-canonical cookies. |
| {"google.izzle", "A=", "B", "/path"}, |
| {"google.izzle", "C ", "D", "/path"}, |
| |
| // A canonical cookie for same eTLD+1. This one will get |
| // dropped out of precaution to avoid confusing the site, |
| // even though there is nothing wrong with it. |
| {"sub.google.izzle", "E", "F", "/path"}, |
| |
| // A canonical cookie for another eTLD+1 |
| {"chromium.org", "G", "H", "/dir"}}; |
| |
| int64_t creation_time = 1; |
| base::Time last_update(base::Time::Now()); |
| for (auto& cookie_info : cookies_info) { |
| stmt.Reset(true); |
| |
| stmt.BindInt64(0, creation_time++); |
| stmt.BindString(1, cookie_info.domain); |
| // TODO(crbug.com/40188414) Test some non-empty values when CanonicalCookie |
| // supports partition key. |
| stmt.BindString(2, net::kEmptyCookiePartitionKey); |
| stmt.BindString(3, cookie_info.name); |
| stmt.BindString(4, cookie_info.value); |
| stmt.BindString(5, cookie_info.path); |
| stmt.BindInt(6, static_cast<int>(CookieSourceScheme::kUnset)); |
| stmt.BindInt(7, SQLitePersistentCookieStore::kDefaultUnknownPort); |
| stmt.BindTime(8, last_update); |
| ASSERT_TRUE(stmt.Run()); |
| } |
| stmt.Clear(); |
| db.reset(); |
| |
| // Reopen the store and confirm that the only cookie loaded is the |
| // canonical one on an unrelated domain. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(1U, cookies.size()); |
| EXPECT_STREQ("chromium.org", cookies[0]->Domain().c_str()); |
| EXPECT_STREQ("G", cookies[0]->Name().c_str()); |
| EXPECT_STREQ("H", cookies[0]->Value().c_str()); |
| EXPECT_STREQ("/dir", cookies[0]->Path().c_str()); |
| EXPECT_EQ(last_update, cookies[0]->LastUpdateDate()); |
| DestroyStore(); |
| |
| // Make sure that we only have one row left. |
| db = std::make_unique<sql::Database>(sql::test::kTestTag); |
| ASSERT_TRUE(db->Open(store_name)); |
| sql::Statement verify_stmt(db->GetUniqueStatement("SELECT * FROM COOKIES")); |
| ASSERT_TRUE(verify_stmt.is_valid()); |
| |
| EXPECT_TRUE(verify_stmt.Step()); |
| EXPECT_TRUE(verify_stmt.Succeeded()); |
| // Confirm only one match. |
| EXPECT_FALSE(verify_stmt.Step()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, PersistIsPersistent) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| static const char kSessionName[] = "session"; |
| static const char kPersistentName[] = "persistent"; |
| |
| // Add a session cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kSessionName, "val", "sessioncookie.com", "/", |
| /*creation=*/base::Time::Now(), |
| /*expiration=*/base::Time(), /*last_access=*/base::Time(), |
| /*last_update=*/base::Time(), /*secure=*/false, /*httponly=*/false, |
| CookieSameSite::NO_RESTRICTION, COOKIE_PRIORITY_DEFAULT)); |
| // Add a persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kPersistentName, "val", "sessioncookie.com", "/", |
| /*creation=*/base::Time::Now() - base::Days(1), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Create a store that loads session cookie and test that the IsPersistent |
| // attribute is restored. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| ASSERT_EQ(2U, cookies.size()); |
| |
| std::map<std::string, CanonicalCookie*> cookie_map; |
| for (const auto& cookie : cookies) |
| cookie_map[cookie->Name()] = cookie.get(); |
| |
| auto it = cookie_map.find(kSessionName); |
| ASSERT_TRUE(it != cookie_map.end()); |
| EXPECT_FALSE(cookie_map[kSessionName]->IsPersistent()); |
| |
| it = cookie_map.find(kPersistentName); |
| ASSERT_TRUE(it != cookie_map.end()); |
| EXPECT_TRUE(cookie_map[kPersistentName]->IsPersistent()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, PriorityIsPersistent) { |
| static const char kDomain[] = "sessioncookie.com"; |
| static const char kLowName[] = "low"; |
| static const char kMediumName[] = "medium"; |
| static const char kHighName[] = "high"; |
| static const char kCookieValue[] = "value"; |
| static const char kCookiePath[] = "/"; |
| |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| |
| // Add a low-priority persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kLowName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(1), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_LOW)); |
| |
| // Add a medium-priority persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kMediumName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(2), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_MEDIUM)); |
| |
| // Add a high-priority persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kHighName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(3), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_HIGH)); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Create a store that loads session cookie and test that the priority |
| // attribute values are restored. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| ASSERT_EQ(3U, cookies.size()); |
| |
| // Put the cookies into a map, by name, so we can easily find them. |
| std::map<std::string, CanonicalCookie*> cookie_map; |
| for (const auto& cookie : cookies) |
| cookie_map[cookie->Name()] = cookie.get(); |
| |
| // Validate that each cookie has the correct priority. |
| auto it = cookie_map.find(kLowName); |
| ASSERT_TRUE(it != cookie_map.end()); |
| EXPECT_EQ(COOKIE_PRIORITY_LOW, cookie_map[kLowName]->Priority()); |
| |
| it = cookie_map.find(kMediumName); |
| ASSERT_TRUE(it != cookie_map.end()); |
| EXPECT_EQ(COOKIE_PRIORITY_MEDIUM, cookie_map[kMediumName]->Priority()); |
| |
| it = cookie_map.find(kHighName); |
| ASSERT_TRUE(it != cookie_map.end()); |
| EXPECT_EQ(COOKIE_PRIORITY_HIGH, cookie_map[kHighName]->Priority()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, SameSiteIsPersistent) { |
| const char kDomain[] = "sessioncookie.com"; |
| const char kNoneName[] = "none"; |
| const char kLaxName[] = "lax"; |
| const char kStrictName[] = "strict"; |
| const char kCookieValue[] = "value"; |
| const char kCookiePath[] = "/"; |
| |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| |
| // Add a non-samesite persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kNoneName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(1), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::NO_RESTRICTION, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Add a lax-samesite persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kLaxName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(2), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::LAX_MODE, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Add a strict-samesite persistent cookie. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kStrictName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(3), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::STRICT_MODE, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Create a store that loads session cookie and test that the SameSite |
| // attribute values are restored. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| ASSERT_EQ(3U, cookies.size()); |
| |
| // Put the cookies into a map, by name, for comparison below. |
| std::map<std::string, CanonicalCookie*> cookie_map; |
| for (const auto& cookie : cookies) |
| cookie_map[cookie->Name()] = cookie.get(); |
| |
| // Validate that each cookie has the correct SameSite. |
| ASSERT_EQ(1u, cookie_map.count(kNoneName)); |
| EXPECT_EQ(CookieSameSite::NO_RESTRICTION, cookie_map[kNoneName]->SameSite()); |
| |
| ASSERT_EQ(1u, cookie_map.count(kLaxName)); |
| EXPECT_EQ(CookieSameSite::LAX_MODE, cookie_map[kLaxName]->SameSite()); |
| |
| ASSERT_EQ(1u, cookie_map.count(kStrictName)); |
| EXPECT_EQ(CookieSameSite::STRICT_MODE, cookie_map[kStrictName]->SameSite()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, SameSiteExtendedTreatedAsUnspecified) { |
| constexpr char kDomain[] = "sessioncookie.com"; |
| constexpr char kExtendedName[] = "extended"; |
| constexpr char kCookieValue[] = "value"; |
| constexpr char kCookiePath[] = "/"; |
| |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| |
| // Add an extended-samesite persistent cookie by first adding a strict-same |
| // site cookie, then turning that into the legacy extended-samesite state with |
| // direct SQL DB access. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| kExtendedName, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(1), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::STRICT_MODE, |
| COOKIE_PRIORITY_DEFAULT)); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Open db. |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(temp_dir_.GetPath().Append(kCookieFilename))); |
| std::string update_stmt( |
| "UPDATE cookies SET samesite=3" // 3 is Extended. |
| " WHERE samesite=2" // 2 is Strict. |
| ); |
| ASSERT_TRUE(connection.Execute(update_stmt)); |
| connection.Close(); |
| |
| // Create a store that loads session cookie and test that the |
| // SameSite=Extended attribute values is ignored. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| ASSERT_EQ(1U, cookies.size()); |
| |
| // Validate that the cookie has the correct SameSite. |
| EXPECT_EQ(kExtendedName, cookies[0]->Name()); |
| EXPECT_EQ(CookieSameSite::UNSPECIFIED, cookies[0]->SameSite()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, SourcePortIsPersistent) { |
| const char kDomain[] = "sessioncookie.com"; |
| const char kCookieValue[] = "value"; |
| const char kCookiePath[] = "/"; |
| |
| struct CookieTestValues { |
| std::string name; |
| int port; |
| }; |
| |
| const std::vector<CookieTestValues> kTestCookies = { |
| {"1", 80}, |
| {"2", 443}, |
| {"3", 1234}, |
| {"4", url::PORT_UNSPECIFIED}, |
| {"5", url::PORT_INVALID}}; |
| |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/true); |
| |
| for (const auto& input : kTestCookies) { |
| // Add some persistent cookies. |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| input.name, kCookieValue, kDomain, kCookiePath, |
| /*creation=*/base::Time::Now() - base::Minutes(1), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time(), /*last_update=*/base::Time(), |
| /*secure=*/true, /*httponly=*/false, CookieSameSite::LAX_MODE, |
| COOKIE_PRIORITY_DEFAULT, |
| /*partition_key=*/std::nullopt, |
| CookieSourceScheme::kUnset /* Doesn't matter for this test. */, |
| input.port)); |
| } |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| // Create a store that loads session cookie and test that the source_port |
| // attribute values are restored. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| ASSERT_EQ(kTestCookies.size(), cookies.size()); |
| |
| // Put the cookies into a map, by name, for comparison below. |
| std::map<std::string, CanonicalCookie*> cookie_map; |
| for (const auto& cookie : cookies) |
| cookie_map[cookie->Name()] = cookie.get(); |
| |
| for (const auto& expected : kTestCookies) { |
| ASSERT_EQ(1u, cookie_map.count(expected.name)); |
| ASSERT_EQ(expected.port, cookie_map[expected.name]->SourcePort()); |
| } |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, UpdateToEncryption) { |
| |
| // Create unencrypted cookie store and write something to it. |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| AddCookie("name", "value123XYZ", "foo.bar", "/", base::Time::Now()); |
| DestroyStore(); |
| |
| // Verify that "value" is visible in the file. This is necessary in order to |
| // have confidence in a later test that "encrypted_value" is not visible. |
| std::string contents = ReadRawDBContents(); |
| EXPECT_NE(0U, contents.length()); |
| EXPECT_NE(contents.find("value123XYZ"), std::string::npos); |
| |
| // Create encrypted cookie store and ensure old cookie still reads. |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/true, /*restore_old_session_cookies=*/false); |
| EXPECT_EQ(1U, cookies.size()); |
| EXPECT_EQ("name", cookies[0]->Name()); |
| EXPECT_EQ("value123XYZ", cookies[0]->Value()); |
| |
| // Make sure we can update existing cookie and add new cookie as encrypted. |
| store_->DeleteCookie(*(cookies[0])); |
| AddCookie("name", "encrypted_value123XYZ", "foo.bar", "/", base::Time::Now()); |
| AddCookie("other", "something456ABC", "foo.bar", "/", |
| base::Time::Now() + base::Microseconds(10)); |
| DestroyStore(); |
| cookies = CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| EXPECT_EQ(2U, cookies.size()); |
| CanonicalCookie* cookie_name = nullptr; |
| CanonicalCookie* cookie_other = nullptr; |
| if (cookies[0]->Name() == "name") { |
| cookie_name = cookies[0].get(); |
| cookie_other = cookies[1].get(); |
| } else { |
| cookie_name = cookies[1].get(); |
| cookie_other = cookies[0].get(); |
| } |
| EXPECT_EQ("encrypted_value123XYZ", cookie_name->Value()); |
| EXPECT_EQ("something456ABC", cookie_other->Value()); |
| DestroyStore(); |
| |
| // Examine the real record to make sure plaintext version doesn't exist. |
| sql::Database db(sql::test::kTestTag); |
| sql::Statement smt; |
| |
| ASSERT_TRUE(db.Open(temp_dir_.GetPath().Append(kCookieFilename))); |
| smt.Assign(db.GetCachedStatement(SQL_FROM_HERE, |
| "SELECT * " |
| "FROM cookies " |
| "WHERE host_key = 'foo.bar'")); |
| int resultcount = 0; |
| for (; smt.Step(); ++resultcount) { |
| for (int i = 0; i < smt.ColumnCount(); i++) { |
| EXPECT_EQ(smt.ColumnString(i).find("value"), std::string::npos); |
| EXPECT_EQ(smt.ColumnString(i).find("something"), std::string::npos); |
| } |
| } |
| EXPECT_EQ(2, resultcount); |
| |
| // Verify that "encrypted_value" is NOT visible in the file. |
| contents = ReadRawDBContents(); |
| EXPECT_NE(0U, contents.length()); |
| EXPECT_EQ(contents.find("encrypted_value123XYZ"), std::string::npos); |
| EXPECT_EQ(contents.find("something456ABC"), std::string::npos); |
| } |
| |
| bool CompareCookies(const std::unique_ptr<CanonicalCookie>& a, |
| const std::unique_ptr<CanonicalCookie>& b) { |
| CHECK(a); |
| CHECK(b); |
| return *a < *b; |
| } |
| |
| // Confirm the store can handle having cookies with identical creation |
| // times stored in it. |
| TEST_F(SQLitePersistentCookieStoreTest, IdenticalCreationTimes) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| base::Time cookie_time(base::Time::Now()); |
| base::Time cookie_expiry(cookie_time + base::Days(1)); |
| AddCookieWithExpiration("A", "B", "example.com", "/", cookie_time, |
| cookie_expiry); |
| AddCookieWithExpiration("C", "B", "example.com", "/", cookie_time, |
| cookie_expiry); |
| AddCookieWithExpiration("A", "B", "example2.com", "/", cookie_time, |
| cookie_expiry); |
| AddCookieWithExpiration("C", "B", "example2.com", "/", cookie_time, |
| cookie_expiry); |
| AddCookieWithExpiration("A", "B", "example.com", "/path", cookie_time, |
| cookie_expiry); |
| AddCookieWithExpiration("C", "B", "example.com", "/path", cookie_time, |
| cookie_expiry); |
| Flush(); |
| DestroyStore(); |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_EQ(6u, read_in_cookies.size()); |
| |
| std::sort(read_in_cookies.begin(), read_in_cookies.end(), &CompareCookies); |
| int i = 0; |
| EXPECT_EQ("A", read_in_cookies[i]->Name()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| |
| i++; |
| EXPECT_EQ("A", read_in_cookies[i]->Name()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/path", read_in_cookies[i]->Path()); |
| |
| i++; |
| EXPECT_EQ("A", read_in_cookies[i]->Name()); |
| EXPECT_EQ("example2.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| |
| i++; |
| EXPECT_EQ("C", read_in_cookies[i]->Name()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| |
| i++; |
| EXPECT_EQ("C", read_in_cookies[i]->Name()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/path", read_in_cookies[i]->Path()); |
| |
| i++; |
| EXPECT_EQ("C", read_in_cookies[i]->Name()); |
| EXPECT_EQ("example2.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, KeyInconsistency) { |
| // Regression testcase for previous disagreement between CookieMonster |
| // and SQLitePersistentCookieStoreTest as to what keys to LoadCookiesForKey |
| // mean. The particular example doesn't, of course, represent an actual in-use |
| // scenario, but while the inconstancy could happen with chrome-extension |
| // URLs in real life, it was irrelevant for them in practice since their |
| // rows would get key = "" which would get sorted before actual domains, |
| // and therefore get loaded first by CookieMonster::FetchAllCookiesIfNecessary |
| // with the task runners involved ensuring that would finish before the |
| // incorrect LoadCookiesForKey got the chance to run. |
| // |
| // This test uses a URL that used to be treated differently by the two |
| // layers that also sorts after other rows to avoid this scenario. |
| |
| // SQLitePersistentCookieStore will run its callbacks on what's passed to it |
| // as |client_task_runner|, and CookieMonster expects to get callbacks from |
| // its PersistentCookieStore on the same thread as its methods are invoked on; |
| // so to avoid needing to post every CookieMonster API call, this uses the |
| // current thread for SQLitePersistentCookieStore's |client_task_runner|. |
| // Note: Cookie encryption is explicitly enabled here to verify threading |
| // model with async initialization functions correctly. |
| Create(/*crypt_cookies=*/true, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, /*enable_exclusive_access=*/false); |
| |
| // Create a cookie on a scheme that doesn't handle cookies by default, |
| // and save it. |
| std::unique_ptr<CookieMonster> cookie_monster = |
| std::make_unique<CookieMonster>(store_.get(), /*net_log=*/nullptr); |
| ResultSavingCookieCallback<bool> cookie_scheme_callback1; |
| cookie_monster->SetCookieableSchemes({"ftp", "http"}, |
| cookie_scheme_callback1.MakeCallback()); |
| cookie_scheme_callback1.WaitUntilDone(); |
| EXPECT_TRUE(cookie_scheme_callback1.result()); |
| ResultSavingCookieCallback<CookieAccessResult> set_cookie_callback; |
| GURL ftp_url("ftp://subdomain.ftperiffic.com/page/"); |
| auto cookie = CanonicalCookie::CreateForTesting(ftp_url, "A=B; max-age=3600", |
| base::Time::Now()); |
| cookie_monster->SetCanonicalCookieAsync(std::move(cookie), ftp_url, |
| CookieOptions::MakeAllInclusive(), |
| set_cookie_callback.MakeCallback()); |
| set_cookie_callback.WaitUntilDone(); |
| EXPECT_TRUE(set_cookie_callback.result().status.IsInclude()); |
| |
| // Also insert a whole bunch of cookies to slow down the background loading of |
| // all the cookies. |
| for (int i = 0; i < 50; ++i) { |
| ResultSavingCookieCallback<CookieAccessResult> set_cookie_callback2; |
| GURL url(base::StringPrintf("http://example%d.com/", i)); |
| auto canonical_cookie = CanonicalCookie::CreateForTesting( |
| url, "A=B; max-age=3600", base::Time::Now()); |
| cookie_monster->SetCanonicalCookieAsync( |
| std::move(canonical_cookie), url, CookieOptions::MakeAllInclusive(), |
| set_cookie_callback2.MakeCallback()); |
| set_cookie_callback2.WaitUntilDone(); |
| EXPECT_TRUE(set_cookie_callback2.result().status.IsInclude()); |
| } |
| |
| net::TestClosure flush_closure; |
| cookie_monster->FlushStore(flush_closure.closure()); |
| flush_closure.WaitForResult(); |
| cookie_monster = nullptr; |
| |
| // Re-create the PersistentCookieStore & CookieMonster. Note that the |
| // destroyed store's ops will happen on same runners as the previous |
| // instances, so they should complete before the new PersistentCookieStore |
| // starts looking at the state on disk. |
| Create(/*crypt_cookies=*/true, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, /*enable_exclusive_access=*/false); |
| cookie_monster = |
| std::make_unique<CookieMonster>(store_.get(), /*net_log=*/nullptr); |
| ResultSavingCookieCallback<bool> cookie_scheme_callback2; |
| cookie_monster->SetCookieableSchemes({"ftp", "http"}, |
| cookie_scheme_callback2.MakeCallback()); |
| cookie_scheme_callback2.WaitUntilDone(); |
| EXPECT_TRUE(cookie_scheme_callback2.result()); |
| |
| // Now try to get the cookie back. |
| GetCookieListCallback get_callback; |
| cookie_monster->GetCookieListWithOptionsAsync( |
| GURL("ftp://subdomain.ftperiffic.com/page"), |
| CookieOptions::MakeAllInclusive(), CookiePartitionKeyCollection(), |
| base::BindOnce(&GetCookieListCallback::Run, |
| base::Unretained(&get_callback))); |
| get_callback.WaitUntilDone(); |
| ASSERT_EQ(1u, get_callback.cookies().size()); |
| EXPECT_EQ("A", get_callback.cookies()[0].Name()); |
| EXPECT_EQ("B", get_callback.cookies()[0].Value()); |
| EXPECT_EQ("subdomain.ftperiffic.com", get_callback.cookies()[0].Domain()); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, OpsIfInitFailed) { |
| // Test to make sure we don't leak pending operations when initialization |
| // fails really hard. To inject the failure, we put a directory where the |
| // database file ought to be. This test relies on an external leak checker |
| // (e.g. lsan) to actual catch thing. |
| ASSERT_TRUE( |
| base::CreateDirectory(temp_dir_.GetPath().Append(kCookieFilename))); |
| Create(/*crypt_cookies=*/false, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, |
| /*enable_exclusive_access=*/false); |
| std::unique_ptr<CookieMonster> cookie_monster = |
| std::make_unique<CookieMonster>(store_.get(), /*net_log=*/nullptr); |
| |
| ResultSavingCookieCallback<CookieAccessResult> set_cookie_callback; |
| GURL url("http://www.example.com/"); |
| auto cookie = CanonicalCookie::CreateForTesting(url, "A=B; max-age=3600", |
| base::Time::Now()); |
| cookie_monster->SetCanonicalCookieAsync(std::move(cookie), url, |
| CookieOptions::MakeAllInclusive(), |
| set_cookie_callback.MakeCallback()); |
| set_cookie_callback.WaitUntilDone(); |
| EXPECT_TRUE(set_cookie_callback.result().status.IsInclude()); |
| |
| // Things should commit once going out of scope. |
| expect_init_errors_ = true; |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, Coalescing) { |
| enum class Op { kAdd, kDelete, kUpdate }; |
| |
| struct TestCase { |
| std::vector<Op> operations; |
| size_t expected_queue_length; |
| }; |
| |
| std::vector<TestCase> testcases = { |
| {{Op::kAdd, Op::kDelete}, 1u}, |
| {{Op::kUpdate, Op::kDelete}, 1u}, |
| {{Op::kAdd, Op::kUpdate, Op::kDelete}, 1u}, |
| {{Op::kUpdate, Op::kUpdate}, 1u}, |
| {{Op::kAdd, Op::kUpdate, Op::kUpdate}, 2u}, |
| {{Op::kDelete, Op::kAdd}, 2u}, |
| {{Op::kDelete, Op::kAdd, Op::kUpdate}, 3u}, |
| {{Op::kDelete, Op::kAdd, Op::kUpdate, Op::kUpdate}, 3u}, |
| {{Op::kDelete, Op::kDelete}, 1u}, |
| {{Op::kDelete, Op::kAdd, Op::kDelete}, 1u}, |
| {{Op::kDelete, Op::kAdd, Op::kUpdate, Op::kDelete}, 1u}}; |
| |
| std::unique_ptr<CanonicalCookie> cookie = CanonicalCookie::CreateForTesting( |
| GURL("http://www.example.com/path"), "Tasty=Yes", base::Time::Now()); |
| |
| for (const TestCase& testcase : testcases) { |
| Create(/*crypt_cookies=*/false, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, |
| /*enable_exclusive_access=*/false); |
| |
| base::RunLoop run_loop; |
| store_->Load(base::BindLambdaForTesting( |
| [&](CanonicalCookieVector cookies) { run_loop.Quit(); }), |
| NetLogWithSource()); |
| run_loop.Run(); |
| |
| // Wedge the background thread to make sure that it doesn't start consuming |
| // the queue. |
| background_task_runner_->PostTask( |
| FROM_HERE, |
| base::BindOnce(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, |
| base::Unretained(this))); |
| |
| // Now run the ops, and check how much gets queued. |
| for (const Op op : testcase.operations) { |
| switch (op) { |
| case Op::kAdd: |
| store_->AddCookie(*cookie); |
| break; |
| |
| case Op::kDelete: |
| store_->DeleteCookie(*cookie); |
| break; |
| |
| case Op::kUpdate: |
| store_->UpdateCookieAccessTime(*cookie); |
| break; |
| } |
| } |
| |
| EXPECT_EQ(testcase.expected_queue_length, |
| store_->GetQueueLengthForTesting()); |
| |
| db_thread_event_.Signal(); |
| DestroyStore(); |
| } |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, NoCoalesceUnrelated) { |
| Create(/*crypt_cookies=*/false, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, |
| /*enable_exclusive_access=*/false); |
| |
| base::RunLoop run_loop; |
| store_->Load(base::BindLambdaForTesting( |
| [&](CanonicalCookieVector cookies) { run_loop.Quit(); }), |
| NetLogWithSource()); |
| run_loop.Run(); |
| |
| std::unique_ptr<CanonicalCookie> cookie1 = CanonicalCookie::CreateForTesting( |
| GURL("http://www.example.com/path"), "Tasty=Yes", base::Time::Now()); |
| |
| std::unique_ptr<CanonicalCookie> cookie2 = CanonicalCookie::CreateForTesting( |
| GURL("http://not.example.com/path"), "Tasty=No", base::Time::Now()); |
| |
| // Wedge the background thread to make sure that it doesn't start consuming |
| // the queue. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, |
| base::Unretained(this))); |
| |
| store_->AddCookie(*cookie1); |
| store_->DeleteCookie(*cookie2); |
| // delete on cookie2 shouldn't cancel op on unrelated cookie1. |
| EXPECT_EQ(2u, store_->GetQueueLengthForTesting()); |
| |
| db_thread_event_.Signal(); |
| } |
| |
| // Locking is only supported on Windows. |
| #if BUILDFLAG(IS_WIN) |
| |
| class SQLitePersistentCookieStoreExclusiveAccessTest |
| : public SQLitePersistentCookieStoreTest, |
| public ::testing::WithParamInterface<bool> { |
| protected: |
| const bool& ShouldBeExclusive() { return GetParam(); } |
| }; |
| |
| TEST_P(SQLitePersistentCookieStoreExclusiveAccessTest, LockedStore) { |
| Create(/*crypt_cookies=*/false, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, |
| /*enable_exclusive_access=*/ShouldBeExclusive()); |
| |
| base::RunLoop run_loop; |
| store_->Load(base::BindLambdaForTesting( |
| [&](CanonicalCookieVector cookies) { run_loop.Quit(); }), |
| NetLogWithSource()); |
| run_loop.Run(); |
| |
| std::unique_ptr<CanonicalCookie> cookie = CanonicalCookie::CreateForTesting( |
| GURL("http://www.example.com/path"), "Tasty=Yes", base::Time::Now()); |
| |
| // Wedge the background thread to make sure that it doesn't start consuming |
| // the queue. |
| background_task_runner_->PostTask( |
| FROM_HERE, base::BindOnce(&SQLitePersistentCookieStoreTest::WaitOnDBEvent, |
| base::Unretained(this))); |
| |
| store_->AddCookie(*cookie); |
| |
| { |
| base::File file( |
| temp_dir_.GetPath().Append(kCookieFilename), |
| base::File::Flags::FLAG_OPEN_ALWAYS | base::File::Flags::FLAG_READ); |
| // If locked, should not be able to open file even for read. |
| EXPECT_EQ(ShouldBeExclusive(), !file.IsValid()); |
| } |
| |
| db_thread_event_.Signal(); |
| } |
| |
| TEST_P(SQLitePersistentCookieStoreExclusiveAccessTest, LockedStoreAlreadyOpen) { |
| base::HistogramTester histograms; |
| base::File file( |
| temp_dir_.GetPath().Append(kCookieFilename), |
| base::File::Flags::FLAG_CREATE | base::File::Flags::FLAG_READ); |
| ASSERT_TRUE(file.IsValid()); |
| |
| Create(/*crypt_cookies=*/false, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, |
| /*enable_exclusive_access=*/ShouldBeExclusive()); |
| |
| base::RunLoop run_loop; |
| store_->Load(base::BindLambdaForTesting( |
| [&](CanonicalCookieVector cookies) { run_loop.Quit(); }), |
| NetLogWithSource()); |
| run_loop.Run(); |
| |
| // Note: The non-exclusive path is verified in the TearDown for the fixture. |
| if (ShouldBeExclusive()) { |
| expect_init_errors_ = true; |
| histograms.ExpectUniqueSample("Cookie.ErrorInitializeDB", |
| sql::SqliteLoggedResultCode::kCantOpen, 1); |
| histograms.ExpectUniqueSample("Cookie.WinGetLastErrorInitializeDB", |
| ERROR_SHARING_VIOLATION, 1); |
| } |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(All, |
| SQLitePersistentCookieStoreExclusiveAccessTest, |
| ::testing::Bool(), |
| [](const auto& info) { |
| return info.param ? "Exclusive" : "NotExclusive"; |
| }); |
| |
| #endif // BUILDFLAG(IS_WIN) |
| |
| TEST_F(SQLitePersistentCookieStoreTest, CorruptStore) { |
| base::HistogramTester histograms; |
| base::WriteFile(temp_dir_.GetPath().Append(kCookieFilename), |
| "SQLite format 3 foobarfoobarfoobar"); |
| |
| Create(/*crypt_cookies=*/false, /*restore_old_session_cookies=*/false, |
| /*use_current_thread=*/true, |
| /*enable_exclusive_access=*/false); |
| |
| base::RunLoop run_loop; |
| store_->Load(base::BindLambdaForTesting( |
| [&](CanonicalCookieVector cookies) { run_loop.Quit(); }), |
| NetLogWithSource()); |
| run_loop.Run(); |
| |
| expect_init_errors_ = true; |
| histograms.ExpectUniqueSample("Cookie.ErrorInitializeDB", |
| sql::SqliteLoggedResultCode::kNotADatabase, 1); |
| } |
| |
| bool CreateV18Schema(sql::Database* db) { |
| sql::MetaTable meta_table; |
| if (!meta_table.Init(db, 18, 18)) { |
| return false; |
| } |
| |
| // Version 18 schema |
| static constexpr char kCreateSql[] = |
| "CREATE TABLE cookies(" |
| "creation_utc INTEGER NOT NULL," |
| "host_key TEXT NOT NULL," |
| "top_frame_site_key TEXT NOT NULL," |
| "name TEXT NOT NULL," |
| "value TEXT NOT NULL," |
| "encrypted_value BLOB NOT NULL," |
| "path TEXT NOT NULL," |
| "expires_utc INTEGER NOT NULL," |
| "is_secure INTEGER NOT NULL," |
| "is_httponly INTEGER NOT NULL," |
| "last_access_utc INTEGER NOT NULL," |
| "has_expires INTEGER NOT NULL," |
| "is_persistent INTEGER NOT NULL," |
| "priority INTEGER NOT NULL," |
| "samesite INTEGER NOT NULL," |
| "source_scheme INTEGER NOT NULL," |
| "source_port INTEGER NOT NULL," |
| "is_same_party INTEGER NOT NULL," |
| "last_update_utc INTEGER NOT NULL," |
| "UNIQUE (host_key, top_frame_site_key, name, path))"; |
| |
| static constexpr char kCreateIndexSql[] = |
| "CREATE UNIQUE INDEX cookies_unique_index " |
| "ON cookies(host_key, top_frame_site_key, name, path)"; |
| |
| return db->Execute(kCreateSql) && db->Execute(kCreateIndexSql); |
| } |
| |
| bool CreateV20Schema(sql::Database* db) { |
| sql::MetaTable meta_table; |
| if (!meta_table.Init(db, 20, 20)) { |
| return false; |
| } |
| |
| // Version 20 schema |
| static constexpr char kCreateSql[] = |
| "CREATE TABLE cookies(" |
| "creation_utc INTEGER NOT NULL," |
| "host_key TEXT NOT NULL," |
| "top_frame_site_key TEXT NOT NULL," |
| "name TEXT NOT NULL," |
| "value TEXT NOT NULL," |
| "encrypted_value BLOB NOT NULL," |
| "path TEXT NOT NULL," |
| "expires_utc INTEGER NOT NULL," |
| "is_secure INTEGER NOT NULL," |
| "is_httponly INTEGER NOT NULL," |
| "last_access_utc INTEGER NOT NULL," |
| "has_expires INTEGER NOT NULL," |
| "is_persistent INTEGER NOT NULL," |
| "priority INTEGER NOT NULL," |
| "samesite INTEGER NOT NULL," |
| "source_scheme INTEGER NOT NULL," |
| "source_port INTEGER NOT NULL," |
| "is_same_party INTEGER NOT NULL," |
| "last_update_utc INTEGER NOT NULL," |
| "UNIQUE (host_key, top_frame_site_key, name, path, source_scheme, " |
| "source_port))"; |
| |
| static constexpr char kCreateIndexSql[] = |
| "CREATE UNIQUE INDEX cookies_unique_index " |
| "ON cookies(host_key, top_frame_site_key, name, path, source_scheme, " |
| "source_port)"; |
| |
| return db->Execute(kCreateSql) && db->Execute(kCreateIndexSql); |
| } |
| |
| bool CreateV21Schema(sql::Database* db) { |
| sql::MetaTable meta_table; |
| if (!meta_table.Init(db, 21, 21)) { |
| return false; |
| } |
| |
| // Version 21 schema |
| static constexpr char kCreateSql[] = |
| "CREATE TABLE cookies(" |
| "creation_utc INTEGER NOT NULL," |
| "host_key TEXT NOT NULL," |
| "top_frame_site_key TEXT NOT NULL," |
| "name TEXT NOT NULL," |
| "value TEXT NOT NULL," |
| "encrypted_value BLOB NOT NULL," |
| "path TEXT NOT NULL," |
| "expires_utc INTEGER NOT NULL," |
| "is_secure INTEGER NOT NULL," |
| "is_httponly INTEGER NOT NULL," |
| "last_access_utc INTEGER NOT NULL," |
| "has_expires INTEGER NOT NULL," |
| "is_persistent INTEGER NOT NULL," |
| "priority INTEGER NOT NULL," |
| "samesite INTEGER NOT NULL," |
| "source_scheme INTEGER NOT NULL," |
| "source_port INTEGER NOT NULL," |
| "last_update_utc INTEGER NOT NULL," |
| "UNIQUE (host_key, top_frame_site_key, name, path, source_scheme, " |
| "source_port))"; |
| |
| static constexpr char kCreateIndexSql[] = |
| "CREATE UNIQUE INDEX cookies_unique_index " |
| "ON cookies(host_key, top_frame_site_key, name, path, source_scheme, " |
| "source_port)"; |
| |
| return db->Execute(kCreateSql) && db->Execute(kCreateIndexSql); |
| } |
| |
| bool CreateV22Schema(sql::Database* db) { |
| sql::MetaTable meta_table; |
| if (!meta_table.Init(db, 22, 22)) { |
| return false; |
| } |
| |
| // Version 22 schema |
| static constexpr char kCreateSql[] = |
| "CREATE TABLE cookies(" |
| "creation_utc INTEGER NOT NULL," |
| "host_key TEXT NOT NULL," |
| "top_frame_site_key TEXT NOT NULL," |
| "name TEXT NOT NULL," |
| "value TEXT NOT NULL," |
| "encrypted_value BLOB NOT NULL," |
| "path TEXT NOT NULL," |
| "expires_utc INTEGER NOT NULL," |
| "is_secure INTEGER NOT NULL," |
| "is_httponly INTEGER NOT NULL," |
| "last_access_utc INTEGER NOT NULL," |
| "has_expires INTEGER NOT NULL," |
| "is_persistent INTEGER NOT NULL," |
| "priority INTEGER NOT NULL," |
| "samesite INTEGER NOT NULL," |
| "source_scheme INTEGER NOT NULL," |
| "source_port INTEGER NOT NULL," |
| "last_update_utc INTEGER NOT NULL," |
| "source_type INTEGER NOT NULL," |
| "UNIQUE (host_key, top_frame_site_key, name, path, source_scheme, " |
| "source_port))"; |
| |
| static constexpr char kCreateIndexSql[] = |
| "CREATE UNIQUE INDEX cookies_unique_index " |
| "ON cookies(host_key, top_frame_site_key, name, path, source_scheme, " |
| "source_port)"; |
| |
| return db->Execute(kCreateSql) && db->Execute(kCreateIndexSql); |
| } |
| |
| bool CreateV23Schema(sql::Database* db) { |
| sql::MetaTable meta_table; |
| if (!meta_table.Init(db, 23, 23)) { |
| return false; |
| } |
| |
| // Version 23 schema |
| static constexpr char kCreateSql[] = |
| "CREATE TABLE cookies(" |
| "creation_utc INTEGER NOT NULL," |
| "host_key TEXT NOT NULL," |
| "top_frame_site_key TEXT NOT NULL," |
| "name TEXT NOT NULL," |
| "value TEXT NOT NULL," |
| "encrypted_value BLOB NOT NULL," |
| "path TEXT NOT NULL," |
| "expires_utc INTEGER NOT NULL," |
| "is_secure INTEGER NOT NULL," |
| "is_httponly INTEGER NOT NULL," |
| "last_access_utc INTEGER NOT NULL," |
| "has_expires INTEGER NOT NULL," |
| "is_persistent INTEGER NOT NULL," |
| "priority INTEGER NOT NULL," |
| "samesite INTEGER NOT NULL," |
| "source_scheme INTEGER NOT NULL," |
| "source_port INTEGER NOT NULL," |
| "last_update_utc INTEGER NOT NULL," |
| "source_type INTEGER NOT NULL," |
| "has_cross_site_ancestor INTEGER NOT NULL);"; |
| |
| static constexpr char kCreateIndexSql[] = |
| "CREATE UNIQUE INDEX cookies_unique_index " |
| "ON cookies(host_key, top_frame_site_key, has_cross_site_ancestor, " |
| "name, path, source_scheme, source_port)"; |
| |
| return db->Execute(kCreateSql) && db->Execute(kCreateIndexSql); |
| } |
| |
| int GetDBCurrentVersionNumber(sql::Database* db) { |
| static constexpr char kGetDBCurrentVersionQuery[] = |
| "SELECT value FROM meta WHERE key='version'"; |
| sql::Statement statement(db->GetUniqueStatement(kGetDBCurrentVersionQuery)); |
| statement.Step(); |
| return statement.ColumnInt(0); |
| } |
| |
| std::vector<CanonicalCookie> CookiesForMigrationTest() { |
| const base::Time now = base::Time::Now(); |
| |
| std::vector<CanonicalCookie> cookies; |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "A", "B", "example.com", "/", /*creation=*/now, /*expiration=*/now, |
| /*last_access=*/now, /*last_update=*/now, /*secure=*/true, |
| /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "C", "B", "example.com", "/", /*creation=*/now, /*expiration=*/now, |
| /*last_access=*/now, /*last_update=*/now, /*secure=*/true, |
| /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "A", "B", "example2.com", "/", /*creation=*/now, /*expiration=*/now, |
| /*last_access=*/now, /*last_update=*/now, /*secure=*/true, |
| /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "C", "B", "example2.com", "/", /*creation=*/now, |
| /*expiration=*/now + base::Days(399), /*last_access=*/now, |
| /*last_update=*/now, |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "A", "B", "example.com", "/path", /*creation=*/now, |
| /*expiration=*/now + base::Days(400), /*last_access=*/now, |
| /*last_update=*/now, |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "C", "B", "example.com", "/path", /*creation=*/now, |
| /*expiration=*/now + base::Days(401), /*last_access=*/now, |
| /*last_update=*/now, |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "D", "", "empty.com", "/", /*creation=*/now, /*expiration=*/now, |
| /*last_access=*/now, /*last_update=*/now, /*secure=*/true, |
| /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT)); |
| return cookies; |
| } |
| |
| // Versions 18, 19, and 20 use the same schema so they can reuse this function. |
| // AddV20CookiesToDB (and future versions) need to set max_expiration_delta to |
| // base::Days(400) to simulate expiration limits introduced in version 19. |
| bool AddV18CookiesToDB(sql::Database* db, |
| base::TimeDelta max_expiration_delta) { |
| std::vector<CanonicalCookie> cookies = CookiesForMigrationTest(); |
| sql::Statement statement(db->GetCachedStatement( |
| SQL_FROM_HERE, |
| "INSERT INTO cookies (creation_utc, top_frame_site_key, host_key, name, " |
| "value, encrypted_value, path, expires_utc, is_secure, is_httponly, " |
| "samesite, last_access_utc, has_expires, is_persistent, priority, " |
| "source_scheme, source_port, is_same_party, last_update_utc) " |
| "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")); |
| if (!statement.is_valid()) { |
| return false; |
| } |
| sql::Transaction transaction(db); |
| if (!transaction.Begin()) { |
| return false; |
| } |
| for (const CanonicalCookie& cookie : cookies) { |
| base::Time max_expiration(cookie.CreationDate() + max_expiration_delta); |
| |
| statement.Reset(true); |
| statement.BindTime(0, cookie.CreationDate()); |
| // TODO (crbug.com/326605834) Once ancestor chain bit changes are |
| // implemented update this method utilize the ancestor bit. |
| base::expected<CookiePartitionKey::SerializedCookiePartitionKey, |
| std::string> |
| serialized_partition_key = |
| CookiePartitionKey::Serialize(cookie.PartitionKey()); |
| EXPECT_TRUE(serialized_partition_key.has_value()); |
| |
| statement.BindString(1, serialized_partition_key->TopLevelSite()); |
| statement.BindString(2, cookie.Domain()); |
| statement.BindString(3, cookie.Name()); |
| statement.BindString(4, cookie.Value()); |
| statement.BindBlob(5, base::span<uint8_t>()); // encrypted_value |
| statement.BindString(6, cookie.Path()); |
| statement.BindTime(7, std::min(cookie.ExpiryDate(), max_expiration)); |
| statement.BindInt(8, cookie.SecureAttribute()); |
| statement.BindInt(9, cookie.IsHttpOnly()); |
| // Note that this, Priority(), and SourceScheme() below nominally rely on |
| // the enums in sqlite_persistent_cookie_store.cc having the same values as |
| // the ones in ../../cookies/cookie_constants.h. But nothing in this test |
| // relies on that equivalence, so it's not worth the hassle to guarantee |
| // that. |
| statement.BindInt(10, static_cast<int>(cookie.SameSite())); |
| statement.BindTime(11, cookie.LastAccessDate()); |
| statement.BindInt(12, cookie.IsPersistent()); |
| statement.BindInt(13, cookie.IsPersistent()); |
| statement.BindInt(14, static_cast<int>(cookie.Priority())); |
| statement.BindInt(15, static_cast<int>(cookie.SourceScheme())); |
| statement.BindInt(16, cookie.SourcePort()); |
| statement.BindInt(17, /*is_same_party=*/false); |
| statement.BindTime(18, cookie.LastUpdateDate()); |
| if (!statement.Run()) { |
| return false; |
| } |
| } |
| return transaction.Commit(); |
| } |
| |
| bool AddV20CookiesToDB(sql::Database* db) { |
| return AddV18CookiesToDB(db, base::Days(400)); |
| } |
| |
| bool AddV21CookiesToDB(sql::Database* db) { |
| std::vector<CanonicalCookie> cookies = CookiesForMigrationTest(); |
| sql::Statement statement(db->GetCachedStatement( |
| SQL_FROM_HERE, |
| "INSERT INTO cookies (creation_utc, top_frame_site_key, host_key, name, " |
| "value, encrypted_value, path, expires_utc, is_secure, is_httponly, " |
| "samesite, last_access_utc, has_expires, is_persistent, priority, " |
| "source_scheme, source_port, last_update_utc) " |
| "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")); |
| if (!statement.is_valid()) { |
| return false; |
| } |
| sql::Transaction transaction(db); |
| if (!transaction.Begin()) { |
| return false; |
| } |
| for (const CanonicalCookie& cookie : cookies) { |
| base::Time max_expiration(cookie.CreationDate() + base::Days(400)); |
| |
| statement.Reset(true); |
| statement.BindTime(0, cookie.CreationDate()); |
| // TODO (crbug.com/326605834) Once ancestor chain bit changes are |
| // implemented update this method utilize the ancestor bit. |
| base::expected<CookiePartitionKey::SerializedCookiePartitionKey, |
| std::string> |
| serialized_partition_key = |
| CookiePartitionKey::Serialize(cookie.PartitionKey()); |
| EXPECT_TRUE(serialized_partition_key.has_value()); |
| |
| statement.BindString(1, serialized_partition_key->TopLevelSite()); |
| statement.BindString(2, cookie.Domain()); |
| statement.BindString(3, cookie.Name()); |
| statement.BindString(4, cookie.Value()); |
| statement.BindBlob(5, base::span<uint8_t>()); // encrypted_value |
| statement.BindString(6, cookie.Path()); |
| statement.BindTime(7, std::min(cookie.ExpiryDate(), max_expiration)); |
| statement.BindInt(8, cookie.SecureAttribute()); |
| statement.BindInt(9, cookie.IsHttpOnly()); |
| // Note that this, Priority(), and SourceScheme() below nominally rely on |
| // the enums in sqlite_persistent_cookie_store.cc having the same values as |
| // the ones in ../../cookies/cookie_constants.h. But nothing in this test |
| // relies on that equivalence, so it's not worth the hassle to guarantee |
| // that. |
| statement.BindInt(10, static_cast<int>(cookie.SameSite())); |
| statement.BindTime(11, cookie.LastAccessDate()); |
| statement.BindInt(12, cookie.IsPersistent()); |
| statement.BindInt(13, cookie.IsPersistent()); |
| statement.BindInt(14, static_cast<int>(cookie.Priority())); |
| statement.BindInt(15, static_cast<int>(cookie.SourceScheme())); |
| statement.BindInt(16, cookie.SourcePort()); |
| statement.BindTime(17, cookie.LastUpdateDate()); |
| if (!statement.Run()) { |
| return false; |
| } |
| } |
| return transaction.Commit(); |
| } |
| |
| bool AddV22CookiesToDB(sql::Database* db, |
| const std::vector<CanonicalCookie>& cookies) { |
| sql::Statement statement(db->GetCachedStatement( |
| SQL_FROM_HERE, |
| "INSERT INTO cookies (creation_utc, top_frame_site_key, host_key, name, " |
| "value, encrypted_value, path, expires_utc, is_secure, is_httponly, " |
| "samesite, last_access_utc, has_expires, is_persistent, priority, " |
| "source_scheme, source_port, last_update_utc, source_type) " |
| "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")); |
| if (!statement.is_valid()) { |
| return false; |
| } |
| sql::Transaction transaction(db); |
| if (!transaction.Begin()) { |
| return false; |
| } |
| for (const CanonicalCookie& cookie : cookies) { |
| base::Time max_expiration(cookie.CreationDate() + base::Days(400)); |
| |
| statement.Reset(true); |
| statement.BindTime(0, cookie.CreationDate()); |
| // TODO (crbug.com/326605834) Once ancestor chain bit changes are |
| // implemented update this method utilize the ancestor bit. |
| base::expected<CookiePartitionKey::SerializedCookiePartitionKey, |
| std::string> |
| serialized_partition_key = |
| CookiePartitionKey::Serialize(cookie.PartitionKey()); |
| EXPECT_TRUE(serialized_partition_key.has_value()); |
| |
| statement.BindString(1, serialized_partition_key->TopLevelSite()); |
| statement.BindString(2, cookie.Domain()); |
| statement.BindString(3, cookie.Name()); |
| statement.BindString(4, cookie.Value()); |
| statement.BindBlob(5, base::span<uint8_t>()); // encrypted_value |
| statement.BindString(6, cookie.Path()); |
| statement.BindTime(7, std::min(cookie.ExpiryDate(), max_expiration)); |
| statement.BindInt(8, cookie.SecureAttribute()); |
| statement.BindInt(9, cookie.IsHttpOnly()); |
| // Note that this, Priority(), and SourceScheme() below nominally rely on |
| // the enums in sqlite_persistent_cookie_store.cc having the same values as |
| // the ones in ../../cookies/cookie_constants.h. But nothing in this test |
| // relies on that equivalence, so it's not worth the hassle to guarantee |
| // that. |
| statement.BindInt(10, static_cast<int>(cookie.SameSite())); |
| statement.BindTime(11, cookie.LastAccessDate()); |
| statement.BindInt(12, cookie.IsPersistent()); |
| statement.BindInt(13, cookie.IsPersistent()); |
| statement.BindInt(14, static_cast<int>(cookie.Priority())); |
| statement.BindInt(15, static_cast<int>(cookie.SourceScheme())); |
| statement.BindInt(16, cookie.SourcePort()); |
| statement.BindTime(17, cookie.LastUpdateDate()); |
| statement.BindInt(18, static_cast<int>(cookie.SourceType())); |
| if (!statement.Run()) { |
| return false; |
| } |
| } |
| return transaction.Commit(); |
| } |
| |
| bool AddV23CookiesToDB(sql::Database* db, |
| const std::vector<CanonicalCookie>& cookies, |
| CookieCryptoDelegate* crypto, |
| bool place_unencrypted_too) { |
| sql::Statement statement(db->GetCachedStatement( |
| SQL_FROM_HERE, |
| "INSERT INTO cookies (creation_utc, host_key, top_frame_site_key, name, " |
| "value, encrypted_value, path, expires_utc, is_secure, is_httponly, " |
| "samesite, last_access_utc, has_expires, is_persistent, priority, " |
| "source_scheme, source_port, last_update_utc, source_type, " |
| "has_cross_site_ancestor) " |
| "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)")); |
| if (!statement.is_valid()) { |
| return false; |
| } |
| sql::Transaction transaction(db); |
| if (!transaction.Begin()) { |
| return false; |
| } |
| for (const CanonicalCookie& cookie : cookies) { |
| base::Time max_expiration(cookie.CreationDate() + base::Days(400)); |
| |
| statement.Reset(true); |
| statement.BindTime(0, cookie.CreationDate()); |
| // TODO (crbug.com/326605834) Once ancestor chain bit changes are |
| // implemented update this method utilize the ancestor bit. |
| base::expected<CookiePartitionKey::SerializedCookiePartitionKey, |
| std::string> |
| serialized_partition_key = |
| CookiePartitionKey::Serialize(cookie.PartitionKey()); |
| EXPECT_TRUE(serialized_partition_key.has_value()); |
| |
| statement.BindString(1, cookie.Domain()); |
| statement.BindString(2, serialized_partition_key->TopLevelSite()); |
| statement.BindString(3, cookie.Name()); |
| if (crypto) { |
| statement.BindString( |
| 4, place_unencrypted_too |
| ? cookie.Value() |
| : ""); // value is encrypted. If `place_unencrypted_too` is |
| // set then place it here too, to test bad databases. |
| std::string encrypted_value; |
| // v23 and below simply encrypted the cookie and stored it in this value. |
| EXPECT_TRUE(crypto->EncryptString(cookie.Value(), &encrypted_value)); |
| statement.BindBlob(5, encrypted_value); // encrypted_value. |
| } else { |
| statement.BindString(4, cookie.Value()); |
| statement.BindBlob(5, base::span<uint8_t>()); // encrypted_value empty. |
| } |
| statement.BindString(6, cookie.Path()); |
| statement.BindTime(7, std::min(cookie.ExpiryDate(), max_expiration)); |
| statement.BindInt(8, cookie.SecureAttribute()); |
| statement.BindInt(9, cookie.IsHttpOnly()); |
| // Note that this, Priority(), and SourceScheme() below nominally rely on |
| // the enums in sqlite_persistent_cookie_store.cc having the same values as |
| // the ones in ../../cookies/cookie_constants.h. But nothing in this test |
| // relies on that equivalence, so it's not worth the hassle to guarantee |
| // that. |
| statement.BindInt(10, static_cast<int>(cookie.SameSite())); |
| statement.BindTime(11, cookie.LastAccessDate()); |
| statement.BindInt(12, cookie.IsPersistent()); |
| statement.BindInt(13, cookie.IsPersistent()); |
| statement.BindInt(14, static_cast<int>(cookie.Priority())); |
| |
| // Version 23 updated any preexisting cookies with a source_scheme value of |
| // kUnset and a is_secure of true to have a source_scheme value of kSecure. |
| // This situation can occur with the test cookies, so update the data to |
| // reflect a v23 cookie store. |
| auto source_scheme = cookie.SourceScheme(); |
| if (cookie.SourceScheme() == CookieSourceScheme::kUnset && |
| cookie.IsSecure()) { |
| source_scheme = CookieSourceScheme::kSecure; |
| } |
| statement.BindInt(15, static_cast<int>(source_scheme)); |
| statement.BindInt(16, cookie.SourcePort()); |
| statement.BindTime(17, cookie.LastUpdateDate()); |
| statement.BindInt(18, static_cast<int>(cookie.SourceType())); |
| statement.BindBool(19, serialized_partition_key->has_cross_site_ancestor()); |
| |
| if (!statement.Run()) { |
| return false; |
| } |
| } |
| return transaction.Commit(); |
| } |
| |
| // Confirm the cookie list passed in has the above cookies in it. |
| void ConfirmCookiesAfterMigrationTest( |
| std::vector<std::unique_ptr<CanonicalCookie>> read_in_cookies, |
| bool expect_last_update_date = false) { |
| ASSERT_EQ(read_in_cookies.size(), 7u); |
| |
| std::sort(read_in_cookies.begin(), read_in_cookies.end(), &CompareCookies); |
| int i = 0; |
| EXPECT_EQ("A", read_in_cookies[i]->Name()); |
| EXPECT_EQ("B", read_in_cookies[i]->Value()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| EXPECT_TRUE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kSecure, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| EXPECT_EQ(read_in_cookies[i]->ExpiryDate(), |
| read_in_cookies[i]->CreationDate()); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| |
| i++; |
| EXPECT_EQ("A", read_in_cookies[i]->Name()); |
| EXPECT_EQ("B", read_in_cookies[i]->Value()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/path", read_in_cookies[i]->Path()); |
| EXPECT_FALSE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kUnset, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| EXPECT_EQ(read_in_cookies[i]->ExpiryDate(), |
| read_in_cookies[i]->CreationDate() + base::Days(400)); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| |
| i++; |
| EXPECT_EQ("A", read_in_cookies[i]->Name()); |
| EXPECT_EQ("B", read_in_cookies[i]->Value()); |
| EXPECT_EQ("example2.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| EXPECT_TRUE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kSecure, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| EXPECT_EQ(read_in_cookies[i]->ExpiryDate(), |
| read_in_cookies[i]->CreationDate()); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| |
| i++; |
| EXPECT_EQ("C", read_in_cookies[i]->Name()); |
| EXPECT_EQ("B", read_in_cookies[i]->Value()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| EXPECT_TRUE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kSecure, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| EXPECT_EQ(read_in_cookies[i]->ExpiryDate(), |
| read_in_cookies[i]->CreationDate()); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| |
| i++; |
| EXPECT_EQ("C", read_in_cookies[i]->Name()); |
| EXPECT_EQ("B", read_in_cookies[i]->Value()); |
| EXPECT_EQ("example.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/path", read_in_cookies[i]->Path()); |
| EXPECT_FALSE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kUnset, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| // The exact time will be within the last minute due to the cap. |
| EXPECT_LE(read_in_cookies[i]->ExpiryDate(), |
| base::Time::Now() + base::Days(400)); |
| EXPECT_GE(read_in_cookies[i]->ExpiryDate(), |
| base::Time::Now() + base::Days(400) - base::Minutes(1)); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| |
| i++; |
| EXPECT_EQ("C", read_in_cookies[i]->Name()); |
| EXPECT_EQ("B", read_in_cookies[i]->Value()); |
| EXPECT_EQ("example2.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| EXPECT_FALSE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kUnset, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| EXPECT_EQ(read_in_cookies[i]->ExpiryDate(), |
| read_in_cookies[i]->CreationDate() + base::Days(399)); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| |
| i++; |
| EXPECT_EQ("D", read_in_cookies[i]->Name()); |
| EXPECT_EQ("", read_in_cookies[i]->Value()); |
| EXPECT_EQ("empty.com", read_in_cookies[i]->Domain()); |
| EXPECT_EQ("/", read_in_cookies[i]->Path()); |
| EXPECT_TRUE(read_in_cookies[i]->SecureAttribute()); |
| EXPECT_EQ(CookieSourceScheme::kSecure, read_in_cookies[i]->SourceScheme()); |
| EXPECT_EQ(read_in_cookies[i]->LastUpdateDate(), |
| expect_last_update_date ? read_in_cookies[i]->CreationDate() |
| : base::Time()); |
| EXPECT_EQ(read_in_cookies[i]->ExpiryDate(), |
| read_in_cookies[i]->CreationDate()); |
| EXPECT_EQ(read_in_cookies[i]->SourceType(), CookieSourceType::kUnknown); |
| } |
| |
| void ConfirmDatabaseVersionAfterMigration(const base::FilePath path, |
| int version) { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(path)); |
| ASSERT_GE(GetDBCurrentVersionNumber(&connection), version); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion19) { |
| // Open db. |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| ASSERT_TRUE(CreateV18Schema(&connection)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 18); |
| ASSERT_TRUE(AddV18CookiesToDB(&connection, base::TimeDelta::Max())); |
| } |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), |
| /*expect_last_update_date=*/true)); |
| DestroyStore(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmDatabaseVersionAfterMigration(database_path, 19)); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion20) { |
| // Open db. |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| // V19's schema is the same as V18, so we can reuse the creation function. |
| ASSERT_TRUE(CreateV18Schema(&connection)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 18); |
| ASSERT_TRUE(AddV18CookiesToDB(&connection, base::TimeDelta::Max())); |
| } |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), |
| /*expect_last_update_date=*/true)); |
| DestroyStore(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmDatabaseVersionAfterMigration(database_path, 20)); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion21) { |
| // Open db. |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| ASSERT_TRUE(CreateV20Schema(&connection)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 20); |
| ASSERT_TRUE(AddV20CookiesToDB(&connection)); |
| } |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), |
| /*expect_last_update_date=*/true)); |
| DestroyStore(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmDatabaseVersionAfterMigration(database_path, 21)); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion22) { |
| // Open db. |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| ASSERT_TRUE(CreateV21Schema(&connection)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 21); |
| ASSERT_TRUE(AddV21CookiesToDB(&connection)); |
| } |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), |
| /*expect_last_update_date=*/true)); |
| DestroyStore(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmDatabaseVersionAfterMigration(database_path, 22)); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, UpgradeToSchemaVersion23) { |
| // Open db. |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| ASSERT_TRUE(CreateV22Schema(&connection)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 22); |
| ASSERT_TRUE(AddV22CookiesToDB(&connection, CookiesForMigrationTest())); |
| } |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), |
| /*expect_last_update_date=*/true)); |
| DestroyStore(); |
| |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmDatabaseVersionAfterMigration(database_path, 23)); |
| } |
| |
| class SQLitePersistentCookieStorev24UpgradeTest |
| : public SQLitePersistentCookieStoreTest, |
| public ::testing::WithParamInterface< |
| std::tuple</*crypto_for_encrypt*/ bool, |
| /*crypto_for_decrypt*/ bool, |
| /*place_unencrypted_too*/ bool, |
| /*kEncryptedAndPlaintextValuesAreInvalid*/ bool>> { |
| protected: |
| void SetUp() override { |
| features_.InitWithFeatureState( |
| features::kEncryptedAndPlaintextValuesAreInvalid, |
| std::get<3>(GetParam())); |
| SQLitePersistentCookieStoreTest::SetUp(); |
| } |
| |
| private: |
| base::test::ScopedFeatureList features_; |
| }; |
| |
| TEST_P(SQLitePersistentCookieStorev24UpgradeTest, UpgradeToSchemaVersion24) { |
| const bool crypto_for_encrypt = std::get<0>(GetParam()); |
| const bool crypto_for_decrypt = std::get<1>(GetParam()); |
| const bool place_unencrypted_too = std::get<2>(GetParam()); |
| const bool drop_dup_values = std::get<3>(GetParam()); |
| |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| { |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| ASSERT_TRUE(CreateV23Schema(&connection)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&connection), 23); |
| auto cryptor = std::make_unique<CookieCryptor>(); |
| |
| ASSERT_TRUE(AddV23CookiesToDB(&connection, CookiesForMigrationTest(), |
| crypto_for_encrypt ? cryptor.get() : nullptr, |
| place_unencrypted_too)); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/crypto_for_decrypt, |
| /*restore_old_session_cookies=*/false); |
| |
| // If encryption is enabled for encrypt and not available for decrypt, then |
| // most cookies will be gone, as the data is encrypted with no way to |
| // decrypt. |
| if (crypto_for_encrypt && !crypto_for_decrypt) { |
| // Subtle: The empty cookie for empty.com will not trigger a cookie load |
| // failure. This is because during the migration there is no crypto so no |
| // migration occurs for any cookie, including the empty one. Then when |
| // attempting to load a v24 store the cookie with an empty value and empty |
| // encrypted value will simply load empty. |
| EXPECT_EQ(read_in_cookies.size(), 1u); |
| // The case of plaintext and encrypted values is always checked when |
| // loading a cookie before the availability of crypto. This means the |
| // error code here depends on whether migration from v23 to v24 was done |
| // with crypto available or not. In this case, crypto was not available |
| // during migration so the values were left alone - meaning that if there |
| // are both plaintext and encrypted values the |
| // kValuesExistInBothEncryptedAndPlaintext error is returned. However, if |
| // this cookie does not have both plaintext and encrypted values, then the |
| // second check is hit which reports encrypted data that cannot be |
| // decrypted - kNoCrypto. Functionality for an already-migrated store (v24 |
| // and above) with both plaintext and encrypted values is tested in the |
| // `OverridePlaintextValue` test below. |
| const base::Histogram::Sample32 expected_bucket = |
| drop_dup_values && place_unencrypted_too |
| ? /*CookieLoadProblem::kValuesExistInBothEncryptedAndPlaintext*/ 8 |
| : /*CookieLoadProblem::kNoCrypto*/ 7; |
| histogram_tester.ExpectBucketCount("Cookie.LoadProblem", expected_bucket, |
| CookiesForMigrationTest().size() - 1); |
| } else { |
| histogram_tester.ExpectTotalCount("Cookie.LoadProblem", 0); |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmCookiesAfterMigrationTest(std::move(read_in_cookies), |
| /*expect_last_update_date=*/true)); |
| } |
| DestroyStore(); |
| } |
| |
| ASSERT_NO_FATAL_FAILURE( |
| ConfirmDatabaseVersionAfterMigration(database_path, 24)); |
| } |
| |
| INSTANTIATE_TEST_SUITE_P(, |
| SQLitePersistentCookieStorev24UpgradeTest, |
| ::testing::Combine(::testing::Bool(), |
| ::testing::Bool(), |
| ::testing::Bool(), |
| ::testing::Bool())); |
| |
| TEST_F(SQLitePersistentCookieStoreTest, CannotModifyHostName) { |
| { |
| CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| AddCookie("A", "B", "sensitive.com", "/", base::Time::Now()); |
| AddCookie("A", "B", "example.com", "/", base::Time::Now()); |
| DestroyStore(); |
| } |
| { |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| // Simulate an attacker modifying hostname to attacker controlled, to |
| // perform a cookie replay attack. |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| sql::Transaction transaction(&connection); |
| ASSERT_TRUE(transaction.Begin()); |
| ASSERT_TRUE( |
| connection.Execute("UPDATE cookies SET host_key='attacker.com' WHERE " |
| "host_key='sensitive.com'")); |
| ASSERT_TRUE(transaction.Commit()); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| auto cookies = CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| // Modified cookie should not load. |
| ASSERT_EQ(cookies.size(), 1u); |
| ASSERT_EQ(cookies[0]->Domain(), "example.com"); |
| ASSERT_EQ(cookies[0]->Name(), "A"); |
| ASSERT_EQ(cookies[0]->Value(), "B"); |
| DestroyStore(); |
| histogram_tester.ExpectBucketCount("Cookie.LoadProblem", |
| /*CookieLoadProblem::kHashFailed*/ 6, 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| auto cookies = CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| // Modified cookie should not load. |
| ASSERT_EQ(cookies.size(), 1u); |
| ASSERT_EQ(cookies[0]->Domain(), "example.com"); |
| ASSERT_EQ(cookies[0]->Name(), "A"); |
| ASSERT_EQ(cookies[0]->Value(), "B"); |
| DestroyStore(); |
| // The hash failure should only appear once, during the first read, as the |
| // invalid cookie gets deleted afterwards. |
| histogram_tester.ExpectTotalCount("Cookie.LoadProblem", 0); |
| } |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, ShortHash) { |
| { |
| CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| AddCookie("A", "B", "sensitive.com", "/", base::Time::Now()); |
| AddCookie("A", "B", "example.com", "/", base::Time::Now()); |
| DestroyStore(); |
| } |
| { |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| // Simulate an attacker modifying hostname to attacker controlled, to |
| // perform a cookie replay attack. |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| sql::Transaction transaction(&connection); |
| sql::Statement set_encrypted_value(connection.GetUniqueStatement( |
| "UPDATE cookies SET encrypted_value=? WHERE host_key='sensitive.com'")); |
| CookieCryptor crypto; |
| // Short string, without a hash, but valid encryption. This verifies that |
| // the decryption code handles short-length encrypted data fine. |
| std::string encrypted_data; |
| crypto.EncryptString("a", &encrypted_data); |
| set_encrypted_value.BindBlob(0, encrypted_data); |
| |
| ASSERT_TRUE(transaction.Begin()); |
| ASSERT_TRUE(set_encrypted_value.Run()); |
| ASSERT_TRUE(transaction.Commit()); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| auto cookies = CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| // Modified cookie should not load. |
| ASSERT_EQ(cookies.size(), 1u); |
| ASSERT_EQ(cookies[0]->Domain(), "example.com"); |
| ASSERT_EQ(cookies[0]->Name(), "A"); |
| ASSERT_EQ(cookies[0]->Value(), "B"); |
| DestroyStore(); |
| histogram_tester.ExpectBucketCount("Cookie.LoadProblem", |
| /*CookieLoadProblem::kHashFailed*/ 6, 1); |
| } |
| { |
| base::HistogramTester histogram_tester; |
| auto cookies = CreateAndLoad(/*crypt_cookies=*/true, |
| /*restore_old_session_cookies=*/false); |
| // Modified cookie should not load. |
| ASSERT_EQ(cookies.size(), 1u); |
| ASSERT_EQ(cookies[0]->Domain(), "example.com"); |
| ASSERT_EQ(cookies[0]->Name(), "A"); |
| ASSERT_EQ(cookies[0]->Value(), "B"); |
| DestroyStore(); |
| // The hash failure should only appear once, during the first read, as the |
| // invalid cookie gets deleted afterwards. |
| histogram_tester.ExpectTotalCount("Cookie.LoadProblem", 0); |
| } |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, |
| UpgradeToSchemaVersion23_ConfirmSourceSchemeRecalculation) { |
| const base::FilePath database_path = |
| temp_dir_.GetPath().Append(kCookieFilename); |
| const base::Time now = base::Time::Now(); |
| std::vector<CanonicalCookie> cookies; |
| |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "secure_true", "A", "example.com", "/", now, now, now, now, |
| /*secure=*/true, /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT, std::optional<CookiePartitionKey>(), |
| CookieSourceScheme::kUnset)); |
| |
| cookies.push_back(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "secure_false", "B", "example.com", "/", now, now, now, now, |
| /*secure=*/false, /*httponly=*/false, CookieSameSite::UNSPECIFIED, |
| COOKIE_PRIORITY_DEFAULT, std::optional<CookiePartitionKey>(), |
| CookieSourceScheme::kUnset)); |
| |
| // Open database, populate and close db. |
| { |
| sql::Database db(sql::test::kTestTag); |
| ASSERT_TRUE(db.Open(database_path)); |
| ASSERT_TRUE(CreateV22Schema(&db)); |
| ASSERT_EQ(GetDBCurrentVersionNumber(&db), 22); |
| ASSERT_TRUE(AddV22CookiesToDB(&db, cookies)); |
| } |
| |
| CanonicalCookieVector read_in_cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/true); |
| |
| EXPECT_EQ(read_in_cookies.size(), cookies.size()); |
| |
| // Reopen database for testing. |
| sql::Database connection(sql::test::kTestTag); |
| ASSERT_TRUE(connection.Open(database_path)); |
| ASSERT_GE(GetDBCurrentVersionNumber(&connection), 23); |
| for (const auto& cookie : cookies) { |
| sql::Statement verify_stmt(connection.GetUniqueStatement( |
| "SELECT source_scheme FROM cookies WHERE is_secure=?")); |
| |
| verify_stmt.BindBool(0, cookie.SecureAttribute()); |
| |
| ASSERT_TRUE(verify_stmt.is_valid()); |
| |
| EXPECT_TRUE(verify_stmt.Step()); |
| EXPECT_EQ( |
| static_cast<int>(cookie.SecureAttribute() ? CookieSourceScheme::kSecure |
| : CookieSourceScheme::kUnset), |
| verify_stmt.ColumnInt(0)); |
| // Confirm that exactly one cookie matches the SQL query |
| EXPECT_FALSE(verify_stmt.Step()); |
| } |
| } |
| |
| class SQLitePersistentCookieStoreTest_OriginBoundCookies |
| : public SQLitePersistentCookieStoreTest { |
| public: |
| // Creates and stores 4 cookies that differ only by scheme and/or port. When |
| // this function returns, the store will be created and all the cookies loaded |
| // into cookies_. |
| void InitializeTest() { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| |
| basic_cookie_ = CanonicalCookie::CreateForTesting( |
| basic_url_, "a=b; max-age=100000", /*creation_time=*/base::Time::Now()); |
| |
| http_cookie_ = std::make_unique<CanonicalCookie>(*basic_cookie_); |
| http_cookie_->SetSourceScheme(CookieSourceScheme::kNonSecure); |
| |
| port_444_cookie_ = std::make_unique<CanonicalCookie>(*basic_cookie_); |
| port_444_cookie_->SetSourcePort(444); |
| |
| http_444_cookie_ = std::make_unique<CanonicalCookie>(*basic_cookie_); |
| http_444_cookie_->SetSourceScheme(CookieSourceScheme::kNonSecure); |
| http_444_cookie_->SetSourcePort(444); |
| |
| store_->AddCookie(*basic_cookie_); |
| store_->AddCookie(*http_cookie_); |
| store_->AddCookie(*port_444_cookie_); |
| store_->AddCookie(*http_444_cookie_); |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| cookies_ = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| |
| EXPECT_EQ(cookies_.size(), 4UL); |
| } |
| |
| GURL basic_url_ = GURL("https://example.com"); |
| std::unique_ptr<net::CanonicalCookie> basic_cookie_; |
| std::unique_ptr<net::CanonicalCookie> http_cookie_; |
| std::unique_ptr<net::CanonicalCookie> port_444_cookie_; |
| std::unique_ptr<net::CanonicalCookie> http_444_cookie_; |
| |
| CanonicalCookieVector cookies_; |
| }; |
| |
| // Tests that cookies which differ only in their scheme and port are considered |
| // distinct. |
| TEST_F(SQLitePersistentCookieStoreTest_OriginBoundCookies, |
| UniquenessConstraint) { |
| InitializeTest(); |
| |
| // Try to add another cookie that is the same as basic_cookie_ except that its |
| // value is different. Value isn't considered as part of the unique constraint |
| // and so this cookie won't be considered unique and should fail to be added. |
| auto basic_cookie2 = |
| CanonicalCookie::CreateForTesting(basic_url_, "a=b2; max-age=100000", |
| /*creation_time=*/base::Time::Now()); |
| |
| store_->AddCookie(*basic_cookie2); |
| |
| // Force the store to write its data to the disk. |
| DestroyStore(); |
| |
| cookies_.clear(); |
| cookies_ = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| |
| // Confirm that basic_cookie2 failed to be added. |
| EXPECT_THAT(cookies_, testing::UnorderedElementsAre( |
| MatchesEveryCookieField(*basic_cookie_), |
| MatchesEveryCookieField(*http_cookie_), |
| MatchesEveryCookieField(*port_444_cookie_), |
| MatchesEveryCookieField(*http_444_cookie_))); |
| } |
| |
| // Tests that deleting a cookie correctly takes the scheme and port into |
| // account. |
| TEST_F(SQLitePersistentCookieStoreTest_OriginBoundCookies, DeleteCookie) { |
| InitializeTest(); |
| |
| // Try to delete just one of the cookies. |
| store_->DeleteCookie(*http_444_cookie_); |
| DestroyStore(); |
| cookies_.clear(); |
| |
| cookies_ = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| |
| // Only the single cookie should be deleted. |
| EXPECT_THAT(cookies_, testing::UnorderedElementsAre( |
| MatchesEveryCookieField(*basic_cookie_), |
| MatchesEveryCookieField(*http_cookie_), |
| MatchesEveryCookieField(*port_444_cookie_))); |
| } |
| |
| // Tests that updating a cookie correctly takes the scheme and port into |
| // account. |
| TEST_F(SQLitePersistentCookieStoreTest_OriginBoundCookies, |
| UpdateCookieAccessTime) { |
| InitializeTest(); |
| |
| base::Time basic_last_access = basic_cookie_->LastAccessDate(); |
| base::Time http_last_access = http_cookie_->LastAccessDate(); |
| base::Time port_444_last_access = port_444_cookie_->LastAccessDate(); |
| base::Time http_444_last_access = http_444_cookie_->LastAccessDate(); |
| |
| base::Time new_last_access = http_444_last_access + base::Hours(1); |
| http_444_cookie_->SetLastAccessDate(new_last_access); |
| |
| store_->UpdateCookieAccessTime(*http_444_cookie_); |
| DestroyStore(); |
| cookies_.clear(); |
| |
| cookies_ = CreateAndLoad(/*crypt_cookies=*/false, |
| /*restore_old_session_cookies=*/false); |
| |
| // All loaded cookies' should have their original LastAccessDate() except for |
| // the one updated to new_last_access. |
| EXPECT_THAT( |
| cookies_, |
| testing::UnorderedElementsAre( |
| MatchesCookieKeyAndLastAccessDate(basic_cookie_->StrictlyUniqueKey(), |
| basic_last_access), |
| MatchesCookieKeyAndLastAccessDate(http_cookie_->StrictlyUniqueKey(), |
| http_last_access), |
| MatchesCookieKeyAndLastAccessDate( |
| port_444_cookie_->StrictlyUniqueKey(), port_444_last_access), |
| MatchesCookieKeyAndLastAccessDate( |
| http_444_cookie_->StrictlyUniqueKey(), new_last_access))); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, SavingPartitionedCookies) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| |
| store_->AddCookie(*CanonicalCookie::CreateUnsafeCookieForTesting( |
| "__Host-foo", "bar", GURL("https://example.com/").host(), "/", |
| /*creation=*/base::Time::Now(), |
| /*expiration=*/base::Time::Now() + base::Days(1), |
| /*last_access=*/base::Time::Now(), |
| /*last_update=*/base::Time::Now(), /*secure=*/true, /*httponly=*/false, |
| CookieSameSite::UNSPECIFIED, COOKIE_PRIORITY_DEFAULT, |
| CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")))); |
| Flush(); |
| |
| std::string got_db_content(ReadRawDBContents()); |
| EXPECT_NE(got_db_content.find("__Host-foo"), std::string::npos); |
| |
| DestroyStore(); |
| } |
| |
| TEST_F(SQLitePersistentCookieStoreTest, LoadingPartitionedCookies) { |
| InitializeStore(/*crypt=*/false, /*restore_old_session_cookies=*/false); |
| DestroyStore(); |
| |
| // Insert a partitioned cookie into the database manually. |
| base::FilePath store_name(temp_dir_.GetPath().Append(kCookieFilename)); |
| std::unique_ptr<sql::Database> db( |
| std::make_unique<sql::Database>(sql::test::kTestTag)); |
| ASSERT_TRUE(db->Open(store_name)); |
| |
| sql::Statement stmt(db->GetUniqueStatement( |
| "INSERT INTO cookies (creation_utc, host_key, top_frame_site_key, name, " |
| "value, encrypted_value, path, expires_utc, is_secure, is_httponly, " |
| "samesite, last_access_utc, has_expires, is_persistent, priority, " |
| "source_scheme, source_port, last_update_utc, source_type, " |
| "has_cross_site_ancestor) " |
| "VALUES (?,?,?,?,?,'',?,?,1,0,0,?,1,1,0,?,?,?,0, 1)")); |
| ASSERT_TRUE(stmt.is_valid()); |
| |
| base::Time creation(base::Time::Now()); |
| base::Time expiration(creation + base::Days(1)); |
| base::Time last_access(base::Time::Now()); |
| base::Time last_update(base::Time::Now()); |
| |
| stmt.BindTime(0, creation); |
| stmt.BindString(1, GURL("https://www.example.com/").host()); |
| stmt.BindString(2, "https://toplevelsite.com"); |
| stmt.BindString(3, "__Host-foo"); |
| stmt.BindString(4, "bar"); |
| stmt.BindString(5, "/"); |
| stmt.BindTime(6, expiration); |
| stmt.BindTime(7, last_access); |
| stmt.BindInt(8, static_cast<int>(CookieSourceScheme::kUnset)); |
| stmt.BindInt(9, SQLitePersistentCookieStore::kDefaultUnknownPort); |
| stmt.BindTime(10, last_update); |
| ASSERT_TRUE(stmt.Run()); |
| stmt.Clear(); |
| db.reset(); |
| |
| CanonicalCookieVector cookies = CreateAndLoad( |
| /*crypt_cookies=*/false, /*restore_old_session_cookies=*/false); |
| |
| EXPECT_EQ(1u, cookies.size()); |
| auto cc = std::move(cookies[0]); |
| EXPECT_EQ("__Host-foo", cc->Name()); |
| EXPECT_EQ("bar", cc->Value()); |
| EXPECT_EQ(GURL("https://www.example.com/").host(), cc->Domain()); |
| EXPECT_TRUE(cc->IsPartitioned()); |
| EXPECT_EQ( |
| CookiePartitionKey::FromURLForTesting(GURL("https://toplevelsite.com")), |
| cc->PartitionKey()); |
| EXPECT_EQ(last_update, cc->LastUpdateDate()); |
| } |
| |
| std::unique_ptr<CanonicalCookie> CreatePartitionedCookie( |
| const std::string& name, |
| const std::string& domain, |
| const std::string& top_frame_site_key, |
| CookiePartitionKey::AncestorChainBit ancestor_chain_bit, |
| CookieSourceScheme scheme = CookieSourceScheme::kUnset, |
| bool partitioned_cookies_enabled = true) { |
| const base::Time now = base::Time::Now(); |
| |
| return CanonicalCookie::CreateUnsafeCookieForTesting( |
| name, "B", domain, "/", now, now, now, now, /*secure=*/true, |
| /*httponly=*/false, CookieSameSite::UNSPECIFIED, COOKIE_PRIORITY_DEFAULT, |
| partitioned_cookies_enabled |
|