Chromium Code Reviews| Index: chrome/browser/extensions/activity_log/activity_actions.cc |
| diff --git a/chrome/browser/extensions/activity_log/activity_actions.cc b/chrome/browser/extensions/activity_log/activity_actions.cc |
| index 93e9b68d007391f331461f9f8d97a6ca4e3334fa..89fe55cf28e23c7e214b572700f25fb77d8d97be 100644 |
| --- a/chrome/browser/extensions/activity_log/activity_actions.cc |
| +++ b/chrome/browser/extensions/activity_log/activity_actions.cc |
| @@ -3,10 +3,21 @@ |
| // found in the LICENSE file. |
| #include <string> |
| +#include "base/command_line.h" |
| #include "base/json/json_string_value_serializer.h" |
| #include "base/logging.h" |
| +#include "base/memory/singleton.h" |
| +#include "base/strings/string_number_conversions.h" |
| #include "base/strings/stringprintf.h" |
| #include "chrome/browser/extensions/activity_log/activity_actions.h" |
| +#include "chrome/browser/extensions/activity_log/api_name_constants.h" |
| +#include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" |
| +#include "chrome/browser/extensions/extension_tab_util.h" |
| +#include "chrome/browser/ui/browser.h" |
| +#include "chrome/common/chrome_switches.h" |
| +#include "content/public/browser/browser_thread.h" |
| +#include "content/public/browser/web_contents.h" |
| +#include "sql/statement.h" |
| namespace { |
| @@ -21,51 +32,321 @@ std::string Serialize(const base::Value* value) { |
| return value_as_text; |
| } |
| +// Gets the URL for a given tab ID. Helper method for APIAction::LookupTabId. |
| +std::string GetURLForTabId(const int tab_id, Profile* profile) { |
|
Matt Perry
2013/07/24 21:06:29
nit: const is unnecessary for a POD type
Matt Perry
2013/07/24 21:06:29
nit: Url, not URL
mvrable
2013/07/25 00:08:42
Done.
mvrable
2013/07/25 00:08:42
Done.
|
| + content::WebContents* contents = NULL; |
| + Browser* browser = NULL; |
| + bool found = ExtensionTabUtil::GetTabById(tab_id, |
| + profile, |
| + true, // search incognito tabs too |
| + &browser, |
| + NULL, |
| + &contents, |
| + NULL); |
| + if (found) { |
| + // Check whether the profile the tab was found in is a normal or incognito |
| + // profile. |
| + if (!browser->profile()->IsOffTheRecord()) { |
| + GURL url = contents->GetURL(); |
| + return std::string(url.spec()); |
| + } else { |
| + return std::string(extensions::APIAction::kIncognitoUrl); |
| + } |
| + } else { |
| + return std::string(); |
| + } |
| +} |
| + |
| +// Sets up the hashmap for mapping extension strings to "ints". The hashmap is |
| +// only set up once because it's quite long; the value is then cached. |
| +class APINameMap { |
| + public: |
| + APINameMap() { |
| + SetupMap(); |
| + } |
| + |
| + // activity_log_api_name_constants.h lists all known API calls as of 5/17. |
| + // This code maps each of those API calls (and events) to short strings |
| + // (integers converted to strings). They're all strings because (1) sqlite |
| + // databases are all strings underneath anyway and (2) the Lookup function |
| + // will simply return the original api_call string if we don't have it in our |
| + // lookup table. |
| + void SetupMap() { |
| + for (size_t i = 0; |
| + i < arraysize(activity_log_api_name_constants::kNames); |
| + ++i) { |
| + std::string name = |
| + std::string(activity_log_api_name_constants::kNames[i]); |
| + std::string num = base::IntToString(i); |
| + names_to_nums_[name] = num; |
| + nums_to_names_[num] = name; |
| + } |
| + } |
| + |
| + static APINameMap* GetInstance() { |
| + return Singleton<APINameMap>::get(); |
| + } |
| + |
| + // This matches an api call to a number, if it's in the lookup table. If not, |
| + // it returns the original api call. |
| + const std::string& ApiToShortname(const std::string& api_call) { |
| + std::map<std::string, std::string>::iterator it = |
| + names_to_nums_.find(api_call); |
| + if (it == names_to_nums_.end()) |
| + return api_call; |
| + else |
| + return it->second; |
| + } |
| + |
| + // This matches a number to an API call -- it's the opposite of |
| + // ApiToShortname. |
| + const std::string& ShortnameToApi(const std::string& shortname) { |
| + std::map<std::string, std::string>::iterator it = |
| + nums_to_names_.find(shortname); |
| + if (it == nums_to_names_.end()) |
| + return shortname; |
| + else |
| + return it->second; |
| + } |
| + |
| + private: |
| + std::map<std::string, std::string> names_to_nums_; // <name, number label> |
| + std::map<std::string, std::string> nums_to_names_; // <number label, name> |
| +}; |
| + |
| } // namespace |
| namespace extensions { |
| +using api::activity_log_private::BlockedChromeActivityDetail; |
| +using api::activity_log_private::ChromeActivityDetail; |
| +using api::activity_log_private::DomActivityDetail; |
| using api::activity_log_private::ExtensionActivity; |
| +// We should log the arguments to these API calls, even if argument logging is |
| +// disabled by default. |
| +const char* APIAction::kAlwaysLog[] = |
| + {"extension.connect", "extension.sendMessage", |
| + "tabs.executeScript", "tabs.insertCSS" }; |
| +const int APIAction::kSizeAlwaysLog = arraysize(kAlwaysLog); |
| + |
| +// A string used in place of the real URL when the URL is hidden because it is |
| +// in an incognito window. Extension activity logs mentioning kIncognitoUrl |
| +// let the user know that an extension is manipulating incognito tabs without |
| +// recording specific data about the pages. |
| +const char* APIAction::kIncognitoUrl = "http://incognito/"; |
| + |
| +// static |
| +void APIAction::LookupTabId(const std::string& api_call, |
| + base::ListValue* args, |
| + Profile* profile) { |
| + if (api_call == "tabs.get" || // api calls, ID as int |
| + api_call == "tabs.connect" || |
| + api_call == "tabs.sendMessage" || |
| + api_call == "tabs.duplicate" || |
| + api_call == "tabs.update" || |
| + api_call == "tabs.reload" || |
| + api_call == "tabs.detectLanguage" || |
| + api_call == "tabs.executeScript" || |
| + api_call == "tabs.insertCSS" || |
| + api_call == "tabs.move" || // api calls, IDs in array |
| + api_call == "tabs.remove" || |
| + api_call == "tabs.onUpdated" || // events, ID as int |
| + api_call == "tabs.onMoved" || |
| + api_call == "tabs.onDetached" || |
| + api_call == "tabs.onAttached" || |
| + api_call == "tabs.onRemoved" || |
| + api_call == "tabs.onReplaced") { |
| + int tab_id; |
| + base::ListValue* id_list; |
| + if (args->GetInteger(0, &tab_id)) { |
| + std::string url = GetURLForTabId(tab_id, profile); |
| + if (url != std::string()) |
| + args->Set(0, new base::StringValue(url)); |
| + } else if ((api_call == "tabs.move" || api_call == "tabs.remove") && |
| + args->GetList(0, &id_list)) { |
| + for (int i = 0; i < static_cast<int>(id_list->GetSize()); ++i) { |
| + if (id_list->GetInteger(i, &tab_id)) { |
| + std::string url = GetURLForTabId(tab_id, profile); |
| + if (url != std::string()) |
| + id_list->Set(i, new base::StringValue(url)); |
| + } else { |
| + LOG(ERROR) << "The tab ID array is malformed at index " << i; |
| + } |
| + } |
| + } |
| + } |
| +} |
| + |
| Action::Action(const std::string& extension_id, |
| const base::Time& time, |
| - ExtensionActivity::ActivityType activity_type) |
| + const ActionType action_type, |
| + const std::string& api_name) |
| : extension_id_(extension_id), |
| time_(time), |
| - activity_type_(activity_type) {} |
| - |
| -WatchdogAction::WatchdogAction(const std::string& extension_id, |
| - const base::Time& time, |
| - const ActionType action_type, |
| - const std::string& api_name, |
| - scoped_ptr<ListValue> args, |
| - const GURL& page_url, |
| - const GURL& arg_url, |
| - scoped_ptr<DictionaryValue> other) |
| - : Action(extension_id, time, ExtensionActivity::ACTIVITY_TYPE_CHROME), |
| action_type_(action_type), |
| - api_name_(api_name), |
| - args_(args.Pass()), |
| - page_url_(page_url), |
| - arg_url_(arg_url), |
| - other_(other.Pass()) {} |
| - |
| -WatchdogAction::~WatchdogAction() {} |
| - |
| -bool WatchdogAction::Record(sql::Connection* db) { |
| - // This methods isn't used and will go away entirely soon once database |
| - // writing moves to the policy objects. |
| - NOTREACHED(); |
| + api_name_(api_name) {} |
| + |
| +Action::~Action() {} |
| + |
| +void Action::set_args(scoped_ptr<ListValue> args) { |
| + args_.reset(args.release()); |
| +} |
| + |
| +ListValue* Action::mutable_args() { |
| + if (!args_.get()) { |
| + args_.reset(new ListValue()); |
| + } |
| + return args_.get(); |
| +} |
| + |
| +void Action::set_page_url(const GURL& page_url) { |
| + page_url_ = page_url; |
| +} |
| + |
| +void Action::set_arg_url(const GURL& arg_url) { |
| + arg_url_ = arg_url; |
| +} |
| + |
| +void Action::set_other(scoped_ptr<DictionaryValue> other) { |
| + other_.reset(other.release()); |
| +} |
| + |
| +DictionaryValue* Action::mutable_other() { |
| + if (!other_.get()) { |
| + other_.reset(new DictionaryValue()); |
| + } |
| + return other_.get(); |
| +} |
| + |
| +bool Action::Record(sql::Connection* db) { |
| + std::string sql_str = |
| + "INSERT INTO " + std::string(FullStreamUIPolicy::kTableName) + |
| + " (extension_id, time, action_type, api_name, args, " |
| + "page_url, page_title, arg_url, other) VALUES (?,?,?,?,?,?,?,?,?)"; |
| + sql::Statement statement(db->GetCachedStatement( |
| + sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); |
| + statement.BindString(0, extension_id()); |
| + statement.BindInt64(1, time().ToInternalValue()); |
| + statement.BindInt(2, static_cast<int>(action_type())); |
| + statement.BindString(3, api_name()); |
| + if (args()) { |
| + statement.BindString(4, Serialize(args())); |
| + } else { |
| + statement.BindNull(4); |
| + } |
| + if (other()) { |
| + statement.BindString(8, Serialize(other())); |
| + } else { |
| + statement.BindNull(8); |
| + } |
| + |
| + url_canon::Replacements<char> url_sanitizer; |
| + if (!CommandLine::ForCurrentProcess()->HasSwitch( |
| + switches::kEnableExtensionActivityLogTesting)) { |
| + url_sanitizer.ClearQuery(); |
| + url_sanitizer.ClearRef(); |
| + } |
| + if (page_url().is_valid()) { |
| + statement.BindString(5, page_url().ReplaceComponents(url_sanitizer).spec()); |
| + } |
| + statement.BindString(6, page_title()); |
| + if (arg_url().is_valid()) { |
| + statement.BindString(7, arg_url().ReplaceComponents(url_sanitizer).spec()); |
| + } |
| + |
| + if (!statement.Run()) { |
| + LOG(ERROR) << "Activity log database I/O failed: " << sql_str; |
| + statement.Clear(); |
| + return false; |
| + } |
| return true; |
| } |
| -scoped_ptr<api::activity_log_private::ExtensionActivity> |
| -WatchdogAction::ConvertToExtensionActivity() { |
| - scoped_ptr<api::activity_log_private::ExtensionActivity> result; |
| +scoped_ptr<ExtensionActivity> Action::ConvertToExtensionActivity() { |
| + scoped_ptr<ExtensionActivity> result(new ExtensionActivity); |
| + |
| + result->extension_id.reset(new std::string(extension_id())); |
| + result->time.reset(new double(time().ToJsTime())); |
| + |
| + switch (action_type()) { |
| + case ACTION_API_CALL: |
| + case ACTION_API_EVENT: { |
| + ChromeActivityDetail* details = new ChromeActivityDetail; |
| + if (action_type() == ACTION_API_CALL) { |
| + details->api_activity_type = |
| + ChromeActivityDetail::API_ACTIVITY_TYPE_CALL; |
| + } else { |
| + details->api_activity_type = |
| + ChromeActivityDetail::API_ACTIVITY_TYPE_EVENT_CALLBACK; |
| + } |
| + details->api_call.reset(new std::string(api_name())); |
| + details->args.reset(new std::string(Serialize(args()))); |
| + details->extra.reset(new std::string(Serialize(other()))); |
| + |
| + result->activity_type = ExtensionActivity::ACTIVITY_TYPE_CHROME; |
| + result->chrome_activity_detail.reset(details); |
| + break; |
| + } |
| + |
| + case ACTION_API_BLOCKED: { |
| + BlockedChromeActivityDetail* details = new BlockedChromeActivityDetail; |
| + details->api_call.reset(new std::string(api_name())); |
| + details->args.reset(new std::string(Serialize(args()))); |
| + details->extra.reset(new std::string(Serialize(other()))); |
| + if (other()) { |
| + int reason; |
| + if (other()->GetInteger("reason", &reason)) { |
| + details->reason = |
| + static_cast<BlockedChromeActivityDetail::Reason>(reason); |
| + } |
| + } |
| + |
| + result->activity_type = ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME; |
| + result->blocked_chrome_activity_detail.reset(details); |
| + break; |
| + } |
| + |
| + case ACTION_DOM_EVENT: |
| + case ACTION_DOM_XHR: |
| + case ACTION_DOM_ACCESS: |
| + case ACTION_CONTENT_SCRIPT: |
| + case ACTION_WEB_REQUEST: { |
| + DomActivityDetail* details = new DomActivityDetail; |
| + |
| + if (action_type() == ACTION_WEB_REQUEST) { |
| + details->dom_activity_type = |
| + DomActivityDetail::DOM_ACTIVITY_TYPE_WEBREQUEST; |
| + } else if (action_type() == ACTION_CONTENT_SCRIPT) { |
| + details->dom_activity_type = |
| + DomActivityDetail::DOM_ACTIVITY_TYPE_INSERTED; |
| + } else { |
| + // TODO(mvrable): This ought to be filled in properly, but since the |
| + // API will change soon don't worry about it now. |
| + details->dom_activity_type = |
| + DomActivityDetail::DOM_ACTIVITY_TYPE_NONE; |
| + } |
| + details->api_call.reset(new std::string(api_name())); |
| + details->args.reset(new std::string(Serialize(args()))); |
| + details->extra.reset(new std::string(Serialize(other()))); |
| + details->url.reset(new std::string(page_url().spec())); |
| + if (!page_title().empty()) |
| + details->url_title.reset(new std::string(page_title())); |
| + |
| + result->activity_type = ExtensionActivity::ACTIVITY_TYPE_DOM; |
| + result->dom_activity_detail.reset(details); |
| + break; |
| + } |
| + |
| + default: |
| + LOG(WARNING) << "Bad activity log entry read from database (type=" |
| + << action_type_ << ")!"; |
| + } |
| + |
| return result.Pass(); |
| } |
| -std::string WatchdogAction::PrintForDebug() { |
| +std::string Action::PrintForDebug() { |
| std::string result = "ID=" + extension_id() + " CATEGORY="; |
| switch (action_type_) { |
| case ACTION_API_CALL: |
| @@ -103,6 +384,10 @@ std::string WatchdogAction::PrintForDebug() { |
| if (page_url_.is_valid()) { |
| result += " PAGE_URL=" + page_url_.spec(); |
| } |
| + if (!page_title_.empty()) { |
| + StringValue title(page_title_); |
| + result += " PAGE_TITLE=" + Serialize(&title); |
| + } |
| if (arg_url_.is_valid()) { |
| result += " ARG_URL=" + arg_url_.spec(); |
| } |