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 "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"
14 namespace extensions
{
18 // Helper for observing extension unloads.
19 class UnloadObserver
: public ExtensionRegistryObserver
{
21 explicit UnloadObserver(ExtensionRegistry
* registry
) : observer_(this) {
22 observer_
.Add(registry
);
24 ~UnloadObserver() override
{}
26 void WaitForUnload(const ExtensionId
& id
) {
27 if (ContainsKey(observed_
, id
))
30 ASSERT_TRUE(loop_runner_
.get() == NULL
);
32 loop_runner_
= new content::MessageLoopRunner();
36 void OnExtensionUnloaded(content::BrowserContext
* browser_context
,
37 const Extension
* extension
,
38 UnloadedExtensionInfo::Reason reason
) override
{
39 observed_
.insert(extension
->id());
40 if (awaited_id_
== extension
->id())
45 ExtensionId awaited_id_
;
46 std::set
<ExtensionId
> observed_
;
47 scoped_refptr
<content::MessageLoopRunner
> loop_runner_
;
48 ScopedObserver
<ExtensionRegistry
, UnloadObserver
> observer_
;
51 // Helper for forcing ContentVerifyJob's to return an error.
52 class JobDelegate
: public ContentVerifyJob::TestDelegate
{
54 JobDelegate() : fail_next_read_(false), fail_next_done_(false) {}
56 virtual ~JobDelegate() {}
58 void set_id(const ExtensionId
& id
) { id_
= id
; }
59 void fail_next_read() { fail_next_read_
= true; }
60 void fail_next_done() { fail_next_done_
= true; }
62 ContentVerifyJob::FailureReason
BytesRead(const ExtensionId
& id
,
64 const char* data
) override
{
65 if (id
== id_
&& fail_next_read_
) {
66 fail_next_read_
= false;
67 return ContentVerifyJob::HASH_MISMATCH
;
69 return ContentVerifyJob::NONE
;
72 ContentVerifyJob::FailureReason
DoneReading(const ExtensionId
& id
) override
{
73 if (id
== id_
&& fail_next_done_
) {
74 fail_next_done_
= false;
75 return ContentVerifyJob::HASH_MISMATCH
;
77 return ContentVerifyJob::NONE
;
81 DISALLOW_COPY_AND_ASSIGN(JobDelegate
);
88 class JobObserver
: public ContentVerifyJob::TestObserver
{
91 virtual ~JobObserver();
93 // Call this to add an expected job result.
94 void ExpectJobResult(const std::string
& extension_id
,
95 const base::FilePath
& relative_path
,
96 bool expected_to_fail
);
98 // Wait to see expected jobs. Returns true if we saw all jobs finish as
99 // expected, or false if any job completed with non-expected success/failure
101 bool WaitForExpectedJobs();
103 // ContentVerifyJob::TestObserver interface
104 void JobStarted(const std::string
& extension_id
,
105 const base::FilePath
& relative_path
) override
;
107 void JobFinished(const std::string
& extension_id
,
108 const base::FilePath
& relative_path
,
109 bool failed
) override
;
112 typedef std::pair
<std::string
, base::FilePath
> ExtensionFile
;
113 typedef std::map
<ExtensionFile
, bool> ExpectedJobs
;
114 ExpectedJobs expected_jobs_
;
115 scoped_refptr
<content::MessageLoopRunner
> loop_runner_
;
116 bool saw_expected_job_results_
;
119 void JobObserver::ExpectJobResult(const std::string
& extension_id
,
120 const base::FilePath
& relative_path
,
121 bool expected_to_fail
) {
122 expected_jobs_
.insert(std::make_pair(
123 ExtensionFile(extension_id
, relative_path
), expected_to_fail
));
126 JobObserver::JobObserver() : saw_expected_job_results_(false) {
129 JobObserver::~JobObserver() {
132 bool JobObserver::WaitForExpectedJobs() {
133 if (!expected_jobs_
.empty()) {
134 loop_runner_
= new content::MessageLoopRunner();
137 return saw_expected_job_results_
;
140 void JobObserver::JobStarted(const std::string
& extension_id
,
141 const base::FilePath
& relative_path
) {
144 void JobObserver::JobFinished(const std::string
& extension_id
,
145 const base::FilePath
& relative_path
,
147 ExpectedJobs::iterator i
= expected_jobs_
.find(ExtensionFile(
148 extension_id
, relative_path
.NormalizePathSeparatorsTo('/')));
149 if (i
!= expected_jobs_
.end()) {
150 if (failed
!= i
->second
) {
151 saw_expected_job_results_
= false;
152 if (loop_runner_
.get())
153 loop_runner_
->Quit();
155 expected_jobs_
.erase(i
);
156 if (expected_jobs_
.empty()) {
157 saw_expected_job_results_
= true;
158 if (loop_runner_
.get())
159 loop_runner_
->Quit();
166 class ContentVerifierTest
: public ExtensionBrowserTest
{
168 ContentVerifierTest() {}
169 ~ContentVerifierTest() override
{}
171 void SetUpCommandLine(base::CommandLine
* command_line
) override
{
172 ExtensionBrowserTest::SetUpCommandLine(command_line
);
173 command_line
->AppendSwitchASCII(
174 switches::kExtensionContentVerification
,
175 switches::kExtensionContentVerificationEnforce
);
178 // Setup our unload observer and JobDelegate, and install a test extension.
179 void SetUpOnMainThread() override
{
180 ExtensionBrowserTest::SetUpOnMainThread();
183 void TearDownOnMainThread() override
{
184 ContentVerifyJob::SetDelegateForTests(NULL
);
185 ContentVerifyJob::SetObserverForTests(NULL
);
186 ExtensionBrowserTest::TearDownOnMainThread();
189 virtual void OpenPageAndWaitForUnload() {
190 unload_observer_
.reset(
191 new UnloadObserver(ExtensionRegistry::Get(profile())));
192 const Extension
* extension
= InstallExtensionFromWebstore(
193 test_data_dir_
.AppendASCII("content_verifier/v1.crx"), 1);
194 ASSERT_TRUE(extension
);
195 id_
= extension
->id();
196 page_url_
= extension
->GetResourceURL("page.html");
197 delegate_
.set_id(id_
);
198 ContentVerifyJob::SetDelegateForTests(&delegate_
);
200 // This call passes false for |check_navigation_success|, because checking
201 // for navigation success needs the WebContents to still exist after the
202 // navigation, whereas this navigation triggers an unload which destroys
204 AddTabAtIndexToBrowser(browser(), 1, page_url_
, ui::PAGE_TRANSITION_LINK
,
207 unload_observer_
->WaitForUnload(id_
);
208 ExtensionPrefs
* prefs
= ExtensionPrefs::Get(profile());
209 int reasons
= prefs
->GetDisableReasons(id_
);
210 EXPECT_TRUE(reasons
& Extension::DISABLE_CORRUPTED
);
212 // This needs to happen before the ExtensionRegistry gets deleted, which
213 // happens before TearDownOnMainThread is called.
214 unload_observer_
.reset();
218 JobDelegate delegate_
;
219 scoped_ptr
<UnloadObserver
> unload_observer_
;
224 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, FailOnRead
) {
225 delegate_
.fail_next_read();
226 OpenPageAndWaitForUnload();
229 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, FailOnDone
) {
230 delegate_
.fail_next_done();
231 OpenPageAndWaitForUnload();
234 IN_PROC_BROWSER_TEST_F(ContentVerifierTest
, DotSlashPaths
) {
235 JobObserver job_observer
;
236 ContentVerifyJob::SetObserverForTests(&job_observer
);
237 std::string id
= "hoipipabpcoomfapcecilckodldhmpgl";
239 job_observer
.ExpectJobResult(
240 id
, base::FilePath(FILE_PATH_LITERAL("background.js")), false);
241 job_observer
.ExpectJobResult(
242 id
, base::FilePath(FILE_PATH_LITERAL("page.html")), false);
243 job_observer
.ExpectJobResult(
244 id
, base::FilePath(FILE_PATH_LITERAL("page.js")), false);
245 job_observer
.ExpectJobResult(
246 id
, base::FilePath(FILE_PATH_LITERAL("dir/page2.html")), false);
247 job_observer
.ExpectJobResult(
248 id
, base::FilePath(FILE_PATH_LITERAL("page2.js")), false);
250 // Install a test extension we copied from the webstore that has actual
251 // signatures, and contains image paths with leading "./".
252 const Extension
* extension
= InstallExtensionFromWebstore(
253 test_data_dir_
.AppendASCII("content_verifier/dot_slash_paths.crx"), 1);
255 ASSERT_TRUE(extension
);
256 ASSERT_EQ(extension
->id(), id
);
258 EXPECT_TRUE(job_observer
.WaitForExpectedJobs());
260 ContentVerifyJob::SetObserverForTests(NULL
);
263 } // namespace extensions