Home | History | Annotate | Download | only in extensions
      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 
     14 namespace extensions {
     15 
     16 namespace {
     17 
     18 // Helper for observing extension unloads.
     19 class 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.
     53 class 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 
     90 class 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 
    121 void 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 
    128 JobObserver::JobObserver() : saw_expected_job_results_(false) {
    129 }
    130 
    131 JobObserver::~JobObserver() {
    132 }
    133 
    134 bool 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 
    142 void JobObserver::JobStarted(const std::string& extension_id,
    143                              const base::FilePath& relative_path) {
    144 }
    145 
    146 void 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 
    168 class 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 
    219 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnRead) {
    220   delegate_.fail_next_read();
    221   OpenPageAndWaitForUnload();
    222 }
    223 
    224 IN_PROC_BROWSER_TEST_F(ContentVerifierTest, FailOnDone) {
    225   delegate_.fail_next_done();
    226   OpenPageAndWaitForUnload();
    227 }
    228 
    229 IN_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