#include "net/extras/sqlite/sqlite_persistent_cookie_store.h"
#include <iterator>
#include <map>
#include <memory>
#include <optional>
#include <set>
#include <tuple>
#include <unordered_set>
#include "base/feature_list.h"
#include "base/files/file_path.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/raw_ptr.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/synchronization/lock.h"
#include "base/task/sequenced_task_runner.h"
#include "base/thread_annotations.h"
#include "base/time/time.h"
#include "base/types/optional_ref.h"
#include "base/values.h"
#include "build/build_config.h"
#include "crypto/sha2.h"
#include "net/base/features.h"
#include "net/cookies/canonical_cookie.h"
#include "net/cookies/cookie_constants.h"
#include "net/cookies/cookie_util.h"
#include "net/cookies/unique_cookie_key.h"
#include "net/extras/sqlite/cookie_crypto_delegate.h"
#include "net/extras/sqlite/sqlite_persistent_store_backend_base.h"
#include "net/log/net_log.h"
#include "net/log/net_log_values.h"
#include "sql/error_delegate_util.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
#include "url/gurl.h"
#include "url/third_party/mozilla/url_parse.h"
using base::Time;
namespace {
static constexpr int kHoursInOneWeek = 24 * 7;
static constexpr int kHoursInOneYear = 24 * 365;
base::Value::Dict CookieKeyedLoadNetLogParams(
const std::string& key,
net::NetLogCaptureMode capture_mode) {
if (!net::NetLogCaptureIncludesSensitive(capture_mode))
return base::Value::Dict();
base::Value::Dict dict;
dict.Set("key", key);
return dict;
}
enum class CookieLoadProblem {
kDecryptFailed = 0,
kNotCanonical = 2,
kOpenDb = 3,
KRecoveryFailed = 4,
kDeleteCookiePartitionFailed = 5,
kHashFailed = 6,
kNoCrypto = 7,
kValuesExistInBothEncryptedAndPlaintext = 8,
kMaxValue = kValuesExistInBothEncryptedAndPlaintext,
};
enum class CookieCommitProblem {
kEncryptFailed = 0,
kAdd = 1,
kUpdateAccess = 2,
kDelete = 3,
kTransactionCommit = 4,
kMaxValue = kTransactionCommit,
};
void RecordCookieLoadProblem(CookieLoadProblem event) {
UMA_HISTOGRAM_ENUMERATION("Cookie.LoadProblem", event);
}
void RecordCookieCommitProblem(CookieCommitProblem event) {
UMA_HISTOGRAM_ENUMERATION("Cookie.CommitProblem", event);
}
void HistogramCookieAge(const net::CanonicalCookie& cookie) {
if (cookie.IsPersistent()) {
if (!cookie.LastUpdateDate().is_null() &&
cookie.SourceType() == net::CookieSourceType::kScript) {
const int script_cookie_age_since_last_update_in_hours =
(Time::Now() - cookie.LastUpdateDate()).InHours();
if (script_cookie_age_since_last_update_in_hours > kHoursInOneWeek) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Cookie.ScriptAgeSinceLastUpdateInHoursGTOneWeek",
script_cookie_age_since_last_update_in_hours, kHoursInOneWeek + 1,
kHoursInOneYear, 100);
} else {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Cookie.ScriptAgeSinceLastUpdateInHoursLTEOneWeek",
script_cookie_age_since_last_update_in_hours, 1,
kHoursInOneWeek + 1, 100);
}
}
} else {
if (!cookie.LastUpdateDate().is_null()) {
const int session_cookie_age_since_last_update_in_hours =
(Time::Now() - cookie.LastUpdateDate()).InHours();
if (session_cookie_age_since_last_update_in_hours > kHoursInOneWeek) {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Cookie.SessionAgeSinceLastUpdateInHoursGTOneWeek",
session_cookie_age_since_last_update_in_hours, kHoursInOneWeek + 1,
kHoursInOneYear, 100);
} else {
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Cookie.SessionAgeSinceLastUpdateInHoursLTEOneWeek",
session_cookie_age_since_last_update_in_hours, 1,
kHoursInOneWeek + 1, 100);
}
}
}
}
}
namespace net {
base::TaskPriority GetCookieStoreBackgroundSequencePriority() {
return base::TaskPriority::USER_BLOCKING;
}
namespace {
const int kCurrentVersionNumber = 24;
const int kCompatibleVersionNumber = 24;
}
class SQLitePersistentCookieStore::Backend
: public SQLitePersistentStoreBackendBase {
public:
Backend(const base::FilePath& path,
scoped_refptr<base::SequencedTaskRunner> client_task_runner,
scoped_refptr<base::SequencedTaskRunner> background_task_runner,
bool restore_old_session_cookies,
std::unique_ptr<CookieCryptoDelegate> crypto_delegate,
bool enable_exclusive_access)
: SQLitePersistentStoreBackendBase(path,
"Cookie",
kCurrentVersionNumber,
kCompatibleVersionNumber,
std::move(background_task_runner),
std::move(client_task_runner),
enable_exclusive_access),
restore_old_session_cookies_(restore_old_session_cookies),
crypto_(std::move(crypto_delegate)) {}
Backend(const Backend&) = delete;
Backend& operator=(const Backend&) = delete;
void Load(LoadedCallback loaded_callback);
void LoadCookiesForKey(base::optional_ref<const std::string> key,
LoadedCallback loaded_callback);
bool MakeCookiesFromSQLStatement(
std::vector<std::unique_ptr<CanonicalCookie>>& cookies,
sql::Statement& statement,
std::unordered_set<std::string>& top_frame_site_keys_to_delete);
void AddCookie(const CanonicalCookie& cc);
void UpdateCookieAccessTime(const CanonicalCookie& cc);
void DeleteCookie(const CanonicalCookie& cc);
size_t GetQueueLengthForTesting();
void DeleteAllInList(const std::list<CookieOrigin>& cookies);
private:
~Backend() override {
DCHECK_EQ(0u, num_pending_);
DCHECK(pending_.empty());
}
std::optional<int> DoMigrateDatabaseSchema() override;
class PendingOperation {
public:
enum OperationType {
COOKIE_ADD,
COOKIE_UPDATEACCESS,
COOKIE_DELETE,
};
PendingOperation(OperationType op, const CanonicalCookie& cc)
: op_(op), cc_(cc) {}
OperationType op() const { return op_; }
const CanonicalCookie& cc() const { return cc_; }
private:
OperationType op_;
CanonicalCookie cc_;
};
private:
void LoadAndNotifyInBackground(base::optional_ref<const std::string> key,
LoadedCallback loaded_callback);
void NotifyLoadCompleteInForeground(LoadedCallback loaded_callback,
bool load_success);
void CryptoHasInitFromLoad(base::optional_ref<const std::string> key,
LoadedCallback loaded_callback);
bool CreateDatabaseSchema() override;
bool DoInitializeDatabase() override;
void ChainLoadCookies(LoadedCallback loaded_callback);
bool LoadCookiesForDomains(const std::set<std::string>& key);
void DeleteTopFrameSiteKeys(
const std::unordered_set<std::string>& top_frame_site_keys);
void BatchOperation(PendingOperation::OperationType op,
const CanonicalCookie& cc);
void DoCommit() override;
void DeleteSessionCookiesOnStartup();
void BackgroundDeleteAllInList(const std::list<CookieOrigin>& cookies);
void FinishedLoadingCookies(LoadedCallback loaded_callback, bool success);
void RecordOpenDBProblem() override {
RecordCookieLoadProblem(CookieLoadProblem::kOpenDb);
}
void RecordDBMigrationProblem() override {
RecordCookieLoadProblem(CookieLoadProblem::kOpenDb);
}
typedef std::list<std::unique_ptr<PendingOperation>> PendingOperationsForKey;
typedef std::map<UniqueCookieKey, PendingOperationsForKey>
PendingOperationsMap;
PendingOperationsMap pending_ GUARDED_BY(lock_);
PendingOperationsMap::size_type num_pending_ GUARDED_BY(lock_) = 0;
base::Lock lock_;
std::vector<std::unique_ptr<CanonicalCookie>> cookies_ GUARDED_BY(lock_);
std::map<std::string, std::set<std::string>> keys_to_load_;
bool restore_old_session_cookies_;
std::unique_ptr<CookieCryptoDelegate> crypto_;
};
namespace {
enum DBCookiePriority {
kCookiePriorityLow = 0,
kCookiePriorityMedium = 1,
kCookiePriorityHigh = 2,
};
DBCookiePriority CookiePriorityToDBCookiePriority(CookiePriority value) {
switch (value) {
case COOKIE_PRIORITY_LOW:
return kCookiePriorityLow;
case COOKIE_PRIORITY_MEDIUM:
return kCookiePriorityMedium;
case COOKIE_PRIORITY_HIGH:
return kCookiePriorityHigh;
}
NOTREACHED();
}
CookiePriority DBCookiePriorityToCookiePriority(DBCookiePriority value) {
switch (value) {
case kCookiePriorityLow:
return COOKIE_PRIORITY_LOW;
case kCookiePriorityMedium:
return COOKIE_PRIORITY_MEDIUM;
case kCookiePriorityHigh:
return COOKIE_PRIORITY_HIGH;
}
NOTREACHED();
}
enum DBCookieSameSite {
kCookieSameSiteUnspecified = -1,
kCookieSameSiteNoRestriction = 0,
kCookieSameSiteLax = 1,
kCookieSameSiteStrict = 2,
kCookieSameSiteExtended = 3
};
DBCookieSameSite CookieSameSiteToDBCookieSameSite(CookieSameSite value) {
switch (value) {
case CookieSameSite::NO_RESTRICTION:
return kCookieSameSiteNoRestriction;
case CookieSameSite::LAX_MODE:
return kCookieSameSiteLax;
case CookieSameSite::STRICT_MODE:
return kCookieSameSiteStrict;
case CookieSameSite::UNSPECIFIED:
return kCookieSameSiteUnspecified;
}
}
CookieSameSite DBCookieSameSiteToCookieSameSite(DBCookieSameSite value) {
CookieSameSite samesite = CookieSameSite::UNSPECIFIED;
switch (value) {
case kCookieSameSiteNoRestriction:
samesite = CookieSameSite::NO_RESTRICTION;
break;
case kCookieSameSiteLax:
samesite = CookieSameSite::LAX_MODE;
break;
case kCookieSameSiteStrict:
samesite = CookieSameSite::STRICT_MODE;
break;
case kCookieSameSiteExtended:
case kCookieSameSiteUnspecified:
samesite = CookieSameSite::UNSPECIFIED;
break;
}
return samesite;
}
enum DBCookieSourceType {
kDBCookieSourceTypeUnknown = 0,
kDBCookieSourceTypeHTTP = 1,
kDBCookieSourceTypeScript = 2,
kDBCookieSourceTypeOther = 3,
};
DBCookieSourceType CookieSourceTypeToDBCookieSourceType(
CookieSourceType value) {
switch (value) {
case CookieSourceType::kUnknown:
return kDBCookieSourceTypeUnknown;
case CookieSourceType::kHTTP:
return kDBCookieSourceTypeHTTP;
case CookieSourceType::kScript:
return kDBCookieSourceTypeScript;
case CookieSourceType::kOther:
return kDBCookieSourceTypeOther;
}
}
CookieSourceType DBCookieSourceTypeToCookieSourceType(
DBCookieSourceType value) {
switch (value) {
case kDBCookieSourceTypeUnknown:
return CookieSourceType::kUnknown;
case kDBCookieSourceTypeHTTP:
return CookieSourceType::kHTTP;
case kDBCookieSourceTypeScript:
return CookieSourceType::kScript;
case kDBCookieSourceTypeOther:
return CookieSourceType::kOther;
default:
return CookieSourceType::kUnknown;
}
}
CookieSourceScheme DBToCookieSourceScheme(int value) {
int enum_max_value = static_cast<int>(CookieSourceScheme::kMaxValue);
if (value < 0 || value > enum_max_value) {
DLOG(WARNING) << "DB read of cookie's source scheme is invalid. Resetting "
"value to unset.";
value = static_cast<int>(
CookieSourceScheme::kUnset);
}
return static_cast<CookieSourceScheme>(value);
}
class IncrementTimeDelta {
public:
explicit IncrementTimeDelta(base::TimeDelta* delta)
: delta_(delta), original_value_(*delta), start_(base::Time::Now()) {}
IncrementTimeDelta(const IncrementTimeDelta&) = delete;
IncrementTimeDelta& operator=(const IncrementTimeDelta&) = delete;
~IncrementTimeDelta() {
*delta_ = original_value_ + base::Time::Now() - start_;
}
private:
raw_ptr<base::TimeDelta> delta_;
base::TimeDelta original_value_;
base::Time start_;
};
bool CreateV22Schema(sql::Database* db) {
CHECK(!db->DoesTableExist("cookies"));
static constexpr char kCreateTableQuery[] =
"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);";
static constexpr char kCreateIndexQuery[] =
"CREATE UNIQUE INDEX cookies_unique_index "
"ON cookies(host_key, top_frame_site_key, name, path, source_scheme, "
"source_port)";
return db->Execute(kCreateTableQuery) && db->Execute(kCreateIndexQuery);
}
bool CreateV23Schema(sql::Database* db) {
CHECK(!db->DoesTableExist("cookies"));
static constexpr char kCreateTableQuery[] =
"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 kCreateIndexQuery[] =
"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(kCreateTableQuery) && db->Execute(kCreateIndexQuery);
}
bool CreateV24Schema(sql::Database* db) {
return CreateV23Schema(db);
}
}
void SQLitePersistentCookieStore::Backend::Load(
LoadedCallback loaded_callback) {
LoadCookiesForKey(std::nullopt, std::move(loaded_callback));
}
void SQLitePersistentCookieStore::Backend::LoadCookiesForKey(
base::optional_ref<const std::string> key,
LoadedCallback loaded_callback) {
if (crypto_) {
crypto_->Init(base::BindOnce(&Backend::CryptoHasInitFromLoad, this,
key.CopyAsOptional(),
std::move(loaded_callback)));
} else {
CryptoHasInitFromLoad(key, std::move(loaded_callback));
}
}
void SQLitePersistentCookieStore::Backend::CryptoHasInitFromLoad(
base::optional_ref<const std::string> key,
LoadedCallback loaded_callback) {
PostBackgroundTask(
FROM_HERE,
base::BindOnce(&Backend::LoadAndNotifyInBackground, this,
key.CopyAsOptional(), std::move(loaded_callback)));
}
void SQLitePersistentCookieStore::Backend::LoadAndNotifyInBackground(
base::optional_ref<const std::string> key,
LoadedCallback loaded_callback) {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
bool success = false;
if (InitializeDatabase()) {
if (!key.has_value()) {
ChainLoadCookies(std::move(loaded_callback));
return;
}
auto it = keys_to_load_.find(*key);
if (it != keys_to_load_.end()) {
success = LoadCookiesForDomains(it->second);
keys_to_load_.erase(it);
} else {
success = true;
}
}
FinishedLoadingCookies(std::move(loaded_callback), success);
}
void SQLitePersistentCookieStore::Backend::NotifyLoadCompleteInForeground(
LoadedCallback loaded_callback,
bool load_success) {
DCHECK(client_task_runner()->RunsTasksInCurrentSequence());
std::vector<std::unique_ptr<CanonicalCookie>> cookies;
{
base::AutoLock locked(lock_);
cookies.swap(cookies_);
}
std::move(loaded_callback).Run(std::move(cookies));
}
bool SQLitePersistentCookieStore::Backend::CreateDatabaseSchema() {
DCHECK(db());
return db()->DoesTableExist("cookies") || CreateV24Schema(db());
}
bool SQLitePersistentCookieStore::Backend::DoInitializeDatabase() {
DCHECK(db());
sql::Statement smt(
db()->GetUniqueStatement("SELECT DISTINCT host_key FROM cookies"));
if (!smt.is_valid()) {
Reset();
return false;
}
std::vector<std::string> host_keys;
while (smt.Step())
host_keys.push_back(smt.ColumnString(0));
for (const auto& domain : host_keys) {
std::string key = CookieMonster::GetKey(domain);
keys_to_load_[key].insert(domain);
}
if (!restore_old_session_cookies_)
DeleteSessionCookiesOnStartup();
return true;
}
void SQLitePersistentCookieStore::Backend::ChainLoadCookies(
LoadedCallback loaded_callback) {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
bool load_success = true;
if (!db()) {
load_success = false;
} else if (keys_to_load_.size() > 0) {
auto it = keys_to_load_.begin();
load_success = LoadCookiesForDomains(it->second);
keys_to_load_.erase(it);
}
if (load_success && keys_to_load_.size() > 0) {
bool success = background_task_runner()->PostTask(
FROM_HERE, base::BindOnce(&Backend::ChainLoadCookies, this,
std::move(loaded_callback)));
if (!success) {
LOG(WARNING) << "Failed to post task from " << FROM_HERE.ToString()
<< " to background_task_runner().";
}
} else {
FinishedLoadingCookies(std::move(loaded_callback), load_success);
}
}
bool SQLitePersistentCookieStore::Backend::LoadCookiesForDomains(
const std::set<std::string>& domains) {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
sql::Statement smt, delete_statement;
if (restore_old_session_cookies_) {
smt.Assign(db()->GetCachedStatement(
SQL_FROM_HERE,
"SELECT creation_utc, host_key, top_frame_site_key, name, value, path, "
"expires_utc, is_secure, is_httponly, last_access_utc, has_expires, "
"is_persistent, priority, encrypted_value, samesite, source_scheme, "
"source_port, last_update_utc, source_type, has_cross_site_ancestor "
"FROM cookies WHERE host_key "
"= "
"?"));
} else {
smt.Assign(db()->GetCachedStatement(
SQL_FROM_HERE,
"SELECT creation_utc, host_key, top_frame_site_key, name, value, path, "
"expires_utc, is_secure, is_httponly, last_access_utc, has_expires, "
"is_persistent, priority, encrypted_value, samesite, source_scheme, "
"source_port, last_update_utc, source_type, has_cross_site_ancestor "
"FROM cookies WHERE "
"host_key = ? AND "
"is_persistent = 1"));
}
delete_statement.Assign(db()->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM cookies WHERE host_key = ?"));
if (!smt.is_valid() || !delete_statement.is_valid()) {
delete_statement.Clear();
smt.Clear();
Reset();
return false;
}
std::vector<std::unique_ptr<CanonicalCookie>> cookies;
std::unordered_set<std::string> top_frame_site_keys_to_delete;
auto it = domains.begin();
bool ok = true;
for (; it != domains.end() && ok; ++it) {
smt.BindString(0, *it);
ok = MakeCookiesFromSQLStatement(cookies, smt,
top_frame_site_keys_to_delete);
smt.Reset(true);
}
DeleteTopFrameSiteKeys(std::move(top_frame_site_keys_to_delete));
if (ok) {
base::AutoLock locked(lock_);
std::move(cookies.begin(), cookies.end(), std::back_inserter(cookies_));
} else {
for (const std::string& domain : domains) {
delete_statement.BindString(0, domain);
if (!delete_statement.Run()) {
RecordCookieLoadProblem(CookieLoadProblem::KRecoveryFailed);
}
delete_statement.Reset(true);
}
}
return true;
}
void SQLitePersistentCookieStore::Backend::DeleteTopFrameSiteKeys(
const std::unordered_set<std::string>& top_frame_site_keys) {
if (top_frame_site_keys.empty())
return;
sql::Statement delete_statement;
delete_statement.Assign(db()->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM cookies WHERE top_frame_site_key = ?"));
if (!delete_statement.is_valid())
return;
for (const std::string& key : top_frame_site_keys) {
delete_statement.BindString(0, key);
if (!delete_statement.Run())
RecordCookieLoadProblem(CookieLoadProblem::kDeleteCookiePartitionFailed);
delete_statement.Reset(true);
}
}
bool SQLitePersistentCookieStore::Backend::MakeCookiesFromSQLStatement(
std::vector<std::unique_ptr<CanonicalCookie>>& cookies,
sql::Statement& statement,
std::unordered_set<std::string>& top_frame_site_keys_to_delete) {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
bool ok = true;
while (statement.Step()) {
std::string domain = statement.ColumnString(1);
std::string value = statement.ColumnString(4);
std::string encrypted_value = statement.ColumnString(13);
const bool encrypted_and_plaintext_values =
!value.empty() && !encrypted_value.empty();
UMA_HISTOGRAM_BOOLEAN("Cookie.EncryptedAndPlaintextValues",
encrypted_and_plaintext_values);
if (encrypted_and_plaintext_values) {
RecordCookieLoadProblem(
CookieLoadProblem::kValuesExistInBothEncryptedAndPlaintext);
ok = false;
continue;
}
if (!encrypted_value.empty()) {
if (!crypto_) {
RecordCookieLoadProblem(CookieLoadProblem::kNoCrypto);
ok = false;
continue;
}
bool decrypt_ok = crypto_->DecryptString(encrypted_value, &value);
if (!decrypt_ok) {
RecordCookieLoadProblem(CookieLoadProblem::kDecryptFailed);
ok = false;
continue;
}
std::string correct_hash = crypto::SHA256HashString(domain);
if (!base::StartsWith(value, correct_hash,
base::CompareCase::SENSITIVE)) {
RecordCookieLoadProblem(CookieLoadProblem::kHashFailed);
ok = false;
continue;
}
value = value.substr(correct_hash.length());
}
base::expected<std::optional<CookiePartitionKey>, std::string>
partition_key = CookiePartitionKey::FromStorage(
statement.ColumnString(2), statement.ColumnBool(19));
if (!partition_key.has_value()) {
top_frame_site_keys_to_delete.insert(statement.ColumnString(2));
continue;
}
std::unique_ptr<net::CanonicalCookie> cc = CanonicalCookie::FromStorage(
statement.ColumnString(3),
value,
domain,
statement.ColumnString(5),
statement.ColumnTime(0),
statement.ColumnTime(6),
statement.ColumnTime(9),
statement.ColumnTime(17),
statement.ColumnBool(7),
statement.ColumnBool(8),
DBCookieSameSiteToCookieSameSite(
static_cast<DBCookieSameSite>(statement.ColumnInt(14))),
DBCookiePriorityToCookiePriority(
static_cast<DBCookiePriority>(statement.ColumnInt(12))),
std::move(partition_key.value()),
DBToCookieSourceScheme(statement.ColumnInt(15)),
statement.ColumnInt(16),
DBCookieSourceTypeToCookieSourceType(
static_cast<DBCookieSourceType>(statement.ColumnInt(18))),
CanonicalCookieFromStorageCallSite::kSqlitePersistentCookieStore);
if (cc) {
DLOG_IF(WARNING, cc->CreationDate() > Time::Now())
<< "CreationDate too recent";
if (!cc->LastUpdateDate().is_null()) {
DLOG_IF(WARNING, cc->LastUpdateDate() > Time::Now())
<< "LastUpdateDate too recent";
UMA_HISTOGRAM_CUSTOM_COUNTS(
"Cookie.DaysSinceRefreshForRetrieval",
(base::Time::Now() - cc->LastUpdateDate()).InDays(), 1, 400, 100);
}
HistogramCookieAge(*cc);
cookies.push_back(std::move(cc));
} else {
RecordCookieLoadProblem(CookieLoadProblem::kNotCanonical);
ok = false;
}
}
return ok;
}
std::optional<int>
SQLitePersistentCookieStore::Backend::DoMigrateDatabaseSchema() {
int cur_version = meta_table()->GetVersionNumber();
if (cur_version == 21) {
SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV22");
sql::Transaction transaction(db());
if (!transaction.Begin()) {
return std::nullopt;
}
if (!db()->Execute("DROP TABLE IF EXISTS cookies_old")) {
return std::nullopt;
}
if (!db()->Execute("ALTER TABLE cookies RENAME TO cookies_old")) {
return std::nullopt;
}
if (!db()->Execute("DROP INDEX IF EXISTS cookies_unique_index")) {
return std::nullopt;
}
if (!CreateV22Schema(db())) {
return std::nullopt;
}
static constexpr char insert_cookies_sql[] =
"INSERT OR REPLACE INTO cookies "
"(creation_utc, host_key, top_frame_site_key, name, value, "
"encrypted_value, path, expires_utc, is_secure, is_httponly, "
"last_access_utc, has_expires, is_persistent, priority, samesite, "
"source_scheme, source_port, last_update_utc, source_type) "
"SELECT creation_utc, host_key, top_frame_site_key, name, value,"
" encrypted_value, path, expires_utc, is_secure, is_httponly,"
" last_access_utc, has_expires, is_persistent, priority, "
" samesite, source_scheme, source_port, last_update_utc, 0 "
"FROM cookies_old ORDER BY creation_utc ASC";
if (!db()->Execute(insert_cookies_sql)) {
return std::nullopt;
}
if (!db()->Execute("DROP TABLE cookies_old")) {
return std::nullopt;
}
++cur_version;
if (!meta_table()->SetVersionNumber(cur_version) ||
!meta_table()->SetCompatibleVersionNumber(
std::min(cur_version, kCompatibleVersionNumber)) ||
!transaction.Commit()) {
return std::nullopt;
}
}
if (cur_version == 22) {
SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV23");
sql::Transaction transaction(db());
if (!transaction.Begin()) {
return std::nullopt;
}
if (!db()->Execute("DROP TABLE IF EXISTS cookies_old")) {
return std::nullopt;
}
if (!db()->Execute("ALTER TABLE cookies RENAME TO cookies_old")) {
return std::nullopt;
}
if (!db()->Execute("DROP INDEX IF EXISTS cookies_unique_index")) {
return std::nullopt;
}
if (!CreateV23Schema(db())) {
return std::nullopt;
}
For the case statement setting source_scheme,
value of 0 reflects int value of CookieSourceScheme::kUnset
value of 2 reflects int value of CookieSourceScheme::kSecure
For the case statement setting has_cross_site_ancestor, it has the
potential to have a origin mismatch due to substring operations.
EX: the domain ample.com will appear as a substring of the domain
example.com even though they are different origins.
We are ok with this because the other elements of the UNIQUE INDEX
will always be different preventing accidental access.
*/
static constexpr char insert_cookies_sql[] =
"INSERT OR REPLACE INTO cookies "
"(creation_utc, host_key, top_frame_site_key, name, value, "
"encrypted_value, path, expires_utc, is_secure, is_httponly, "
"last_access_utc, has_expires, is_persistent, priority, samesite, "
"source_scheme, source_port, last_update_utc, source_type, "
"has_cross_site_ancestor) "
"SELECT creation_utc, host_key, top_frame_site_key, name, value,"
" encrypted_value, path, expires_utc, is_secure, is_httponly,"
" last_access_utc, has_expires, is_persistent, priority, "
" samesite, "
" CASE WHEN source_scheme = 0 AND is_secure = 1 "
" THEN 2 ELSE source_scheme END, "
" source_port, last_update_utc, source_type, "
" CASE WHEN INSTR(top_frame_site_key, '://') > 0 AND host_key "
" LIKE CONCAT('%', SUBSTR(top_frame_site_key, "
" INSTR(top_frame_site_key,'://') + 3), '%') "
" THEN 0 ELSE 1 "
" END AS has_cross_site_ancestor "
"FROM cookies_old ORDER BY creation_utc ASC";
if (!db()->Execute(insert_cookies_sql)) {
return std::nullopt;
}
if (!db()->Execute("DROP TABLE cookies_old")) {
return std::nullopt;
}
++cur_version;
if (!meta_table()->SetVersionNumber(cur_version) ||
!meta_table()->SetCompatibleVersionNumber(
std::min(cur_version, kCompatibleVersionNumber)) ||
!transaction.Commit()) {
return std::nullopt;
}
}
if (cur_version == 23) {
SCOPED_UMA_HISTOGRAM_TIMER("Cookie.TimeDatabaseMigrationToV24");
sql::Transaction transaction(db());
if (!transaction.Begin()) {
return std::nullopt;
}
if (crypto_) {
sql::Statement select_smt, update_smt;
select_smt.Assign(db()->GetCachedStatement(
SQL_FROM_HERE,
"SELECT rowid, host_key, encrypted_value, value FROM cookies"));
update_smt.Assign(db()->GetCachedStatement(
SQL_FROM_HERE,
"UPDATE cookies SET encrypted_value=?, value=? WHERE "
"rowid=?"));
if (!select_smt.is_valid() || !update_smt.is_valid()) {
return std::nullopt;
}
std::map<int64_t, std::string> encrypted_values;
while (select_smt.Step()) {
int64_t rowid = select_smt.ColumnInt64(0);
std::string domain = select_smt.ColumnString(1);
std::string encrypted_value = select_smt.ColumnString(2);
std::string value = select_smt.ColumnString(3);
std::string decrypted_value;
if (encrypted_value.empty() && !value.empty()) {
decrypted_value = value;
} else {
if (!crypto_->DecryptString(encrypted_value, &decrypted_value)) {
RecordCookieLoadProblem(CookieLoadProblem::kDecryptFailed);
continue;
}
}
std::string new_encrypted_value;
if (!crypto_->EncryptString(
base::StrCat(
{crypto::SHA256HashString(domain), decrypted_value}),
&new_encrypted_value)) {
RecordCookieCommitProblem(CookieCommitProblem::kEncryptFailed);
continue;
}
encrypted_values[rowid] = new_encrypted_value;
}
for (const auto& entry : encrypted_values) {
update_smt.Reset(true);
update_smt.BindString( 0, entry.second);
update_smt.BindString( 1, {});
update_smt.BindInt64( 2, entry.first);
if (!update_smt.Run()) {
return std::nullopt;
}
}
}
++cur_version;
if (!meta_table()->SetVersionNumber(cur_version) ||
!meta_table()->SetCompatibleVersionNumber(
std::min(cur_version, kCompatibleVersionNumber)) ||
!transaction.Commit()) {
return std::nullopt;
}
}
return std::make_optional(cur_version);
}
void SQLitePersistentCookieStore::Backend::AddCookie(
const CanonicalCookie& cc) {
BatchOperation(PendingOperation::COOKIE_ADD, cc);
}
void SQLitePersistentCookieStore::Backend::UpdateCookieAccessTime(
const CanonicalCookie& cc) {
BatchOperation(PendingOperation::COOKIE_UPDATEACCESS, cc);
}
void SQLitePersistentCookieStore::Backend::DeleteCookie(
const CanonicalCookie& cc) {
BatchOperation(PendingOperation::COOKIE_DELETE, cc);
}
void SQLitePersistentCookieStore::Backend::BatchOperation(
PendingOperation::OperationType op,
const CanonicalCookie& cc) {
constexpr base::TimeDelta kCommitInterval = base::Seconds(30);
constexpr size_t kCommitAfterBatchSize = 512;
DCHECK(!background_task_runner()->RunsTasksInCurrentSequence());
auto po = std::make_unique<PendingOperation>(op, cc);
PendingOperationsMap::size_type num_pending;
{
base::AutoLock locked(lock_);
auto key = cc.StrictlyUniqueKey();
auto iter_and_result = pending_.emplace(key, PendingOperationsForKey());
PendingOperationsForKey& ops_for_key = iter_and_result.first->second;
if (!iter_and_result.second) {
if (po->op() == PendingOperation::COOKIE_DELETE) {
ops_for_key.clear();
} else if (po->op() == PendingOperation::COOKIE_UPDATEACCESS) {
if (!ops_for_key.empty() &&
ops_for_key.back()->op() == PendingOperation::COOKIE_UPDATEACCESS) {
ops_for_key.pop_back();
}
DCHECK_LE(ops_for_key.size(), 2u);
} else {
DCHECK_LE(ops_for_key.size(), 1u);
}
}
ops_for_key.push_back(std::move(po));
num_pending = ++num_pending_;
}
if (num_pending == 1) {
if (!background_task_runner()->PostDelayedTask(
FROM_HERE, base::BindOnce(&Backend::Commit, this),
kCommitInterval)) {
DUMP_WILL_BE_NOTREACHED() << "background_task_runner() is not running.";
}
} else if (num_pending == kCommitAfterBatchSize) {
PostBackgroundTask(FROM_HERE, base::BindOnce(&Backend::Commit, this));
}
}
void SQLitePersistentCookieStore::Backend::DoCommit() {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
PendingOperationsMap ops;
{
base::AutoLock locked(lock_);
pending_.swap(ops);
num_pending_ = 0;
}
if (!db() || ops.empty())
return;
sql::Statement add_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, "
"last_access_utc, has_expires, is_persistent, priority, samesite, "
"source_scheme, source_port, last_update_utc, source_type, "
"has_cross_site_ancestor) "
"VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)"));
if (!add_statement.is_valid())
return;
sql::Statement update_access_statement(db()->GetCachedStatement(
SQL_FROM_HERE,
"UPDATE cookies SET last_access_utc=? WHERE "
"name=? AND host_key=? AND top_frame_site_key=? AND path=? AND "
"source_scheme=? AND source_port=? AND has_cross_site_ancestor=?"));
if (!update_access_statement.is_valid())
return;
sql::Statement delete_statement(db()->GetCachedStatement(
SQL_FROM_HERE,
"DELETE FROM cookies WHERE "
"name=? AND host_key=? AND top_frame_site_key=? AND path=? AND "
"source_scheme=? AND source_port=? AND has_cross_site_ancestor=?"));
if (!delete_statement.is_valid())
return;
sql::Transaction transaction(db());
if (!transaction.Begin())
return;
for (auto& kv : ops) {
for (std::unique_ptr<PendingOperation>& po_entry : kv.second) {
std::unique_ptr<PendingOperation> po(std::move(po_entry));
base::expected<CookiePartitionKey::SerializedCookiePartitionKey,
std::string>
serialized_partition_key =
CookiePartitionKey::Serialize(po->cc().PartitionKey());
if (!serialized_partition_key.has_value()) {
continue;
}
switch (po->op()) {
case PendingOperation::COOKIE_ADD:
add_statement.Reset(true);
add_statement.BindTime(0, po->cc().CreationDate());
add_statement.BindString(1, po->cc().Domain());
add_statement.BindString(2, serialized_partition_key->TopLevelSite());
add_statement.BindString(3, po->cc().Name());
if (crypto_) {
std::string encrypted_value;
if (!crypto_->EncryptString(
base::StrCat({crypto::SHA256HashString(po->cc().Domain()),
po->cc().Value()}),
&encrypted_value)) {
DLOG(WARNING) << "Could not encrypt a cookie, skipping add.";
RecordCookieCommitProblem(CookieCommitProblem::kEncryptFailed);
continue;
}
add_statement.BindCString(4, "");
add_statement.BindBlob(5, std::move(encrypted_value));
} else {
add_statement.BindString(4, po->cc().Value());
add_statement.BindBlob(5,
base::span<uint8_t>());
}
add_statement.BindString(6, po->cc().Path());
add_statement.BindTime(7, po->cc().ExpiryDate());
add_statement.BindBool(8, po->cc().SecureAttribute());
add_statement.BindBool(9, po->cc().IsHttpOnly());
add_statement.BindTime(10, po->cc().LastAccessDate());
add_statement.BindBool(11, po->cc().IsPersistent());
add_statement.BindBool(12, po->cc().IsPersistent());
add_statement.BindInt(
13, CookiePriorityToDBCookiePriority(po->cc().Priority()));
add_statement.BindInt(
14, CookieSameSiteToDBCookieSameSite(po->cc().SameSite()));
add_statement.BindInt(15, static_cast<int>(po->cc().SourceScheme()));
add_statement.BindInt(16, po->cc().SourcePort());
add_statement.BindTime(17, po->cc().LastUpdateDate());
add_statement.BindInt(
18, CookieSourceTypeToDBCookieSourceType(po->cc().SourceType()));
add_statement.BindBool(
19, serialized_partition_key->has_cross_site_ancestor());
if (!add_statement.Run()) {
DLOG(WARNING) << "Could not add a cookie to the DB.";
RecordCookieCommitProblem(CookieCommitProblem::kAdd);
}
break;
case PendingOperation::COOKIE_UPDATEACCESS:
update_access_statement.Reset(true);
update_access_statement.BindTime(0, po->cc().LastAccessDate());
update_access_statement.BindString(1, po->cc().Name());
update_access_statement.BindString(2, po->cc().Domain());
update_access_statement.BindString(
3, serialized_partition_key->TopLevelSite());
update_access_statement.BindString(4, po->cc().Path());
update_access_statement.BindInt(
5, static_cast<int>(po->cc().SourceScheme()));
update_access_statement.BindInt(6, po->cc().SourcePort());
update_access_statement.BindBool(
7, serialized_partition_key->has_cross_site_ancestor());
if (!update_access_statement.Run()) {
DLOG(WARNING)
<< "Could not update cookie last access time in the DB.";
RecordCookieCommitProblem(CookieCommitProblem::kUpdateAccess);
}
break;
case PendingOperation::COOKIE_DELETE:
delete_statement.Reset(true);
delete_statement.BindString(0, po->cc().Name());
delete_statement.BindString(1, po->cc().Domain());
delete_statement.BindString(2,
serialized_partition_key->TopLevelSite());
delete_statement.BindString(3, po->cc().Path());
delete_statement.BindInt(4,
static_cast<int>(po->cc().SourceScheme()));
delete_statement.BindInt(5, po->cc().SourcePort());
delete_statement.BindBool(
6, serialized_partition_key->has_cross_site_ancestor());
if (!delete_statement.Run()) {
DLOG(WARNING) << "Could not delete a cookie from the DB.";
RecordCookieCommitProblem(CookieCommitProblem::kDelete);
}
break;
default:
NOTREACHED();
}
}
}
bool commit_ok = transaction.Commit();
if (!commit_ok) {
RecordCookieCommitProblem(CookieCommitProblem::kTransactionCommit);
}
}
size_t SQLitePersistentCookieStore::Backend::GetQueueLengthForTesting() {
DCHECK(client_task_runner()->RunsTasksInCurrentSequence());
size_t total = 0u;
{
base::AutoLock locked(lock_);
for (const auto& key_val : pending_) {
total += key_val.second.size();
}
}
return total;
}
void SQLitePersistentCookieStore::Backend::DeleteAllInList(
const std::list<CookieOrigin>& cookies) {
if (cookies.empty())
return;
if (background_task_runner()->RunsTasksInCurrentSequence()) {
BackgroundDeleteAllInList(cookies);
} else {
PostBackgroundTask(
FROM_HERE,
base::BindOnce(&Backend::BackgroundDeleteAllInList, this, cookies));
}
}
void SQLitePersistentCookieStore::Backend::DeleteSessionCookiesOnStartup() {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
if (!db()->Execute("DELETE FROM cookies WHERE is_persistent != 1"))
LOG(WARNING) << "Unable to delete session cookies.";
}
void SQLitePersistentCookieStore::Backend::BackgroundDeleteAllInList(
const std::list<CookieOrigin>& cookies) {
DCHECK(background_task_runner()->RunsTasksInCurrentSequence());
if (!db())
return;
Commit();
sql::Statement delete_statement(db()->GetCachedStatement(
SQL_FROM_HERE, "DELETE FROM cookies WHERE host_key=? AND is_secure=?"));
if (!delete_statement.is_valid()) {
LOG(WARNING) << "Unable to delete cookies on shutdown.";
return;
}
sql::Transaction transaction(db());
if (!transaction.Begin()) {
LOG(WARNING) << "Unable to delete cookies on shutdown.";
return;
}
for (const auto& cookie : cookies) {
const GURL url(cookie_util::CookieOriginToURL(cookie.first, cookie.second));
if (!url.is_valid())
continue;
delete_statement.Reset(true);
delete_statement.BindString(0, cookie.first);
delete_statement.BindInt(1, cookie.second);
if (!delete_statement.Run()) {
LOG(WARNING) << "Could not delete a cookie from the DB.";
}
}
if (!transaction.Commit())
LOG(WARNING) << "Unable to delete cookies on shutdown.";
}
void SQLitePersistentCookieStore::Backend::FinishedLoadingCookies(
LoadedCallback loaded_callback,
bool success) {
PostClientTask(FROM_HERE,
base::BindOnce(&Backend::NotifyLoadCompleteInForeground, this,
std::move(loaded_callback), success));
}
SQLitePersistentCookieStore::SQLitePersistentCookieStore(
const base::FilePath& path,
const scoped_refptr<base::SequencedTaskRunner>& client_task_runner,
const scoped_refptr<base::SequencedTaskRunner>& background_task_runner,
bool restore_old_session_cookies,
std::unique_ptr<CookieCryptoDelegate> crypto_delegate,
bool enable_exclusive_access)
: backend_(base::MakeRefCounted<Backend>(path,
client_task_runner,
background_task_runner,
restore_old_session_cookies,
std::move(crypto_delegate),
enable_exclusive_access)) {}
void SQLitePersistentCookieStore::DeleteAllInList(
const std::list<CookieOrigin>& cookies) {
backend_->DeleteAllInList(cookies);
}
void SQLitePersistentCookieStore::Load(LoadedCallback loaded_callback,
const NetLogWithSource& net_log) {
DCHECK(!loaded_callback.is_null());
net_log_ = net_log;
net_log_.BeginEvent(NetLogEventType::COOKIE_PERSISTENT_STORE_LOAD);
backend_->Load(base::BindOnce(&SQLitePersistentCookieStore::CompleteLoad,
this, std::move(loaded_callback)));
}
void SQLitePersistentCookieStore::LoadCookiesForKey(
const std::string& key,
LoadedCallback loaded_callback) {
DCHECK(!loaded_callback.is_null());
net_log_.AddEvent(NetLogEventType::COOKIE_PERSISTENT_STORE_KEY_LOAD_STARTED,
[&](NetLogCaptureMode capture_mode) {
return CookieKeyedLoadNetLogParams(key, capture_mode);
});
backend_->LoadCookiesForKey(
key, base::BindOnce(&SQLitePersistentCookieStore::CompleteKeyedLoad, this,
key, std::move(loaded_callback)));
}
void SQLitePersistentCookieStore::AddCookie(const CanonicalCookie& cc) {
backend_->AddCookie(cc);
}
void SQLitePersistentCookieStore::UpdateCookieAccessTime(
const CanonicalCookie& cc) {
backend_->UpdateCookieAccessTime(cc);
}
void SQLitePersistentCookieStore::DeleteCookie(const CanonicalCookie& cc) {
backend_->DeleteCookie(cc);
}
void SQLitePersistentCookieStore::SetForceKeepSessionState() {
}
void SQLitePersistentCookieStore::SetBeforeCommitCallback(
base::RepeatingClosure callback) {
backend_->SetBeforeCommitCallback(std::move(callback));
}
void SQLitePersistentCookieStore::Flush(base::OnceClosure callback) {
backend_->Flush(std::move(callback));
}
size_t SQLitePersistentCookieStore::GetQueueLengthForTesting() {
return backend_->GetQueueLengthForTesting();
}
SQLitePersistentCookieStore::~SQLitePersistentCookieStore() {
net_log_.AddEventWithStringParams(
NetLogEventType::COOKIE_PERSISTENT_STORE_CLOSED, "type",
"SQLitePersistentCookieStore");
backend_->Close();
}
void SQLitePersistentCookieStore::CompleteLoad(
LoadedCallback callback,
std::vector<std::unique_ptr<CanonicalCookie>> cookie_list) {
net_log_.EndEvent(NetLogEventType::COOKIE_PERSISTENT_STORE_LOAD);
std::move(callback).Run(std::move(cookie_list));
}
void SQLitePersistentCookieStore::CompleteKeyedLoad(
const std::string& key,
LoadedCallback callback,
std::vector<std::unique_ptr<CanonicalCookie>> cookie_list) {
net_log_.AddEventWithStringParams(
NetLogEventType::COOKIE_PERSISTENT_STORE_KEY_LOAD_COMPLETED, "domain",
key);
std::move(callback).Run(std::move(cookie_list));
}
}