| 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(int tab_id, Profile* profile) { |
| 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 // TODO(mvrable): details->reason isn't filled in; fix this after |
| 298 // converting logging to using the types from |
| 299 // BlockedChromeActivityDetail::Reason. |
| 300 details->reason = BlockedChromeActivityDetail::REASON_NONE; |
| 301 |
| 302 result->activity_type = ExtensionActivity::ACTIVITY_TYPE_BLOCKED_CHROME; |
| 303 result->blocked_chrome_activity_detail.reset(details); |
| 304 break; |
| 305 } |
| 306 |
| 307 case ACTION_DOM_EVENT: |
| 308 case ACTION_DOM_XHR: |
| 309 case ACTION_DOM_ACCESS: |
| 310 case ACTION_CONTENT_SCRIPT: |
| 311 case ACTION_WEB_REQUEST: { |
| 312 DomActivityDetail* details = new DomActivityDetail; |
| 313 |
| 314 if (action_type() == ACTION_WEB_REQUEST) { |
| 315 details->dom_activity_type = |
| 316 DomActivityDetail::DOM_ACTIVITY_TYPE_WEBREQUEST; |
| 317 } else if (action_type() == ACTION_CONTENT_SCRIPT) { |
| 318 details->dom_activity_type = |
| 319 DomActivityDetail::DOM_ACTIVITY_TYPE_INSERTED; |
| 320 } else { |
| 321 // TODO(mvrable): This ought to be filled in properly, but since the |
| 322 // API will change soon don't worry about it now. |
| 323 details->dom_activity_type = |
| 324 DomActivityDetail::DOM_ACTIVITY_TYPE_NONE; |
| 325 } |
| 326 details->api_call.reset(new std::string(api_name())); |
| 327 details->args.reset(new std::string(Serialize(args()))); |
| 328 details->extra.reset(new std::string(Serialize(other()))); |
| 329 details->url.reset(new std::string(page_url().spec())); |
| 330 if (!page_title().empty()) |
| 331 details->url_title.reset(new std::string(page_title())); |
| 332 |
| 333 result->activity_type = ExtensionActivity::ACTIVITY_TYPE_DOM; |
| 334 result->dom_activity_detail.reset(details); |
| 335 break; |
| 336 } |
| 337 |
| 338 default: |
| 339 LOG(WARNING) << "Bad activity log entry read from database (type=" |
| 340 << action_type_ << ")!"; |
| 341 } |
| 342 |
| 65 return result.Pass(); | 343 return result.Pass(); |
| 66 } | 344 } |
| 67 | 345 |
| 68 std::string WatchdogAction::PrintForDebug() { | 346 std::string Action::PrintForDebug() { |
| 69 std::string result = "ID=" + extension_id() + " CATEGORY="; | 347 std::string result = "ID=" + extension_id() + " CATEGORY="; |
| 70 switch (action_type_) { | 348 switch (action_type_) { |
| 71 case ACTION_API_CALL: | 349 case ACTION_API_CALL: |
| 72 result += "api_call"; | 350 result += "api_call"; |
| 73 break; | 351 break; |
| 74 case ACTION_API_EVENT: | 352 case ACTION_API_EVENT: |
| 75 result += "api_event_callback"; | 353 result += "api_event_callback"; |
| 76 break; | 354 break; |
| 77 case ACTION_WEB_REQUEST: | 355 case ACTION_WEB_REQUEST: |
| 78 result += "webrequest"; | 356 result += "webrequest"; |
| (...skipping 17 matching lines...) Expand all Loading... |
| 96 result += base::StringPrintf("type%d", static_cast<int>(action_type_)); | 374 result += base::StringPrintf("type%d", static_cast<int>(action_type_)); |
| 97 } | 375 } |
| 98 | 376 |
| 99 result += " API=" + api_name_; | 377 result += " API=" + api_name_; |
| 100 if (args_.get()) { | 378 if (args_.get()) { |
| 101 result += " ARGS=" + Serialize(args_.get()); | 379 result += " ARGS=" + Serialize(args_.get()); |
| 102 } | 380 } |
| 103 if (page_url_.is_valid()) { | 381 if (page_url_.is_valid()) { |
| 104 result += " PAGE_URL=" + page_url_.spec(); | 382 result += " PAGE_URL=" + page_url_.spec(); |
| 105 } | 383 } |
| 384 if (!page_title_.empty()) { |
| 385 StringValue title(page_title_); |
| 386 result += " PAGE_TITLE=" + Serialize(&title); |
| 387 } |
| 106 if (arg_url_.is_valid()) { | 388 if (arg_url_.is_valid()) { |
| 107 result += " ARG_URL=" + arg_url_.spec(); | 389 result += " ARG_URL=" + arg_url_.spec(); |
| 108 } | 390 } |
| 109 if (other_.get()) { | 391 if (other_.get()) { |
| 110 result += " OTHER=" + Serialize(other_.get()); | 392 result += " OTHER=" + Serialize(other_.get()); |
| 111 } | 393 } |
| 112 | 394 |
| 113 return result; | 395 return result; |
| 114 } | 396 } |
| 115 | 397 |
| 116 } // namespace extensions | 398 } // namespace extensions |
| OLD | NEW |