This source file includes following definitions.
- retention_time_
- InitDatabase
- ProcessAction
- QueueAction
- FlushDatabase
- DoReadFilteredData
- DoRemoveActions
- DoRemoveURLs
- DoRemoveExtensionData
- DoDeleteDatabase
- ReadFilteredData
- RemoveActions
- RemoveURLs
- RemoveExtensionData
- DeleteDatabase
- OnDatabaseFailure
- OnDatabaseClose
- CleanOlderThan
- CleanStringTables
- Close
#include "chrome/browser/extensions/activity_log/counting_policy.h"
#include <map>
#include <string>
#include <vector>
#include "base/callback.h"
#include "base/files/file_path.h"
#include "base/json/json_reader.h"
#include "base/json/json_string_value_serializer.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "chrome/common/chrome_constants.h"
using content::BrowserThread;
namespace {
using extensions::Action;
const int kCleaningDelayInHours = 12;
struct ApiList {
Action::ActionType type;
const char* name;
};
const ApiList kAlwaysLog[] = {
{Action::ACTION_API_CALL, "bookmarks.create"},
{Action::ACTION_API_CALL, "bookmarks.update"},
{Action::ACTION_API_CALL, "cookies.get"},
{Action::ACTION_API_CALL, "cookies.getAll"},
{Action::ACTION_API_CALL, "extension.connect"},
{Action::ACTION_API_CALL, "extension.sendMessage"},
{Action::ACTION_API_CALL, "fileSystem.chooseEntry"},
{Action::ACTION_API_CALL, "socket.bind"},
{Action::ACTION_API_CALL, "socket.connect"},
{Action::ACTION_API_CALL, "socket.create"},
{Action::ACTION_API_CALL, "socket.listen"},
{Action::ACTION_API_CALL, "tabs.executeScript"},
{Action::ACTION_API_CALL, "tabs.insertCSS"},
{Action::ACTION_API_CALL, "types.ChromeSetting.clear"},
{Action::ACTION_API_CALL, "types.ChromeSetting.get"},
{Action::ACTION_API_CALL, "types.ChromeSetting.set"},
{Action::ACTION_CONTENT_SCRIPT, ""},
{Action::ACTION_DOM_ACCESS, "Document.createElement"},
{Action::ACTION_DOM_ACCESS, "Document.createElementNS"},
};
const char* kTableContentFields[] = {
"count", "extension_id_x", "time", "action_type", "api_name_x", "args_x",
"page_url_x", "page_title_x", "arg_url_x", "other_x"};
const char* kTableFieldTypes[] = {
"INTEGER NOT NULL DEFAULT 1", "INTEGER NOT NULL", "INTEGER", "INTEGER",
"INTEGER", "INTEGER", "INTEGER", "INTEGER", "INTEGER",
"INTEGER"};
static const char kPolicyMiscSetup[] =
"DROP VIEW IF EXISTS activitylog_uncompressed;\n"
"CREATE VIEW activitylog_uncompressed AS\n"
"SELECT count,\n"
" x1.value AS extension_id,\n"
" time,\n"
" action_type,\n"
" x2.value AS api_name,\n"
" x3.value AS args,\n"
" x4.value AS page_url,\n"
" x5.value AS page_title,\n"
" x6.value AS arg_url,\n"
" x7.value AS other,\n"
" activitylog_compressed.rowid AS activity_id\n"
"FROM activitylog_compressed\n"
" LEFT JOIN string_ids AS x1 ON (x1.id = extension_id_x)\n"
" LEFT JOIN string_ids AS x2 ON (x2.id = api_name_x)\n"
" LEFT JOIN string_ids AS x3 ON (x3.id = args_x)\n"
" LEFT JOIN url_ids AS x4 ON (x4.id = page_url_x)\n"
" LEFT JOIN string_ids AS x5 ON (x5.id = page_title_x)\n"
" LEFT JOIN url_ids AS x6 ON (x6.id = arg_url_x)\n"
" LEFT JOIN string_ids AS x7 ON (x7.id = other_x);\n"
"CREATE INDEX IF NOT EXISTS activitylog_compressed_index\n"
"ON activitylog_compressed(extension_id_x, action_type, api_name_x,\n"
" args_x, page_url_x, page_title_x, arg_url_x, other_x)";
static const char kStringTableCleanup[] =
"DELETE FROM string_ids WHERE id NOT IN\n"
"(SELECT extension_id_x FROM activitylog_compressed\n"
" WHERE extension_id_x IS NOT NULL\n"
" UNION SELECT api_name_x FROM activitylog_compressed\n"
" WHERE api_name_x IS NOT NULL\n"
" UNION SELECT args_x FROM activitylog_compressed\n"
" WHERE args_x IS NOT NULL\n"
" UNION SELECT page_title_x FROM activitylog_compressed\n"
" WHERE page_title_x IS NOT NULL\n"
" UNION SELECT other_x FROM activitylog_compressed\n"
" WHERE other_x IS NOT NULL)";
static const char kUrlTableCleanup[] =
"DELETE FROM url_ids WHERE id NOT IN\n"
"(SELECT page_url_x FROM activitylog_compressed\n"
" WHERE page_url_x IS NOT NULL\n"
" UNION SELECT arg_url_x FROM activitylog_compressed\n"
" WHERE arg_url_x IS NOT NULL)";
}
namespace extensions {
const char* CountingPolicy::kTableName = "activitylog_compressed";
const char* CountingPolicy::kReadViewName = "activitylog_uncompressed";
CountingPolicy::CountingPolicy(Profile* profile)
: ActivityLogDatabasePolicy(
profile,
base::FilePath(chrome::kExtensionActivityLogFilename)),
string_table_("string_ids"),
url_table_("url_ids"),
retention_time_(base::TimeDelta::FromHours(60)) {
for (size_t i = 0; i < arraysize(kAlwaysLog); i++) {
api_arg_whitelist_.insert(
std::make_pair(kAlwaysLog[i].type, kAlwaysLog[i].name));
}
}
CountingPolicy::~CountingPolicy() {}
bool CountingPolicy::InitDatabase(sql::Connection* db) {
if (!Util::DropObsoleteTables(db))
return false;
if (!string_table_.Initialize(db))
return false;
if (!url_table_.Initialize(db))
return false;
if (!ActivityDatabase::InitializeTable(db,
kTableName,
kTableContentFields,
kTableFieldTypes,
arraysize(kTableContentFields)))
return false;
return db->Execute(kPolicyMiscSetup);
}
void CountingPolicy::ProcessAction(scoped_refptr<Action> action) {
ScheduleAndForget(this, &CountingPolicy::QueueAction, action);
}
void CountingPolicy::QueueAction(scoped_refptr<Action> action) {
if (activity_database()->is_db_valid()) {
action = action->Clone();
Util::StripPrivacySensitiveFields(action);
Util::StripArguments(api_arg_whitelist_, action);
base::Time new_date = action->time().LocalMidnight();
if (new_date != queued_actions_date_)
activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
queued_actions_date_ = new_date;
ActionQueue::iterator queued_entry = queued_actions_.find(action);
if (queued_entry == queued_actions_.end()) {
queued_actions_[action] = 1;
} else {
using std::max;
queued_entry->first->set_time(
max(queued_entry->first->time(), action->time()));
queued_entry->second++;
}
activity_database()->AdviseFlush(queued_actions_.size());
}
}
bool CountingPolicy::FlushDatabase(sql::Connection* db) {
static const char* matched_columns[] = {
"extension_id_x", "action_type", "api_name_x", "args_x", "page_url_x",
"page_title_x", "arg_url_x", "other_x"};
ActionQueue queue;
queue.swap(queued_actions_);
bool clean_database = (last_database_cleaning_time_.is_null() ||
Now() - last_database_cleaning_time_ >
base::TimeDelta::FromHours(kCleaningDelayInHours));
if (queue.empty() && !clean_database)
return true;
sql::Transaction transaction(db);
if (!transaction.Begin())
return false;
std::string locate_str =
"SELECT rowid FROM " + std::string(kTableName) +
" WHERE time >= ? AND time < ?";
std::string insert_str =
"INSERT INTO " + std::string(kTableName) + "(count, time";
std::string update_str =
"UPDATE " + std::string(kTableName) +
" SET count = count + ?, time = max(?, time)"
" WHERE rowid = ?";
for (size_t i = 0; i < arraysize(matched_columns); i++) {
locate_str = base::StringPrintf(
"%s AND %s IS ?", locate_str.c_str(), matched_columns[i]);
insert_str =
base::StringPrintf("%s, %s", insert_str.c_str(), matched_columns[i]);
}
insert_str += ") VALUES (?, ?";
for (size_t i = 0; i < arraysize(matched_columns); i++) {
insert_str += ", ?";
}
locate_str += " ORDER BY time DESC LIMIT 1";
insert_str += ")";
for (ActionQueue::iterator i = queue.begin(); i != queue.end(); ++i) {
const Action& action = *i->first;
int count = i->second;
base::Time day_start = action.time().LocalMidnight();
base::Time next_day = Util::AddDays(day_start, 1);
int64 id;
std::vector<int64> matched_values;
if (!string_table_.StringToInt(db, action.extension_id(), &id))
return false;
matched_values.push_back(id);
matched_values.push_back(static_cast<int>(action.action_type()));
if (!string_table_.StringToInt(db, action.api_name(), &id))
return false;
matched_values.push_back(id);
if (action.args()) {
std::string args = Util::Serialize(action.args());
if (args.length() > 10000) {
args = "[\"<too_large>\"]";
}
if (!string_table_.StringToInt(db, args, &id))
return false;
matched_values.push_back(id);
} else {
matched_values.push_back(-1);
}
std::string page_url_string = action.SerializePageUrl();
if (!page_url_string.empty()) {
if (!url_table_.StringToInt(db, page_url_string, &id))
return false;
matched_values.push_back(id);
} else {
matched_values.push_back(-1);
}
if (!action.page_title().empty()) {
if (!string_table_.StringToInt(db, action.page_title(), &id))
return false;
matched_values.push_back(id);
} else {
matched_values.push_back(-1);
}
std::string arg_url_string = action.SerializeArgUrl();
if (!arg_url_string.empty()) {
if (!url_table_.StringToInt(db, arg_url_string, &id))
return false;
matched_values.push_back(id);
} else {
matched_values.push_back(-1);
}
if (action.other()) {
if (!string_table_.StringToInt(db, Util::Serialize(action.other()), &id))
return false;
matched_values.push_back(id);
} else {
matched_values.push_back(-1);
}
sql::Statement locate_statement(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), locate_str.c_str()));
locate_statement.BindInt64(0, day_start.ToInternalValue());
locate_statement.BindInt64(1, next_day.ToInternalValue());
for (size_t j = 0; j < matched_values.size(); j++) {
if (matched_values[j] == -1)
locate_statement.BindNull(j + 2);
else
locate_statement.BindInt64(j + 2, matched_values[j]);
}
if (locate_statement.Step()) {
int64 rowid = locate_statement.ColumnInt64(0);
sql::Statement update_statement(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), update_str.c_str()));
update_statement.BindInt(0, count);
update_statement.BindInt64(1, action.time().ToInternalValue());
update_statement.BindInt64(2, rowid);
if (!update_statement.Run())
return false;
} else if (locate_statement.Succeeded()) {
sql::Statement insert_statement(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), insert_str.c_str()));
insert_statement.BindInt(0, count);
insert_statement.BindInt64(1, action.time().ToInternalValue());
for (size_t j = 0; j < matched_values.size(); j++) {
if (matched_values[j] == -1)
insert_statement.BindNull(j + 2);
else
insert_statement.BindInt64(j + 2, matched_values[j]);
}
if (!insert_statement.Run())
return false;
} else {
return false;
}
}
if (clean_database) {
base::Time cutoff = (Now() - retention_time()).LocalMidnight();
if (!CleanOlderThan(db, cutoff))
return false;
last_database_cleaning_time_ = Now();
}
if (!transaction.Commit())
return false;
return true;
}
scoped_ptr<Action::ActionVector> CountingPolicy::DoReadFilteredData(
const std::string& extension_id,
const Action::ActionType type,
const std::string& api_name,
const std::string& page_url,
const std::string& arg_url,
const int days_ago) {
activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
scoped_ptr<Action::ActionVector> actions(new Action::ActionVector());
sql::Connection* db = GetDatabaseConnection();
if (!db)
return actions.Pass();
std::string where_str = "";
std::string where_next = "";
if (!extension_id.empty()) {
where_str += "extension_id=?";
where_next = " AND ";
}
if (!api_name.empty()) {
where_str += where_next + "api_name=?";
where_next = " AND ";
}
if (type != Action::ACTION_ANY) {
where_str += where_next + "action_type=?";
where_next = " AND ";
}
if (!page_url.empty()) {
where_str += where_next + "page_url LIKE ?";
where_next = " AND ";
}
if (!arg_url.empty()) {
where_str += where_next + "arg_url LIKE ?";
where_next = " AND ";
}
if (days_ago >= 0)
where_str += where_next + "time BETWEEN ? AND ?";
std::string query_str = base::StringPrintf(
"SELECT extension_id,time, action_type, api_name, args, page_url,"
"page_title, arg_url, other, count, activity_id FROM %s %s %s ORDER BY "
"count DESC, time DESC LIMIT 300",
kReadViewName,
where_str.empty() ? "" : "WHERE",
where_str.c_str());
sql::Statement query(db->GetUniqueStatement(query_str.c_str()));
int i = -1;
if (!extension_id.empty())
query.BindString(++i, extension_id);
if (!api_name.empty())
query.BindString(++i, api_name);
if (type != Action::ACTION_ANY)
query.BindInt(++i, static_cast<int>(type));
if (!page_url.empty())
query.BindString(++i, page_url + "%");
if (!arg_url.empty())
query.BindString(++i, arg_url + "%");
if (days_ago >= 0) {
int64 early_bound;
int64 late_bound;
Util::ComputeDatabaseTimeBounds(Now(), days_ago, &early_bound, &late_bound);
query.BindInt64(++i, early_bound);
query.BindInt64(++i, late_bound);
}
while (query.is_valid() && query.Step()) {
scoped_refptr<Action> action =
new Action(query.ColumnString(0),
base::Time::FromInternalValue(query.ColumnInt64(1)),
static_cast<Action::ActionType>(query.ColumnInt(2)),
query.ColumnString(3), query.ColumnInt64(10));
if (query.ColumnType(4) != sql::COLUMN_TYPE_NULL) {
scoped_ptr<base::Value> parsed_value(
base::JSONReader::Read(query.ColumnString(4)));
if (parsed_value && parsed_value->IsType(base::Value::TYPE_LIST)) {
action->set_args(make_scoped_ptr(
static_cast<base::ListValue*>(parsed_value.release())));
}
}
action->ParsePageUrl(query.ColumnString(5));
action->set_page_title(query.ColumnString(6));
action->ParseArgUrl(query.ColumnString(7));
if (query.ColumnType(8) != sql::COLUMN_TYPE_NULL) {
scoped_ptr<base::Value> parsed_value(
base::JSONReader::Read(query.ColumnString(8)));
if (parsed_value && parsed_value->IsType(base::Value::TYPE_DICTIONARY)) {
action->set_other(make_scoped_ptr(
static_cast<base::DictionaryValue*>(parsed_value.release())));
}
}
action->set_count(query.ColumnInt(9));
actions->push_back(action);
}
return actions.Pass();
}
void CountingPolicy::DoRemoveActions(const std::vector<int64>& action_ids) {
if (action_ids.empty())
return;
sql::Connection* db = GetDatabaseConnection();
if (!db) {
LOG(ERROR) << "Unable to connect to database";
return;
}
activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
sql::Transaction transaction(db);
if (!transaction.Begin())
return;
std::string statement_str =
base::StringPrintf("DELETE FROM %s WHERE rowid = ?", kTableName);
sql::Statement statement(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), statement_str.c_str()));
for (size_t i = 0; i < action_ids.size(); i++) {
statement.Reset(true);
statement.BindInt64(0, action_ids[i]);
if (!statement.Run()) {
LOG(ERROR) << "Removing activities from database failed: "
<< statement.GetSQLStatement();
break;
}
}
CleanStringTables(db);
if (!transaction.Commit()) {
LOG(ERROR) << "Removing activities from database failed";
}
}
void CountingPolicy::DoRemoveURLs(const std::vector<GURL>& restrict_urls) {
sql::Connection* db = GetDatabaseConnection();
if (!db) {
LOG(ERROR) << "Unable to connect to database";
return;
}
activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
if (restrict_urls.empty()) {
std::string sql_str = base::StringPrintf(
"UPDATE %s SET page_url_x=NULL,page_title_x=NULL,arg_url_x=NULL",
kTableName);
sql::Statement statement;
statement.Assign(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
if (!statement.Run()) {
LOG(ERROR) << "Removing all URLs from database failed: "
<< statement.GetSQLStatement();
return;
}
}
for (size_t i = 0; i < restrict_urls.size(); ++i) {
int64 url_id;
if (!restrict_urls[i].is_valid() ||
!url_table_.StringToInt(db, restrict_urls[i].spec(), &url_id)) {
continue;
}
std::string sql_str = base::StringPrintf(
"UPDATE %s SET page_url_x=NULL,page_title_x=NULL WHERE page_url_x IS ?",
kTableName);
sql::Statement statement;
statement.Assign(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
statement.BindInt64(0, url_id);
if (!statement.Run()) {
LOG(ERROR) << "Removing page URL from database failed: "
<< statement.GetSQLStatement();
return;
}
sql_str = base::StringPrintf(
"UPDATE %s SET arg_url_x=NULL WHERE arg_url_x IS ?", kTableName);
statement.Assign(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
statement.BindInt64(0, url_id);
if (!statement.Run()) {
LOG(ERROR) << "Removing arg URL from database failed: "
<< statement.GetSQLStatement();
return;
}
}
CleanStringTables(db);
}
void CountingPolicy::DoRemoveExtensionData(const std::string& extension_id) {
if (extension_id.empty())
return;
sql::Connection* db = GetDatabaseConnection();
if (!db) {
LOG(ERROR) << "Unable to connect to database";
return;
}
activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately);
std::string sql_str = base::StringPrintf(
"DELETE FROM %s WHERE extension_id_x=?", kTableName);
sql::Statement statement(
db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), sql_str.c_str()));
int64 id;
if (string_table_.StringToInt(db, extension_id, &id)) {
statement.BindInt64(0, id);
} else {
statement.Clear();
return;
}
if (!statement.Run()) {
LOG(ERROR) << "Removing URLs for extension "
<< extension_id << "from database failed: "
<< statement.GetSQLStatement();
}
CleanStringTables(db);
}
void CountingPolicy::DoDeleteDatabase() {
sql::Connection* db = GetDatabaseConnection();
if (!db) {
LOG(ERROR) << "Unable to connect to database";
return;
}
queued_actions_.clear();
std::string sql_str = base::StringPrintf("DELETE FROM %s", kTableName);
sql::Statement statement(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE),
sql_str.c_str()));
if (!statement.Run()) {
LOG(ERROR) << "Deleting the database failed: "
<< statement.GetSQLStatement();
return;
}
statement.Clear();
string_table_.ClearCache();
statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
"DELETE FROM string_ids"));
if (!statement.Run()) {
LOG(ERROR) << "Deleting the database failed: "
<< statement.GetSQLStatement();
return;
}
statement.Clear();
url_table_.ClearCache();
statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
"DELETE FROM url_ids"));
if (!statement.Run()) {
LOG(ERROR) << "Deleting the database failed: "
<< statement.GetSQLStatement();
return;
}
statement.Clear();
statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
"VACUUM"));
if (!statement.Run()) {
LOG(ERROR) << "Vacuuming the database failed: "
<< statement.GetSQLStatement();
}
}
void CountingPolicy::ReadFilteredData(
const std::string& extension_id,
const Action::ActionType type,
const std::string& api_name,
const std::string& page_url,
const std::string& arg_url,
const int days_ago,
const base::Callback
<void(scoped_ptr<Action::ActionVector>)>& callback) {
BrowserThread::PostTaskAndReplyWithResult(
BrowserThread::DB,
FROM_HERE,
base::Bind(&CountingPolicy::DoReadFilteredData,
base::Unretained(this),
extension_id,
type,
api_name,
page_url,
arg_url,
days_ago),
callback);
}
void CountingPolicy::RemoveActions(const std::vector<int64>& action_ids) {
ScheduleAndForget(this, &CountingPolicy::DoRemoveActions, action_ids);
}
void CountingPolicy::RemoveURLs(const std::vector<GURL>& restrict_urls) {
ScheduleAndForget(this, &CountingPolicy::DoRemoveURLs, restrict_urls);
}
void CountingPolicy::RemoveExtensionData(const std::string& extension_id) {
ScheduleAndForget(this, &CountingPolicy::DoRemoveExtensionData, extension_id);
}
void CountingPolicy::DeleteDatabase() {
ScheduleAndForget(this, &CountingPolicy::DoDeleteDatabase);
}
void CountingPolicy::OnDatabaseFailure() {
queued_actions_.clear();
}
void CountingPolicy::OnDatabaseClose() {
delete this;
}
bool CountingPolicy::CleanOlderThan(sql::Connection* db,
const base::Time& cutoff) {
std::string clean_statement =
"DELETE FROM " + std::string(kTableName) + " WHERE time < ?";
sql::Statement cleaner(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE),
clean_statement.c_str()));
cleaner.BindInt64(0, cutoff.ToInternalValue());
if (!cleaner.Run())
return false;
return CleanStringTables(db);
}
bool CountingPolicy::CleanStringTables(sql::Connection* db) {
sql::Statement cleaner1(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), kStringTableCleanup));
if (!cleaner1.Run())
return false;
if (db->GetLastChangeCount() > 0)
string_table_.ClearCache();
sql::Statement cleaner2(db->GetCachedStatement(
sql::StatementID(SQL_FROM_HERE), kUrlTableCleanup));
if (!cleaner2.Run())
return false;
if (db->GetLastChangeCount() > 0)
url_table_.ClearCache();
return true;
}
void CountingPolicy::Close() {
DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::DB));
ScheduleAndForget(activity_database(), &ActivityDatabase::Close);
}
}