OLD | NEW |
1 // Copyright (c) 2011 The Chromium Authors. All rights reserved. | 1 // Copyright (c) 2011 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 "chrome/browser/extensions/activity_log/activity_log.h" | 5 #include "chrome/browser/extensions/activity_log/activity_log.h" |
6 | 6 |
7 #include <set> | 7 #include <set> |
8 #include <vector> | 8 #include <vector> |
9 | 9 |
10 #include "base/command_line.h" | 10 #include "base/command_line.h" |
(...skipping 22 matching lines...) Expand all Loading... |
33 #include "chrome/common/pref_names.h" | 33 #include "chrome/common/pref_names.h" |
34 #include "components/browser_context_keyed_service/browser_context_dependency_ma
nager.h" | 34 #include "components/browser_context_keyed_service/browser_context_dependency_ma
nager.h" |
35 #include "content/public/browser/web_contents.h" | 35 #include "content/public/browser/web_contents.h" |
36 #include "third_party/re2/re2/re2.h" | 36 #include "third_party/re2/re2/re2.h" |
37 #include "url/gurl.h" | 37 #include "url/gurl.h" |
38 | 38 |
39 namespace constants = activity_log_constants; | 39 namespace constants = activity_log_constants; |
40 | 40 |
41 namespace { | 41 namespace { |
42 | 42 |
43 // Concatenate arguments. | 43 using extensions::Action; |
44 std::string MakeArgList(const base::ListValue* args) { | 44 using constants::kArgUrlPlaceholder; |
45 std::string call_signature; | 45 |
46 base::ListValue::const_iterator it = args->begin(); | 46 // Specifies a possible action to take to get an extracted URL in the ApiInfo |
47 for (; it != args->end(); ++it) { | 47 // structure below. |
48 std::string arg; | 48 enum Transformation { |
49 JSONStringValueSerializer serializer(&arg); | 49 NONE, |
50 if (serializer.SerializeAndOmitBinaryValues(**it)) { | 50 DICT_LOOKUP, |
51 if (it != args->begin()) | 51 LOOKUP_TAB_ID, |
52 call_signature += ", "; | 52 }; |
53 call_signature += arg; | 53 |
| 54 // Information about specific Chrome and DOM APIs, such as which contain |
| 55 // arguments that should be extracted into the arg_url field of an Action. |
| 56 struct ApiInfo { |
| 57 // The lookup key consists of the action_type and api_name in the Action |
| 58 // object. |
| 59 Action::ActionType action_type; |
| 60 const char* api_name; |
| 61 |
| 62 // If non-negative, an index into args might contain a URL to be extracted |
| 63 // into arg_url. |
| 64 int arg_url_index; |
| 65 |
| 66 // A transformation to apply to the data found at index arg_url_index in the |
| 67 // argument list. |
| 68 // |
| 69 // If NONE, the data is expected to be a string which is treated as a URL. |
| 70 // |
| 71 // If LOOKUP_TAB_ID, the data is either an integer which is treated as a tab |
| 72 // ID and translated (in the context of a provided Profile), or a list of tab |
| 73 // IDs which are translated. |
| 74 // |
| 75 // If DICT_LOOKUP, the data is expected to be a dictionary, and |
| 76 // arg_url_dict_path is a path (list of keys delimited by ".") where a URL |
| 77 // string is to be found. |
| 78 Transformation arg_url_transform; |
| 79 const char* arg_url_dict_path; |
| 80 }; |
| 81 |
| 82 static const ApiInfo kApiInfoTable[] = { |
| 83 // Tabs APIs that require tab ID translation |
| 84 {Action::ACTION_API_CALL, "tabs.connect", 0, LOOKUP_TAB_ID, NULL}, |
| 85 {Action::ACTION_API_CALL, "tabs.detectLanguage", 0, LOOKUP_TAB_ID, NULL}, |
| 86 {Action::ACTION_API_CALL, "tabs.duplicate", 0, LOOKUP_TAB_ID, NULL}, |
| 87 {Action::ACTION_API_CALL, "tabs.executeScript", 0, LOOKUP_TAB_ID, NULL}, |
| 88 {Action::ACTION_API_CALL, "tabs.get", 0, LOOKUP_TAB_ID, NULL}, |
| 89 {Action::ACTION_API_CALL, "tabs.insertCSS", 0, LOOKUP_TAB_ID, NULL}, |
| 90 {Action::ACTION_API_CALL, "tabs.move", 0, LOOKUP_TAB_ID, NULL}, |
| 91 {Action::ACTION_API_CALL, "tabs.reload", 0, LOOKUP_TAB_ID, NULL}, |
| 92 {Action::ACTION_API_CALL, "tabs.remove", 0, LOOKUP_TAB_ID, NULL}, |
| 93 {Action::ACTION_API_CALL, "tabs.sendMessage", 0, LOOKUP_TAB_ID, NULL}, |
| 94 {Action::ACTION_API_CALL, "tabs.update", 0, LOOKUP_TAB_ID, NULL}, |
| 95 |
| 96 {Action::ACTION_API_EVENT, "tabs.onUpdated", 0, LOOKUP_TAB_ID, NULL}, |
| 97 {Action::ACTION_API_EVENT, "tabs.onMoved", 0, LOOKUP_TAB_ID, NULL}, |
| 98 {Action::ACTION_API_EVENT, "tabs.onDetached", 0, LOOKUP_TAB_ID, NULL}, |
| 99 {Action::ACTION_API_EVENT, "tabs.onAttached", 0, LOOKUP_TAB_ID, NULL}, |
| 100 {Action::ACTION_API_EVENT, "tabs.onRemoved", 0, LOOKUP_TAB_ID, NULL}, |
| 101 {Action::ACTION_API_EVENT, "tabs.onReplaced", 0, LOOKUP_TAB_ID, NULL}, |
| 102 |
| 103 // Other APIs that accept URLs as strings |
| 104 {Action::ACTION_API_CALL, "windows.create", 0, DICT_LOOKUP, "url"}, |
| 105 |
| 106 {Action::ACTION_DOM_ACCESS, "Location.assign", 0, NONE, NULL}, |
| 107 {Action::ACTION_DOM_ACCESS, "Location.replace", 0, NONE, NULL}, |
| 108 {Action::ACTION_DOM_ACCESS, "XMLHttpRequest.open", 1, NONE, NULL}, |
| 109 }; |
| 110 |
| 111 // A singleton class which provides lookups into the kApiInfoTable data |
| 112 // structure. It inserts all data into a map on first lookup. |
| 113 class ApiInfoDatabase { |
| 114 public: |
| 115 static ApiInfoDatabase* GetInstance() { |
| 116 return Singleton<ApiInfoDatabase>::get(); |
| 117 } |
| 118 |
| 119 // Retrieves an ApiInfo record for the given Action type. Returns either a |
| 120 // pointer to the record, or NULL if no such record was found. |
| 121 const ApiInfo* Lookup(Action::ActionType action_type, |
| 122 const std::string& api_name) const { |
| 123 std::map<std::string, const ApiInfo*>::const_iterator i = |
| 124 api_database_.find(api_name); |
| 125 if (i == api_database_.end()) |
| 126 return NULL; |
| 127 if (i->second->action_type != action_type) |
| 128 return NULL; |
| 129 return i->second; |
| 130 } |
| 131 |
| 132 private: |
| 133 ApiInfoDatabase() { |
| 134 for (size_t i = 0; i < arraysize(kApiInfoTable); i++) { |
| 135 const ApiInfo* info = &kApiInfoTable[i]; |
| 136 api_database_[info->api_name] = info; |
54 } | 137 } |
55 } | 138 } |
56 return call_signature; | 139 virtual ~ApiInfoDatabase() {} |
57 } | |
58 | 140 |
59 // Gets the URL for a given tab ID. Helper method for LookupTabId. Returns | 141 // The map is keyed by API name only, since API names aren't be repeated |
| 142 // across multiple action types in kApiInfoTable. However, the action type |
| 143 // should still be checked before returning a positive match. |
| 144 std::map<std::string, const ApiInfo*> api_database_; |
| 145 |
| 146 friend struct DefaultSingletonTraits<ApiInfoDatabase>; |
| 147 DISALLOW_COPY_AND_ASSIGN(ApiInfoDatabase); |
| 148 }; |
| 149 |
| 150 // Gets the URL for a given tab ID. Helper method for ExtractUrls. Returns |
60 // true if able to perform the lookup. The URL is stored to *url, and | 151 // true if able to perform the lookup. The URL is stored to *url, and |
61 // *is_incognito is set to indicate whether the URL is for an incognito tab. | 152 // *is_incognito is set to indicate whether the URL is for an incognito tab. |
62 bool GetUrlForTabId(int tab_id, | 153 bool GetUrlForTabId(int tab_id, |
63 Profile* profile, | 154 Profile* profile, |
64 GURL* url, | 155 GURL* url, |
65 bool* is_incognito) { | 156 bool* is_incognito) { |
66 content::WebContents* contents = NULL; | 157 content::WebContents* contents = NULL; |
67 Browser* browser = NULL; | 158 Browser* browser = NULL; |
68 bool found = ExtensionTabUtil::GetTabById(tab_id, | 159 bool found = ExtensionTabUtil::GetTabById(tab_id, |
69 profile, | 160 profile, |
70 true, // search incognito tabs too | 161 true, // search incognito tabs too |
71 &browser, | 162 &browser, |
72 NULL, | 163 NULL, |
73 &contents, | 164 &contents, |
74 NULL); | 165 NULL); |
75 if (found) { | 166 if (found) { |
76 *url = contents->GetURL(); | 167 *url = contents->GetURL(); |
77 *is_incognito = browser->profile()->IsOffTheRecord(); | 168 *is_incognito = browser->profile()->IsOffTheRecord(); |
78 return true; | 169 return true; |
79 } else { | 170 } else { |
80 return false; | 171 return false; |
81 } | 172 } |
82 } | 173 } |
83 | 174 |
84 // Translate tab IDs to URLs in tabs API calls. Mutates the Action object in | 175 // Performs processing of the Action object to extract URLs from the argument |
85 // place. There is a small chance that the URL translation could be wrong, if | 176 // list and translate tab IDs to URLs, according to the API call metadata in |
86 // the tab has already been navigated by the time of invocation. | 177 // kApiInfoTable. Mutates the Action object in place. There is a small chance |
| 178 // that the tab id->URL translation could be wrong, if the tab has already been |
| 179 // navigated by the time of invocation. |
87 // | 180 // |
88 // If a single tab ID is translated to a URL, the URL is stored into arg_url | 181 // Any extracted URL is stored into the arg_url field of the action, and the |
89 // where it can more easily be searched for in the database. For APIs that | 182 // URL in the argument list is replaced with the marker value "<arg_url>". For |
90 // take a list of tab IDs, replace each tab ID with the URL in the argument | 183 // APIs that take a list of tab IDs, extracts the first valid URL into arg_url |
91 // list; we can only extract a single URL into arg_url so arbitrarily pull out | 184 // and overwrites the other tab IDs in the argument list with the translated |
92 // the first one. | 185 // URL. |
93 void LookupTabIds(scoped_refptr<extensions::Action> action, Profile* profile) { | 186 void ExtractUrls(scoped_refptr<Action> action, Profile* profile) { |
94 const std::string& api_call = action->api_name(); | 187 const ApiInfo* api_info = ApiInfoDatabase::GetInstance()->Lookup( |
95 if (api_call == "tabs.get" || // api calls, ID as int | 188 action->action_type(), action->api_name()); |
96 api_call == "tabs.connect" || | 189 if (api_info == NULL) |
97 api_call == "tabs.sendMessage" || | 190 return; |
98 api_call == "tabs.duplicate" || | 191 |
99 api_call == "tabs.update" || | 192 int url_index = api_info->arg_url_index; |
100 api_call == "tabs.reload" || | 193 |
101 api_call == "tabs.detectLanguage" || | 194 if (!action->args() || url_index < 0 || |
102 api_call == "tabs.executeScript" || | 195 static_cast<size_t>(url_index) >= action->args()->GetSize()) |
103 api_call == "tabs.insertCSS" || | 196 return; |
104 api_call == "tabs.move" || // api calls, IDs in array | 197 |
105 api_call == "tabs.remove" || | 198 // Do not overwrite an existing arg_url value in the Action, so that callers |
106 api_call == "tabs.onUpdated" || // events, ID as int | 199 // have the option of doing custom arg_url extraction. |
107 api_call == "tabs.onMoved" || | 200 if (action->arg_url().is_valid()) |
108 api_call == "tabs.onDetached" || | 201 return; |
109 api_call == "tabs.onAttached" || | 202 |
110 api_call == "tabs.onRemoved" || | 203 GURL arg_url; |
111 api_call == "tabs.onReplaced") { | 204 bool arg_incognito = action->page_incognito(); |
112 int tab_id; | 205 |
113 base::ListValue* id_list; | 206 switch (api_info->arg_url_transform) { |
114 base::ListValue* args = action->mutable_args(); | 207 case NONE: { |
115 if (args->GetInteger(0, &tab_id)) { | 208 // No translation needed; just extract the URL directly from a raw string |
116 // Single tab ID to translate. | 209 // or from a dictionary. |
117 GURL url; | 210 std::string url_string; |
118 bool is_incognito; | 211 if (action->args()->GetString(url_index, &url_string)) { |
119 if (GetUrlForTabId(tab_id, profile, &url, &is_incognito)) { | 212 arg_url = GURL(url_string); |
120 action->set_arg_url(url); | 213 if (arg_url.is_valid()) { |
121 action->set_arg_incognito(is_incognito); | 214 action->mutable_args() |
122 } | 215 ->Set(url_index, new StringValue(kArgUrlPlaceholder)); |
123 } else if ((api_call == "tabs.move" || api_call == "tabs.remove") && | |
124 args->GetList(0, &id_list)) { | |
125 // Array of tab IDs to translate. | |
126 for (int i = 0; i < static_cast<int>(id_list->GetSize()); ++i) { | |
127 if (id_list->GetInteger(i, &tab_id)) { | |
128 GURL url; | |
129 bool is_incognito; | |
130 if (GetUrlForTabId(tab_id, profile, &url, &is_incognito) && | |
131 !is_incognito) { | |
132 id_list->Set(i, new base::StringValue(url.spec())); | |
133 if (i == 0) { | |
134 action->set_arg_url(url); | |
135 action->set_arg_incognito(is_incognito); | |
136 } | |
137 } | |
138 } else { | |
139 LOG(ERROR) << "The tab ID array is malformed at index " << i; | |
140 } | 216 } |
141 } | 217 } |
| 218 break; |
142 } | 219 } |
| 220 |
| 221 case DICT_LOOKUP: { |
| 222 // Look up the URL from a dictionary at the specified location. |
| 223 DictionaryValue* dict = NULL; |
| 224 std::string url_string; |
| 225 CHECK(api_info->arg_url_dict_path); |
| 226 |
| 227 if (action->mutable_args()->GetDictionary(url_index, &dict) && |
| 228 dict->GetString(api_info->arg_url_dict_path, &url_string)) { |
| 229 arg_url = GURL(url_string); |
| 230 if (arg_url.is_valid()) |
| 231 dict->SetString(api_info->arg_url_dict_path, kArgUrlPlaceholder); |
| 232 } |
| 233 break; |
| 234 } |
| 235 |
| 236 case LOOKUP_TAB_ID: { |
| 237 // Translation of tab IDs to URLs has been requested. There are two |
| 238 // cases to consider: either a single integer or a list of integers (when |
| 239 // multiple tabs are manipulated). |
| 240 int tab_id; |
| 241 base::ListValue* tab_list = NULL; |
| 242 if (action->args()->GetInteger(url_index, &tab_id)) { |
| 243 // Single tab ID to translate. |
| 244 GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito); |
| 245 if (arg_url.is_valid()) { |
| 246 action->mutable_args()->Set(url_index, |
| 247 new StringValue(kArgUrlPlaceholder)); |
| 248 } |
| 249 } else if (action->mutable_args()->GetList(url_index, &tab_list)) { |
| 250 // A list of possible IDs to translate. Work through in reverse order |
| 251 // so the last one translated is left in arg_url. |
| 252 int extracted_index = -1; // Which list item is copied to arg_url? |
| 253 for (int i = tab_list->GetSize() - 1; i >= 0; --i) { |
| 254 if (tab_list->GetInteger(i, &tab_id) && |
| 255 GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito)) { |
| 256 if (!arg_incognito) |
| 257 tab_list->Set(i, new base::StringValue(arg_url.spec())); |
| 258 extracted_index = i; |
| 259 } |
| 260 } |
| 261 if (extracted_index >= 0) |
| 262 tab_list->Set(extracted_index, new StringValue(kArgUrlPlaceholder)); |
| 263 } |
| 264 break; |
| 265 } |
| 266 |
| 267 default: |
| 268 NOTREACHED(); |
| 269 } |
| 270 |
| 271 if (arg_url.is_valid()) { |
| 272 action->set_arg_incognito(arg_incognito); |
| 273 action->set_arg_url(arg_url); |
143 } | 274 } |
144 } | 275 } |
145 | 276 |
146 } // namespace | 277 } // namespace |
147 | 278 |
148 namespace extensions { | 279 namespace extensions { |
149 | 280 |
150 // ActivityLogFactory | 281 // ActivityLogFactory |
151 | 282 |
152 ActivityLogFactory* ActivityLogFactory::GetInstance() { | 283 ActivityLogFactory* ActivityLogFactory::GetInstance() { |
(...skipping 192 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
345 } | 476 } |
346 | 477 |
347 // LOG ACTIONS. ---------------------------------------------------------------- | 478 // LOG ACTIONS. ---------------------------------------------------------------- |
348 | 479 |
349 void ActivityLog::LogAction(scoped_refptr<Action> action) { | 480 void ActivityLog::LogAction(scoped_refptr<Action> action) { |
350 if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id())) | 481 if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id())) |
351 return; | 482 return; |
352 | 483 |
353 // Perform some preprocessing of the Action data: convert tab IDs to URLs and | 484 // Perform some preprocessing of the Action data: convert tab IDs to URLs and |
354 // mask out incognito URLs if appropriate. | 485 // mask out incognito URLs if appropriate. |
355 if ((action->action_type() == Action::ACTION_API_CALL || | 486 ExtractUrls(action, profile_); |
356 action->action_type() == Action::ACTION_API_EVENT) && | |
357 StartsWithASCII(action->api_name(), "tabs.", true)) { | |
358 LookupTabIds(action, profile_); | |
359 } | |
360 | 487 |
361 if (IsDatabaseEnabled() && policy_) | 488 if (IsDatabaseEnabled() && policy_) |
362 policy_->ProcessAction(action); | 489 policy_->ProcessAction(action); |
363 if (IsWatchdogAppActive()) | 490 if (IsWatchdogAppActive()) |
364 observers_->Notify(&Observer::OnExtensionActivity, action); | 491 observers_->Notify(&Observer::OnExtensionActivity, action); |
365 if (testing_mode_) | 492 if (testing_mode_) |
366 LOG(INFO) << action->PrintForDebug(); | 493 LOG(INFO) << action->PrintForDebug(); |
367 } | 494 } |
368 | 495 |
369 void ActivityLog::OnScriptsExecuted( | 496 void ActivityLog::OnScriptsExecuted( |
(...skipping 87 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
457 RemoveURLs(urls); | 584 RemoveURLs(urls); |
458 } | 585 } |
459 | 586 |
460 void ActivityLog::DeleteDatabase() { | 587 void ActivityLog::DeleteDatabase() { |
461 if (!policy_) | 588 if (!policy_) |
462 return; | 589 return; |
463 policy_->DeleteDatabase(); | 590 policy_->DeleteDatabase(); |
464 } | 591 } |
465 | 592 |
466 } // namespace extensions | 593 } // namespace extensions |
OLD | NEW |