blob: 70fbe5a847d4a25be8df5a8ed9c23a0611c58d39 [file] [log] [blame]
// Copyright 2020 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 "chrome/browser/browsing_data/access_context_audit_database.h"
#include <tuple>
#include "base/files/file_util.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/rand_util.h"
#include "components/browsing_data/core/features.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "net/cookies/cookie_util.h"
#include "sql/database.h"
#include "sql/error_delegate_util.h"
#include "sql/meta_table.h"
#include "sql/statement.h"
#include "sql/transaction.h"
namespace {
const base::FilePath::CharType kDatabaseName[] =
FILE_PATH_LITERAL("AccessContextAudit");
static const int kVersionNumber = 1;
// Callback that is fired upon an SQLite error, razes the database if the error
// is considered catastrphoic.
void DatabaseErrorCallback(sql::Database* db,
const base::FilePath& db_path,
int extended_error,
sql::Statement* stmt) {
if (sql::IsErrorCatastrophic(extended_error)) {
// Prevent reentrant calls.
db->reset_error_callback();
// After this call, the |db| handle is poisoned so that future calls will
// return errors until the handle is re-opened.
db->RazeAndClose();
// The DLOG(WARNING) below is intended to draw immediate attention to errors
// in newly-written code. Database corruption is generally a result of OS
// or hardware issues, not coding errors at the client level, so displaying
// the error would probably lead to confusion. The ignored call signals the
// test-expectation framework that the error was handled.
std::ignore = sql::Database::IsExpectedSqliteError(extended_error);
return;
}
// The default handling is to assert on debug and to ignore on release.
if (!sql::Database::IsExpectedSqliteError(extended_error))
DLOG(FATAL) << db->GetErrorMessage();
}
// Returns true if a cookie table already exists in |db|, but is missing the
// is_persistent field.
bool CookieTableMissingIsPersistent(sql::Database* db) {
if (!db->DoesTableExist("cookies"))
return false;
return !db->DoesColumnExist("cookies", "is_persistent");
}
// Removes all cookie records in |db| with is_persistent = false.
bool DeleteNonPersistentCookies(sql::Database* db) {
const char kRemoveNonPersistent[] =
"DELETE FROM cookies WHERE is_persistent != 1";
return db->Execute(kRemoveNonPersistent);
}
bool IsContentSettingSessionOnly(
const GURL& url,
const ContentSettingsForOneType& content_settings) {
// ContentSettingsForOneType are in order of decreasing specificity, such
// that the first matching entry defines the effective content setting.
for (const auto& setting : content_settings) {
// A match is performed against both primary and secondary patterns. This
// aligns with the behavior in CookieSettingsBase::ShouldDeleteCookieOnExit,
// which is used by the cookie store.
if (setting.primary_pattern.Matches(url) &&
setting.secondary_pattern.Matches(url)) {
return setting.GetContentSetting() ==
ContentSetting::CONTENT_SETTING_SESSION_ONLY;
}
}
NOTREACHED();
return false;
}
} // namespace
AccessContextAuditDatabase::AccessRecord::AccessRecord(
const url::Origin& top_frame_origin,
const std::string& name,
const std::string& domain,
const std::string& path,
const base::Time& last_access_time,
bool is_persistent)
: top_frame_origin(top_frame_origin),
type(StorageAPIType::kCookie),
name(name),
domain(domain),
path(path),
last_access_time(last_access_time),
is_persistent(is_persistent) {}
AccessContextAuditDatabase::AccessRecord::AccessRecord(
const url::Origin& top_frame_origin,
const StorageAPIType& type,
const url::Origin& origin,
const base::Time& last_access_time)
: top_frame_origin(top_frame_origin),
type(type),
origin(origin),
last_access_time(last_access_time) {
DCHECK(type != StorageAPIType::kCookie);
}
AccessContextAuditDatabase::AccessRecord::~AccessRecord() = default;
AccessContextAuditDatabase::AccessRecord::AccessRecord(
const AccessRecord& other) = default;
AccessContextAuditDatabase::AccessRecord&
AccessContextAuditDatabase::AccessRecord::operator=(const AccessRecord& other) =
default;
AccessContextAuditDatabase::AccessContextAuditDatabase(
const base::FilePath& path_to_database_dir)
: db_({.exclusive_locking = true,
.page_size = 4096,
// Cache values generated assuming ~5000 individual pieces of client
// storage API data, each accessed in an average of 3 different
// contexts (complete speculation, most will be 1, some will be >50),
// with an average of 40bytes per audit entry.
// TODO(crbug.com/1083384): Revist these numbers.
.cache_size = 128}),
db_file_path_(path_to_database_dir.Append(kDatabaseName)) {}
void AccessContextAuditDatabase::Init(bool restore_non_persistent_cookies) {
db_.set_histogram_tag("Access Context Audit");
db_.set_error_callback(
base::BindRepeating(&DatabaseErrorCallback, &db_, db_file_path_));
if (!db_.Open(db_file_path_))
return;
// Scope database initialisation in a transaction.
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
if (!meta_table_.Init(&db_, kVersionNumber, kVersionNumber))
return;
if (meta_table_.GetCompatibleVersionNumber() > kVersionNumber) {
LOG(ERROR) << "Access Context Audit database is too new, kVersionNumber"
<< kVersionNumber << ", GetCompatibleVersionNumber="
<< meta_table_.GetCompatibleVersionNumber();
// No error will have been caught by the SQLite error handler, manually
// shut the the database.
transaction.Rollback();
db_.Close();
return;
}
if (!InitializeSchema())
return;
if (!restore_non_persistent_cookies)
DeleteNonPersistentCookies(&db_);
transaction.Commit();
// Computing metrics is costly, only perform it in 1% of startups.
if (base::RandInt(1, 100) == 50)
ComputeDatabaseMetrics();
}
bool AccessContextAuditDatabase::InitializeSchema() {
if (CookieTableMissingIsPersistent(&db_)) {
// Simply remove the table in this case. Due to a flag misconfiguration this
// version of the table was pushed to all canary users for a short period.
// TODO(crbug.com/1102006): Remove this code before M86 branch point.
const char kDropCookiesTable[] = "DROP TABLE cookies";
if (!db_.Execute(kDropCookiesTable))
return false;
}
const char kCreateCookiesTable[] =
"CREATE TABLE IF NOT EXISTS cookies "
"(top_frame_origin TEXT NOT NULL,"
"name TEXT NOT NULL,"
"domain TEXT NOT NULL,"
"path TEXT NOT NULL,"
"access_utc INTEGER NOT NULL,"
"is_persistent INTEGER NOT NULL,"
"PRIMARY KEY (top_frame_origin, name, domain, path))";
if (!db_.Execute(kCreateCookiesTable))
return false;
// When user controls to clear third-party data are enabled and a user
// deletes their history, we keep records of cross-site storage access but
// replace the top_frame_origin with an empty string. The empty strings
// get interpreted as opaque origins when we load data from the db into
// AccessRecords.
const char kCreateStorageApiTable[] =
"CREATE TABLE IF NOT EXISTS originStorageAPIs"
"(top_frame_origin TEXT NOT NULL,"
"type INTEGER NOT NULL,"
"origin TEXT NOT NULL,"
"access_utc INTEGER NOT NULL,"
"PRIMARY KEY (top_frame_origin, origin, type))";
return db_.Execute(kCreateStorageApiTable);
}
void AccessContextAuditDatabase::ComputeDatabaseMetrics() {
// Calculate database file size in KB.
int64_t file_size = 0;
if (!base::GetFileSize(db_file_path_, &file_size))
return;
base::UmaHistogramMemoryKB("Privacy.AccessContextAudit.DatabaseSize",
static_cast<int>(file_size / 1024));
// Count the total number of records stored.
std::string record_count =
"SELECT (SELECT COUNT(*) FROM cookies) + "
"(SELECT COUNT(*) FROM originStorageAPIs)";
sql::Statement record_count_statement(
db_.GetCachedStatement(SQL_FROM_HERE, record_count.c_str()));
base::UmaHistogramCounts100000(
"Privacy.AccessContextAudit.RecordCount",
record_count_statement.Step() ? record_count_statement.ColumnInt(0) : 0);
// Count the unique number of unique top frame origins.
std::string top_frame_origin_count =
"SELECT COUNT(*) FROM (SELECT DISTINCT top_frame_origin FROM cookies"
" UNION SELECT DISTINCT top_frame_origin FROM originStorageAPIs)";
sql::Statement top_frame_origin_count_statement(
db_.GetCachedStatement(SQL_FROM_HERE, top_frame_origin_count.c_str()));
base::UmaHistogramCounts10000(
"Privacy.AccessContextAudit.TopFrameOriginCount",
top_frame_origin_count_statement.Step()
? top_frame_origin_count_statement.ColumnInt(0)
: 0);
// Count the number of unique origins using origin keyed storage APIs.
std::string storage_origin_count =
"SELECT COUNT(DISTINCT origin) FROM originStorageAPIs";
sql::Statement storage_origin_count_statement(
db_.GetCachedStatement(SQL_FROM_HERE, storage_origin_count.c_str()));
base::UmaHistogramCounts10000(
"Privacy.AccessContextAudit.StorageOriginCount",
storage_origin_count_statement.Step()
? storage_origin_count_statement.ColumnInt(0)
: 0);
// Count the number of unique cookie domains.
std::string cookie_domain_count =
"SELECT COUNT(DISTINCT domain) FROM cookies";
sql::Statement cookie_domain_count_statement(
db_.GetCachedStatement(SQL_FROM_HERE, cookie_domain_count.c_str()));
base::UmaHistogramCounts10000("Privacy.AccessContextAudit.CookieDomainCount",
cookie_domain_count_statement.Step()
? cookie_domain_count_statement.ColumnInt(0)
: 0);
}
void AccessContextAuditDatabase::AddRecords(
const std::vector<AccessContextAuditDatabase::AccessRecord>& records) {
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
// Create both insert statements ahead of iterating over records. These are
// highly likely to both be used, and should be in the statement cache.
const char kInsertCookieRecord[] =
"INSERT OR REPLACE INTO cookies "
"(top_frame_origin, name, domain, path, access_utc, is_persistent) "
"VALUES (?, ?, ?, ?, ?, ?)";
sql::Statement insert_cookie(
db_.GetCachedStatement(SQL_FROM_HERE, kInsertCookieRecord));
const char kInsertStorageApiRecord[] =
"INSERT OR REPLACE INTO originStorageAPIs"
"(top_frame_origin, type, origin, access_utc) "
"VALUES (?, ?, ?, ?)";
sql::Statement insert_storage_api(
db_.GetCachedStatement(SQL_FROM_HERE, kInsertStorageApiRecord));
for (const auto& record : records) {
if (record.type == StorageAPIType::kCookie) {
insert_cookie.BindString(0, record.top_frame_origin.Serialize());
insert_cookie.BindString(1, record.name);
insert_cookie.BindString(2, record.domain);
insert_cookie.BindString(3, record.path);
insert_cookie.BindTime(4, record.last_access_time);
insert_cookie.BindBool(5, record.is_persistent);
if (!insert_cookie.Run())
return;
insert_cookie.Reset(true);
} else {
insert_storage_api.BindString(0,
record.top_frame_origin.opaque()
? ""
: record.top_frame_origin.Serialize());
insert_storage_api.BindInt(1, static_cast<int>(record.type));
insert_storage_api.BindString(2, record.origin.Serialize());
insert_storage_api.BindTime(3, record.last_access_time);
if (!insert_storage_api.Run())
return;
insert_storage_api.Reset(true);
}
}
transaction.Commit();
}
void AccessContextAuditDatabase::RemoveRecord(const AccessRecord& record) {
sql::Statement remove_statement;
if (record.type == StorageAPIType::kCookie) {
const char kRemoveCookieRecord[] =
"DELETE FROM cookies WHERE top_frame_origin = ? AND name = ? AND "
"domain = ? AND path = ?";
remove_statement.Assign(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveCookieRecord));
remove_statement.BindString(0, record.top_frame_origin.Serialize());
remove_statement.BindString(1, record.name);
remove_statement.BindString(2, record.domain);
remove_statement.BindString(3, record.path);
} else {
const char kRemoveStorageApiRecord[] =
"DELETE FROM originStorageAPIs WHERE top_frame_origin = ? AND type = ? "
"AND origin = ?";
remove_statement.Assign(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveStorageApiRecord));
remove_statement.BindString(0, record.top_frame_origin.Serialize());
remove_statement.BindInt(1, static_cast<int>(record.type));
remove_statement.BindString(2, record.origin.Serialize());
}
remove_statement.Run();
}
void AccessContextAuditDatabase::RemoveAllRecords() {
// Perform deletions with the WHERE clause omitted to trigger the SQLite table
// truncation optimisation.
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
const char kClearCookiesTable[] = "DELETE FROM cookies";
if (!db_.Execute(kClearCookiesTable))
return;
const char kClearStorageApiTable[] = "DELETE FROM originStorageAPIs";
if (!db_.Execute(kClearStorageApiTable))
return;
transaction.Commit();
}
namespace {
bool IsSameSite(const url::Origin& origin1, const url::Origin& origin2) {
return net::registry_controlled_domains::SameDomainOrHost(
origin1, origin2,
net::registry_controlled_domains::INCLUDE_PRIVATE_REGISTRIES);
}
std::vector<AccessContextAuditDatabase::AccessRecord>
SelectCrossSiteStorageRecordsWithoutTopLevelOrigins(
const std::vector<AccessContextAuditDatabase::AccessRecord>&
storage_records) {
std::map<std::tuple<url::Origin, AccessContextAuditDatabase::StorageAPIType>,
base::Time>
storage_to_last_access_map;
for (const auto& record : storage_records) {
if (!IsSameSite(record.top_frame_origin, record.origin)) {
auto key = std::make_tuple(record.origin, record.type);
auto it = storage_to_last_access_map.find(key);
// We check the map to see if we have a cross-site storage access record
// for the storage origin and type. Since this may coalesce multiple
// cross-site storage records into one, we want to record the most recent
// cross-site storage access time.
if (it == storage_to_last_access_map.end() ||
it->second < record.last_access_time) {
storage_to_last_access_map[key] = record.last_access_time;
}
}
}
std::vector<AccessContextAuditDatabase::AccessRecord> result;
for (const auto& item : storage_to_last_access_map) {
result.emplace_back(url::Origin(),
/* type= */ std::get<1>(item.first),
/* storage_origin= */ std::get<0>(item.first),
/* last_access_time= */ item.second);
}
return result;
}
} // namespace
void AccessContextAuditDatabase::RemoveAllRecordsHistory() {
std::vector<AccessContextAuditDatabase::AccessRecord>
cross_site_storage_records;
if (base::FeatureList::IsEnabled(
browsing_data::features::kEnableRemovingAllThirdPartyCookies)) {
cross_site_storage_records =
SelectCrossSiteStorageRecordsWithoutTopLevelOrigins(
GetStorageRecords());
}
RemoveAllRecords();
AddRecords(cross_site_storage_records);
}
void AccessContextAuditDatabase::RemoveAllRecordsForTimeRange(base::Time begin,
base::Time end) {
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
const char kRemoveCookieRecords[] =
"DELETE FROM cookies WHERE access_utc BETWEEN ? AND ?";
sql::Statement remove_cookies(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveCookieRecords));
remove_cookies.BindTime(0, begin);
remove_cookies.BindTime(1, end);
if (!remove_cookies.Run())
return;
const char kRemoveStorageApiRecords[] =
"DELETE FROM originStorageAPIs WHERE access_utc BETWEEN ? AND ?";
sql::Statement remove_storage_apis(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveStorageApiRecords));
remove_storage_apis.BindTime(0, begin);
remove_storage_apis.BindTime(1, end);
if (!remove_storage_apis.Run())
return;
transaction.Commit();
}
void AccessContextAuditDatabase::RemoveAllRecordsForTimeRangeHistory(
base::Time begin,
base::Time end) {
std::vector<AccessContextAuditDatabase::AccessRecord>
cross_site_storage_records;
if (base::FeatureList::IsEnabled(
browsing_data::features::kEnableRemovingAllThirdPartyCookies)) {
cross_site_storage_records =
SelectCrossSiteStorageRecordsWithoutTopLevelOrigins(
GetStorageRecordsForTimeRange(begin, end));
}
RemoveAllRecordsForTimeRange(begin, end);
AddRecords(cross_site_storage_records);
}
void AccessContextAuditDatabase::RemoveSessionOnlyRecords(
const ContentSettingsForOneType& content_settings) {
// ContentSettingsForOneType is a list of settings in decreasing specificity
// for origins, ending with a setting that matches all and is the default.
DCHECK(content_settings.size());
if (content_settings.size() == 1) {
DCHECK_EQ(content_settings[0].primary_pattern,
ContentSettingsPattern::Wildcard());
DCHECK_EQ(content_settings[0].secondary_pattern,
ContentSettingsPattern::Wildcard());
if (content_settings[0].GetContentSetting() ==
ContentSetting::CONTENT_SETTING_SESSION_ONLY) {
RemoveAllRecords();
}
// As only the default content setting is set, there is no need to inspect
// any individual records.
return;
}
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
// Extract the set of all domains from the cookies table, determine the
// effective content setting, and store for removal if appropriate.
const char kSelectCookieDomains[] = "SELECT DISTINCT domain FROM cookies";
sql::Statement select_cookie_domains(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectCookieDomains));
std::vector<std::string> cookie_domains_for_removal;
while (select_cookie_domains.Step()) {
auto domain = select_cookie_domains.ColumnString(0);
GURL url = net::cookie_util::CookieOriginToURL(domain,
/* is_https */ false);
GURL secure_url = net::cookie_util::CookieOriginToURL(domain,
/* is_https */ true);
if (IsContentSettingSessionOnly(url, content_settings) ||
IsContentSettingSessionOnly(secure_url, content_settings)) {
cookie_domains_for_removal.emplace_back(std::move(domain));
}
}
// Repeat the above, but for the origin keyed storage API table.
const char kSelectStorageOrigins[] =
"SELECT DISTINCT origin FROM originStorageAPIs";
sql::Statement select_storage_origins(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectStorageOrigins));
std::vector<std::string> storage_origins_for_removal;
while (select_storage_origins.Step()) {
auto origin = select_storage_origins.ColumnString(0);
if (IsContentSettingSessionOnly(GURL(origin), content_settings))
storage_origins_for_removal.emplace_back(origin);
}
// Remove entries belonging to cookie domains and origins identified as having
// a SESSION_ONLY content setting.
const char kRemoveCookieRecords[] = "DELETE FROM cookies WHERE domain = ?";
sql::Statement remove_cookies(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveCookieRecords));
for (const auto& domain : cookie_domains_for_removal) {
remove_cookies.BindString(0, domain);
if (!remove_cookies.Run())
return;
remove_cookies.Reset(true);
}
const char kRemoveStorageApiRecords[] =
"DELETE FROM originStorageAPIs WHERE origin = ?";
sql::Statement remove_storage_apis(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveStorageApiRecords));
for (const auto& origin : storage_origins_for_removal) {
remove_storage_apis.BindString(0, origin);
if (!remove_storage_apis.Run())
return;
remove_storage_apis.Reset(true);
}
transaction.Commit();
}
void AccessContextAuditDatabase::RemoveAllRecordsForCookie(
const std::string& name,
const std::string& domain,
const std::string& path) {
const char kRemoveCookieRecords[] =
"DELETE FROM cookies WHERE name = ? AND domain = ? AND path = ?";
sql::Statement remove_statement(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveCookieRecords));
remove_statement.BindString(0, name);
remove_statement.BindString(1, domain);
remove_statement.BindString(2, path);
remove_statement.Run();
}
void AccessContextAuditDatabase::RemoveAllRecordsForOriginKeyedStorage(
const url::Origin& origin,
StorageAPIType type) {
const char kRemoveStorageApiRecords[] =
"DELETE FROM originStorageAPIs WHERE origin = ? AND type = ?";
sql::Statement remove_statement(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveStorageApiRecords));
remove_statement.BindString(0, origin.Serialize());
remove_statement.BindInt(1, static_cast<int>(type));
remove_statement.Run();
}
void AccessContextAuditDatabase::RemoveAllRecordsForTopFrameOrigins(
const std::vector<url::Origin>& origins) {
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
// If user controls to remove cross-site cookies are enabled, we need to keep
// track of cross-site storage access records. We need to clear storage for
// these origins so that they cannot be used to recover the cross-site cookies
// after they are removed.
std::vector<AccessContextAuditDatabase::AccessRecord>
cross_site_storage_records;
if (base::FeatureList::IsEnabled(
browsing_data::features::kEnableRemovingAllThirdPartyCookies)) {
cross_site_storage_records =
SelectCrossSiteStorageRecordsWithoutTopLevelOrigins(
GetStorageRecordsForTopFrameOrigins(origins));
}
// Remove all records with a top frame origin present in |origins| from both
// the cookies and storage API tables.
const char kRemoveTopFrameFromCookies[] =
"DELETE FROM cookies WHERE top_frame_origin = ?";
sql::Statement remove_cookies(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveTopFrameFromCookies));
const char kRemoveTopFrameFromStorageApis[] =
"DELETE FROM originStorageAPIs WHERE top_frame_origin = ?";
sql::Statement remove_storage_apis(
db_.GetCachedStatement(SQL_FROM_HERE, kRemoveTopFrameFromStorageApis));
for (const auto& origin : origins) {
remove_storage_apis.BindString(0, origin.Serialize());
if (!remove_storage_apis.Run())
return;
remove_storage_apis.Reset(true);
remove_cookies.BindString(0, origin.Serialize());
if (!remove_cookies.Run())
return;
remove_cookies.Reset(true);
}
if (!transaction.Commit())
return;
AddRecords(cross_site_storage_records);
}
std::vector<AccessContextAuditDatabase::AccessRecord>
AccessContextAuditDatabase::GetCookieRecords() {
std::vector<AccessContextAuditDatabase::AccessRecord> records;
const char kSelectCookieRecords[] =
"SELECT top_frame_origin, name, domain, path, access_utc, is_persistent "
"FROM cookies";
sql::Statement select_cookies(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectCookieRecords));
while (select_cookies.Step()) {
records.emplace_back(
url::Origin::Create(GURL(select_cookies.ColumnString(0))),
select_cookies.ColumnString(1), select_cookies.ColumnString(2),
select_cookies.ColumnString(3), select_cookies.ColumnTime(4),
select_cookies.ColumnBool(5));
}
return records;
}
namespace {
AccessContextAuditDatabase::AccessRecord StorageAccessRecordFromStatement(
sql::Statement& statement) {
return AccessContextAuditDatabase::AccessRecord(
// If the top-frame origin is empty string, that means we deleted the
// top_frame_origin of a cross-site access record. In this case we set the
// AccessRecord's top_frame_origin to an opaque origin.
statement.ColumnString(0) == ""
? url::Origin()
: url::Origin::Create(GURL(statement.ColumnString(0))),
static_cast<AccessContextAuditDatabase::StorageAPIType>(
statement.ColumnInt(1)),
url::Origin::Create(GURL(statement.ColumnString(2))),
statement.ColumnTime(3));
}
} // namespace
std::vector<AccessContextAuditDatabase::AccessRecord>
AccessContextAuditDatabase::GetStorageRecords() {
std::vector<AccessContextAuditDatabase::AccessRecord> records;
const char kSelectStorageApiRecords[] =
"SELECT top_frame_origin, type, origin, access_utc FROM "
"originStorageAPIs";
sql::Statement select_storage_api(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectStorageApiRecords));
while (select_storage_api.Step()) {
records.emplace_back(StorageAccessRecordFromStatement(select_storage_api));
}
return records;
}
std::vector<AccessContextAuditDatabase::AccessRecord>
AccessContextAuditDatabase::GetStorageRecordsForTopFrameOrigins(
const std::vector<url::Origin>& origins) {
std::vector<AccessContextAuditDatabase::AccessRecord> records;
const char kSelectStorageApiRecords[] =
"SELECT top_frame_origin, type, origin, access_utc FROM "
"originStorageAPIs WHERE top_frame_origin = ?";
sql::Statement select_storage_api(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectStorageApiRecords));
for (const auto& origin : origins) {
select_storage_api.BindString(0, origin.Serialize());
while (select_storage_api.Step()) {
records.emplace_back(
StorageAccessRecordFromStatement(select_storage_api));
}
select_storage_api.Reset(true);
}
return records;
}
std::vector<AccessContextAuditDatabase::AccessRecord>
AccessContextAuditDatabase::GetStorageRecordsForTimeRange(base::Time begin,
base::Time end) {
std::vector<AccessContextAuditDatabase::AccessRecord> records;
const char kSelectStorageApiRecords[] =
"SELECT top_frame_origin, type, origin, access_utc FROM "
"originStorageAPIs WHERE access_utc BETWEEN ? AND ?";
sql::Statement select_storage_api(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectStorageApiRecords));
select_storage_api.BindTime(0, begin);
select_storage_api.BindTime(1, end);
while (select_storage_api.Step()) {
records.emplace_back(StorageAccessRecordFromStatement(select_storage_api));
}
return records;
}
std::vector<AccessContextAuditDatabase::AccessRecord>
AccessContextAuditDatabase::GetAllRecords() {
std::vector<AccessContextAuditDatabase::AccessRecord> records =
GetCookieRecords();
std::vector<AccessContextAuditDatabase::AccessRecord> tmp_records =
GetStorageRecords();
records.insert(records.end(), std::make_move_iterator(tmp_records.begin()),
std::make_move_iterator(tmp_records.end()));
return records;
}
void AccessContextAuditDatabase::RemoveStorageApiRecords(
const std::set<StorageAPIType>& storage_api_types,
base::RepeatingCallback<bool(const url::Origin&)> origin_matcher,
base::Time begin,
base::Time end) {
sql::Transaction transaction(&db_);
if (!transaction.Begin())
return;
// As only the time ranges can be simply expressed as part of an SQL query,
// removing records that match the provided parameters requires first
// retrieving all records within the specified time range from the database.
// The number of records retrieved is sub-optimal by at most a factor of
// StorageAPIType::kMaxType, so we're not missing sub-linear optimization
// opportunity here.
const char kSelectOriginAndType[] =
"SELECT origin, type FROM originStorageAPIs WHERE access_utc BETWEEN ? "
"AND ?";
sql::Statement select_storage_api(
db_.GetCachedStatement(SQL_FROM_HERE, kSelectOriginAndType));
select_storage_api.BindTime(0, begin);
select_storage_api.BindTime(1, end);
// Filter the returned records based on the provided parameters, maintaining
// a list of origin and storage type pairs for removal from the database.
std::vector<std::pair<url::Origin, StorageAPIType>>
origin_type_pairs_for_removal;
while (select_storage_api.Step()) {
auto origin = url::Origin::Create(GURL(select_storage_api.ColumnString(0)));
auto type = static_cast<StorageAPIType>(select_storage_api.ColumnInt(1));
if (storage_api_types.count(type) &&
(!origin_matcher || origin_matcher.Run(origin))) {
origin_type_pairs_for_removal.emplace_back(origin, type);
}
}
const char kDeleteOnOriginAndType[] =
"DELETE FROM originStorageAPIs WHERE origin = ? AND type = ?";
sql::Statement remove_statement(
db_.GetCachedStatement(SQL_FROM_HERE, kDeleteOnOriginAndType));
for (const auto& origin_type : origin_type_pairs_for_removal) {
remove_statement.BindString(0, origin_type.first.Serialize());
remove_statement.BindInt(1, static_cast<int>(origin_type.second));
if (!remove_statement.Run())
return;
remove_statement.Reset(true);
}
transaction.Commit();
}