• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
3 // found in the LICENSE file.
4 
5 #include "chrome/browser/extensions/activity_log/activity_log.h"
6 
7 #include <set>
8 #include <vector>
9 
10 #include "base/command_line.h"
11 #include "base/json/json_string_value_serializer.h"
12 #include "base/lazy_instance.h"
13 #include "base/logging.h"
14 #include "base/strings/string_util.h"
15 #include "base/strings/utf_string_conversions.h"
16 #include "base/threading/thread_checker.h"
17 #include "chrome/browser/extensions/activity_log/activity_action_constants.h"
18 #include "chrome/browser/extensions/activity_log/counting_policy.h"
19 #include "chrome/browser/extensions/activity_log/fullstream_ui_policy.h"
20 #include "chrome/browser/extensions/api/activity_log_private/activity_log_private_api.h"
21 #include "chrome/browser/extensions/extension_tab_util.h"
22 #include "chrome/browser/prefs/pref_service_syncable.h"
23 #include "chrome/browser/prerender/prerender_manager.h"
24 #include "chrome/browser/prerender/prerender_manager_factory.h"
25 #include "chrome/browser/profiles/profile.h"
26 #include "chrome/browser/ui/browser.h"
27 #include "chrome/common/chrome_constants.h"
28 #include "chrome/common/chrome_switches.h"
29 #include "chrome/common/pref_names.h"
30 #include "content/public/browser/browser_thread.h"
31 #include "content/public/browser/web_contents.h"
32 #include "extensions/browser/extension_registry.h"
33 #include "extensions/browser/extension_registry_factory.h"
34 #include "extensions/browser/extension_system.h"
35 #include "extensions/browser/extension_system_provider.h"
36 #include "extensions/browser/extensions_browser_client.h"
37 #include "extensions/common/extension.h"
38 #include "extensions/common/one_shot_event.h"
39 #include "third_party/re2/re2/re2.h"
40 #include "url/gurl.h"
41 
42 #if !defined(OS_ANDROID)
43 #include "chrome/browser/extensions/activity_log/uma_policy.h"
44 #endif
45 
46 namespace constants = activity_log_constants;
47 
48 namespace extensions {
49 
50 namespace {
51 
52 using constants::kArgUrlPlaceholder;
53 using content::BrowserThread;
54 
55 // If DOM API methods start with this string, we flag them as being of type
56 // DomActionType::XHR.
57 const char kDomXhrPrefix[] = "XMLHttpRequest.";
58 
59 // Specifies a possible action to take to get an extracted URL in the ApiInfo
60 // structure below.
61 enum Transformation {
62   NONE,
63   DICT_LOOKUP,
64   LOOKUP_TAB_ID,
65 };
66 
67 // Information about specific Chrome and DOM APIs, such as which contain
68 // arguments that should be extracted into the arg_url field of an Action.
69 struct ApiInfo {
70   // The lookup key consists of the action_type and api_name in the Action
71   // object.
72   Action::ActionType action_type;
73   const char* api_name;
74 
75   // If non-negative, an index into args might contain a URL to be extracted
76   // into arg_url.
77   int arg_url_index;
78 
79   // A transformation to apply to the data found at index arg_url_index in the
80   // argument list.
81   //
82   // If NONE, the data is expected to be a string which is treated as a URL.
83   //
84   // If LOOKUP_TAB_ID, the data is either an integer which is treated as a tab
85   // ID and translated (in the context of a provided Profile), or a list of tab
86   // IDs which are translated.
87   //
88   // If DICT_LOOKUP, the data is expected to be a dictionary, and
89   // arg_url_dict_path is a path (list of keys delimited by ".") where a URL
90   // string is to be found.
91   Transformation arg_url_transform;
92   const char* arg_url_dict_path;
93 };
94 
95 static const ApiInfo kApiInfoTable[] = {
96     // Tabs APIs that require tab ID translation
97     {Action::ACTION_API_CALL, "tabs.connect", 0, LOOKUP_TAB_ID, NULL},
98     {Action::ACTION_API_CALL, "tabs.detectLanguage", 0, LOOKUP_TAB_ID, NULL},
99     {Action::ACTION_API_CALL, "tabs.duplicate", 0, LOOKUP_TAB_ID, NULL},
100     {Action::ACTION_API_CALL, "tabs.executeScript", 0, LOOKUP_TAB_ID, NULL},
101     {Action::ACTION_API_CALL, "tabs.get", 0, LOOKUP_TAB_ID, NULL},
102     {Action::ACTION_API_CALL, "tabs.insertCSS", 0, LOOKUP_TAB_ID, NULL},
103     {Action::ACTION_API_CALL, "tabs.move", 0, LOOKUP_TAB_ID, NULL},
104     {Action::ACTION_API_CALL, "tabs.reload", 0, LOOKUP_TAB_ID, NULL},
105     {Action::ACTION_API_CALL, "tabs.remove", 0, LOOKUP_TAB_ID, NULL},
106     {Action::ACTION_API_CALL, "tabs.sendMessage", 0, LOOKUP_TAB_ID, NULL},
107     {Action::ACTION_API_CALL, "tabs.update", 0, LOOKUP_TAB_ID, NULL},
108     {Action::ACTION_API_EVENT, "tabs.onUpdated", 0, LOOKUP_TAB_ID, NULL},
109     {Action::ACTION_API_EVENT, "tabs.onMoved", 0, LOOKUP_TAB_ID, NULL},
110     {Action::ACTION_API_EVENT, "tabs.onDetached", 0, LOOKUP_TAB_ID, NULL},
111     {Action::ACTION_API_EVENT, "tabs.onAttached", 0, LOOKUP_TAB_ID, NULL},
112     {Action::ACTION_API_EVENT, "tabs.onRemoved", 0, LOOKUP_TAB_ID, NULL},
113     {Action::ACTION_API_EVENT, "tabs.onReplaced", 0, LOOKUP_TAB_ID, NULL},
114 
115     // Other APIs that accept URLs as strings
116     {Action::ACTION_API_CALL, "bookmarks.create", 0, DICT_LOOKUP, "url"},
117     {Action::ACTION_API_CALL, "bookmarks.update", 1, DICT_LOOKUP, "url"},
118     {Action::ACTION_API_CALL, "cookies.get", 0, DICT_LOOKUP, "url"},
119     {Action::ACTION_API_CALL, "cookies.getAll", 0, DICT_LOOKUP, "url"},
120     {Action::ACTION_API_CALL, "cookies.remove", 0, DICT_LOOKUP, "url"},
121     {Action::ACTION_API_CALL, "cookies.set", 0, DICT_LOOKUP, "url"},
122     {Action::ACTION_API_CALL, "downloads.download", 0, DICT_LOOKUP, "url"},
123     {Action::ACTION_API_CALL, "history.addUrl", 0, DICT_LOOKUP, "url"},
124     {Action::ACTION_API_CALL, "history.deleteUrl", 0, DICT_LOOKUP, "url"},
125     {Action::ACTION_API_CALL, "history.getVisits", 0, DICT_LOOKUP, "url"},
126     {Action::ACTION_API_CALL, "webstore.install", 0, NONE, NULL},
127     {Action::ACTION_API_CALL, "windows.create", 0, DICT_LOOKUP, "url"},
128     {Action::ACTION_DOM_ACCESS, "Document.location", 0, NONE, NULL},
129     {Action::ACTION_DOM_ACCESS, "HTMLAnchorElement.href", 0, NONE, NULL},
130     {Action::ACTION_DOM_ACCESS, "HTMLButtonElement.formAction", 0, NONE, NULL},
131     {Action::ACTION_DOM_ACCESS, "HTMLEmbedElement.src", 0, NONE, NULL},
132     {Action::ACTION_DOM_ACCESS, "HTMLFormElement.action", 0, NONE, NULL},
133     {Action::ACTION_DOM_ACCESS, "HTMLFrameElement.src", 0, NONE, NULL},
134     {Action::ACTION_DOM_ACCESS, "HTMLHtmlElement.manifest", 0, NONE, NULL},
135     {Action::ACTION_DOM_ACCESS, "HTMLIFrameElement.src", 0, NONE, NULL},
136     {Action::ACTION_DOM_ACCESS, "HTMLImageElement.longDesc", 0, NONE, NULL},
137     {Action::ACTION_DOM_ACCESS, "HTMLImageElement.src", 0, NONE, NULL},
138     {Action::ACTION_DOM_ACCESS, "HTMLImageElement.lowsrc", 0, NONE, NULL},
139     {Action::ACTION_DOM_ACCESS, "HTMLInputElement.formAction", 0, NONE, NULL},
140     {Action::ACTION_DOM_ACCESS, "HTMLInputElement.src", 0, NONE, NULL},
141     {Action::ACTION_DOM_ACCESS, "HTMLLinkElement.href", 0, NONE, NULL},
142     {Action::ACTION_DOM_ACCESS, "HTMLMediaElement.src", 0, NONE, NULL},
143     {Action::ACTION_DOM_ACCESS, "HTMLMediaElement.currentSrc", 0, NONE, NULL},
144     {Action::ACTION_DOM_ACCESS, "HTMLModElement.cite", 0, NONE, NULL},
145     {Action::ACTION_DOM_ACCESS, "HTMLObjectElement.data", 0, NONE, NULL},
146     {Action::ACTION_DOM_ACCESS, "HTMLQuoteElement.cite", 0, NONE, NULL},
147     {Action::ACTION_DOM_ACCESS, "HTMLScriptElement.src", 0, NONE, NULL},
148     {Action::ACTION_DOM_ACCESS, "HTMLSourceElement.src", 0, NONE, NULL},
149     {Action::ACTION_DOM_ACCESS, "HTMLTrackElement.src", 0, NONE, NULL},
150     {Action::ACTION_DOM_ACCESS, "HTMLVideoElement.poster", 0, NONE, NULL},
151     {Action::ACTION_DOM_ACCESS, "Location.assign", 0, NONE, NULL},
152     {Action::ACTION_DOM_ACCESS, "Location.replace", 0, NONE, NULL},
153     {Action::ACTION_DOM_ACCESS, "Window.location", 0, NONE, NULL},
154     {Action::ACTION_DOM_ACCESS, "XMLHttpRequest.open", 1, NONE, NULL}};
155 
156 // A singleton class which provides lookups into the kApiInfoTable data
157 // structure.  It inserts all data into a map on first lookup.
158 class ApiInfoDatabase {
159  public:
GetInstance()160   static ApiInfoDatabase* GetInstance() {
161     return Singleton<ApiInfoDatabase>::get();
162   }
163 
164   // Retrieves an ApiInfo record for the given Action type.  Returns either a
165   // pointer to the record, or NULL if no such record was found.
Lookup(Action::ActionType action_type,const std::string & api_name) const166   const ApiInfo* Lookup(Action::ActionType action_type,
167                         const std::string& api_name) const {
168     std::map<std::string, const ApiInfo*>::const_iterator i =
169         api_database_.find(api_name);
170     if (i == api_database_.end())
171       return NULL;
172     if (i->second->action_type != action_type)
173       return NULL;
174     return i->second;
175   }
176 
177  private:
ApiInfoDatabase()178   ApiInfoDatabase() {
179     for (size_t i = 0; i < arraysize(kApiInfoTable); i++) {
180       const ApiInfo* info = &kApiInfoTable[i];
181       api_database_[info->api_name] = info;
182     }
183   }
~ApiInfoDatabase()184   virtual ~ApiInfoDatabase() {}
185 
186   // The map is keyed by API name only, since API names aren't be repeated
187   // across multiple action types in kApiInfoTable.  However, the action type
188   // should still be checked before returning a positive match.
189   std::map<std::string, const ApiInfo*> api_database_;
190 
191   friend struct DefaultSingletonTraits<ApiInfoDatabase>;
192   DISALLOW_COPY_AND_ASSIGN(ApiInfoDatabase);
193 };
194 
195 // Gets the URL for a given tab ID.  Helper method for ExtractUrls.  Returns
196 // true if able to perform the lookup.  The URL is stored to *url, and
197 // *is_incognito is set to indicate whether the URL is for an incognito tab.
GetUrlForTabId(int tab_id,Profile * profile,GURL * url,bool * is_incognito)198 bool GetUrlForTabId(int tab_id,
199                     Profile* profile,
200                     GURL* url,
201                     bool* is_incognito) {
202   content::WebContents* contents = NULL;
203   Browser* browser = NULL;
204   bool found = ExtensionTabUtil::GetTabById(
205       tab_id,
206       profile,
207       true,  // Search incognito tabs, too.
208       &browser,
209       NULL,
210       &contents,
211       NULL);
212 
213   if (found) {
214     *url = contents->GetURL();
215     *is_incognito = browser->profile()->IsOffTheRecord();
216     return true;
217   } else {
218     return false;
219   }
220 }
221 
222 // Resolves an argument URL relative to a base page URL.  If the page URL is
223 // not valid, then only absolute argument URLs are supported.
ResolveUrl(const GURL & base,const std::string & arg,GURL * arg_out)224 bool ResolveUrl(const GURL& base, const std::string& arg, GURL* arg_out) {
225   if (base.is_valid())
226     *arg_out = base.Resolve(arg);
227   else
228     *arg_out = GURL(arg);
229 
230   return arg_out->is_valid();
231 }
232 
233 // Performs processing of the Action object to extract URLs from the argument
234 // list and translate tab IDs to URLs, according to the API call metadata in
235 // kApiInfoTable.  Mutates the Action object in place.  There is a small chance
236 // that the tab id->URL translation could be wrong, if the tab has already been
237 // navigated by the time of invocation.
238 //
239 // Any extracted URL is stored into the arg_url field of the action, and the
240 // URL in the argument list is replaced with the marker value "<arg_url>".  For
241 // APIs that take a list of tab IDs, extracts the first valid URL into arg_url
242 // and overwrites the other tab IDs in the argument list with the translated
243 // URL.
ExtractUrls(scoped_refptr<Action> action,Profile * profile)244 void ExtractUrls(scoped_refptr<Action> action, Profile* profile) {
245   const ApiInfo* api_info = ApiInfoDatabase::GetInstance()->Lookup(
246       action->action_type(), action->api_name());
247   if (api_info == NULL)
248     return;
249 
250   int url_index = api_info->arg_url_index;
251 
252   if (!action->args() || url_index < 0 ||
253       static_cast<size_t>(url_index) >= action->args()->GetSize())
254     return;
255 
256   // Do not overwrite an existing arg_url value in the Action, so that callers
257   // have the option of doing custom arg_url extraction.
258   if (action->arg_url().is_valid())
259     return;
260 
261   GURL arg_url;
262   bool arg_incognito = action->page_incognito();
263 
264   switch (api_info->arg_url_transform) {
265     case NONE: {
266       // No translation needed; just extract the URL directly from a raw string
267       // or from a dictionary.  Succeeds if we can find a string in the
268       // argument list and that the string resolves to a valid URL.
269       std::string url_string;
270       if (action->args()->GetString(url_index, &url_string) &&
271           ResolveUrl(action->page_url(), url_string, &arg_url)) {
272         action->mutable_args()->Set(url_index,
273                                     new base::StringValue(kArgUrlPlaceholder));
274       }
275       break;
276     }
277 
278     case DICT_LOOKUP: {
279       CHECK(api_info->arg_url_dict_path);
280       // Look up the URL from a dictionary at the specified location.  Succeeds
281       // if we can find a dictionary in the argument list, the dictionary
282       // contains the specified key, and the corresponding value resolves to a
283       // valid URL.
284       base::DictionaryValue* dict = NULL;
285       std::string url_string;
286       if (action->mutable_args()->GetDictionary(url_index, &dict) &&
287           dict->GetString(api_info->arg_url_dict_path, &url_string) &&
288           ResolveUrl(action->page_url(), url_string, &arg_url)) {
289         dict->SetString(api_info->arg_url_dict_path, kArgUrlPlaceholder);
290       }
291       break;
292     }
293 
294     case LOOKUP_TAB_ID: {
295       // Translation of tab IDs to URLs has been requested.  There are two
296       // cases to consider: either a single integer or a list of integers (when
297       // multiple tabs are manipulated).
298       int tab_id;
299       base::ListValue* tab_list = NULL;
300       if (action->args()->GetInteger(url_index, &tab_id)) {
301         // Single tab ID to translate.
302         GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito);
303         if (arg_url.is_valid()) {
304           action->mutable_args()->Set(
305               url_index, new base::StringValue(kArgUrlPlaceholder));
306         }
307       } else if (action->mutable_args()->GetList(url_index, &tab_list)) {
308         // A list of possible IDs to translate.  Work through in reverse order
309         // so the last one translated is left in arg_url.
310         int extracted_index = -1;  // Which list item is copied to arg_url?
311         for (int i = tab_list->GetSize() - 1; i >= 0; --i) {
312           if (tab_list->GetInteger(i, &tab_id) &&
313               GetUrlForTabId(tab_id, profile, &arg_url, &arg_incognito)) {
314             if (!arg_incognito)
315               tab_list->Set(i, new base::StringValue(arg_url.spec()));
316             extracted_index = i;
317           }
318         }
319         if (extracted_index >= 0) {
320           tab_list->Set(
321               extracted_index, new base::StringValue(kArgUrlPlaceholder));
322         }
323       }
324       break;
325     }
326 
327     default:
328       NOTREACHED();
329   }
330 
331   if (arg_url.is_valid()) {
332     action->set_arg_incognito(arg_incognito);
333     action->set_arg_url(arg_url);
334   }
335 }
336 
337 }  // namespace
338 
339 // SET THINGS UP. --------------------------------------------------------------
340 
341 static base::LazyInstance<BrowserContextKeyedAPIFactory<ActivityLog> >
342     g_factory = LAZY_INSTANCE_INITIALIZER;
343 
GetFactoryInstance()344 BrowserContextKeyedAPIFactory<ActivityLog>* ActivityLog::GetFactoryInstance() {
345   return g_factory.Pointer();
346 }
347 
348 // static
GetInstance(content::BrowserContext * context)349 ActivityLog* ActivityLog::GetInstance(content::BrowserContext* context) {
350   return ActivityLog::GetFactoryInstance()->Get(
351       Profile::FromBrowserContext(context));
352 }
353 
354 // Use GetInstance instead of directly creating an ActivityLog.
ActivityLog(content::BrowserContext * context)355 ActivityLog::ActivityLog(content::BrowserContext* context)
356     : database_policy_(NULL),
357       database_policy_type_(ActivityLogPolicy::POLICY_INVALID),
358       uma_policy_(NULL),
359       profile_(Profile::FromBrowserContext(context)),
360       db_enabled_(false),
361       testing_mode_(false),
362       has_threads_(true),
363       extension_registry_observer_(this),
364       watchdog_apps_active_(0) {
365   // This controls whether logging statements are printed & which policy is set.
366   testing_mode_ = CommandLine::ForCurrentProcess()->HasSwitch(
367     switches::kEnableExtensionActivityLogTesting);
368 
369   // Check if the watchdog extension is previously installed and active.
370   // It was originally a boolean, but we've had to move to an integer. Handle
371   // the legacy case.
372   // TODO(felt): In M34, remove the legacy code & old pref.
373   if (profile_->GetPrefs()->GetBoolean(prefs::kWatchdogExtensionActiveOld))
374     profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive, 1);
375   watchdog_apps_active_ =
376       profile_->GetPrefs()->GetInteger(prefs::kWatchdogExtensionActive);
377 
378   observers_ = new ObserverListThreadSafe<Observer>;
379 
380   // Check that the right threads exist for logging to the database.
381   // If not, we shouldn't try to do things that require them.
382   if (!BrowserThread::IsMessageLoopValid(BrowserThread::DB) ||
383       !BrowserThread::IsMessageLoopValid(BrowserThread::FILE) ||
384       !BrowserThread::IsMessageLoopValid(BrowserThread::IO)) {
385     has_threads_ = false;
386   }
387 
388   db_enabled_ = has_threads_
389       && (CommandLine::ForCurrentProcess()->
390           HasSwitch(switches::kEnableExtensionActivityLogging)
391       || watchdog_apps_active_);
392 
393   ExtensionSystem::Get(profile_)->ready().Post(
394       FROM_HERE,
395       base::Bind(&ActivityLog::StartObserving, base::Unretained(this)));
396 
397 // None of this should run on Android since the AL is behind ENABLE_EXTENSION
398 // checks. However, UmaPolicy can't even compile on Android because it uses
399 // BrowserList and related classes that aren't compiled for Android.
400 #if !defined(OS_ANDROID)
401   if (!profile_->IsOffTheRecord())
402     uma_policy_ = new UmaPolicy(profile_);
403 #endif
404 
405   ChooseDatabasePolicy();
406 }
407 
SetDatabasePolicy(ActivityLogPolicy::PolicyType policy_type)408 void ActivityLog::SetDatabasePolicy(
409     ActivityLogPolicy::PolicyType policy_type) {
410   if (database_policy_type_ == policy_type)
411     return;
412   if (!IsDatabaseEnabled() && !IsWatchdogAppActive())
413     return;
414 
415   // Deleting the old policy takes place asynchronously, on the database
416   // thread.  Initializing a new policy below similarly happens
417   // asynchronously.  Since the two operations are both queued for the
418   // database, the queue ordering should ensure that the deletion completes
419   // before database initialization occurs.
420   //
421   // However, changing policies at runtime is still not recommended, and
422   // likely only should be done for unit tests.
423   if (database_policy_)
424     database_policy_->Close();
425 
426   switch (policy_type) {
427     case ActivityLogPolicy::POLICY_FULLSTREAM:
428       database_policy_ = new FullStreamUIPolicy(profile_);
429       break;
430     case ActivityLogPolicy::POLICY_COUNTS:
431       database_policy_ = new CountingPolicy(profile_);
432       break;
433     default:
434       NOTREACHED();
435   }
436   database_policy_->Init();
437   database_policy_type_ = policy_type;
438 }
439 
~ActivityLog()440 ActivityLog::~ActivityLog() {
441   if (uma_policy_)
442     uma_policy_->Close();
443   if (database_policy_)
444     database_policy_->Close();
445 }
446 
447 // MAINTAIN STATUS. ------------------------------------------------------------
448 
StartObserving()449 void ActivityLog::StartObserving() {
450   extension_registry_observer_.Add(ExtensionRegistry::Get(profile_));
451 }
452 
ChooseDatabasePolicy()453 void ActivityLog::ChooseDatabasePolicy() {
454   if (!(IsDatabaseEnabled() || IsWatchdogAppActive()))
455     return;
456   if (testing_mode_)
457     SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
458   else
459     SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
460 }
461 
IsDatabaseEnabled()462 bool ActivityLog::IsDatabaseEnabled() {
463   // Make sure we are not enabled when there are no threads.
464   DCHECK(has_threads_ || !db_enabled_);
465   return db_enabled_;
466 }
467 
IsWatchdogAppActive()468 bool ActivityLog::IsWatchdogAppActive() {
469   return (watchdog_apps_active_ > 0);
470 }
471 
SetWatchdogAppActiveForTesting(bool active)472 void ActivityLog::SetWatchdogAppActiveForTesting(bool active) {
473   watchdog_apps_active_ = active ? 1 : 0;
474 }
475 
OnExtensionLoaded(content::BrowserContext * browser_context,const Extension * extension)476 void ActivityLog::OnExtensionLoaded(content::BrowserContext* browser_context,
477                                     const Extension* extension) {
478   if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
479   if (has_threads_)
480     db_enabled_ = true;
481   watchdog_apps_active_++;
482   profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
483                                    watchdog_apps_active_);
484   if (watchdog_apps_active_ == 1)
485     ChooseDatabasePolicy();
486 }
487 
OnExtensionUnloaded(content::BrowserContext * browser_context,const Extension * extension,UnloadedExtensionInfo::Reason reason)488 void ActivityLog::OnExtensionUnloaded(content::BrowserContext* browser_context,
489                                       const Extension* extension,
490                                       UnloadedExtensionInfo::Reason reason) {
491   if (!ActivityLogAPI::IsExtensionWhitelisted(extension->id())) return;
492   watchdog_apps_active_--;
493   profile_->GetPrefs()->SetInteger(prefs::kWatchdogExtensionActive,
494                                    watchdog_apps_active_);
495   if (watchdog_apps_active_ == 0 &&
496       !CommandLine::ForCurrentProcess()->HasSwitch(
497           switches::kEnableExtensionActivityLogging)) {
498     db_enabled_ = false;
499   }
500 }
501 
502 // OnExtensionUnloaded will also be called right before this.
OnExtensionUninstalled(content::BrowserContext * browser_context,const Extension * extension)503 void ActivityLog::OnExtensionUninstalled(
504     content::BrowserContext* browser_context,
505     const Extension* extension) {
506   if (ActivityLogAPI::IsExtensionWhitelisted(extension->id()) &&
507       !CommandLine::ForCurrentProcess()->HasSwitch(
508           switches::kEnableExtensionActivityLogging) &&
509       watchdog_apps_active_ == 0) {
510     DeleteDatabase();
511   } else if (database_policy_) {
512     database_policy_->RemoveExtensionData(extension->id());
513   }
514 }
515 
AddObserver(ActivityLog::Observer * observer)516 void ActivityLog::AddObserver(ActivityLog::Observer* observer) {
517   observers_->AddObserver(observer);
518 }
519 
RemoveObserver(ActivityLog::Observer * observer)520 void ActivityLog::RemoveObserver(ActivityLog::Observer* observer) {
521   observers_->RemoveObserver(observer);
522 }
523 
524 // static
RegisterProfilePrefs(user_prefs::PrefRegistrySyncable * registry)525 void ActivityLog::RegisterProfilePrefs(
526     user_prefs::PrefRegistrySyncable* registry) {
527   registry->RegisterIntegerPref(
528       prefs::kWatchdogExtensionActive,
529       false,
530       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
531   registry->RegisterBooleanPref(
532       prefs::kWatchdogExtensionActiveOld,
533       false,
534       user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
535 }
536 
537 // LOG ACTIONS. ----------------------------------------------------------------
538 
LogAction(scoped_refptr<Action> action)539 void ActivityLog::LogAction(scoped_refptr<Action> action) {
540   if (ActivityLogAPI::IsExtensionWhitelisted(action->extension_id()))
541     return;
542 
543   // Perform some preprocessing of the Action data: convert tab IDs to URLs and
544   // mask out incognito URLs if appropriate.
545   ExtractUrls(action, profile_);
546 
547   // Mark DOM XHR requests as such, for easier processing later.
548   if (action->action_type() == Action::ACTION_DOM_ACCESS &&
549       StartsWithASCII(action->api_name(), kDomXhrPrefix, true) &&
550       action->other()) {
551     base::DictionaryValue* other = action->mutable_other();
552     int dom_verb = -1;
553     if (other->GetInteger(constants::kActionDomVerb, &dom_verb) &&
554         dom_verb == DomActionType::METHOD) {
555       other->SetInteger(constants::kActionDomVerb, DomActionType::XHR);
556     }
557   }
558 
559   if (uma_policy_)
560     uma_policy_->ProcessAction(action);
561   if (IsDatabaseEnabled() && database_policy_)
562     database_policy_->ProcessAction(action);
563   if (IsWatchdogAppActive())
564     observers_->Notify(&Observer::OnExtensionActivity, action);
565   if (testing_mode_)
566     VLOG(1) << action->PrintForDebug();
567 }
568 
OnScriptsExecuted(const content::WebContents * web_contents,const ExecutingScriptsMap & extension_ids,int32 on_page_id,const GURL & on_url)569 void ActivityLog::OnScriptsExecuted(
570     const content::WebContents* web_contents,
571     const ExecutingScriptsMap& extension_ids,
572     int32 on_page_id,
573     const GURL& on_url) {
574   Profile* profile =
575       Profile::FromBrowserContext(web_contents->GetBrowserContext());
576   ExtensionRegistry* registry = ExtensionRegistry::Get(profile);
577   for (ExecutingScriptsMap::const_iterator it = extension_ids.begin();
578        it != extension_ids.end(); ++it) {
579     const Extension* extension =
580         registry->GetExtensionById(it->first, ExtensionRegistry::ENABLED);
581     if (!extension || ActivityLogAPI::IsExtensionWhitelisted(extension->id()))
582       continue;
583 
584     // If OnScriptsExecuted is fired because of tabs.executeScript, the list
585     // of content scripts will be empty.  We don't want to log it because
586     // the call to tabs.executeScript will have already been logged anyway.
587     if (!it->second.empty()) {
588       scoped_refptr<Action> action;
589       action = new Action(extension->id(),
590                           base::Time::Now(),
591                           Action::ACTION_CONTENT_SCRIPT,
592                           "");  // no API call here
593       action->set_page_url(on_url);
594       action->set_page_title(base::UTF16ToUTF8(web_contents->GetTitle()));
595       action->set_page_incognito(
596           web_contents->GetBrowserContext()->IsOffTheRecord());
597 
598       const prerender::PrerenderManager* prerender_manager =
599           prerender::PrerenderManagerFactory::GetForProfile(profile);
600       if (prerender_manager &&
601           prerender_manager->IsWebContentsPrerendering(web_contents, NULL))
602         action->mutable_other()->SetBoolean(constants::kActionPrerender, true);
603       for (std::set<std::string>::const_iterator it2 = it->second.begin();
604            it2 != it->second.end();
605            ++it2) {
606         action->mutable_args()->AppendString(*it2);
607       }
608       LogAction(action);
609     }
610   }
611 }
612 
OnApiEventDispatched(const std::string & extension_id,const std::string & event_name,scoped_ptr<base::ListValue> event_args)613 void ActivityLog::OnApiEventDispatched(const std::string& extension_id,
614                                        const std::string& event_name,
615                                        scoped_ptr<base::ListValue> event_args) {
616   DCHECK_CURRENTLY_ON(BrowserThread::UI);
617   scoped_refptr<Action> action = new Action(extension_id,
618                                             base::Time::Now(),
619                                             Action::ACTION_API_EVENT,
620                                             event_name);
621   action->set_args(event_args.Pass());
622   LogAction(action);
623 }
624 
OnApiFunctionCalled(const std::string & extension_id,const std::string & api_name,scoped_ptr<base::ListValue> args)625 void ActivityLog::OnApiFunctionCalled(const std::string& extension_id,
626                                       const std::string& api_name,
627                                       scoped_ptr<base::ListValue> args) {
628   DCHECK_CURRENTLY_ON(BrowserThread::UI);
629   scoped_refptr<Action> action = new Action(extension_id,
630                                             base::Time::Now(),
631                                             Action::ACTION_API_CALL,
632                                             api_name);
633   action->set_args(args.Pass());
634   LogAction(action);
635 }
636 
637 // LOOKUP ACTIONS. -------------------------------------------------------------
638 
GetFilteredActions(const std::string & extension_id,const Action::ActionType type,const std::string & api_name,const std::string & page_url,const std::string & arg_url,const int daysAgo,const base::Callback<void (scoped_ptr<std::vector<scoped_refptr<Action>>>)> & callback)639 void ActivityLog::GetFilteredActions(
640     const std::string& extension_id,
641     const Action::ActionType type,
642     const std::string& api_name,
643     const std::string& page_url,
644     const std::string& arg_url,
645     const int daysAgo,
646     const base::Callback
647         <void(scoped_ptr<std::vector<scoped_refptr<Action> > >)>& callback) {
648   if (database_policy_) {
649     database_policy_->ReadFilteredData(
650         extension_id, type, api_name, page_url, arg_url, daysAgo, callback);
651   }
652 }
653 
654 // DELETE ACTIONS. -------------------------------------------------------------
655 
RemoveActions(const std::vector<int64> & action_ids)656 void ActivityLog::RemoveActions(const std::vector<int64>& action_ids) {
657   if (!database_policy_)
658     return;
659   database_policy_->RemoveActions(action_ids);
660 }
661 
RemoveURLs(const std::vector<GURL> & restrict_urls)662 void ActivityLog::RemoveURLs(const std::vector<GURL>& restrict_urls) {
663   if (!database_policy_)
664     return;
665   database_policy_->RemoveURLs(restrict_urls);
666 }
667 
RemoveURLs(const std::set<GURL> & restrict_urls)668 void ActivityLog::RemoveURLs(const std::set<GURL>& restrict_urls) {
669   if (!database_policy_)
670     return;
671 
672   std::vector<GURL> urls;
673   for (std::set<GURL>::const_iterator it = restrict_urls.begin();
674        it != restrict_urls.end(); ++it) {
675     urls.push_back(*it);
676   }
677   database_policy_->RemoveURLs(urls);
678 }
679 
RemoveURL(const GURL & url)680 void ActivityLog::RemoveURL(const GURL& url) {
681   if (url.is_empty())
682     return;
683   std::vector<GURL> urls;
684   urls.push_back(url);
685   RemoveURLs(urls);
686 }
687 
DeleteDatabase()688 void ActivityLog::DeleteDatabase() {
689   if (!database_policy_)
690     return;
691   database_policy_->DeleteDatabase();
692 }
693 
694 template <>
DeclareFactoryDependencies()695 void BrowserContextKeyedAPIFactory<ActivityLog>::DeclareFactoryDependencies() {
696   DependsOn(ExtensionsBrowserClient::Get()->GetExtensionSystemFactory());
697   DependsOn(ExtensionRegistryFactory::GetInstance());
698 }
699 
700 }  // namespace extensions
701