Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / extensions / content_verifier_browsertest.cc
bloba547f1d2b4b5a65c22ba0b043e8b272175f1d93f
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.
5 #include <list>
6 #include <set>
7 #include <string>
9 #include "base/scoped_observer.h"
10 #include "chrome/browser/extensions/extension_browsertest.h"
11 #include "chrome/common/chrome_switches.h"
12 #include "content/public/test/test_utils.h"
13 #include "extensions/browser/content_verifier.h"
14 #include "extensions/browser/content_verify_job.h"
15 #include "extensions/browser/extension_prefs.h"
16 #include "extensions/browser/extension_registry.h"
17 #include "extensions/browser/extension_registry_observer.h"
19 namespace extensions {
21 namespace {
23 // Helper for observing extension unloads.
24 class UnloadObserver : public ExtensionRegistryObserver {
25 public:
26 explicit UnloadObserver(ExtensionRegistry* registry) : observer_(this) {
27 observer_.Add(registry);
29 ~UnloadObserver() override {}
31 void WaitForUnload(const ExtensionId& id) {
32 if (ContainsKey(observed_, id))
33 return;
35 ASSERT_TRUE(loop_runner_.get() == NULL);
36 awaited_id_ = id;
37 loop_runner_ = new content::MessageLoopRunner();
38 loop_runner_->Run();
41 void OnExtensionUnloaded(content::BrowserContext* browser_context,
42 const Extension* extension,
43 UnloadedExtensionInfo::Reason reason) override {
44 observed_.insert(extension->id());
45 if (awaited_id_ == extension->id())
46 loop_runner_->Quit();
49 private:
50 ExtensionId awaited_id_;
51 std::set<ExtensionId> observed_;
52 scoped_refptr<content::MessageLoopRunner> loop_runner_;
53 ScopedObserver<ExtensionRegistry, UnloadObserver> observer_;
56 // Helper for forcing ContentVerifyJob's to return an error.
57 class JobDelegate : public ContentVerifyJob::TestDelegate {
58 public:
59 JobDelegate() : fail_next_read_(false), fail_next_done_(false) {}
61 virtual ~JobDelegate() {}
63 void set_id(const ExtensionId& id) { id_ = id; }
64 void fail_next_read() { fail_next_read_ = true; }
65 void fail_next_done() { fail_next_done_ = true; }
67 ContentVerifyJob::FailureReason BytesRead(const ExtensionId& id,
68 int count,
69 const char* data) override {
70 if (id == id_ && fail_next_read_) {
71 fail_next_read_ = false;
72 return ContentVerifyJob::HASH_MISMATCH;
74 return ContentVerifyJob::NONE;
77 ContentVerifyJob::FailureReason DoneReading(const ExtensionId& id) override {
78 if (id == id_ && fail_next_done_) {
79 fail_next_done_ = false;
80 return ContentVerifyJob::HASH_MISMATCH;
82 return ContentVerifyJob::NONE;
85 private:
86 DISALLOW_COPY_AND_ASSIGN(JobDelegate);
88 ExtensionId id_;
89 bool fail_next_read_;
90 bool fail_next_done_;
93 class JobObserver : public ContentVerifyJob::TestObserver {
94 public:
95 JobObserver();
96 virtual ~JobObserver();
98 enum class Result { SUCCESS, FAILURE };
100 // Call this to add an expected job result.
101 void ExpectJobResult(const std::string& extension_id,
102 const base::FilePath& relative_path,
103 Result expected_result);
105 // Wait to see expected jobs. Returns true when we've seen all expected jobs
106 // finish, or false if there was an error or timeout.
107 bool WaitForExpectedJobs();
109 // ContentVerifyJob::TestObserver interface
110 void JobStarted(const std::string& extension_id,
111 const base::FilePath& relative_path) override;
113 void JobFinished(const std::string& extension_id,
114 const base::FilePath& relative_path,
115 bool failed) override;
117 private:
118 struct ExpectedResult {
119 public:
120 std::string extension_id;
121 base::FilePath path;
122 Result result;
124 ExpectedResult(const std::string& extension_id, const base::FilePath& path,
125 Result result) {
126 this->extension_id = extension_id;
127 this->path = path;
128 this->result = result;
131 std::list<ExpectedResult> expectations_;
132 content::BrowserThread::ID creation_thread_;
133 scoped_refptr<content::MessageLoopRunner> loop_runner_;
136 void JobObserver::ExpectJobResult(const std::string& extension_id,
137 const base::FilePath& relative_path,
138 Result expected_result) {
139 expectations_.push_back(ExpectedResult(
140 extension_id, relative_path, expected_result));
143 JobObserver::JobObserver() {
144 EXPECT_TRUE(
145 content::BrowserThread::GetCurrentThreadIdentifier(&creation_thread_));
148 JobObserver::~JobObserver() {
151 bool JobObserver::WaitForExpectedJobs() {
152 EXPECT_TRUE(content::BrowserThread::CurrentlyOn(creation_thread_));
153 if (!expectations_.empty()) {
154 loop_runner_ = new content::MessageLoopRunner();
155 loop_runner_->Run();
156 loop_runner_ = nullptr;
158 return expectations_.empty();
161 void JobObserver::JobStarted(const std::string& extension_id,
162 const base::FilePath& relative_path) {
165 void JobObserver::JobFinished(const std::string& extension_id,
166 const base::FilePath& relative_path,
167 bool failed) {
168 if (!content::BrowserThread::CurrentlyOn(creation_thread_)) {
169 content::BrowserThread::PostTask(
170 creation_thread_, FROM_HERE,
171 base::Bind(&JobObserver::JobFinished, base::Unretained(this),
172 extension_id, relative_path, failed));
173 return;
175 Result result = failed ? Result::FAILURE : Result::SUCCESS;
176 bool found = false;
177 for (std::list<ExpectedResult>::iterator i = expectations_.begin();
178 i != expectations_.end(); ++i) {
179 if (i->extension_id == extension_id && i->path == relative_path &&
180 i->result == result) {
181 found = true;
182 expectations_.erase(i);
183 break;
186 if (found) {
187 if (expectations_.empty() && loop_runner_.get())
188 loop_runner_->Quit();
189 } else {
190 LOG(WARNING) << "Ignoring unexpected JobFinished " << extension_id << "/"
191 << relative_path.value() << " failed:" << failed;
195 class VerifierObserver : public ContentVerifier::TestObserver {
196 public:
197 VerifierObserver();
198 virtual ~VerifierObserver();
200 const std::set<std::string>& completed_fetches() {
201 return completed_fetches_;
204 // Returns when we've seen OnFetchComplete for |extension_id|.
205 void WaitForFetchComplete(const std::string& extension_id);
207 // ContentVerifier::TestObserver
208 void OnFetchComplete(const std::string& extension_id, bool success) override;
210 private:
211 std::set<std::string> completed_fetches_;
212 std::string id_to_wait_for_;
213 scoped_refptr<content::MessageLoopRunner> loop_runner_;
216 VerifierObserver::VerifierObserver() {
219 VerifierObserver::~VerifierObserver() {
222 void VerifierObserver::WaitForFetchComplete(const std::string& extension_id) {
223 EXPECT_TRUE(id_to_wait_for_.empty());
224 EXPECT_EQ(loop_runner_.get(), nullptr);
225 id_to_wait_for_ = extension_id;
226 loop_runner_ = new content::MessageLoopRunner();
227 loop_runner_->Run();
228 id_to_wait_for_.clear();
229 loop_runner_ = nullptr;
232 void VerifierObserver::OnFetchComplete(const std::string& extension_id,
233 bool success) {
234 completed_fetches_.insert(extension_id);
235 if (extension_id == id_to_wait_for_)
236 loop_runner_->Quit();
239 } // namespace
241 class ContentVerifierTest : public ExtensionBrowserTest {
242 public:
243 ContentVerifierTest() {}
244 ~ContentVerifierTest() override {}
246 void SetUpCommandLine(base::CommandLine* command_line) override {
247 ExtensionBrowserTest::SetUpCommandLine(command_line);
248 command_line->AppendSwitchASCII(
249 switches::kExtensionContentVerification,
250 switches::kExtensionContentVerificationEnforce);
253 void SetUpOnMainThread() override {
254 ExtensionBrowserTest::SetUpOnMainThread();
257 void TearDownOnMainThread() override {
258 ContentVerifier::SetObserverForTests(NULL);
259 ContentVerifyJob::SetDelegateForTests(NULL);
260 ContentVerifyJob::SetObserverForTests(NULL);
261 ExtensionBrowserTest::TearDownOnMainThread();
264 virtual void OpenPageAndWaitForUnload() {
265 unload_observer_.reset(
266 new UnloadObserver(ExtensionRegistry::Get(profile())));
267 const Extension* extension = InstallExtensionFromWebstore(
268 test_data_dir_.AppendASCII("content_verifier/v1.crx"), 1);
269 ASSERT_TRUE(extension);
270 id_ = extension->id();
271 page_url_ = extension->GetResourceURL("page.html");
272 delegate_.set_id(id_);
273 ContentVerifyJob::SetDelegateForTests(&delegate_);
275 // This call passes false for |check_navigation_success|, because checking
276 // for navigation success needs the WebContents to still exist after the
277 // navigation, whereas this navigation triggers an unload which destroys
278 // the WebContents.
279 AddTabAtIndexToBrowser(browser(), 1, page_url_, ui::PAGE_TRANSITION_LINK,
280 false);
282 unload_observer_->WaitForUnload(id_);
283 ExtensionPrefs* prefs = ExtensionPrefs::Get(profile());
284 int reasons = prefs->GetDisableReasons(id_);
285 EXPECT_TRUE(reasons & Extension::DISABLE_CORRUPTED);
287 // This needs to happen before the ExtensionRegistry gets deleted, which
288 // happens before TearDownOnMainThread is called.
289 unload_observer_.reset();
292 protected:
293 JobDelegate delegate_;
294 scoped_ptr<UnloadObserver> unload_observer_;
295 ExtensionId id_;
296 GURL page_url_;
299 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) {
300 delegate_.fail_next_read();
301 OpenPageAndWaitForUnload();
304 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnDone) {
305 delegate_.fail_next_done();
306 OpenPageAndWaitForUnload();
309 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, DotSlashPaths) {
310 JobObserver job_observer;
311 ContentVerifyJob::SetObserverForTests(&job_observer);
312 std::string id = "hoipipabpcoomfapcecilckodldhmpgl";
314 job_observer.ExpectJobResult(
315 id, base::FilePath(FILE_PATH_LITERAL("background.js")),
316 JobObserver::Result::SUCCESS);
317 job_observer.ExpectJobResult(id,
318 base::FilePath(FILE_PATH_LITERAL("page.html")),
319 JobObserver::Result::SUCCESS);
320 job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("page.js")),
321 JobObserver::Result::SUCCESS);
322 job_observer.ExpectJobResult(
323 id, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")),
324 JobObserver::Result::SUCCESS);
325 job_observer.ExpectJobResult(id,
326 base::FilePath(FILE_PATH_LITERAL("page2.js")),
327 JobObserver::Result::SUCCESS);
328 job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("cs1.js")),
329 JobObserver::Result::SUCCESS);
330 job_observer.ExpectJobResult(id, base::FilePath(FILE_PATH_LITERAL("cs2.js")),
331 JobObserver::Result::SUCCESS);
333 VerifierObserver verifier_observer;
334 ContentVerifier::SetObserverForTests(&verifier_observer);
336 // Install a test extension we copied from the webstore that has actual
337 // signatures, and contains paths with a leading "./" in various places.
338 const Extension* extension = InstallExtensionFromWebstore(
339 test_data_dir_.AppendASCII("content_verifier/dot_slash_paths.crx"), 1);
341 ASSERT_TRUE(extension);
342 ASSERT_EQ(extension->id(), id);
344 // The content scripts might fail verification the first time since the
345 // one-time processing might not be finished yet - if that's the case then
346 // we want to wait until that work is done.
347 if (!ContainsKey(verifier_observer.completed_fetches(), id))
348 verifier_observer.WaitForFetchComplete(id);
350 // Now disable/re-enable the extension to cause the content scripts to be
351 // read again.
352 DisableExtension(id);
353 EnableExtension(id);
355 EXPECT_TRUE(job_observer.WaitForExpectedJobs());
357 ContentVerifyJob::SetObserverForTests(NULL);
360 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, ContentScripts) {
361 VerifierObserver verifier_observer;
362 ContentVerifier::SetObserverForTests(&verifier_observer);
364 // Install an extension with content scripts. The initial read of the content
365 // scripts will fail verification because they are read before the content
366 // verification system has completed a one-time processing of the expected
367 // hashes. (The extension only contains the root level hashes of the merkle
368 // tree, but the content verification system builds the entire tree and
369 // caches it in the extension install directory - see ContentHashFetcher for
370 // more details).
371 std::string id = "jmllhlobpjcnnomjlipadejplhmheiif";
372 const Extension* extension = InstallExtensionFromWebstore(
373 test_data_dir_.AppendASCII("content_verifier/content_script.crx"), 1);
374 ASSERT_TRUE(extension);
375 ASSERT_EQ(extension->id(), id);
377 // Wait for the content verification code to finish processing the hashes.
378 if (!ContainsKey(verifier_observer.completed_fetches(), id))
379 verifier_observer.WaitForFetchComplete(id);
381 // Now disable the extension, since content scripts are read at enable time,
382 // set up our job observer, and re-enable, expecting a success this time.
383 DisableExtension(id);
384 JobObserver job_observer;
385 ContentVerifyJob::SetObserverForTests(&job_observer);
386 job_observer.ExpectJobResult(id,
387 base::FilePath(FILE_PATH_LITERAL("script.js")),
388 JobObserver::Result::SUCCESS);
389 EnableExtension(id);
390 EXPECT_TRUE(job_observer.WaitForExpectedJobs());
392 // Now alter the contents of the content script, reload the extension, and
393 // expect to see a job failure due to the content script content hash not
394 // being what was signed by the webstore.
395 base::FilePath scriptfile = extension->path().AppendASCII("script.js");
396 std::string extra = "some_extra_function_call();";
397 ASSERT_TRUE(base::AppendToFile(scriptfile, extra.data(), extra.size()));
398 DisableExtension(id);
399 job_observer.ExpectJobResult(id,
400 base::FilePath(FILE_PATH_LITERAL("script.js")),
401 JobObserver::Result::FAILURE);
402 EnableExtension(id);
403 EXPECT_TRUE(job_observer.WaitForExpectedJobs());
405 ContentVerifyJob::SetObserverForTests(NULL);
408 } // namespace extensions