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