1// Copyright 2014 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/scoped_observer.h"
6#include "chrome/browser/extensions/extension_browsertest.h"
7#include "chrome/common/chrome_switches.h"
8#include "content/public/test/test_utils.h"
9#include "extensions/browser/content_verify_job.h"
10#include "extensions/browser/extension_prefs.h"
11#include "extensions/browser/extension_registry.h"
12#include "extensions/browser/extension_registry_observer.h"
13
14namespace extensions {
15
16namespace {
17
18// Helper for observing extension unloads.
19class UnloadObserver : public ExtensionRegistryObserver {
20 public:
21  explicit UnloadObserver(ExtensionRegistry* registry) : observer_(this) {
22    observer_.Add(registry);
23  }
24  virtual ~UnloadObserver() {}
25
26  void WaitForUnload(const ExtensionId& id) {
27    if (ContainsKey(observed_, id))
28      return;
29
30    ASSERT_TRUE(loop_runner_.get() == NULL);
31    awaited_id_ = id;
32    loop_runner_ = new content::MessageLoopRunner();
33    loop_runner_->Run();
34  }
35
36  virtual void OnExtensionUnloaded(
37      content::BrowserContext* browser_context,
38      const Extension* extension,
39      UnloadedExtensionInfo::Reason reason) OVERRIDE {
40    observed_.insert(extension->id());
41    if (awaited_id_ == extension->id())
42      loop_runner_->Quit();
43  }
44
45 private:
46  ExtensionId awaited_id_;
47  std::set<ExtensionId> observed_;
48  scoped_refptr<content::MessageLoopRunner> loop_runner_;
49  ScopedObserver<ExtensionRegistry, UnloadObserver> observer_;
50};
51
52// Helper for forcing ContentVerifyJob's to return an error.
53class JobDelegate : public ContentVerifyJob::TestDelegate {
54 public:
55  JobDelegate() : fail_next_read_(false), fail_next_done_(false) {}
56
57  virtual ~JobDelegate() {}
58
59  void set_id(const ExtensionId& id) { id_ = id; }
60  void fail_next_read() { fail_next_read_ = true; }
61  void fail_next_done() { fail_next_done_ = true; }
62
63  virtual ContentVerifyJob::FailureReason BytesRead(const ExtensionId& id,
64                                                    int count,
65                                                    const char* data) OVERRIDE {
66    if (id == id_ && fail_next_read_) {
67      fail_next_read_ = false;
68      return ContentVerifyJob::HASH_MISMATCH;
69    }
70    return ContentVerifyJob::NONE;
71  }
72
73  virtual ContentVerifyJob::FailureReason DoneReading(
74      const ExtensionId& id) OVERRIDE {
75    if (id == id_ && fail_next_done_) {
76      fail_next_done_ = false;
77      return ContentVerifyJob::HASH_MISMATCH;
78    }
79    return ContentVerifyJob::NONE;
80  }
81
82 private:
83  DISALLOW_COPY_AND_ASSIGN(JobDelegate);
84
85  ExtensionId id_;
86  bool fail_next_read_;
87  bool fail_next_done_;
88};
89
90class JobObserver : public ContentVerifyJob::TestObserver {
91 public:
92  JobObserver();
93  virtual ~JobObserver();
94
95  // Call this to add an expected job result.
96  void ExpectJobResult(const std::string& extension_id,
97                       const base::FilePath& relative_path,
98                       bool expected_to_fail);
99
100  // Wait to see expected jobs. Returns true if we saw all jobs finish as
101  // expected, or false if any job completed with non-expected success/failure
102  // status.
103  bool WaitForExpectedJobs();
104
105  // ContentVerifyJob::TestObserver interface
106  virtual void JobStarted(const std::string& extension_id,
107                          const base::FilePath& relative_path) OVERRIDE;
108
109  virtual void JobFinished(const std::string& extension_id,
110                           const base::FilePath& relative_path,
111                           bool failed) OVERRIDE;
112
113 private:
114  typedef std::pair<std::string, base::FilePath> ExtensionFile;
115  typedef std::map<ExtensionFile, bool> ExpectedJobs;
116  ExpectedJobs expected_jobs_;
117  scoped_refptr<content::MessageLoopRunner> loop_runner_;
118  bool saw_expected_job_results_;
119};
120
121void JobObserver::ExpectJobResult(const std::string& extension_id,
122                                  const base::FilePath& relative_path,
123                                  bool expected_to_fail) {
124  expected_jobs_.insert(std::make_pair(
125      ExtensionFile(extension_id, relative_path), expected_to_fail));
126}
127
128JobObserver::JobObserver() : saw_expected_job_results_(false) {
129}
130
131JobObserver::~JobObserver() {
132}
133
134bool JobObserver::WaitForExpectedJobs() {
135  if (!expected_jobs_.empty()) {
136    loop_runner_ = new content::MessageLoopRunner();
137    loop_runner_->Run();
138  }
139  return saw_expected_job_results_;
140}
141
142void JobObserver::JobStarted(const std::string& extension_id,
143                             const base::FilePath& relative_path) {
144}
145
146void JobObserver::JobFinished(const std::string& extension_id,
147                              const base::FilePath& relative_path,
148                              bool failed) {
149  ExpectedJobs::iterator i = expected_jobs_.find(ExtensionFile(
150      extension_id, relative_path.NormalizePathSeparatorsTo('/')));
151  if (i != expected_jobs_.end()) {
152    if (failed != i->second) {
153      saw_expected_job_results_ = false;
154      if (loop_runner_.get())
155        loop_runner_->Quit();
156    }
157    expected_jobs_.erase(i);
158    if (expected_jobs_.empty()) {
159      saw_expected_job_results_ = true;
160      if (loop_runner_.get())
161        loop_runner_->Quit();
162    }
163  }
164}
165
166}  // namespace
167
168class ContentVerifierTest : public ExtensionBrowserTest {
169 public:
170  ContentVerifierTest() {}
171  virtual ~ContentVerifierTest() {}
172
173  virtual void SetUpCommandLine(base::CommandLine* command_line) OVERRIDE {
174    ExtensionBrowserTest::SetUpCommandLine(command_line);
175    command_line->AppendSwitchASCII(
176        switches::kExtensionContentVerification,
177        switches::kExtensionContentVerificationEnforce);
178  }
179
180  // Setup our unload observer and JobDelegate, and install a test extension.
181  virtual void SetUpOnMainThread() OVERRIDE {
182    ExtensionBrowserTest::SetUpOnMainThread();
183  }
184
185  virtual void TearDownOnMainThread() override {
186    ContentVerifyJob::SetDelegateForTests(NULL);
187    ContentVerifyJob::SetObserverForTests(NULL);
188    ExtensionBrowserTest::TearDownOnMainThread();
189  }
190
191  virtual void OpenPageAndWaitForUnload() {
192    unload_observer_.reset(
193        new UnloadObserver(ExtensionRegistry::Get(profile())));
194    const Extension* extension = InstallExtensionFromWebstore(
195        test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1);
196    ASSERT_TRUE(extension);
197    id_ = extension->id();
198    page_url_ = extension->GetResourceURL("page.html");
199    delegate_.set_id(id_);
200    ContentVerifyJob::SetDelegateForTests(&delegate_);
201    AddTabAtIndex(1, page_url_, ui::PAGE_TRANSITION_LINK);
202    unload_observer_->WaitForUnload(id_);
203    ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
204    int reasons = prefs->GetDisableReasons(id_);
205    EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED);
206
207    // This needs to happen before the ExtensionRegistry gets deleted, which
208    // happens before TearDownOnMainThread is called.
209    unload_observer_.reset();
210  }
211
212 protected:
213  JobDelegate delegate_;
214  scoped_ptr<UnloadObserver> unload_observer_;
215  ExtensionId id_;
216  GURL page_url_;
217};
218
219IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) {
220  delegate_.fail_next_read();
221  OpenPageAndWaitForUnload();
222}
223
224IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnDone) {
225  delegate_.fail_next_done();
226  OpenPageAndWaitForUnload();
227}
228
229IN_PROC_BROWSER_TEST_F(ContentVerifierTest, DotSlashPaths) {
230  JobObserver job_observer;
231  ContentVerifyJob::SetObserverForTests(&job_observer);
232  std::string id = "hoipipabpcoomfapcecilckodldhmpgl";
233
234  job_observer.ExpectJobResult(
235      id, base::FilePath(FILE_PATH_LITERAL("background.js")), false);
236  job_observer.ExpectJobResult(
237      id, base::FilePath(FILE_PATH_LITERAL("page.html")), false);
238  job_observer.ExpectJobResult(
239      id, base::FilePath(FILE_PATH_LITERAL("page.js")), false);
240  job_observer.ExpectJobResult(
241      id, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")), false);
242  job_observer.ExpectJobResult(
243      id, base::FilePath(FILE_PATH_LITERAL("page2.js")), false);
244
245  // Install a test extension we copied from the webstore that has actual
246  // signatures, and contains image paths with leading "./".
247  const Extension* extension = InstallExtensionFromWebstore(
248      test_data_dir_.AppendASCII("content_verifier/dot_slash_paths.crx"), 1);
249
250  ASSERT_TRUE(extension);
251  ASSERT_EQ(extension->id(), id);
252
253  EXPECT_TRUE(job_observer.WaitForExpectedJobs());
254
255  ContentVerifyJob::SetObserverForTests(NULL);
256}
257
258}  // namespace extensions
259