Home | History | Annotate | Download | only in browser
      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 "extensions/browser/content_verifier.h"
      6 
      7 #include <algorithm>
      8 
      9 #include "base/files/file_path.h"
     10 #include "base/stl_util.h"
     11 #include "base/strings/string_util.h"
     12 #include "content/public/browser/browser_thread.h"
     13 #include "extensions/browser/content_hash_fetcher.h"
     14 #include "extensions/browser/content_hash_reader.h"
     15 #include "extensions/browser/content_verifier_delegate.h"
     16 #include "extensions/browser/content_verifier_io_data.h"
     17 #include "extensions/browser/extension_registry.h"
     18 #include "extensions/common/constants.h"
     19 #include "extensions/common/extension_l10n_util.h"
     20 
     21 namespace extensions {
     22 
     23 ContentVerifier::ContentVerifier(content::BrowserContext* context,
     24                                  ContentVerifierDelegate* delegate)
     25     : shutdown_(false),
     26       context_(context),
     27       delegate_(delegate),
     28       fetcher_(new ContentHashFetcher(
     29           context,
     30           delegate,
     31           base::Bind(&ContentVerifier::OnFetchComplete, this))),
     32       observer_(this),
     33       io_data_(new ContentVerifierIOData) {
     34 }
     35 
     36 ContentVerifier::~ContentVerifier() {
     37 }
     38 
     39 void ContentVerifier::Start() {
     40   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
     41   observer_.Add(registry);
     42 }
     43 
     44 void ContentVerifier::Shutdown() {
     45   shutdown_ = true;
     46   content::BrowserThread::PostTask(
     47       content::BrowserThread::IO,
     48       FROM_HERE,
     49       base::Bind(&ContentVerifierIOData::Clear, io_data_));
     50   observer_.RemoveAll();
     51   fetcher_.reset();
     52 }
     53 
     54 ContentVerifyJob* ContentVerifier::CreateJobFor(
     55     const std::string& extension_id,
     56     const base::FilePath& extension_root,
     57     const base::FilePath& relative_path) {
     58   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
     59 
     60   const ContentVerifierIOData::ExtensionData* data =
     61       io_data_->GetData(extension_id);
     62   if (!data)
     63     return NULL;
     64 
     65   std::set<base::FilePath> paths;
     66   paths.insert(relative_path);
     67   if (!ShouldVerifyAnyPaths(extension_id, extension_root, paths))
     68     return NULL;
     69 
     70   // TODO(asargent) - we can probably get some good performance wins by having
     71   // a cache of ContentHashReader's that we hold onto past the end of each job.
     72   return new ContentVerifyJob(
     73       new ContentHashReader(extension_id,
     74                             data->version,
     75                             extension_root,
     76                             relative_path,
     77                             delegate_->PublicKey()),
     78       base::Bind(&ContentVerifier::VerifyFailed, this, extension_id));
     79 }
     80 
     81 void ContentVerifier::VerifyFailed(const std::string& extension_id,
     82                                    ContentVerifyJob::FailureReason reason) {
     83   if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
     84     content::BrowserThread::PostTask(
     85         content::BrowserThread::UI,
     86         FROM_HERE,
     87         base::Bind(&ContentVerifier::VerifyFailed, this, extension_id, reason));
     88     return;
     89   }
     90   if (shutdown_)
     91     return;
     92 
     93   VLOG(1) << "VerifyFailed " << extension_id << " reason:" << reason;
     94 
     95   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
     96   const Extension* extension =
     97       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
     98 
     99   if (!extension)
    100     return;
    101 
    102   if (reason == ContentVerifyJob::MISSING_ALL_HASHES) {
    103     // If we failed because there were no hashes yet for this extension, just
    104     // request some.
    105     fetcher_->DoFetch(extension, true /* force */);
    106   } else {
    107     delegate_->VerifyFailed(extension_id, reason);
    108   }
    109 }
    110 
    111 static base::FilePath MakeImagePathRelative(const base::FilePath& path) {
    112   if (path.ReferencesParent())
    113     return base::FilePath();
    114 
    115   std::vector<base::FilePath::StringType> parts;
    116   path.GetComponents(&parts);
    117   if (parts.empty())
    118     return base::FilePath();
    119 
    120   // Remove the first component if it is '.' or '/' or '//'.
    121   const base::FilePath::StringType separators(
    122       base::FilePath::kSeparators, base::FilePath::kSeparatorsLength);
    123   if (!parts[0].empty() &&
    124       (parts[0] == base::FilePath::kCurrentDirectory ||
    125        parts[0].find_first_not_of(separators) == std::string::npos))
    126     parts.erase(parts.begin());
    127 
    128   // Note that elsewhere we always normalize path separators to '/' so this
    129   // should work for all platforms.
    130   return base::FilePath(JoinString(parts, '/'));
    131 }
    132 
    133 void ContentVerifier::OnExtensionLoaded(
    134     content::BrowserContext* browser_context,
    135     const Extension* extension) {
    136   if (shutdown_)
    137     return;
    138 
    139   ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
    140   if (mode != ContentVerifierDelegate::NONE) {
    141     // The browser image paths from the extension may not be relative (eg
    142     // they might have leading '/' or './'), so we strip those to make
    143     // comparing to actual relative paths work later on.
    144     std::set<base::FilePath> original_image_paths =
    145         delegate_->GetBrowserImagePaths(extension);
    146 
    147     scoped_ptr<std::set<base::FilePath>> image_paths(
    148         new std::set<base::FilePath>);
    149     for (const auto& path : original_image_paths) {
    150       image_paths->insert(MakeImagePathRelative(path));
    151     }
    152 
    153     scoped_ptr<ContentVerifierIOData::ExtensionData> data(
    154         new ContentVerifierIOData::ExtensionData(
    155             image_paths.Pass(),
    156             extension->version() ? *extension->version() : base::Version()));
    157     content::BrowserThread::PostTask(content::BrowserThread::IO,
    158                                      FROM_HERE,
    159                                      base::Bind(&ContentVerifierIOData::AddData,
    160                                                 io_data_,
    161                                                 extension->id(),
    162                                                 base::Passed(&data)));
    163     fetcher_->ExtensionLoaded(extension);
    164   }
    165 }
    166 
    167 void ContentVerifier::OnExtensionUnloaded(
    168     content::BrowserContext* browser_context,
    169     const Extension* extension,
    170     UnloadedExtensionInfo::Reason reason) {
    171   if (shutdown_)
    172     return;
    173   content::BrowserThread::PostTask(
    174       content::BrowserThread::IO,
    175       FROM_HERE,
    176       base::Bind(
    177           &ContentVerifierIOData::RemoveData, io_data_, extension->id()));
    178   if (fetcher_)
    179     fetcher_->ExtensionUnloaded(extension);
    180 }
    181 
    182 void ContentVerifier::OnFetchCompleteHelper(const std::string& extension_id,
    183                                             bool shouldVerifyAnyPathsResult) {
    184   if (shouldVerifyAnyPathsResult)
    185     delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
    186 }
    187 
    188 void ContentVerifier::OnFetchComplete(
    189     const std::string& extension_id,
    190     bool success,
    191     bool was_force_check,
    192     const std::set<base::FilePath>& hash_mismatch_paths) {
    193   if (shutdown_)
    194     return;
    195 
    196   VLOG(1) << "OnFetchComplete " << extension_id << " success:" << success;
    197 
    198   ExtensionRegistry* registry = ExtensionRegistry::Get(context_);
    199   const Extension* extension =
    200       registry->GetExtensionById(extension_id, ExtensionRegistry::EVERYTHING);
    201   if (!delegate_ || !extension)
    202     return;
    203 
    204   ContentVerifierDelegate::Mode mode = delegate_->ShouldBeVerified(*extension);
    205   if (was_force_check && !success &&
    206       mode == ContentVerifierDelegate::ENFORCE_STRICT) {
    207     // We weren't able to get verified_contents.json or weren't able to compute
    208     // hashes.
    209     delegate_->VerifyFailed(extension_id, ContentVerifyJob::MISSING_ALL_HASHES);
    210   } else {
    211     content::BrowserThread::PostTaskAndReplyWithResult(
    212         content::BrowserThread::IO,
    213         FROM_HERE,
    214         base::Bind(&ContentVerifier::ShouldVerifyAnyPaths,
    215                    this,
    216                    extension_id,
    217                    extension->path(),
    218                    hash_mismatch_paths),
    219         base::Bind(
    220             &ContentVerifier::OnFetchCompleteHelper, this, extension_id));
    221   }
    222 }
    223 
    224 bool ContentVerifier::ShouldVerifyAnyPaths(
    225     const std::string& extension_id,
    226     const base::FilePath& extension_root,
    227     const std::set<base::FilePath>& relative_paths) {
    228   DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
    229   const ContentVerifierIOData::ExtensionData* data =
    230       io_data_->GetData(extension_id);
    231   if (!data)
    232     return false;
    233 
    234   const std::set<base::FilePath>& browser_images = *(data->browser_image_paths);
    235 
    236   base::FilePath locales_dir = extension_root.Append(kLocaleFolder);
    237   scoped_ptr<std::set<std::string> > all_locales;
    238 
    239   for (std::set<base::FilePath>::const_iterator i = relative_paths.begin();
    240        i != relative_paths.end();
    241        ++i) {
    242     const base::FilePath& relative_path = *i;
    243 
    244     if (relative_path == base::FilePath(kManifestFilename))
    245       continue;
    246 
    247     if (ContainsKey(browser_images, relative_path))
    248       continue;
    249 
    250     base::FilePath full_path = extension_root.Append(relative_path);
    251     if (locales_dir.IsParent(full_path)) {
    252       if (!all_locales) {
    253         // TODO(asargent) - see if we can cache this list longer to avoid
    254         // having to fetch it more than once for a given run of the
    255         // browser. Maybe it can never change at runtime? (Or if it can, maybe
    256         // there is an event we can listen for to know to drop our cache).
    257         all_locales.reset(new std::set<std::string>);
    258         extension_l10n_util::GetAllLocales(all_locales.get());
    259       }
    260 
    261       // Since message catalogs get transcoded during installation, we want
    262       // to skip those paths.
    263       if (full_path.DirName().DirName() == locales_dir &&
    264           !extension_l10n_util::ShouldSkipValidation(
    265               locales_dir, full_path.DirName(), *all_locales))
    266         continue;
    267     }
    268     return true;
    269   }
    270   return false;
    271 }
    272 
    273 }  // namespace extensions
    274