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