Home | History | Annotate | Download | only in extensions
      1 // Copyright (c) 2013 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 "chrome/browser/chromeos/extensions/external_cache.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/bind_helpers.h"
      9 #include "base/callback.h"
     10 #include "base/file_util.h"
     11 #include "base/files/file_enumerator.h"
     12 #include "base/location.h"
     13 #include "base/logging.h"
     14 #include "base/strings/string_util.h"
     15 #include "base/values.h"
     16 #include "base/version.h"
     17 #include "chrome/browser/chrome_notification_types.h"
     18 #include "chrome/browser/extensions/crx_installer.h"
     19 #include "chrome/browser/extensions/external_provider_impl.h"
     20 #include "chrome/browser/extensions/updater/extension_downloader.h"
     21 #include "chrome/common/extensions/extension_constants.h"
     22 #include "content/public/browser/notification_details.h"
     23 #include "content/public/browser/notification_service.h"
     24 #include "content/public/browser/notification_source.h"
     25 #include "extensions/common/extension.h"
     26 #include "net/url_request/url_request_context_getter.h"
     27 
     28 namespace chromeos {
     29 
     30 ExternalCache::ExternalCache(const base::FilePath& cache_dir,
     31                              net::URLRequestContextGetter* request_context,
     32                              const scoped_refptr<base::SequencedTaskRunner>&
     33                                  backend_task_runner,
     34                              Delegate* delegate,
     35                              bool always_check_updates,
     36                              bool wait_for_cache_initialization)
     37     : local_cache_(cache_dir, 0, base::TimeDelta(), backend_task_runner),
     38       request_context_(request_context),
     39       backend_task_runner_(backend_task_runner),
     40       delegate_(delegate),
     41       always_check_updates_(always_check_updates),
     42       wait_for_cache_initialization_(wait_for_cache_initialization),
     43       cached_extensions_(new base::DictionaryValue()),
     44       weak_ptr_factory_(this) {
     45   notification_registrar_.Add(
     46       this,
     47       chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
     48       content::NotificationService::AllBrowserContextsAndSources());
     49 }
     50 
     51 ExternalCache::~ExternalCache() {
     52 }
     53 
     54 void ExternalCache::Shutdown(const base::Closure& callback) {
     55   local_cache_.Shutdown(callback);
     56 }
     57 
     58 void ExternalCache::UpdateExtensionsList(
     59     scoped_ptr<base::DictionaryValue> prefs) {
     60   extensions_ = prefs.Pass();
     61 
     62   if (extensions_->empty()) {
     63     // If list of know extensions is empty, don't init cache on disk. It is
     64     // important shortcut for test to don't wait forever for cache dir
     65     // initialization that should happen outside of Chrome on real device.
     66     cached_extensions_->Clear();
     67     UpdateExtensionLoader();
     68     return;
     69   }
     70 
     71   if (local_cache_.is_uninitialized()) {
     72     local_cache_.Init(wait_for_cache_initialization_,
     73                       base::Bind(&ExternalCache::CheckCache,
     74                                  weak_ptr_factory_.GetWeakPtr()));
     75   } else {
     76     CheckCache();
     77   }
     78 }
     79 
     80 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
     81   for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
     82        !it.IsAtEnd(); it.Advance()) {
     83     const base::DictionaryValue* entry = NULL;
     84     if (!it.value().GetAsDictionary(&entry)) {
     85       NOTREACHED() << "ExternalCache found bad entry with type "
     86                    << it.value().GetType();
     87       continue;
     88     }
     89 
     90     std::string external_crx;
     91     if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
     92                          &external_crx) &&
     93         external_crx == path.value()) {
     94       std::string id = it.key();
     95       LOG(ERROR) << "ExternalCache extension at " << path.value()
     96                  << " failed to install, deleting it.";
     97       cached_extensions_->Remove(id, NULL);
     98       extensions_->Remove(id, NULL);
     99 
    100       local_cache_.RemoveExtension(id);
    101       UpdateExtensionLoader();
    102 
    103       // Don't try to DownloadMissingExtensions() from here,
    104       // since it can cause a fail/retry loop.
    105       return;
    106     }
    107   }
    108   LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
    109 }
    110 
    111 void ExternalCache::RemoveExtensions(const std::vector<std::string>& ids) {
    112   if (ids.empty())
    113     return;
    114 
    115   for (size_t i = 0; i < ids.size(); ++i) {
    116     cached_extensions_->Remove(ids[i], NULL);
    117     extensions_->Remove(ids[i], NULL);
    118     local_cache_.RemoveExtension(ids[i]);
    119   }
    120   UpdateExtensionLoader();
    121 }
    122 
    123 bool ExternalCache::GetExtension(const std::string& id,
    124                                  base::FilePath* file_path,
    125                                  std::string* version) {
    126   return local_cache_.GetExtension(id, file_path, version);
    127 }
    128 
    129 void ExternalCache::Observe(int type,
    130                             const content::NotificationSource& source,
    131                             const content::NotificationDetails& details) {
    132   switch (type) {
    133     case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
    134       extensions::CrxInstaller* installer =
    135           content::Source<extensions::CrxInstaller>(source).ptr();
    136       OnDamagedFileDetected(installer->source_file());
    137       break;
    138     }
    139 
    140     default:
    141       NOTREACHED();
    142   }
    143 }
    144 
    145 void ExternalCache::OnExtensionDownloadFailed(
    146     const std::string& id,
    147     extensions::ExtensionDownloaderDelegate::Error error,
    148     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
    149     const std::set<int>& request_ids) {
    150   if (error == NO_UPDATE_AVAILABLE) {
    151     if (!cached_extensions_->HasKey(id)) {
    152       LOG(ERROR) << "ExternalCache extension " << id
    153                  << " not found on update server";
    154       delegate_->OnExtensionDownloadFailed(id, error);
    155     } else {
    156       delegate_->OnExtensionLoadedInCache(id);
    157     }
    158   } else {
    159     LOG(ERROR) << "ExternalCache failed to download extension " << id
    160                << ", error " << error;
    161     delegate_->OnExtensionDownloadFailed(id, error);
    162   }
    163 }
    164 
    165 void ExternalCache::OnExtensionDownloadFinished(
    166     const std::string& id,
    167     const base::FilePath& path,
    168     bool file_ownership_passed,
    169     const GURL& download_url,
    170     const std::string& version,
    171     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
    172     const std::set<int>& request_ids) {
    173   DCHECK(file_ownership_passed);
    174   local_cache_.PutExtension(id, path, version,
    175                             base::Bind(&ExternalCache::OnPutExtension,
    176                                        weak_ptr_factory_.GetWeakPtr(),
    177                                        id));
    178 }
    179 
    180 bool ExternalCache::IsExtensionPending(const std::string& id) {
    181   // Pending means that there is no installed version yet.
    182   return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
    183 }
    184 
    185 bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
    186                                                 std::string* version) {
    187   base::DictionaryValue* extension_dictionary = NULL;
    188   if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
    189     if (extension_dictionary->GetString(
    190             extensions::ExternalProviderImpl::kExternalVersion, version)) {
    191       return true;
    192     }
    193     *version = delegate_->GetInstalledExtensionVersion(id);
    194     return !version->empty();
    195   }
    196   return false;
    197 }
    198 
    199 void ExternalCache::UpdateExtensionLoader() {
    200   VLOG(1) << "Notify ExternalCache delegate about cache update";
    201   if (delegate_)
    202     delegate_->OnExtensionListsUpdated(cached_extensions_.get());
    203 }
    204 
    205 void ExternalCache::CheckCache() {
    206   if (local_cache_.is_shutdown())
    207     return;
    208 
    209   // If request_context_ is missing we can't download anything.
    210   if (!downloader_ && request_context_) {
    211     downloader_.reset(
    212         new extensions::ExtensionDownloader(this, request_context_));
    213   }
    214 
    215   cached_extensions_->Clear();
    216   for (base::DictionaryValue::Iterator it(*extensions_.get());
    217        !it.IsAtEnd(); it.Advance()) {
    218     const base::DictionaryValue* entry = NULL;
    219     if (!it.value().GetAsDictionary(&entry)) {
    220       LOG(ERROR) << "ExternalCache found bad entry with type "
    221                  << it.value().GetType();
    222       continue;
    223     }
    224 
    225     bool keep_if_present =
    226         entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
    227     std::string external_update_url;
    228     entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    229                      &external_update_url);
    230     if (downloader_ && !keep_if_present) {
    231       GURL update_url;
    232       if (!external_update_url.empty())
    233         update_url = GURL(external_update_url);
    234       else if (always_check_updates_)
    235         update_url = extension_urls::GetWebstoreUpdateUrl();
    236 
    237       if (update_url.is_valid())
    238         downloader_->AddPendingExtension(it.key(), update_url, 0);
    239     }
    240 
    241     base::FilePath file_path;
    242     std::string version;
    243     if (local_cache_.GetExtension(it.key(), &file_path, &version)) {
    244       // Copy entry to don't modify it inside extensions_.
    245       base::DictionaryValue* entry_copy = entry->DeepCopy();
    246 
    247       if (extension_urls::IsWebstoreUpdateUrl(GURL(external_update_url))) {
    248         entry_copy->SetBoolean(
    249             extensions::ExternalProviderImpl::kIsFromWebstore, true);
    250       }
    251       entry_copy->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    252                          NULL);
    253       entry_copy->SetString(extensions::ExternalProviderImpl::kExternalVersion,
    254                             version);
    255       entry_copy->SetString(extensions::ExternalProviderImpl::kExternalCrx,
    256                             file_path.value());
    257       cached_extensions_->Set(it.key(), entry_copy);
    258     } else {
    259       bool has_external_crx = entry->HasKey(
    260           extensions::ExternalProviderImpl::kExternalCrx);
    261       bool is_already_installed =
    262           !delegate_->GetInstalledExtensionVersion(it.key()).empty();
    263       if (keep_if_present || has_external_crx || is_already_installed) {
    264         // Copy entry to don't modify it inside extensions_.
    265         cached_extensions_->Set(it.key(), entry->DeepCopy());
    266       }
    267     }
    268   }
    269 
    270   if (downloader_)
    271     downloader_->StartAllPending(NULL);
    272 
    273   VLOG(1) << "Updated ExternalCache, there are "
    274           << cached_extensions_->size() << " extensions cached";
    275 
    276   UpdateExtensionLoader();
    277 }
    278 
    279 void ExternalCache::OnPutExtension(const std::string& id,
    280                                    const base::FilePath& file_path,
    281                                    bool file_ownership_passed) {
    282   if (local_cache_.is_shutdown() || file_ownership_passed) {
    283     backend_task_runner_->PostTask(FROM_HERE,
    284         base::Bind(base::IgnoreResult(&base::DeleteFile), file_path, true));
    285     return;
    286   }
    287 
    288   VLOG(1) << "ExternalCache installed a new extension in the cache " << id;
    289 
    290   base::DictionaryValue* entry = NULL;
    291   if (!extensions_->GetDictionary(id, &entry)) {
    292     LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
    293     return;
    294   }
    295 
    296   // Copy entry to don't modify it inside extensions_.
    297   entry = entry->DeepCopy();
    298 
    299   std::string version;
    300   if (!local_cache_.GetExtension(id, NULL, &version)) {
    301     // Copy entry to don't modify it inside extensions_.
    302     LOG(ERROR) << "Can't find installed extension in cache " << id;
    303     return;
    304   }
    305 
    306   std::string update_url;
    307   if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    308                        &update_url) &&
    309       extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
    310     entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
    311   }
    312   entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
    313   entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
    314   entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
    315                    file_path.value());
    316 
    317   cached_extensions_->Set(id, entry);
    318   if (delegate_)
    319     delegate_->OnExtensionLoadedInCache(id);
    320   UpdateExtensionLoader();
    321 }
    322 
    323 std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
    324     const std::string& id) {
    325   return std::string();
    326 }
    327 
    328 }  // namespace chromeos
    329