Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2013 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 #include <string> | 5 #include <string> |
| 6 #include "base/command_line.h" | |
| 6 #include "base/json/json_string_value_serializer.h" | 7 #include "base/json/json_string_value_serializer.h" |
| 7 #include "base/logging.h" | 8 #include "base/logging.h" |
| 9 #include "base/memory/singleton.h" | |
| 10 #include "base/strings/string_number_conversions.h" | |
| 8 #include "base/strings/stringprintf.h" | 11 #include "base/strings/stringprintf.h" |
| 9 #include "chrome/browser/extensions/activity_log/activity_actions.h" | 12 #include "chrome/browser/extensions/activity_log/activity_actions.h" |
| 13 #include "chrome/browser/extensions/activity_log/api_name_constants.h" | |
| 14 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h" | |
| 15 #include "chrome/browser/extensions/extension_tab_util.h" | |
| 16 #include "chrome/browser/ui/browser.h" | |
| 17 #include "chrome/common/chrome_switches.h" | |
| 18 #include "content/public/browser/browser_thread.h" | |
| 19 #include "content/public/browser/web_contents.h" | |
| 20 #include "sql/statement.h" | |
| 10 | 21 |
| 11 namespace { | 22 namespace { |
| 12 | 23 |
| 13 std::string Serialize(const base::Value* value) { | 24 std::string Serialize(const base::Value* value) { |
| 14 std::string value_as_text; | 25 std::string value_as_text; |
| 15 if (!value) { | 26 if (!value) { |
| 16 value_as_text = "null"; | 27 value_as_text = "null"; |
| 17 } else { | 28 } else { |
| 18 JSONStringValueSerializer serializer(&value_as_text); | 29 JSONStringValueSerializer serializer(&value_as_text); |
| 19 serializer.SerializeAndOmitBinaryValues(*value); | 30 serializer.SerializeAndOmitBinaryValues(*value); |
| 20 } | 31 } |
| 21 return value_as_text; | 32 return value_as_text; |
| 22 } | 33 } |
| 23 | 34 |
| 35 // Gets the URL for a given tab ID. Helper method for APIAction::LookupTabId. | |
| 36 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.
| |
| 37 content::WebContents* contents = NULL; | |
| 38 Browser* browser = NULL; | |
| 39 bool found = ExtensionTabUtil::GetTabById(tab_id, | |
| 40 profile, | |
| 41 true, // search incognito tabs too | |
| 42 &browser, | |
| 43 NULL, | |
| 44 &contents, | |
| 45 NULL); | |
| 46 if (found) { | |
| 47 // Check whether the profile the tab was found in is a normal or incognito | |
| 48 // profile. | |
| 49 if (!browser->profile()->IsOffTheRecord()) { | |
| 50 GURL url = contents->GetURL(); | |
| 51 return std::string(url.spec()); | |
| 52 } else { | |
| 53 return std::string(extensions::APIAction::kIncognitoUrl); | |
| 54 } | |
| 55 } else { | |
| 56 return std::string(); | |
| 57 } | |
| 58 } | |
| 59 | |
| 60 // Sets up the hashmap for mapping extension strings to "ints". The hashmap is | |
| 61 // only set up once because it's quite long; the value is then cached. | |
| 62 class APINameMap { | |
| 63 public: | |
| 64 APINameMap() { | |
| 65 SetupMap(); | |
| 66 } | |
| 67 | |
| 68 // activity_log_api_name_constants.h lists all known API calls as of 5/17. | |
| 69 // This code maps each of those API calls (and events) to short strings | |
| 70 // (integers converted to strings). They're all strings because (1) sqlite | |
| 71 // databases are all strings underneath anyway and (2) the Lookup function | |
| 72 // will simply return the original api_call string if we don't have it in our | |
| 73 // lookup table. | |
| 74 void SetupMap() { | |
| 75 for (size_t i = 0; | |
| 76 i < arraysize(activity_log_api_name_constants::kNames); | |
| 77 ++i) { | |
| 78 std::string name = | |
| 79 std::string(activity_log_api_name_constants::kNames[i]); | |
| 80 std::string num = base::IntToString(i); | |
| 81 names_to_nums_[name] = num; | |
| 82 nums_to_names_[num] = name; | |
| 83 } | |
| 84 } | |
| 85 | |
| 86 static APINameMap* GetInstance() { | |
| 87 return Singleton<APINameMap>::get(); | |
| 88 } | |
| 89 | |
| 90 // This matches an api call to a number, if it's in the lookup table. If not, | |
| 91 // it returns the original api call. | |
| 92 const std::string& ApiToShortname(const std::string& api_call) { | |
| 93 std::map<std::string, std::string>::iterator it = | |
| 94 names_to_nums_.find(api_call); | |
| 95 if (it == names_to_nums_.end()) | |
| 96 return api_call; | |
| 97 else | |
| 98 return it->second; | |
| 99 } | |
| 100 | |
| 101 // This matches a number to an API call -- it's the opposite of | |
| 102 // ApiToShortname. | |
| 103 const std::string& ShortnameToApi(const std::string& shortname) { | |
| 104 std::map<std::string, std::string>::iterator it = | |
| 105 nums_to_names_.find(shortname); | |
| 106 if (it == nums_to_names_.end()) | |
| 107 return shortname; | |
| 108 else | |
| 109 return it->second; | |
| 110 } | |
| 111 | |
| 112 private: | |
| 113 std::map<std::string, std::string> names_to_nums_; // <name, number label> | |
| 114 std::map<std::string, std::string> nums_to_names_; // <number label, name> | |
| 115 }; | |
| 116 | |
| 24 } // namespace | 117 } // namespace |
| 25 | 118 |
| 26 namespace extensions { | 119 namespace extensions { |
| 27 | 120 |
| 121 using api::activity_log_private::BlockedChromeActivityDetail; | |
| 122 using api::activity_log_private::ChromeActivityDetail; | |
| 123 using api::activity_log_private::DomActivityDetail; | |
| 28 using api::activity_log_private::ExtensionActivity; | 124 using api::activity_log_private::ExtensionActivity; |
| 29 | 125 |
| 126 // We should log the arguments to these API calls, even if argument logging is | |
| 127 // disabled by default. | |
| 128 const char* APIAction::kAlwaysLog[] = | |
| 129 {"extension.connect", "extension.sendMessage", | |
| 130 "tabs.executeScript", "tabs.insertCSS" }; | |
| 131 const int APIAction::kSizeAlwaysLog = arraysize(kAlwaysLog); | |
| 132 | |
| 133 // A string used in place of the real URL when the URL is hidden because it is | |
| 134 // in an incognito window. Extension activity logs mentioning kIncognitoUrl | |
| 135 // let the user know that an extension is manipulating incognito tabs without | |
| 136 // recording specific data about the pages. | |
| 137 const char* APIAction::kIncognitoUrl = "http://incognito/"; | |
| 138 | |
| 139 // static | |
| 140 void APIAction::LookupTabId(const std::string& api_call, | |
| 141 base::ListValue* args, | |
| 142 Profile* profile) { | |
| 143 if (api_call == "tabs.get" || // api calls, ID as int | |
| 144 api_call == "tabs.connect" || | |
| 145 api_call == "tabs.sendMessage" || | |
| 146 api_call == "tabs.duplicate" || | |
| 147 api_call == "tabs.update" || | |
| 148 api_call == "tabs.reload" || | |
| 149 api_call == "tabs.detectLanguage" || | |
| 150 api_call == "tabs.executeScript" || | |
| 151 api_call == "tabs.insertCSS" || | |
| 152 api_call == "tabs.move" || // api calls, IDs in array | |
| 153 api_call == "tabs.remove" || | |
| 154 api_call == "tabs.onUpdated" || // events, ID as int | |
| 155 api_call == "tabs.onMoved" || | |
| 156 api_call == "tabs.onDetached" || | |
| 157 api_call == "tabs.onAttached" || | |
| 158 api_call == "tabs.onRemoved" || | |
| 159 api_call == "tabs.onReplaced") { | |
| 160 int tab_id; | |
| 161 base::ListValue* id_list; | |
| 162 if (args->GetInteger(0, &tab_id)) { | |
| 163 std::string url = GetURLForTabId(tab_id, profile); | |
| 164 if (url != std::string()) | |
| 165 args->Set(0, new base::StringValue(url)); | |
| 166 } else if ((api_call == "tabs.move" || api_call == "tabs.remove") && | |
| 167 args->GetList(0, &id_list)) { | |
| 168 for (int i = 0; i < static_cast<int>(id_list->GetSize()); ++i) { | |
| 169 if (id_list->GetInteger(i, &tab_id)) { | |
| 170 std::string url = GetURLForTabId(tab_id, profile); | |
| 171 if (url != std::string()) | |
| 172 id_list->Set(i, new base::StringValue(url)); | |
| 173 } else { | |
| 174 LOG(ERROR) << "The tab ID array is malformed at index " << i; | |
| 175 } | |
| 176 } | |
| 177 } | |
| 178 } | |
| 179 } | |
| 180 | |
| 30 Action::Action(const std::string& extension_id, | 181 Action::Action(const std::string& extension_id, |
| 31 const base::Time& time, | 182 const base::Time& time, |
| 32 ExtensionActivity::ActivityType activity_type) | 183 const ActionType action_type, |
| 184 const std::string& api_name) | |
| 33 : extension_id_(extension_id), | 185 : extension_id_(extension_id), |
| 34 time_(time), | 186 time_(time), |
| 35 activity_type_(activity_type) {} | |
| 36 | |
| 37 WatchdogAction::WatchdogAction(const std::string& extension_id, | |
| 38 const base::Time& time, | |
| 39 const ActionType action_type, | |
| 40 const std::string& api_name, | |
| 41 scoped_ptr<ListValue> args, | |
| 42 const GURL& page_url, | |
| 43 const GURL& arg_url, | |
| 44 scoped_ptr<DictionaryValue> other) | |
| 45 : Action(extension_id, time, ExtensionActivity::ACTIVITY_TYPE_CHROME), | |
| 46 action_type_(action_type), | 187 action_type_(action_type), |
| 47 api_name_(api_name), | 188 api_name_(api_name) {} |
| 48 args_(args.Pass()), | 189 |
| 49 page_url_(page_url), | 190 Action::~Action() {} |
| 50 arg_url_(arg_url), | 191 |
| 51 other_(other.Pass()) {} | 192 void Action::set_args(scoped_ptr<ListValue> args) { |
| 52 | 193 args_.reset(args.release()); |
| 53 WatchdogAction::~WatchdogAction() {} | 194 } |
| 54 | 195 |
| 55 bool WatchdogAction::Record(sql::Connection* db) { | 196 ListValue* Action::mutable_args() { |
| 56 // This methods isn't used and will go away entirely soon once database | 197 if (!args_.get()) { |
| 57 // writing moves to the policy objects. | 198 args_.reset(new ListValue()); |
| 58 NOTREACHED(); | 199 } |
| 200 return args_.get(); | |
| 201 } | |
| 202 | |
| 203 void Action::set_page_url(const GURL& page_url) { | |
| 204 page_url_ = page_url; | |
| 205 } | |
| 206 | |
| 207 void Action::set_arg_url(const GURL& arg_url) { | |
| 208 arg_url_ = arg_url; | |
| 209 } | |
| 210 | |
| 211 void Action::set_other(scoped_ptr<DictionaryValue> other) { | |
| 212 other_.reset(other.release()); | |
| 213 } | |
| 214 | |
| 215 DictionaryValue* Action::mutable_other() { | |
| 216 if (!other_.get()) { | |
| 217 other_.reset(new DictionaryValue()); | |
| 218 } | |
| 219 return other_.get(); | |
| 220 } | |
| 221 | |
| 222 bool Action::Record(sql::Connection* db) { | |
| 223 std::string sql_str = | |
| 224 "INSERT INTO " + std::string(FullStreamUIPolicy::kTableName) + | |
| 225 " (extension_id, time, action_type, api_name, args, " | |
| 226 "page_url, page_title, arg_url, other) VALUES (?,?,?,?,?,?,?,?,?)"; | |
| 227 sql::Statement statement(db->GetCachedStatement( | |
| 228 sql::StatementID(SQL_FROM_HERE), sql_str.c_str())); | |
| 229 statement.BindString(0, extension_id()); | |
| 230 statement.BindInt64(1, time().ToInternalValue()); | |
| 231 statement.BindInt(2, static_cast<int>(action_type())); | |
| 232 statement.BindString(3, api_name()); | |
| 233 if (args()) { | |
| 234 statement.BindString(4, Serialize(args())); | |
| 235 } else { | |
| 236 statement.BindNull(4); | |
| 237 } | |
| 238 if (other()) { | |
| 239 statement.BindString(8, Serialize(other())); | |
| 240 } else { | |
| 241 statement.BindNull(8); | |
| 242 } | |
| 243 | |
| 244 url_canon::Replacements<char> url_sanitizer; | |
| 245 if (!CommandLine::ForCurrentProcess()->HasSwitch( | |
| 246 switches::kEnableExtensionActivityLogTesting)) { | |
| 247 url_sanitizer.ClearQuery(); | |
| 248 url_sanitizer.ClearRef(); | |
| 249 } | |
| 250 if (page_url().is_valid()) { | |
| 251 statement.BindString(5, page_url().ReplaceComponents(url_sanitizer).spec()); | |
| 252 } | |
| 253 statement.BindString(6, page_title()); | |
| 254 if (arg_url().is_valid()) { | |
| 255 statement.BindString(7, arg_url().ReplaceComponents(url_sanitizer).spec()); | |
| 256 } | |
| 257 | |
| 258 if (!statement.Run()) { | |
| 259 LOG(ERROR) << "Activity log database I/O failed: " << sql_str; | |
| 260 statement.Clear(); | |
| 261 return false; | |
| 262 } | |
| 59 return true; | 263 return true; |
| 60 } | 264 } |
| 61 | 265 |
| 62 scoped_ptr<api::activity_log_private::ExtensionActivity> | 266 scoped_ptr<ExtensionActivity> Action::ConvertToExtensionActivity() { |
| 63 WatchdogAction::ConvertToExtensionActivity() { | 267 scoped_ptr<ExtensionActivity> result(new ExtensionActivity); |
| 64 scoped_ptr<api::activity_log_private::ExtensionActivity> result; | 268 |
| 269 result->extension_id.reset(new std::string(extension_id())); | |
| 270 result->time.reset(new double(time().ToJsTime())); | |
| 271 | |
| 272 switch (action_type()) { | |
| 273 case ACTION_API_CALL: | |
| 274 case ACTION_API_EVENT: { | |
| 275 ChromeActivityDetail* details = new ChromeActivityDetail; | |
| 276 if (action_type() == ACTION_API_CALL) { | |
| 277 details->api_activity_type = | |
| 278 ChromeActivityDetail::API_ACTIVITY_TYPE_CALL; | |
| 279 } else { | |
| 280 details->api_activity_type = | |
| 281 ChromeActivityDetail::API_ACTIVITY_TYPE_EVENT_CALLBACK; | |
| 282 } | |
| 283 details->api_call.reset(new std::string(api_name())); | |
| 284 details->args.reset(new std::string(Serialize(args()))); | |
| 285 details->extra.reset(new std::string(Serialize(other()))); | |
| 286 | |
| 287 result->activity_type = ExtensionActivity::ACTIVITY_TYPE_CHROME; | |
| 288 result->chrome_activity_detail.reset(details); | |
| 289 break; | |
| 290 } | |
| 291 | |
| 292 case ACTION_API_BLOCKED: { | |
| 293 BlockedChromeActivityDetail* details = new BlockedChromeActivityDetail; | |
| 294 details->api_call.reset(new std::string(api_name())); | |
| 295 details->args.reset(new std::string(Serialize(args()))); | |
| 296 details->extra.reset(new std::string(Serialize(other()))); | |
| 297 if (other()) { | |
| 298 int reason; | |
| 299 if (other()->GetInteger("reason", &reason)) { | |
| 300 details->reason = | |
| 301 static_cast<BlockedChromeActivityDetail::Reason>(reason); | |
| 302 } | |
| 303 } | |
| 304 | |
| 305 result->activity_type = ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME; | |
| 306 result->blocked_chrome_activity_detail.reset(details); | |
| 307 break; | |
| 308 } | |
| 309 | |
| 310 case ACTION_DOM_EVENT: | |
| 311 case ACTION_DOM_XHR: | |
| 312 case ACTION_DOM_ACCESS: | |
| 313 case ACTION_CONTENT_SCRIPT: | |
| 314 case ACTION_WEB_REQUEST: { | |
| 315 DomActivityDetail* details = new DomActivityDetail; | |
| 316 | |
| 317 if (action_type() == ACTION_WEB_REQUEST) { | |
| 318 details->dom_activity_type = | |
| 319 DomActivityDetail::DOM_ACTIVITY_TYPE_WEBREQUEST; | |
| 320 } else if (action_type() == ACTION_CONTENT_SCRIPT) { | |
| 321 details->dom_activity_type = | |
| 322 DomActivityDetail::DOM_ACTIVITY_TYPE_INSERTED; | |
| 323 } else { | |
| 324 // TODO(mvrable): This ought to be filled in properly, but since the | |
| 325 // API will change soon don't worry about it now. | |
| 326 details->dom_activity_type = | |
| 327 DomActivityDetail::DOM_ACTIVITY_TYPE_NONE; | |
| 328 } | |
| 329 details->api_call.reset(new std::string(api_name())); | |
| 330 details->args.reset(new std::string(Serialize(args()))); | |
| 331 details->extra.reset(new std::string(Serialize(other()))); | |
| 332 details->url.reset(new std::string(page_url().spec())); | |
| 333 if (!page_title().empty()) | |
| 334 details->url_title.reset(new std::string(page_title())); | |
| 335 | |
| 336 result->activity_type = ExtensionActivity::ACTIVITY_TYPE_DOM; | |
| 337 result->dom_activity_detail.reset(details); | |
| 338 break; | |
| 339 } | |
| 340 | |
| 341 default: | |
| 342 LOG(WARNING) << "Bad activity log entry read from database (type=" | |
| 343 << action_type_ << ")!"; | |
| 344 } | |
| 345 | |
| 65 return result.Pass(); | 346 return result.Pass(); |
| 66 } | 347 } |
| 67 | 348 |
| 68 std::string WatchdogAction::PrintForDebug() { | 349 std::string Action::PrintForDebug() { |
| 69 std::string result = "ID=" + extension_id() + " CATEGORY="; | 350 std::string result = "ID=" + extension_id() + " CATEGORY="; |
| 70 switch (action_type_) { | 351 switch (action_type_) { |
| 71 case ACTION_API_CALL: | 352 case ACTION_API_CALL: |
| 72 result += "api_call"; | 353 result += "api_call"; |
| 73 break; | 354 break; |
| 74 case ACTION_API_EVENT: | 355 case ACTION_API_EVENT: |
| 75 result += "api_event_callback"; | 356 result += "api_event_callback"; |
| 76 break; | 357 break; |
| 77 case ACTION_WEB_REQUEST: | 358 case ACTION_WEB_REQUEST: |
| 78 result += "webrequest"; | 359 result += "webrequest"; |
| (...skipping 17 matching lines...) Expand all Loading... | |
| 96 result += base::StringPrintf("type%d", static_cast<int>(action_type_)); | 377 result += base::StringPrintf("type%d", static_cast<int>(action_type_)); |
| 97 } | 378 } |
| 98 | 379 |
| 99 result += " API=" + api_name_; | 380 result += " API=" + api_name_; |
| 100 if (args_.get()) { | 381 if (args_.get()) { |
| 101 result += " ARGS=" + Serialize(args_.get()); | 382 result += " ARGS=" + Serialize(args_.get()); |
| 102 } | 383 } |
| 103 if (page_url_.is_valid()) { | 384 if (page_url_.is_valid()) { |
| 104 result += " PAGE_URL=" + page_url_.spec(); | 385 result += " PAGE_URL=" + page_url_.spec(); |
| 105 } | 386 } |
| 387 if (!page_title_.empty()) { | |
| 388 StringValue title(page_title_); | |
| 389 result += " PAGE_TITLE=" + Serialize(&title); | |
| 390 } | |
| 106 if (arg_url_.is_valid()) { | 391 if (arg_url_.is_valid()) { |
| 107 result += " ARG_URL=" + arg_url_.spec(); | 392 result += " ARG_URL=" + arg_url_.spec(); |
| 108 } | 393 } |
| 109 if (other_.get()) { | 394 if (other_.get()) { |
| 110 result += " OTHER=" + Serialize(other_.get()); | 395 result += " OTHER=" + Serialize(other_.get()); |
| 111 } | 396 } |
| 112 | 397 |
| 113 return result; | 398 return result; |
| 114 } | 399 } |
| 115 | 400 |
| 116 } // namespace extensions | 401 } // namespace extensions |
| OLD | NEW |