1// Copyright (c) 2012 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 "base/command_line.h"
6#include "base/memory/scoped_ptr.h"
7#include "base/message_loop/message_loop.h"
8#include "base/run_loop.h"
9#include "base/synchronization/waitable_event.h"
10#include "chrome/browser/extensions/activity_log/activity_action_constants.h"
11#include "chrome/browser/extensions/activity_log/activity_log.h"
12#include "chrome/browser/extensions/extension_service.h"
13#include "chrome/browser/extensions/test_extension_system.h"
14#include "chrome/browser/prerender/prerender_handle.h"
15#include "chrome/browser/prerender/prerender_manager.h"
16#include "chrome/browser/prerender/prerender_manager_factory.h"
17#include "chrome/common/chrome_constants.h"
18#include "chrome/common/chrome_switches.h"
19#include "chrome/test/base/chrome_render_view_host_test_harness.h"
20#include "chrome/test/base/testing_profile.h"
21#include "content/public/test/test_browser_thread_bundle.h"
22#include "extensions/browser/extension_registry.h"
23#include "extensions/common/dom_action_types.h"
24#include "extensions/common/extension_builder.h"
25#include "sql/statement.h"
26#include "testing/gtest/include/gtest/gtest.h"
27
28#if defined(OS_CHROMEOS)
29#include "chrome/browser/chromeos/login/users/user_manager.h"
30#include "chrome/browser/chromeos/settings/cros_settings.h"
31#include "chrome/browser/chromeos/settings/device_settings_service.h"
32#endif
33
34namespace {
35
36const char kExtensionId[] = "abc";
37
38const char* kUrlApiCalls[] = {
39    "HTMLButtonElement.formAction", "HTMLEmbedElement.src",
40    "HTMLFormElement.action",       "HTMLFrameElement.src",
41    "HTMLHtmlElement.manifest",     "HTMLIFrameElement.src",
42    "HTMLImageElement.longDesc",    "HTMLImageElement.src",
43    "HTMLImageElement.lowsrc",      "HTMLInputElement.formAction",
44    "HTMLInputElement.src",         "HTMLLinkElement.href",
45    "HTMLMediaElement.src",         "HTMLMediaElement.currentSrc",
46    "HTMLModElement.cite",          "HTMLObjectElement.data",
47    "HTMLQuoteElement.cite",        "HTMLScriptElement.src",
48    "HTMLSourceElement.src",        "HTMLTrackElement.src",
49    "HTMLVideoElement.poster"};
50
51}  // namespace
52
53namespace extensions {
54
55class ActivityLogTest : public ChromeRenderViewHostTestHarness {
56 protected:
57  virtual void SetUp() OVERRIDE {
58    ChromeRenderViewHostTestHarness::SetUp();
59#if defined OS_CHROMEOS
60    test_user_manager_.reset(new chromeos::ScopedTestUserManager());
61#endif
62    CommandLine command_line(CommandLine::NO_PROGRAM);
63    CommandLine::ForCurrentProcess()->AppendSwitch(
64        switches::kEnableExtensionActivityLogging);
65    CommandLine::ForCurrentProcess()->AppendSwitch(
66        switches::kEnableExtensionActivityLogTesting);
67    extension_service_ = static_cast<TestExtensionSystem*>(
68        ExtensionSystem::Get(profile()))->CreateExtensionService
69            (&command_line, base::FilePath(), false);
70    base::RunLoop().RunUntilIdle();
71  }
72
73  virtual void TearDown() OVERRIDE {
74#if defined OS_CHROMEOS
75    test_user_manager_.reset();
76#endif
77    base::RunLoop().RunUntilIdle();
78    ChromeRenderViewHostTestHarness::TearDown();
79  }
80
81  static void RetrieveActions_LogAndFetchActions0(
82      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
83    ASSERT_EQ(0, static_cast<int>(i->size()));
84  }
85
86  static void RetrieveActions_LogAndFetchActions2(
87      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
88    ASSERT_EQ(2, static_cast<int>(i->size()));
89  }
90
91  void SetPolicy(bool log_arguments) {
92    ActivityLog* activity_log = ActivityLog::GetInstance(profile());
93    if (log_arguments)
94      activity_log->SetDatabasePolicy(ActivityLogPolicy::POLICY_FULLSTREAM);
95    else
96      activity_log->SetDatabasePolicy(ActivityLogPolicy::POLICY_COUNTS);
97  }
98
99  bool GetDatabaseEnabled() {
100    ActivityLog* activity_log = ActivityLog::GetInstance(profile());
101    return activity_log->IsDatabaseEnabled();
102  }
103
104  bool GetWatchdogActive() {
105    ActivityLog* activity_log = ActivityLog::GetInstance(profile());
106    return activity_log->IsWatchdogAppActive();
107  }
108
109  static void Arguments_Prerender(
110      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
111    ASSERT_EQ(1U, i->size());
112    scoped_refptr<Action> last = i->front();
113
114    ASSERT_EQ("odlameecjipmbmbejkplpemijjgpljce", last->extension_id());
115    ASSERT_EQ(Action::ACTION_CONTENT_SCRIPT, last->action_type());
116    ASSERT_EQ("[\"script\"]",
117              ActivityLogPolicy::Util::Serialize(last->args()));
118    ASSERT_EQ("http://www.google.com/", last->SerializePageUrl());
119    ASSERT_EQ("{\"prerender\":true}",
120              ActivityLogPolicy::Util::Serialize(last->other()));
121    ASSERT_EQ("", last->api_name());
122    ASSERT_EQ("", last->page_title());
123    ASSERT_EQ("", last->SerializeArgUrl());
124  }
125
126  static void RetrieveActions_ArgUrlExtraction(
127      scoped_ptr<std::vector<scoped_refptr<Action> > > i) {
128    const base::DictionaryValue* other = NULL;
129    int dom_verb = -1;
130
131    ASSERT_EQ(4U, i->size());
132    scoped_refptr<Action> action = i->at(0);
133    ASSERT_EQ("XMLHttpRequest.open", action->api_name());
134    ASSERT_EQ("[\"POST\",\"\\u003Carg_url>\"]",
135              ActivityLogPolicy::Util::Serialize(action->args()));
136    ASSERT_EQ("http://api.google.com/", action->arg_url().spec());
137    // Test that the dom_verb field was changed to XHR (from METHOD).  This
138    // could be tested on all retrieved XHR actions but it would be redundant,
139    // so just test once.
140    other = action->other();
141    ASSERT_TRUE(other);
142    ASSERT_TRUE(other->GetInteger(activity_log_constants::kActionDomVerb,
143                                  &dom_verb));
144    ASSERT_EQ(DomActionType::XHR, dom_verb);
145
146    action = i->at(1);
147    ASSERT_EQ("XMLHttpRequest.open", action->api_name());
148    ASSERT_EQ("[\"POST\",\"\\u003Carg_url>\"]",
149              ActivityLogPolicy::Util::Serialize(action->args()));
150    ASSERT_EQ("http://www.google.com/api/", action->arg_url().spec());
151
152    action = i->at(2);
153    ASSERT_EQ("XMLHttpRequest.open", action->api_name());
154    ASSERT_EQ("[\"POST\",\"/api/\"]",
155              ActivityLogPolicy::Util::Serialize(action->args()));
156    ASSERT_FALSE(action->arg_url().is_valid());
157
158    action = i->at(3);
159    ASSERT_EQ("windows.create", action->api_name());
160    ASSERT_EQ("[{\"url\":\"\\u003Carg_url>\"}]",
161              ActivityLogPolicy::Util::Serialize(action->args()));
162    ASSERT_EQ("http://www.google.co.uk/", action->arg_url().spec());
163  }
164
165  static void RetrieveActions_ArgUrlApiCalls(
166      scoped_ptr<std::vector<scoped_refptr<Action> > > actions) {
167    size_t api_calls_size = arraysize(kUrlApiCalls);
168    const base::DictionaryValue* other = NULL;
169    int dom_verb = -1;
170
171    ASSERT_EQ(api_calls_size, actions->size());
172
173    for (size_t i = 0; i < actions->size(); i++) {
174      scoped_refptr<Action> action = actions->at(i);
175      ASSERT_EQ(kExtensionId, action->extension_id());
176      ASSERT_EQ(Action::ACTION_DOM_ACCESS, action->action_type());
177      ASSERT_EQ(kUrlApiCalls[i], action->api_name());
178      ASSERT_EQ("[\"\\u003Carg_url>\"]",
179                ActivityLogPolicy::Util::Serialize(action->args()));
180      ASSERT_EQ("http://www.google.co.uk/", action->arg_url().spec());
181      other = action->other();
182      ASSERT_TRUE(other);
183      ASSERT_TRUE(
184          other->GetInteger(activity_log_constants::kActionDomVerb, &dom_verb));
185      ASSERT_EQ(DomActionType::SETTER, dom_verb);
186    }
187  }
188
189  ExtensionService* extension_service_;
190
191#if defined OS_CHROMEOS
192  chromeos::ScopedTestDeviceSettingsService test_device_settings_service_;
193  chromeos::ScopedTestCrosSettings test_cros_settings_;
194  scoped_ptr<chromeos::ScopedTestUserManager> test_user_manager_;
195#endif
196};
197
198TEST_F(ActivityLogTest, Construct) {
199  ASSERT_TRUE(GetDatabaseEnabled());
200  ASSERT_FALSE(GetWatchdogActive());
201}
202
203TEST_F(ActivityLogTest, LogAndFetchActions) {
204  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
205  scoped_ptr<base::ListValue> args(new base::ListValue());
206  ASSERT_TRUE(GetDatabaseEnabled());
207
208  // Write some API calls
209  scoped_refptr<Action> action = new Action(kExtensionId,
210                                            base::Time::Now(),
211                                            Action::ACTION_API_CALL,
212                                            "tabs.testMethod");
213  activity_log->LogAction(action);
214  action = new Action(kExtensionId,
215                      base::Time::Now(),
216                      Action::ACTION_DOM_ACCESS,
217                      "document.write");
218  action->set_page_url(GURL("http://www.google.com"));
219  activity_log->LogAction(action);
220
221  activity_log->GetFilteredActions(
222      kExtensionId,
223      Action::ACTION_ANY,
224      "",
225      "",
226      "",
227      0,
228      base::Bind(ActivityLogTest::RetrieveActions_LogAndFetchActions2));
229}
230
231TEST_F(ActivityLogTest, LogPrerender) {
232  scoped_refptr<const Extension> extension =
233      ExtensionBuilder()
234          .SetManifest(DictionaryBuilder()
235                       .Set("name", "Test extension")
236                       .Set("version", "1.0.0")
237                       .Set("manifest_version", 2))
238          .Build();
239  extension_service_->AddExtension(extension.get());
240  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
241  ASSERT_TRUE(GetDatabaseEnabled());
242  GURL url("http://www.google.com");
243
244  prerender::PrerenderManager* prerender_manager =
245      prerender::PrerenderManagerFactory::GetForProfile(
246          Profile::FromBrowserContext(profile()));
247
248  prerender_manager->OnCookieStoreLoaded();
249
250  const gfx::Size kSize(640, 480);
251  scoped_ptr<prerender::PrerenderHandle> prerender_handle(
252      prerender_manager->AddPrerenderFromLocalPredictor(
253          url,
254          web_contents()->GetController().GetDefaultSessionStorageNamespace(),
255          kSize));
256
257  const std::vector<content::WebContents*> contentses =
258      prerender_manager->GetAllPrerenderingContents();
259  ASSERT_EQ(1U, contentses.size());
260  content::WebContents *contents = contentses[0];
261  ASSERT_TRUE(prerender_manager->IsWebContentsPrerendering(contents, NULL));
262
263  TabHelper::ScriptExecutionObserver::ExecutingScriptsMap executing_scripts;
264  executing_scripts[extension->id()].insert("script");
265
266  static_cast<TabHelper::ScriptExecutionObserver*>(activity_log)->
267      OnScriptsExecuted(contents, executing_scripts, 0, url);
268
269  activity_log->GetFilteredActions(
270      extension->id(),
271      Action::ACTION_ANY,
272      "",
273      "",
274      "",
275      0,
276      base::Bind(ActivityLogTest::Arguments_Prerender));
277
278  prerender_manager->CancelAllPrerenders();
279}
280
281TEST_F(ActivityLogTest, ArgUrlExtraction) {
282  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
283  scoped_ptr<base::ListValue> args(new base::ListValue());
284
285  base::Time now = base::Time::Now();
286
287  // Submit a DOM API call which should have its URL extracted into the arg_url
288  // field.
289  scoped_refptr<Action> action = new Action(kExtensionId,
290                                            now,
291                                            Action::ACTION_DOM_ACCESS,
292                                            "XMLHttpRequest.open");
293  action->set_page_url(GURL("http://www.google.com/"));
294  action->mutable_args()->AppendString("POST");
295  action->mutable_args()->AppendString("http://api.google.com/");
296  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
297                                      DomActionType::METHOD);
298  activity_log->LogAction(action);
299
300  // Submit a DOM API call with a relative URL in the argument, which should be
301  // resolved relative to the page URL.
302  action = new Action(kExtensionId,
303                      now - base::TimeDelta::FromSeconds(1),
304                      Action::ACTION_DOM_ACCESS,
305                      "XMLHttpRequest.open");
306  action->set_page_url(GURL("http://www.google.com/"));
307  action->mutable_args()->AppendString("POST");
308  action->mutable_args()->AppendString("/api/");
309  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
310                                      DomActionType::METHOD);
311  activity_log->LogAction(action);
312
313  // Submit a DOM API call with a relative URL but no base page URL against
314  // which to resolve.
315  action = new Action(kExtensionId,
316                      now - base::TimeDelta::FromSeconds(2),
317                      Action::ACTION_DOM_ACCESS,
318                      "XMLHttpRequest.open");
319  action->mutable_args()->AppendString("POST");
320  action->mutable_args()->AppendString("/api/");
321  action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
322                                      DomActionType::METHOD);
323  activity_log->LogAction(action);
324
325  // Submit an API call with an embedded URL.
326  action = new Action(kExtensionId,
327                      now - base::TimeDelta::FromSeconds(3),
328                      Action::ACTION_API_CALL,
329                      "windows.create");
330  action->set_args(
331      ListBuilder()
332          .Append(DictionaryBuilder().Set("url", "http://www.google.co.uk"))
333          .Build());
334  activity_log->LogAction(action);
335
336  activity_log->GetFilteredActions(
337      kExtensionId,
338      Action::ACTION_ANY,
339      "",
340      "",
341      "",
342      -1,
343      base::Bind(ActivityLogTest::RetrieveActions_ArgUrlExtraction));
344}
345
346TEST_F(ActivityLogTest, UninstalledExtension) {
347  scoped_refptr<const Extension> extension =
348      ExtensionBuilder()
349          .SetManifest(DictionaryBuilder()
350                       .Set("name", "Test extension")
351                       .Set("version", "1.0.0")
352                       .Set("manifest_version", 2))
353          .Build();
354
355  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
356  scoped_ptr<base::ListValue> args(new base::ListValue());
357  ASSERT_TRUE(GetDatabaseEnabled());
358
359  // Write some API calls
360  scoped_refptr<Action> action = new Action(extension->id(),
361                                            base::Time::Now(),
362                                            Action::ACTION_API_CALL,
363                                            "tabs.testMethod");
364  activity_log->LogAction(action);
365  action = new Action(extension->id(),
366                      base::Time::Now(),
367                      Action::ACTION_DOM_ACCESS,
368                      "document.write");
369  action->set_page_url(GURL("http://www.google.com"));
370
371  activity_log->OnExtensionUninstalled(NULL, extension);
372  activity_log->GetFilteredActions(
373      extension->id(),
374      Action::ACTION_ANY,
375      "",
376      "",
377      "",
378      -1,
379      base::Bind(ActivityLogTest::RetrieveActions_LogAndFetchActions0));
380}
381
382TEST_F(ActivityLogTest, ArgUrlApiCalls) {
383  ActivityLog* activity_log = ActivityLog::GetInstance(profile());
384  scoped_ptr<base::ListValue> args(new base::ListValue());
385  base::Time now = base::Time::Now();
386  int api_calls_size = arraysize(kUrlApiCalls);
387  scoped_refptr<Action> action;
388
389  for (int i = 0; i < api_calls_size; i++) {
390    action = new Action(kExtensionId,
391                        now - base::TimeDelta::FromSeconds(i),
392                        Action::ACTION_DOM_ACCESS,
393                        kUrlApiCalls[i]);
394    action->mutable_args()->AppendString("http://www.google.co.uk");
395    action->mutable_other()->SetInteger(activity_log_constants::kActionDomVerb,
396                                        DomActionType::SETTER);
397    activity_log->LogAction(action);
398  }
399
400  activity_log->GetFilteredActions(
401      kExtensionId,
402      Action::ACTION_ANY,
403      "",
404      "",
405      "",
406      -1,
407      base::Bind(ActivityLogTest::RetrieveActions_ArgUrlApiCalls));
408}
409
410}  // namespace extensions
411