1// Copyright 2013 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" 6 7#include "base/callback.h" 8#include "base/command_line.h" 9#include "base/files/file_path.h" 10#include "base/json/json_reader.h" 11#include "base/json/json_string_value_serializer.h" 12#include "base/logging.h" 13#include "base/strings/string16.h" 14#include "base/strings/stringprintf.h" 15#include "chrome/browser/extensions/activity_log/activity_action_constants.h" 16#include "chrome/browser/extensions/activity_log/activity_database.h" 17#include "chrome/browser/profiles/profile.h" 18#include "chrome/common/chrome_constants.h" 19#include "chrome/common/chrome_switches.h" 20#include "extensions/common/dom_action_types.h" 21#include "extensions/common/extension.h" 22#include "sql/error_delegate_util.h" 23#include "sql/transaction.h" 24#include "url/gurl.h" 25 26using base::Callback; 27using base::FilePath; 28using base::Time; 29using base::Unretained; 30using content::BrowserThread; 31 32namespace constants = activity_log_constants; 33 34namespace extensions { 35 36const char* FullStreamUIPolicy::kTableName = "activitylog_full"; 37const char* FullStreamUIPolicy::kTableContentFields[] = { 38 "extension_id", "time", "action_type", "api_name", "args", "page_url", 39 "page_title", "arg_url", "other" 40}; 41const char* FullStreamUIPolicy::kTableFieldTypes[] = { 42 "LONGVARCHAR NOT NULL", "INTEGER", "INTEGER", "LONGVARCHAR", "LONGVARCHAR", 43 "LONGVARCHAR", "LONGVARCHAR", "LONGVARCHAR", "LONGVARCHAR" 44}; 45const int FullStreamUIPolicy::kTableFieldCount = 46 arraysize(FullStreamUIPolicy::kTableContentFields); 47 48FullStreamUIPolicy::FullStreamUIPolicy(Profile* profile) 49 : ActivityLogDatabasePolicy( 50 profile, 51 FilePath(chrome::kExtensionActivityLogFilename)) {} 52 53FullStreamUIPolicy::~FullStreamUIPolicy() {} 54 55bool FullStreamUIPolicy::InitDatabase(sql::Connection* db) { 56 if (!Util::DropObsoleteTables(db)) 57 return false; 58 59 // Create the unified activity log entry table. 60 return ActivityDatabase::InitializeTable(db, 61 kTableName, 62 kTableContentFields, 63 kTableFieldTypes, 64 arraysize(kTableContentFields)); 65} 66 67bool FullStreamUIPolicy::FlushDatabase(sql::Connection* db) { 68 if (queued_actions_.empty()) 69 return true; 70 71 sql::Transaction transaction(db); 72 if (!transaction.Begin()) 73 return false; 74 75 std::string sql_str = 76 "INSERT INTO " + std::string(FullStreamUIPolicy::kTableName) + 77 " (extension_id, time, action_type, api_name, args, " 78 "page_url, page_title, arg_url, other) VALUES (?,?,?,?,?,?,?,?,?)"; 79 sql::Statement statement(db->GetCachedStatement( 80 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 81 82 Action::ActionVector::size_type i; 83 for (i = 0; i != queued_actions_.size(); ++i) { 84 const Action& action = *queued_actions_[i].get(); 85 statement.Reset(true); 86 statement.BindString(0, action.extension_id()); 87 statement.BindInt64(1, action.time().ToInternalValue()); 88 statement.BindInt(2, static_cast<int>(action.action_type())); 89 statement.BindString(3, action.api_name()); 90 if (action.args()) { 91 statement.BindString(4, Util::Serialize(action.args())); 92 } 93 std::string page_url_string = action.SerializePageUrl(); 94 if (!page_url_string.empty()) { 95 statement.BindString(5, page_url_string); 96 } 97 if (!action.page_title().empty()) { 98 statement.BindString(6, action.page_title()); 99 } 100 std::string arg_url_string = action.SerializeArgUrl(); 101 if (!arg_url_string.empty()) { 102 statement.BindString(7, arg_url_string); 103 } 104 if (action.other()) { 105 statement.BindString(8, Util::Serialize(action.other())); 106 } 107 108 if (!statement.Run()) { 109 LOG(ERROR) << "Activity log database I/O failed: " << sql_str; 110 return false; 111 } 112 } 113 114 if (!transaction.Commit()) 115 return false; 116 117 queued_actions_.clear(); 118 return true; 119} 120 121scoped_ptr<Action::ActionVector> FullStreamUIPolicy::DoReadFilteredData( 122 const std::string& extension_id, 123 const Action::ActionType type, 124 const std::string& api_name, 125 const std::string& page_url, 126 const std::string& arg_url, 127 const int days_ago) { 128 // Ensure data is flushed to the database first so that we query over all 129 // data. 130 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 131 scoped_ptr<Action::ActionVector> actions(new Action::ActionVector()); 132 133 sql::Connection* db = GetDatabaseConnection(); 134 if (!db) { 135 return actions.Pass(); 136 } 137 138 // Build up the query based on which parameters were specified. 139 std::string where_str = ""; 140 std::string where_next = ""; 141 if (!extension_id.empty()) { 142 where_str += "extension_id=?"; 143 where_next = " AND "; 144 } 145 if (!api_name.empty()) { 146 where_str += where_next + "api_name=?"; 147 where_next = " AND "; 148 } 149 if (type != Action::ACTION_ANY) { 150 where_str += where_next + "action_type=?"; 151 where_next = " AND "; 152 } 153 if (!page_url.empty()) { 154 where_str += where_next + "page_url LIKE ?"; 155 where_next = " AND "; 156 } 157 if (!arg_url.empty()) { 158 where_str += where_next + "arg_url LIKE ?"; 159 } 160 if (days_ago >= 0) 161 where_str += where_next + "time BETWEEN ? AND ?"; 162 std::string query_str = base::StringPrintf( 163 "SELECT extension_id,time,action_type,api_name,args,page_url,page_title," 164 "arg_url,other,rowid FROM %s %s %s ORDER BY time DESC LIMIT 300", 165 kTableName, 166 where_str.empty() ? "" : "WHERE", 167 where_str.c_str()); 168 sql::Statement query(db->GetUniqueStatement(query_str.c_str())); 169 int i = -1; 170 if (!extension_id.empty()) 171 query.BindString(++i, extension_id); 172 if (!api_name.empty()) 173 query.BindString(++i, api_name); 174 if (type != Action::ACTION_ANY) 175 query.BindInt(++i, static_cast<int>(type)); 176 if (!page_url.empty()) 177 query.BindString(++i, page_url + "%"); 178 if (!arg_url.empty()) 179 query.BindString(++i, arg_url + "%"); 180 if (days_ago >= 0) { 181 int64 early_bound; 182 int64 late_bound; 183 Util::ComputeDatabaseTimeBounds(Now(), days_ago, &early_bound, &late_bound); 184 query.BindInt64(++i, early_bound); 185 query.BindInt64(++i, late_bound); 186 } 187 188 // Execute the query and get results. 189 while (query.is_valid() && query.Step()) { 190 scoped_refptr<Action> action = 191 new Action(query.ColumnString(0), 192 base::Time::FromInternalValue(query.ColumnInt64(1)), 193 static_cast<Action::ActionType>(query.ColumnInt(2)), 194 query.ColumnString(3), query.ColumnInt64(9)); 195 196 if (query.ColumnType(4) != sql::COLUMN_TYPE_NULL) { 197 scoped_ptr<base::Value> parsed_value( 198 base::JSONReader::Read(query.ColumnString(4))); 199 if (parsed_value && parsed_value->IsType(base::Value::TYPE_LIST)) { 200 action->set_args(make_scoped_ptr( 201 static_cast<base::ListValue*>(parsed_value.release()))); 202 } 203 } 204 205 action->ParsePageUrl(query.ColumnString(5)); 206 action->set_page_title(query.ColumnString(6)); 207 action->ParseArgUrl(query.ColumnString(7)); 208 209 if (query.ColumnType(8) != sql::COLUMN_TYPE_NULL) { 210 scoped_ptr<base::Value> parsed_value( 211 base::JSONReader::Read(query.ColumnString(8))); 212 if (parsed_value && parsed_value->IsType(base::Value::TYPE_DICTIONARY)) { 213 action->set_other(make_scoped_ptr( 214 static_cast<base::DictionaryValue*>(parsed_value.release()))); 215 } 216 } 217 actions->push_back(action); 218 } 219 220 return actions.Pass(); 221} 222 223void FullStreamUIPolicy::DoRemoveActions(const std::vector<int64>& action_ids) { 224 if (action_ids.empty()) 225 return; 226 227 sql::Connection* db = GetDatabaseConnection(); 228 if (!db) { 229 LOG(ERROR) << "Unable to connect to database"; 230 return; 231 } 232 233 // Flush data first so the activity removal affects queued-up data as well. 234 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 235 236 sql::Transaction transaction(db); 237 if (!transaction.Begin()) 238 return; 239 240 std::string statement_str = 241 base::StringPrintf("DELETE FROM %s WHERE rowid = ?", kTableName); 242 sql::Statement statement(db->GetCachedStatement( 243 sql::StatementID(SQL_FROM_HERE), statement_str.c_str())); 244 for (size_t i = 0; i < action_ids.size(); i++) { 245 statement.Reset(true); 246 statement.BindInt64(0, action_ids[i]); 247 if (!statement.Run()) { 248 LOG(ERROR) << "Removing activities from database failed: " 249 << statement.GetSQLStatement(); 250 return; 251 } 252 } 253 254 if (!transaction.Commit()) { 255 LOG(ERROR) << "Removing activities from database failed"; 256 } 257} 258 259void FullStreamUIPolicy::DoRemoveURLs(const std::vector<GURL>& restrict_urls) { 260 sql::Connection* db = GetDatabaseConnection(); 261 if (!db) { 262 LOG(ERROR) << "Unable to connect to database"; 263 return; 264 } 265 266 // Make sure any queued in memory are sent to the database before cleaning. 267 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 268 269 // If no restrictions then then all URLs need to be removed. 270 if (restrict_urls.empty()) { 271 sql::Statement statement; 272 std::string sql_str = base::StringPrintf( 273 "UPDATE %s SET page_url=NULL,page_title=NULL,arg_url=NULL", 274 kTableName); 275 statement.Assign(db->GetCachedStatement( 276 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 277 278 if (!statement.Run()) { 279 LOG(ERROR) << "Removing URLs from database failed: " 280 << statement.GetSQLStatement(); 281 } 282 return; 283 } 284 285 // If URLs are specified then restrict to only those URLs. 286 for (size_t i = 0; i < restrict_urls.size(); ++i) { 287 if (!restrict_urls[i].is_valid()) { 288 continue; 289 } 290 291 // Remove any matching page url info. 292 sql::Statement statement; 293 std::string sql_str = base::StringPrintf( 294 "UPDATE %s SET page_url=NULL,page_title=NULL WHERE page_url=?", 295 kTableName); 296 statement.Assign(db->GetCachedStatement( 297 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 298 statement.BindString(0, restrict_urls[i].spec()); 299 300 if (!statement.Run()) { 301 LOG(ERROR) << "Removing page URL from database failed: " 302 << statement.GetSQLStatement(); 303 return; 304 } 305 306 // Remove any matching arg urls. 307 sql_str = base::StringPrintf("UPDATE %s SET arg_url=NULL WHERE arg_url=?", 308 kTableName); 309 statement.Assign(db->GetCachedStatement( 310 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 311 statement.BindString(0, restrict_urls[i].spec()); 312 313 if (!statement.Run()) { 314 LOG(ERROR) << "Removing arg URL from database failed: " 315 << statement.GetSQLStatement(); 316 return; 317 } 318 } 319} 320 321void FullStreamUIPolicy::DoRemoveExtensionData( 322 const std::string& extension_id) { 323 if (extension_id.empty()) 324 return; 325 326 sql::Connection* db = GetDatabaseConnection(); 327 if (!db) { 328 LOG(ERROR) << "Unable to connect to database"; 329 return; 330 } 331 332 // Make sure any queued in memory are sent to the database before cleaning. 333 activity_database()->AdviseFlush(ActivityDatabase::kFlushImmediately); 334 335 std::string sql_str = base::StringPrintf( 336 "DELETE FROM %s WHERE extension_id=?", kTableName); 337 sql::Statement statement; 338 statement.Assign( 339 db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 340 statement.BindString(0, extension_id); 341 if (!statement.Run()) { 342 LOG(ERROR) << "Removing URLs for extension " 343 << extension_id << "from database failed: " 344 << statement.GetSQLStatement(); 345 } 346} 347 348void FullStreamUIPolicy::DoDeleteDatabase() { 349 sql::Connection* db = GetDatabaseConnection(); 350 if (!db) { 351 LOG(ERROR) << "Unable to connect to database"; 352 return; 353 } 354 355 queued_actions_.clear(); 356 357 // Not wrapped in a transaction because the deletion should happen even if 358 // the vacuuming fails. 359 std::string sql_str = base::StringPrintf("DELETE FROM %s;", kTableName); 360 sql::Statement statement(db->GetCachedStatement( 361 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); 362 if (!statement.Run()) { 363 LOG(ERROR) << "Deleting the database failed: " 364 << statement.GetSQLStatement(); 365 return; 366 } 367 statement.Clear(); 368 statement.Assign(db->GetCachedStatement(sql::StatementID(SQL_FROM_HERE), 369 "VACUUM")); 370 if (!statement.Run()) { 371 LOG(ERROR) << "Vacuuming the database failed: " 372 << statement.GetSQLStatement(); 373 } 374} 375 376void FullStreamUIPolicy::OnDatabaseFailure() { 377 queued_actions_.clear(); 378} 379 380void FullStreamUIPolicy::OnDatabaseClose() { 381 delete this; 382} 383 384void FullStreamUIPolicy::Close() { 385 // The policy object should have never been created if there's no DB thread. 386 DCHECK(BrowserThread::IsMessageLoopValid(BrowserThread::DB)); 387 ScheduleAndForget(activity_database(), &ActivityDatabase::Close); 388} 389 390void FullStreamUIPolicy::ReadFilteredData( 391 const std::string& extension_id, 392 const Action::ActionType type, 393 const std::string& api_name, 394 const std::string& page_url, 395 const std::string& arg_url, 396 const int days_ago, 397 const base::Callback 398 <void(scoped_ptr<Action::ActionVector>)>& callback) { 399 BrowserThread::PostTaskAndReplyWithResult( 400 BrowserThread::DB, 401 FROM_HERE, 402 base::Bind(&FullStreamUIPolicy::DoReadFilteredData, 403 base::Unretained(this), 404 extension_id, 405 type, 406 api_name, 407 page_url, 408 arg_url, 409 days_ago), 410 callback); 411} 412 413void FullStreamUIPolicy::RemoveActions(const std::vector<int64>& action_ids) { 414 ScheduleAndForget(this, &FullStreamUIPolicy::DoRemoveActions, action_ids); 415} 416 417void FullStreamUIPolicy::RemoveURLs(const std::vector<GURL>& restrict_urls) { 418 ScheduleAndForget(this, &FullStreamUIPolicy::DoRemoveURLs, restrict_urls); 419} 420 421void FullStreamUIPolicy::RemoveExtensionData(const std::string& extension_id) { 422 ScheduleAndForget( 423 this, &FullStreamUIPolicy::DoRemoveExtensionData, extension_id); 424} 425 426void FullStreamUIPolicy::DeleteDatabase() { 427 ScheduleAndForget(this, &FullStreamUIPolicy::DoDeleteDatabase); 428} 429 430scoped_refptr<Action> FullStreamUIPolicy::ProcessArguments( 431 scoped_refptr<Action> action) const { 432 return action; 433} 434 435void FullStreamUIPolicy::ProcessAction(scoped_refptr<Action> action) { 436 // TODO(mvrable): Right now this argument stripping updates the Action object 437 // in place, which isn't good if there are other users of the object. When 438 // database writing is moved to policy class, the modifications should be 439 // made locally. 440 action = ProcessArguments(action); 441 ScheduleAndForget(this, &FullStreamUIPolicy::QueueAction, action); 442} 443 444void FullStreamUIPolicy::QueueAction(scoped_refptr<Action> action) { 445 if (activity_database()->is_db_valid()) { 446 queued_actions_.push_back(action); 447 activity_database()->AdviseFlush(queued_actions_.size()); 448 } 449} 450 451} // namespace extensions 452