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