blob: cc012200deef8b6c3d032999a3f8ac3e9a909106 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <map>
#include <set>
#include "base/bind.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/message_loop.h"
#include "base/scoped_temp_dir.h"
#include "base/stl_util.h"
#include "base/synchronization/waitable_event.h"
#include "base/test/thread_test_helper.h"
#include "base/time.h"
#include "chrome/browser/net/clear_on_exit_policy.h"
#include "chrome/browser/net/sqlite_persistent_cookie_store.h"
#include "chrome/common/chrome_constants.h"
#include "content/public/test/test_browser_thread.h"
#include "googleurl/src/gurl.h"
#include "net/cookies/canonical_cookie.h"
#include "sql/connection.h"
#include "sql/meta_table.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "webkit/quota/mock_special_storage_policy.h"
using content::BrowserThread;
typedef std::vector<net::CanonicalCookie*> CanonicalCookieVector;
class SQLitePersistentCookieStoreTest : public testing::Test {
public:
SQLitePersistentCookieStoreTest()
: ui_thread_(BrowserThread::UI),
db_thread_(BrowserThread::DB),
io_thread_(BrowserThread::IO),
loaded_event_(false, false),
key_loaded_event_(false, false),
db_thread_event_(false, false) {
}
void OnLoaded(const CanonicalCookieVector& cookies) {
cookies_ = cookies;
loaded_event_.Signal();
}
void OnKeyLoaded(const CanonicalCookieVector& cookies) {
cookies_ = cookies;
key_loaded_event_.Signal();
}
void Load(CanonicalCookieVector* cookies) {
store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded,
base::Unretained(this)));
loaded_event_.Wait();
*cookies = cookies_;
}
void DestroyStore() {
store_ = NULL;
// Make sure we wait until the destructor has run by waiting for all pending
// tasks on the DB thread to run.
scoped_refptr<base::ThreadTestHelper> helper(
new base::ThreadTestHelper(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
ASSERT_TRUE(helper->Run());
}
void CreateAndLoad(bool restore_old_session_cookies,
CanonicalCookieVector* cookies) {
store_ = new SQLitePersistentCookieStore(
temp_dir_.path().Append(chrome::kCookieFilename),
restore_old_session_cookies,
NULL);
Load(cookies);
}
void InitializeStore(bool restore_old_session_cookies) {
CanonicalCookieVector cookies;
CreateAndLoad(restore_old_session_cookies, &cookies);
ASSERT_EQ(0U, cookies.size());
}
// We have to create this method to wrap WaitableEvent::Wait, since we cannot
// bind a non-void returning method as a Closure.
void WaitOnDBEvent() {
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(
net::CanonicalCookie(GURL(), name, value, domain, path, std::string(),
std::string(), creation, creation, creation, false,
false));
}
virtual void SetUp() {
ui_thread_.Start();
db_thread_.Start();
io_thread_.Start();
ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
}
protected:
content::TestBrowserThread ui_thread_;
content::TestBrowserThread db_thread_;
content::TestBrowserThread io_thread_;
base::WaitableEvent loaded_event_;
base::WaitableEvent key_loaded_event_;
base::WaitableEvent db_thread_event_;
CanonicalCookieVector cookies_;
ScopedTempDir temp_dir_;
scoped_refptr<SQLitePersistentCookieStore> store_;
};
TEST_F(SQLitePersistentCookieStoreTest, TestInvalidMetaTableRecovery) {
InitializeStore(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(false, &cookies);
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();
STLDeleteElements(&cookies);
// Now corrupt the meta table.
{
sql::Connection db;
ASSERT_TRUE(db.Open(temp_dir_.path().Append(chrome::kCookieFilename)));
sql::MetaTable meta_table_;
meta_table_.Init(&db, 1, 1);
ASSERT_TRUE(db.Execute("DELETE FROM meta"));
db.Close();
}
// Upon loading, the database should be reset to a good, blank state.
CreateAndLoad(false, &cookies);
ASSERT_EQ(0U, cookies.size());
// Verify that, after, recovery, the database persists properly.
AddCookie("X", "Y", "foo.bar", "/", base::Time::Now());
DestroyStore();
CreateAndLoad(false, &cookies);
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());
STLDeleteElements(&cookies);
}
// Test if data is stored as expected in the SQLite database.
TEST_F(SQLitePersistentCookieStoreTest, TestPersistance) {
InitializeStore(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(false, &cookies);
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();
STLDeleteElements(&cookies);
// Reload and check if the cookie has been removed.
CreateAndLoad(false, &cookies);
ASSERT_EQ(0U, cookies.size());
}
// Test that priority load of cookies for a specfic domain key could be
// completed before the entire store is loaded
TEST_F(SQLitePersistentCookieStoreTest, TestLoadCookiesForKey) {
InitializeStore(false);
base::Time t = base::Time::Now();
AddCookie("A", "B", "foo.bar", "/", t);
t += base::TimeDelta::FromInternalValue(10);
AddCookie("A", "B", "www.aaa.com", "/", t);
t += base::TimeDelta::FromInternalValue(10);
AddCookie("A", "B", "travel.aaa.com", "/", t);
t += base::TimeDelta::FromInternalValue(10);
AddCookie("A", "B", "www.bbb.com", "/", t);
DestroyStore();
store_ = new SQLitePersistentCookieStore(
temp_dir_.path().Append(chrome::kCookieFilename), false, NULL);
// 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.
BrowserThread::PostTask(
BrowserThread::DB, FROM_HERE,
base::Bind(&SQLitePersistentCookieStoreTest::WaitOnDBEvent,
base::Unretained(this)));
store_->Load(base::Bind(&SQLitePersistentCookieStoreTest::OnLoaded,
base::Unretained(this)));
store_->LoadCookiesForKey("aaa.com",
base::Bind(&SQLitePersistentCookieStoreTest::OnKeyLoaded,
base::Unretained(this)));
BrowserThread::PostTask(
BrowserThread::DB, FROM_HERE,
base::Bind(&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();
key_loaded_event_.Wait();
ASSERT_EQ(loaded_event_.IsSignaled(), false);
std::set<std::string> cookies_loaded;
for (CanonicalCookieVector::const_iterator it = cookies_.begin();
it != cookies_.end();
++it) {
cookies_loaded.insert((*it)->Domain().c_str());
}
STLDeleteElements(&cookies_);
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();
loaded_event_.Wait();
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);
STLDeleteElements(&cookies_);
}
// Test that we can force the database to be written by calling Flush().
TEST_F(SQLitePersistentCookieStoreTest, TestFlush) {
InitializeStore(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.
FilePath path = temp_dir_.path().Append(chrome::kCookieFilename);
base::PlatformFileInfo info;
ASSERT_TRUE(file_util::GetFileInfo(path, &info));
int64 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::TimeDelta::FromMicroseconds(c);
std::string name(1, c);
std::string value(1000, c);
AddCookie(name, value, "foo.bar", "/", t);
}
// Call Flush() and wait until the DB thread is idle.
store_->Flush(base::Closure());
scoped_refptr<base::ThreadTestHelper> helper(
new base::ThreadTestHelper(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
ASSERT_TRUE(helper->Run());
// We forced a write, so now the file will be bigger.
ASSERT_TRUE(file_util::GetFileInfo(path, &info));
ASSERT_GT(info.size, base_size);
}
// Counts the number of times Callback() has been run.
class CallbackCounter : public base::RefCountedThreadSafe<CallbackCounter> {
public:
CallbackCounter() : callback_count_(0) {}
void Callback() {
++callback_count_;
}
int callback_count() {
return callback_count_;
}
private:
friend class base::RefCountedThreadSafe<CallbackCounter>;
~CallbackCounter() {}
volatile int callback_count_;
};
// Test that we can get a completion callback after a Flush().
TEST_F(SQLitePersistentCookieStoreTest, TestFlushCompletionCallback) {
InitializeStore(false);
// Put some data - any data - on disk, so that Flush is not a no-op.
AddCookie("A", "B", "foo.bar", "/", base::Time::Now());
scoped_refptr<CallbackCounter> counter(new CallbackCounter());
// Callback shouldn't be invoked until we call Flush().
ASSERT_EQ(0, counter->callback_count());
store_->Flush(base::Bind(&CallbackCounter::Callback, counter.get()));
scoped_refptr<base::ThreadTestHelper> helper(
new base::ThreadTestHelper(
BrowserThread::GetMessageLoopProxyForThread(BrowserThread::DB)));
ASSERT_TRUE(helper->Run());
ASSERT_EQ(1, counter->callback_count());
}
// Test loading old session cookies from the disk.
TEST_F(SQLitePersistentCookieStoreTest, TestLoadOldSessionCookies) {
InitializeStore(true);
// Add a session cookie.
store_->AddCookie(
net::CanonicalCookie(
GURL(), "C", "D", "sessioncookie.com", "/", std::string(),
std::string(), base::Time::Now(), base::Time(),
base::Time::Now(), false, false));
// 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(true, &cookies);
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());
STLDeleteElements(&cookies);
}
// Test loading old session cookies from the disk.
TEST_F(SQLitePersistentCookieStoreTest, TestDontLoadOldSessionCookies) {
InitializeStore(true);
// Add a session cookie.
store_->AddCookie(
net::CanonicalCookie(
GURL(), "C", "D", "sessioncookie.com", "/", std::string(),
std::string(), base::Time::Now(), base::Time(),
base::Time::Now(), false, false));
// 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(false, &cookies);
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.
CreateAndLoad(true, &cookies);
ASSERT_EQ(0U, cookies.size());
}
TEST_F(SQLitePersistentCookieStoreTest, PersistIsPersistent) {
InitializeStore(true);
static const char kSessionName[] = "session";
static const char kPersistentName[] = "persistent";
// Add a session cookie.
store_->AddCookie(
net::CanonicalCookie(
GURL(), kSessionName, "val", "sessioncookie.com", "/",
std::string(), std::string(),
base::Time::Now(), base::Time(), base::Time::Now(),
false, false));
// Add a persistent cookie.
store_->AddCookie(
net::CanonicalCookie(
GURL(), kPersistentName, "val", "sessioncookie.com", "/",
std::string(), std::string(),
base::Time::Now() - base::TimeDelta::FromDays(1), base::Time::Now(),
base::Time::Now(), false, false));
// Create a store that loads session cookie and test that the the IsPersistent
// attribute is restored.
CanonicalCookieVector cookies;
CreateAndLoad(true, &cookies);
ASSERT_EQ(2U, cookies.size());
std::map<std::string, net::CanonicalCookie*> cookie_map;
for (CanonicalCookieVector::const_iterator it = cookies.begin();
it != cookies.end();
++it) {
cookie_map[(*it)->Name()] = *it;
}
std::map<std::string, net::CanonicalCookie*>::const_iterator 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());
STLDeleteElements(&cookies);
}
namespace {
// True if the given cookie is in the vector.
bool IsCookiePresent(const CanonicalCookieVector& cookies,
const std::string& domain,
const std::string& name,
const std::string& value,
bool secure) {
for (unsigned i = 0; i < cookies.size(); ++i) {
if (cookies[i]->Domain() == domain &&
cookies[i]->Name() == name &&
cookies[i]->Value() == value &&
cookies[i]->IsSecure() == secure) {
return true;
}
}
return false;
}
} // namespace
// Test deleting cookies as mandated by the clear on exit policy on shutdown.
TEST_F(SQLitePersistentCookieStoreTest, TestClearOnExitPolicy) {
std::string protected_origin("protected.com");
std::string session_origin("session.com");
std::string other_origin("other.com");
scoped_refptr<quota::MockSpecialStoragePolicy> storage_policy =
new quota::MockSpecialStoragePolicy;
scoped_refptr<ClearOnExitPolicy> clear_policy =
new ClearOnExitPolicy(storage_policy.get());
storage_policy->AddProtected(GURL(std::string("http://") + protected_origin));
storage_policy->AddSessionOnly(
GURL(std::string("http://") + protected_origin));
storage_policy->AddSessionOnly(
GURL(std::string("http://") + session_origin));
CanonicalCookieVector cookies;
store_ = new SQLitePersistentCookieStore(
temp_dir_.path().Append(chrome::kCookieFilename),
false,
clear_policy.get());
Load(&cookies);
ASSERT_EQ(0U, cookies.size());
// Add some cookies.
base::Time t = base::Time::Now();
AddCookie("A", "1", protected_origin, "/", t);
t += base::TimeDelta::FromInternalValue(10);
AddCookie("B", "2", session_origin, "/", t);
t += base::TimeDelta::FromInternalValue(10);
AddCookie("C", "3", other_origin, "/", t);
// A secure cookie on session_origin.
t += base::TimeDelta::FromInternalValue(10);
store_->AddCookie(
net::CanonicalCookie(GURL(), "D", "4", session_origin, "/", std::string(),
std::string(), t, t, t, true, false));
// First, check that we can override the policy.
store_->SetForceKeepSessionState();
// Force the store to write its data to the disk.
DestroyStore();
// Create a store test that the cookie on session_origin does not exist.
store_ = new SQLitePersistentCookieStore(
temp_dir_.path().Append(chrome::kCookieFilename),
false,
clear_policy.get());
Load(&cookies);
EXPECT_EQ(4U, cookies.size());
EXPECT_TRUE(IsCookiePresent(cookies, protected_origin, "A", "1", false));
EXPECT_TRUE(IsCookiePresent(cookies, session_origin, "B", "2", false));
EXPECT_TRUE(IsCookiePresent(cookies, other_origin, "C", "3", false));
EXPECT_TRUE(IsCookiePresent(cookies, session_origin, "D", "4", true));
// This time, the clear on exit policy should be in effect.
DestroyStore();
STLDeleteElements(&cookies);
// Create a store test that the cookie on session_origin does not exist.
store_ = new SQLitePersistentCookieStore(
temp_dir_.path().Append(chrome::kCookieFilename),
false,
clear_policy.get());
Load(&cookies);
EXPECT_EQ(3U, cookies.size());
EXPECT_TRUE(IsCookiePresent(cookies, protected_origin, "A", "1", false));
EXPECT_TRUE(IsCookiePresent(cookies, other_origin, "C", "3", false));
EXPECT_TRUE(IsCookiePresent(cookies, session_origin, "D", "4", true));
DestroyStore();
STLDeleteElements(&cookies);
}