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/browser_thread.h"
     23 #include "content/public/browser/notification_details.h"
     24 #include "content/public/browser/notification_service.h"
     25 #include "content/public/browser/notification_source.h"
     26 #include "extensions/common/extension.h"
     27 
     28 namespace chromeos {
     29 
     30 namespace {
     31 
     32 // File name extension for CRX files (not case sensitive).
     33 const char kCRXFileExtension[] = ".crx";
     34 
     35 // Delay between checks for flag file presence when waiting for the cache to
     36 // become ready.
     37 const int64_t kCacheStatusPollingDelayMs = 1000;
     38 
     39 }  // namespace
     40 
     41 const char ExternalCache::kCacheReadyFlagFileName[] = ".initialized";
     42 
     43 ExternalCache::ExternalCache(const base::FilePath& cache_dir,
     44                              net::URLRequestContextGetter* request_context,
     45                              const scoped_refptr<base::SequencedTaskRunner>&
     46                                  backend_task_runner,
     47                              Delegate* delegate,
     48                              bool always_check_updates,
     49                              bool wait_for_cache_initialization)
     50     : cache_dir_(cache_dir),
     51       request_context_(request_context),
     52       delegate_(delegate),
     53       shutdown_(false),
     54       always_check_updates_(always_check_updates),
     55       wait_for_cache_initialization_(wait_for_cache_initialization),
     56       cached_extensions_(new base::DictionaryValue()),
     57       backend_task_runner_(backend_task_runner),
     58       weak_ptr_factory_(this) {
     59   notification_registrar_.Add(
     60       this,
     61       chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
     62       content::NotificationService::AllBrowserContextsAndSources());
     63 }
     64 
     65 ExternalCache::~ExternalCache() {
     66 }
     67 
     68 void ExternalCache::Shutdown(const base::Closure& callback) {
     69   DCHECK(!shutdown_);
     70   shutdown_ = true;
     71   backend_task_runner_->PostTask(FROM_HERE,
     72                                  base::Bind(&ExternalCache::BackendShudown,
     73                                             callback));
     74 }
     75 
     76 void ExternalCache::UpdateExtensionsList(
     77     scoped_ptr<base::DictionaryValue> prefs) {
     78   if (shutdown_)
     79     return;
     80 
     81   extensions_ = prefs.Pass();
     82   if (extensions_->empty()) {
     83     // Don't check cache and clear it if there are no extensions in the list.
     84     // It is important case because login to supervised user shouldn't clear
     85     // cache for normal users.
     86     // TODO(dpolukhin): introduce reference counting to preserve cache elements
     87     // when they are not needed for current user but needed for other users.
     88     cached_extensions_->Clear();
     89     UpdateExtensionLoader();
     90   } else {
     91     CheckCache();
     92   }
     93 }
     94 
     95 void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
     96   if (shutdown_)
     97     return;
     98 
     99   for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
    100        !it.IsAtEnd(); it.Advance()) {
    101     const base::DictionaryValue* entry = NULL;
    102     if (!it.value().GetAsDictionary(&entry)) {
    103       NOTREACHED() << "ExternalCache found bad entry with type "
    104                    << it.value().GetType();
    105       continue;
    106     }
    107 
    108     std::string external_crx;
    109     if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
    110                          &external_crx) &&
    111         external_crx == path.value()) {
    112 
    113       LOG(ERROR) << "ExternalCache extension at " << path.value()
    114                  << " failed to install, deleting it.";
    115       cached_extensions_->Remove(it.key(), NULL);
    116       UpdateExtensionLoader();
    117 
    118       // The file will be downloaded again on the next restart.
    119       if (cache_dir_.IsParent(path)) {
    120         backend_task_runner_->PostTask(
    121             FROM_HERE,
    122             base::Bind(base::IgnoreResult(base::DeleteFile),
    123             path,
    124             true));
    125       }
    126 
    127       // Don't try to DownloadMissingExtensions() from here,
    128       // since it can cause a fail/retry loop.
    129       return;
    130     }
    131   }
    132   LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
    133 }
    134 
    135 void ExternalCache::Observe(int type,
    136                             const content::NotificationSource& source,
    137                             const content::NotificationDetails& details) {
    138   if (shutdown_)
    139     return;
    140 
    141   switch (type) {
    142     case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
    143       extensions::CrxInstaller* installer =
    144           content::Source<extensions::CrxInstaller>(source).ptr();
    145       OnDamagedFileDetected(installer->source_file());
    146       break;
    147     }
    148 
    149     default:
    150       NOTREACHED();
    151   }
    152 }
    153 
    154 void ExternalCache::OnExtensionDownloadFailed(
    155     const std::string& id,
    156     extensions::ExtensionDownloaderDelegate::Error error,
    157     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
    158     const std::set<int>& request_ids) {
    159   if (shutdown_)
    160     return;
    161 
    162   if (error == NO_UPDATE_AVAILABLE) {
    163     if (!cached_extensions_->HasKey(id)) {
    164       LOG(ERROR) << "ExternalCache extension " << id
    165                  << " not found on update server";
    166     }
    167   } else {
    168     LOG(ERROR) << "ExternalCache failed to download extension " << id
    169                << ", error " << error;
    170   }
    171 }
    172 
    173 void ExternalCache::OnExtensionDownloadFinished(
    174     const std::string& id,
    175     const base::FilePath& path,
    176     const GURL& download_url,
    177     const std::string& version,
    178     const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
    179     const std::set<int>& request_ids) {
    180   if (shutdown_)
    181     return;
    182 
    183   backend_task_runner_->PostTask(
    184       FROM_HERE,
    185       base::Bind(&ExternalCache::BackendInstallCacheEntry,
    186                  weak_ptr_factory_.GetWeakPtr(),
    187                  cache_dir_,
    188                  id,
    189                  path,
    190                  version));
    191 }
    192 
    193 bool ExternalCache::IsExtensionPending(const std::string& id) {
    194   // Pending means that there is no installed version yet.
    195   return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
    196 }
    197 
    198 bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
    199                                                 std::string* version) {
    200   DictionaryValue* extension_dictionary = NULL;
    201   if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
    202     if (extension_dictionary->GetString(
    203             extensions::ExternalProviderImpl::kExternalVersion, version)) {
    204       return true;
    205     }
    206     *version = delegate_->GetInstalledExtensionVersion(id);
    207     return !version->empty();
    208   }
    209   return false;
    210 }
    211 
    212 void ExternalCache::UpdateExtensionLoader() {
    213   if (shutdown_)
    214     return;
    215 
    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 (wait_for_cache_initialization_) {
    223     backend_task_runner_->PostTask(
    224         FROM_HERE,
    225         base::Bind(&ExternalCache::BackendCheckCacheStatus,
    226                    weak_ptr_factory_.GetWeakPtr(),
    227                    cache_dir_));
    228   } else {
    229     CheckCacheContents();
    230   }
    231 }
    232 
    233 // static
    234 void ExternalCache::BackendCheckCacheStatus(
    235     base::WeakPtr<ExternalCache> external_cache,
    236     const base::FilePath& cache_dir) {
    237   content::BrowserThread::PostTask(
    238       content::BrowserThread::UI,
    239       FROM_HERE,
    240       base::Bind(&ExternalCache::OnCacheStatusChecked,
    241           external_cache,
    242           base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName))));
    243 }
    244 
    245 void ExternalCache::OnCacheStatusChecked(bool ready) {
    246   if (shutdown_)
    247     return;
    248 
    249   if (ready) {
    250     CheckCacheContents();
    251   } else {
    252     content::BrowserThread::PostDelayedTask(
    253         content::BrowserThread::UI,
    254         FROM_HERE,
    255         base::Bind(&ExternalCache::CheckCache,
    256                    weak_ptr_factory_.GetWeakPtr()),
    257         base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs));
    258   }
    259 }
    260 
    261 void ExternalCache::CheckCacheContents() {
    262   if (shutdown_)
    263     return;
    264 
    265   backend_task_runner_->PostTask(
    266       FROM_HERE,
    267       base::Bind(&ExternalCache::BackendCheckCacheContents,
    268                  weak_ptr_factory_.GetWeakPtr(),
    269                  cache_dir_,
    270                  base::Passed(make_scoped_ptr(extensions_->DeepCopy()))));
    271 }
    272 
    273 // static
    274 void ExternalCache::BackendCheckCacheContents(
    275     base::WeakPtr<ExternalCache> external_cache,
    276     const base::FilePath& cache_dir,
    277     scoped_ptr<base::DictionaryValue> prefs) {
    278   BackendCheckCacheContentsInternal(cache_dir, prefs.get());
    279   content::BrowserThread::PostTask(content::BrowserThread::UI,
    280                                    FROM_HERE,
    281                                    base::Bind(&ExternalCache::OnCacheUpdated,
    282                                               external_cache,
    283                                               base::Passed(&prefs)));
    284 }
    285 
    286 // static
    287 void ExternalCache::BackendCheckCacheContentsInternal(
    288     const base::FilePath& cache_dir,
    289     base::DictionaryValue* prefs) {
    290   // Start by verifying that the cache_dir exists.
    291   if (!base::DirectoryExists(cache_dir)) {
    292     // Create it now.
    293     if (!base::CreateDirectory(cache_dir)) {
    294       LOG(ERROR) << "Failed to create ExternalCache directory at "
    295                  << cache_dir.value();
    296     }
    297 
    298     // Nothing else to do. Cache won't be used.
    299     return;
    300   }
    301 
    302   // Enumerate all the files in the cache |cache_dir|, including directories
    303   // and symlinks. Each unrecognized file will be erased.
    304   int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
    305       base::FileEnumerator::SHOW_SYM_LINKS;
    306   base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
    307   for (base::FilePath path = enumerator.Next();
    308        !path.empty(); path = enumerator.Next()) {
    309     base::FileEnumerator::FileInfo info = enumerator.GetInfo();
    310     std::string basename = path.BaseName().value();
    311 
    312     if (info.IsDirectory() || base::IsLink(info.GetName())) {
    313       LOG(ERROR) << "Erasing bad file in ExternalCache directory: " << basename;
    314       base::DeleteFile(path, true /* recursive */);
    315       continue;
    316     }
    317 
    318     // Skip flag file that indicates that cache is ready.
    319     if (basename == kCacheReadyFlagFileName)
    320       continue;
    321 
    322     // crx files in the cache are named <extension-id>-<version>.crx.
    323     std::string id;
    324     std::string version;
    325     if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
    326       size_t n = basename.find('-');
    327       if (n != std::string::npos && n + 1 < basename.size() - 4) {
    328         id = basename.substr(0, n);
    329         // Size of |version| = total size - "<id>" - "-" - ".crx"
    330         version = basename.substr(n + 1, basename.size() - 5 - id.size());
    331       }
    332     }
    333 
    334     base::DictionaryValue* entry = NULL;
    335     if (!extensions::Extension::IdIsValid(id)) {
    336       LOG(ERROR) << "Bad extension id in ExternalCache: " << id;
    337       id.clear();
    338     } else if (!prefs->GetDictionary(id, &entry)) {
    339       LOG(WARNING) << basename << " is in the cache but is not configured by "
    340                    << "the ExternalCache source, and will be erased.";
    341       id.clear();
    342     }
    343 
    344     if (!Version(version).IsValid()) {
    345       LOG(ERROR) << "Bad extension version in ExternalCache: " << version;
    346       version.clear();
    347     }
    348 
    349     if (id.empty() || version.empty()) {
    350       LOG(ERROR) << "Invalid file in ExternalCache, erasing: " << basename;
    351       base::DeleteFile(path, true /* recursive */);
    352       continue;
    353     }
    354 
    355     // Enforce a lower-case id.
    356     id = StringToLowerASCII(id);
    357 
    358     std::string update_url;
    359     std::string prev_version_string;
    360     std::string prev_crx;
    361     if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    362                          &update_url)) {
    363       VLOG(1) << "ExternalCache found cached version " << version
    364               << " for extension id: " << id;
    365       entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
    366       entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
    367                        version);
    368       entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
    369                        path.value());
    370       if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
    371         entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore,
    372                           true);
    373       }
    374     } else if (
    375         entry->GetString(extensions::ExternalProviderImpl::kExternalVersion,
    376                          &prev_version_string) &&
    377         entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
    378                          &prev_crx)) {
    379       Version prev_version(prev_version_string);
    380       Version curr_version(version);
    381       DCHECK(prev_version.IsValid());
    382       DCHECK(curr_version.IsValid());
    383       if (prev_version.CompareTo(curr_version) <= 0) {
    384         VLOG(1) << "ExternalCache found old cached version "
    385                 << prev_version_string << " path: " << prev_crx;
    386         base::FilePath prev_crx_file(prev_crx);
    387         if (cache_dir.IsParent(prev_crx_file)) {
    388           // Only delete old cached files under cache_dir_ folder.
    389           base::DeleteFile(base::FilePath(prev_crx), true /* recursive */);
    390         }
    391         entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
    392                          version);
    393         entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
    394                          path.value());
    395       } else {
    396         VLOG(1) << "ExternalCache found old cached version "
    397                 << version << " path: " << path.value();
    398         base::DeleteFile(path, true /* recursive */);
    399       }
    400     } else {
    401       NOTREACHED() << "ExternalCache found bad entry for extension id: " << id
    402                    << " file path: " << path.value();
    403     }
    404   }
    405 }
    406 
    407 void ExternalCache::OnCacheUpdated(scoped_ptr<base::DictionaryValue> prefs) {
    408   DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
    409   if (shutdown_)
    410     return;
    411 
    412   // If request_context_ is missing we can't download anything.
    413   if (!downloader_ && request_context_) {
    414     downloader_.reset(
    415         new extensions::ExtensionDownloader(this, request_context_));
    416   }
    417 
    418   cached_extensions_->Clear();
    419   for (base::DictionaryValue::Iterator it(*extensions_.get());
    420        !it.IsAtEnd(); it.Advance()) {
    421     const base::DictionaryValue* entry = NULL;
    422     if (!it.value().GetAsDictionary(&entry)) {
    423       LOG(ERROR) << "ExternalCache found bad entry with type "
    424                  << it.value().GetType();
    425       continue;
    426     }
    427 
    428     bool keep_if_present =
    429         entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
    430     // Check for updates for all extensions configured except for extensions
    431     // marked as keep_if_present.
    432     if (downloader_ && !keep_if_present) {
    433       GURL update_url;
    434       std::string external_update_url;
    435       if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    436                            &external_update_url)) {
    437         update_url = GURL(external_update_url);
    438       } else if (always_check_updates_) {
    439         update_url = extension_urls::GetWebstoreUpdateUrl();
    440       }
    441       if (update_url.is_valid())
    442         downloader_->AddPendingExtension(it.key(), update_url, 0);
    443     }
    444 
    445     base::DictionaryValue* cached_entry = NULL;
    446     if (prefs->GetDictionary(it.key(), &cached_entry)) {
    447       bool has_external_crx = cached_entry->HasKey(
    448           extensions::ExternalProviderImpl::kExternalCrx);
    449       bool is_already_installed =
    450           !delegate_->GetInstalledExtensionVersion(it.key()).empty();
    451       if (!downloader_ || keep_if_present || has_external_crx ||
    452           is_already_installed) {
    453         scoped_ptr<base::Value> value;
    454         prefs->Remove(it.key(), &value);
    455         cached_extensions_->Set(it.key(), value.release());
    456       }
    457     }
    458   }
    459   if (downloader_)
    460     downloader_->StartAllPending();
    461 
    462   VLOG(1) << "Updated ExternalCache, there are "
    463           << cached_extensions_->size() << " extensions cached";
    464 
    465   UpdateExtensionLoader();
    466 }
    467 
    468 // static
    469 void ExternalCache::BackendInstallCacheEntry(
    470     base::WeakPtr<ExternalCache> external_cache,
    471     const base::FilePath& cache_dir,
    472     const std::string& id,
    473     const base::FilePath& path,
    474     const std::string& version) {
    475   Version version_validator(version);
    476   if (!version_validator.IsValid()) {
    477     LOG(ERROR) << "ExternalCache downloaded extension " << id << " but got bad "
    478                << "version: " << version;
    479     base::DeleteFile(path, true /* recursive */);
    480     return;
    481   }
    482 
    483   std::string basename = id + "-" + version + kCRXFileExtension;
    484   base::FilePath cached_crx_path = cache_dir.Append(basename);
    485 
    486   if (base::PathExists(cached_crx_path)) {
    487     LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite "
    488                  << "an existing cached crx.";
    489     base::DeleteFile(cached_crx_path, true /* recursive */);
    490   }
    491 
    492   if (!base::DirectoryExists(cache_dir)) {
    493     LOG(ERROR) << "AppPack cache directory does not exist, creating now: "
    494                << cache_dir.value();
    495     if (!base::CreateDirectory(cache_dir)) {
    496       LOG(ERROR) << "Failed to create the AppPack cache dir!";
    497       base::DeleteFile(path, true /* recursive */);
    498       return;
    499     }
    500   }
    501 
    502   if (!base::Move(path, cached_crx_path)) {
    503     LOG(ERROR) << "Failed to move AppPack crx from " << path.value()
    504                << " to " << cached_crx_path.value();
    505     base::DeleteFile(path, true /* recursive */);
    506     return;
    507   }
    508 
    509   content::BrowserThread::PostTask(
    510       content::BrowserThread::UI,
    511       FROM_HERE,
    512       base::Bind(&ExternalCache::OnCacheEntryInstalled,
    513                  external_cache,
    514                  id,
    515                  cached_crx_path,
    516                  version));
    517 }
    518 
    519 void ExternalCache::OnCacheEntryInstalled(const std::string& id,
    520                                           const base::FilePath& path,
    521                                           const std::string& version) {
    522   if (shutdown_)
    523     return;
    524 
    525   VLOG(1) << "AppPack installed a new extension in the cache: " << path.value();
    526 
    527   base::DictionaryValue* entry = NULL;
    528   if (!extensions_->GetDictionary(id, &entry)) {
    529     LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
    530     return;
    531   }
    532 
    533   // Copy entry to don't modify it inside extensions_.
    534   entry = entry->DeepCopy();
    535 
    536   std::string update_url;
    537   if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
    538                        &update_url) &&
    539       extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
    540     entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
    541   }
    542   entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
    543   entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
    544   entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
    545                    path.value());
    546 
    547   cached_extensions_->Set(id, entry);
    548   UpdateExtensionLoader();
    549 }
    550 
    551 // static
    552 void ExternalCache::BackendShudown(const base::Closure& callback) {
    553   content::BrowserThread::PostTask(content::BrowserThread::UI,
    554                                    FROM_HERE,
    555                                    callback);
    556 }
    557 
    558 std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
    559     const std::string& id) {
    560   return std::string();
    561 }
    562 
    563 }  // namespace chromeos
    564