Home | History | Annotate | Download | only in updater
      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 "chrome/browser/extensions/updater/local_extension_cache.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/files/file_enumerator.h"
      9 #include "base/files/file_util.h"
     10 #include "base/sequenced_task_runner.h"
     11 #include "base/strings/string_util.h"
     12 #include "base/sys_info.h"
     13 #include "base/version.h"
     14 #include "components/crx_file/id_util.h"
     15 #include "content/public/browser/browser_thread.h"
     16 
     17 namespace extensions {
     18 namespace {
     19 
     20 // File name extension for CRX files (not case sensitive).
     21 const char kCRXFileExtension[] = ".crx";
     22 
     23 // Delay between checks for flag file presence when waiting for the cache to
     24 // become ready.
     25 const int64_t kCacheStatusPollingDelayMs = 1000;
     26 
     27 }  // namespace
     28 
     29 const char LocalExtensionCache::kCacheReadyFlagFileName[] = ".initialized";
     30 
     31 LocalExtensionCache::LocalExtensionCache(
     32     const base::FilePath& cache_dir,
     33     uint64 max_cache_size,
     34     const base::TimeDelta& max_cache_age,
     35     const scoped_refptr<base::SequencedTaskRunner>& backend_task_runner)
     36     : cache_dir_(cache_dir),
     37       max_cache_size_(max_cache_size),
     38       min_cache_age_(base::Time::Now() - max_cache_age),
     39       backend_task_runner_(backend_task_runner),
     40       state_(kUninitialized),
     41       weak_ptr_factory_(this),
     42       cache_status_polling_delay_(
     43           base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs)) {
     44 }
     45 
     46 LocalExtensionCache::~LocalExtensionCache() {
     47   if (state_ == kReady)
     48     CleanUp();
     49 }
     50 
     51 void LocalExtensionCache::Init(bool wait_for_cache_initialization,
     52                                const base::Closure& callback) {
     53   DCHECK_EQ(state_, kUninitialized);
     54 
     55   state_ = kWaitInitialization;
     56   if (wait_for_cache_initialization)
     57     CheckCacheStatus(callback);
     58   else
     59     CheckCacheContents(callback);
     60 }
     61 
     62 void LocalExtensionCache::Shutdown(const base::Closure& callback) {
     63   DCHECK_NE(state_, kShutdown);
     64   if (state_ == kReady)
     65     CleanUp();
     66   state_ = kShutdown;
     67   backend_task_runner_->PostTaskAndReply(FROM_HERE,
     68       base::Bind(&base::DoNothing), callback);
     69 }
     70 
     71 bool LocalExtensionCache::GetExtension(const std::string& id,
     72                                        base::FilePath* file_path,
     73                                        std::string* version) {
     74   if (state_ != kReady)
     75     return false;
     76 
     77   CacheMap::iterator it = cached_extensions_.find(id);
     78   if (it == cached_extensions_.end())
     79     return false;
     80 
     81   if (file_path) {
     82     *file_path = it->second.file_path;
     83 
     84     // If caller is not interesting in file_path, extension is not used.
     85     base::Time now = base::Time::Now();
     86     backend_task_runner_->PostTask(FROM_HERE,
     87         base::Bind(&LocalExtensionCache::BackendMarkFileUsed,
     88         it->second.file_path, now));
     89     it->second.last_used = now;
     90   }
     91 
     92   if (version)
     93     *version = it->second.version;
     94 
     95   return true;
     96 }
     97 
     98 void LocalExtensionCache::PutExtension(const std::string& id,
     99                                        const base::FilePath& file_path,
    100                                        const std::string& version,
    101                                        const PutExtensionCallback& callback) {
    102   if (state_ != kReady) {
    103     callback.Run(file_path, true);
    104     return;
    105   }
    106 
    107   Version version_validator(version);
    108   if (!version_validator.IsValid()) {
    109     LOG(ERROR) << "Extension " << id << " has bad version " << version;
    110     callback.Run(file_path, true);
    111     return;
    112   }
    113 
    114   CacheMap::iterator it = cached_extensions_.find(id);
    115   if (it != cached_extensions_.end()) {
    116     Version new_version(version);
    117     Version prev_version(it->second.version);
    118     if (new_version.CompareTo(prev_version) <= 0) {
    119       LOG(WARNING) << "Cache contains newer or the same version "
    120                    << prev_version.GetString() << " for extension "
    121                    << id << " version " << version;
    122       callback.Run(file_path, true);
    123       return;
    124     }
    125   }
    126 
    127   backend_task_runner_->PostTask(
    128       FROM_HERE,
    129       base::Bind(&LocalExtensionCache::BackendInstallCacheEntry,
    130                   weak_ptr_factory_.GetWeakPtr(),
    131                   cache_dir_,
    132                   id,
    133                   file_path,
    134                   version,
    135                   callback));
    136 }
    137 
    138 bool LocalExtensionCache::RemoveExtension(const std::string& id) {
    139   if (state_ != kReady)
    140     return false;
    141 
    142   CacheMap::iterator it = cached_extensions_.find(id);
    143   if (it == cached_extensions_.end())
    144     return false;
    145 
    146   backend_task_runner_->PostTask(
    147       FROM_HERE,
    148       base::Bind(
    149           &LocalExtensionCache::BackendRemoveCacheEntry, cache_dir_, id));
    150 
    151   cached_extensions_.erase(it);
    152   return true;
    153 }
    154 
    155 bool LocalExtensionCache::GetStatistics(uint64* cache_size,
    156                                         size_t* extensions_count) {
    157   if (state_ != kReady)
    158     return false;
    159 
    160   *cache_size = 0;
    161   for (CacheMap::iterator it = cached_extensions_.begin();
    162        it != cached_extensions_.end(); ++it) {
    163     *cache_size += it->second.size;
    164   }
    165   *extensions_count = cached_extensions_.size();
    166 
    167   return true;
    168 }
    169 
    170 void LocalExtensionCache::SetCacheStatusPollingDelayForTests(
    171     const base::TimeDelta& delay) {
    172   cache_status_polling_delay_ = delay;
    173 }
    174 
    175 void LocalExtensionCache::CheckCacheStatus(const base::Closure& callback) {
    176   if (state_ == kShutdown) {
    177     callback.Run();
    178     return;
    179   }
    180 
    181   backend_task_runner_->PostTask(
    182       FROM_HERE,
    183       base::Bind(&LocalExtensionCache::BackendCheckCacheStatus,
    184                   weak_ptr_factory_.GetWeakPtr(),
    185                   cache_dir_,
    186                   callback));
    187 }
    188 
    189 // static
    190 void LocalExtensionCache::BackendCheckCacheStatus(
    191     base::WeakPtr<LocalExtensionCache> local_cache,
    192     const base::FilePath& cache_dir,
    193     const base::Closure& callback) {
    194   const bool exists =
    195       base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName));
    196 
    197   static bool first_check = true;
    198   if (first_check && !exists && !base::SysInfo::IsRunningOnChromeOS()) {
    199     LOG(WARNING) << "Extensions will not be installed from update URLs until "
    200                  << cache_dir.AppendASCII(kCacheReadyFlagFileName).value()
    201                  << " exists.";
    202   }
    203   first_check = false;
    204 
    205   content::BrowserThread::PostTask(
    206       content::BrowserThread::UI,
    207       FROM_HERE,
    208       base::Bind(&LocalExtensionCache::OnCacheStatusChecked,
    209                  local_cache,
    210                  exists,
    211                  callback));
    212 }
    213 
    214 void LocalExtensionCache::OnCacheStatusChecked(bool ready,
    215                                                const base::Closure& callback) {
    216   if (state_ == kShutdown) {
    217     callback.Run();
    218     return;
    219   }
    220 
    221   if (ready) {
    222     CheckCacheContents(callback);
    223   } else {
    224     content::BrowserThread::PostDelayedTask(
    225         content::BrowserThread::UI,
    226         FROM_HERE,
    227         base::Bind(&LocalExtensionCache::CheckCacheStatus,
    228                    weak_ptr_factory_.GetWeakPtr(),
    229                    callback),
    230         cache_status_polling_delay_);
    231   }
    232 }
    233 
    234 void LocalExtensionCache::CheckCacheContents(const base::Closure& callback) {
    235   DCHECK_EQ(state_, kWaitInitialization);
    236   backend_task_runner_->PostTask(
    237       FROM_HERE,
    238       base::Bind(&LocalExtensionCache::BackendCheckCacheContents,
    239                  weak_ptr_factory_.GetWeakPtr(),
    240                  cache_dir_,
    241                  callback));
    242 }
    243 
    244 // static
    245 void LocalExtensionCache::BackendCheckCacheContents(
    246     base::WeakPtr<LocalExtensionCache> local_cache,
    247     const base::FilePath& cache_dir,
    248     const base::Closure& callback) {
    249   scoped_ptr<CacheMap> cache_content(new CacheMap);
    250   BackendCheckCacheContentsInternal(cache_dir, cache_content.get());
    251   content::BrowserThread::PostTask(
    252       content::BrowserThread::UI,
    253       FROM_HERE,
    254       base::Bind(&LocalExtensionCache::OnCacheContentsChecked,
    255                  local_cache,
    256                  base::Passed(&cache_content),
    257                  callback));
    258 }
    259 
    260 // static
    261 void LocalExtensionCache::BackendCheckCacheContentsInternal(
    262     const base::FilePath& cache_dir,
    263     CacheMap* cache_content) {
    264   // Start by verifying that the cache_dir exists.
    265   if (!base::DirectoryExists(cache_dir)) {
    266     // Create it now.
    267     if (!base::CreateDirectory(cache_dir)) {
    268       LOG(ERROR) << "Failed to create cache directory at "
    269                  << cache_dir.value();
    270     }
    271 
    272     // Nothing else to do. Cache is empty.
    273     return;
    274   }
    275 
    276   // Enumerate all the files in the cache |cache_dir|, including directories
    277   // and symlinks. Each unrecognized file will be erased.
    278   int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES;
    279   base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
    280   for (base::FilePath path = enumerator.Next();
    281        !path.empty(); path = enumerator.Next()) {
    282     base::FileEnumerator::FileInfo info = enumerator.GetInfo();
    283     std::string basename = path.BaseName().value();
    284 
    285     if (info.IsDirectory() || base::IsLink(info.GetName())) {
    286       LOG(ERROR) << "Erasing bad file in cache directory: " << basename;
    287       base::DeleteFile(path, true /* recursive */);
    288       continue;
    289     }
    290 
    291     // Skip flag file that indicates that cache is ready.
    292     if (basename == kCacheReadyFlagFileName)
    293       continue;
    294 
    295     // crx files in the cache are named <extension-id>-<version>.crx.
    296     std::string id;
    297     std::string version;
    298     if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
    299       size_t n = basename.find('-');
    300       if (n != std::string::npos && n + 1 < basename.size() - 4) {
    301         id = basename.substr(0, n);
    302         // Size of |version| = total size - "<id>" - "-" - ".crx"
    303         version = basename.substr(n + 1, basename.size() - 5 - id.size());
    304       }
    305     }
    306 
    307     // Enforce a lower-case id.
    308     id = base::StringToLowerASCII(id);
    309     if (!crx_file::id_util::IdIsValid(id)) {
    310       LOG(ERROR) << "Bad extension id in cache: " << id;
    311       id.clear();
    312     }
    313 
    314     if (!Version(version).IsValid()) {
    315       LOG(ERROR) << "Bad extension version in cache: " << version;
    316       version.clear();
    317     }
    318 
    319     if (id.empty() || version.empty()) {
    320       LOG(ERROR) << "Invalid file in cache, erasing: " << basename;
    321       base::DeleteFile(path, true /* recursive */);
    322       continue;
    323     }
    324 
    325     VLOG(1) << "Found cached version " << version
    326             << " for extension id " << id;
    327 
    328     CacheMap::iterator it = cache_content->find(id);
    329     if (it != cache_content->end()) {
    330       // |cache_content| already has version for this ID. Removed older one.
    331       Version curr_version(version);
    332       Version prev_version(it->second.version);
    333       if (prev_version.CompareTo(curr_version) <= 0) {
    334         base::DeleteFile(base::FilePath(it->second.file_path),
    335                          true /* recursive */);
    336         cache_content->erase(id);
    337         VLOG(1) << "Remove older version " << it->second.version
    338                 << " for extension id " << id;
    339       } else {
    340         base::DeleteFile(path, true /* recursive */);
    341         VLOG(1) << "Remove older version " << version
    342                 << " for extension id " << id;
    343         continue;
    344       }
    345     }
    346 
    347     cache_content->insert(std::make_pair(id, CacheItemInfo(
    348         version, info.GetLastModifiedTime(), info.GetSize(), path)));
    349   }
    350 }
    351 
    352 void LocalExtensionCache::OnCacheContentsChecked(
    353     scoped_ptr<CacheMap> cache_content,
    354     const base::Closure& callback) {
    355   cache_content->swap(cached_extensions_);
    356   state_ = kReady;
    357   callback.Run();
    358 }
    359 
    360 // static
    361 void LocalExtensionCache::BackendMarkFileUsed(const base::FilePath& file_path,
    362                                               const base::Time& time) {
    363   base::TouchFile(file_path, time, time);
    364 }
    365 
    366 // static
    367 void LocalExtensionCache::BackendInstallCacheEntry(
    368     base::WeakPtr<LocalExtensionCache> local_cache,
    369     const base::FilePath& cache_dir,
    370     const std::string& id,
    371     const base::FilePath& file_path,
    372     const std::string& version,
    373     const PutExtensionCallback& callback) {
    374   std::string basename = id + "-" + version + kCRXFileExtension;
    375   base::FilePath cached_crx_path = cache_dir.AppendASCII(basename);
    376 
    377   bool was_error = false;
    378   if (base::PathExists(cached_crx_path)) {
    379     LOG(ERROR) << "File already exists " << file_path.value();
    380     cached_crx_path = file_path;
    381     was_error = true;
    382   }
    383 
    384   base::File::Info info;
    385   if (!was_error) {
    386     if (!base::Move(file_path, cached_crx_path)) {
    387       LOG(ERROR) << "Failed to copy from " << file_path.value()
    388                  << " to " << cached_crx_path.value();
    389       cached_crx_path = file_path;
    390       was_error = true;
    391     } else {
    392       was_error = !base::GetFileInfo(cached_crx_path, &info);
    393       VLOG(1) << "Cache entry installed for extension id " << id
    394               << " version " << version;
    395     }
    396   }
    397 
    398   content::BrowserThread::PostTask(
    399       content::BrowserThread::UI,
    400       FROM_HERE,
    401       base::Bind(&LocalExtensionCache::OnCacheEntryInstalled,
    402                  local_cache,
    403                  id,
    404                  CacheItemInfo(version, info.last_modified,
    405                                info.size, cached_crx_path),
    406                  was_error,
    407                  callback));
    408 }
    409 
    410 void LocalExtensionCache::OnCacheEntryInstalled(
    411     const std::string& id,
    412     const CacheItemInfo& info,
    413     bool was_error,
    414     const PutExtensionCallback& callback) {
    415   if (state_ == kShutdown || was_error) {
    416     callback.Run(info.file_path, true);
    417     return;
    418   }
    419 
    420   CacheMap::iterator it = cached_extensions_.find(id);
    421   if (it != cached_extensions_.end()) {
    422     Version new_version(info.version);
    423     Version prev_version(it->second.version);
    424     if (new_version.CompareTo(prev_version) <= 0) {
    425       DCHECK(0) << "Cache contains newer or the same version";
    426       callback.Run(info.file_path, true);
    427       return;
    428     }
    429     it->second = info;
    430   } else {
    431     it = cached_extensions_.insert(std::make_pair(id, info)).first;
    432   }
    433   // Time from file system can have lower precision so use precise "now".
    434   it->second.last_used = base::Time::Now();
    435 
    436   callback.Run(info.file_path, false);
    437 }
    438 
    439 // static
    440 void LocalExtensionCache::BackendRemoveCacheEntry(
    441     const base::FilePath& cache_dir,
    442     const std::string& id) {
    443   std::string file_pattern = id + "-*" + kCRXFileExtension;
    444   base::FileEnumerator enumerator(cache_dir,
    445                                   false /* not recursive */,
    446                                   base::FileEnumerator::FILES,
    447                                   file_pattern);
    448   for (base::FilePath path = enumerator.Next(); !path.empty();
    449        path = enumerator.Next()) {
    450     base::DeleteFile(path, false);
    451     VLOG(1) << "Removed cached file " << path.value();
    452   }
    453 }
    454 
    455 // static
    456 bool LocalExtensionCache::CompareCacheItemsAge(const CacheMap::iterator& lhs,
    457                                                const CacheMap::iterator& rhs) {
    458   return lhs->second.last_used < rhs->second.last_used;
    459 }
    460 
    461 void LocalExtensionCache::CleanUp() {
    462   DCHECK_EQ(state_, kReady);
    463 
    464   std::vector<CacheMap::iterator> items;
    465   items.reserve(cached_extensions_.size());
    466   uint64_t total_size = 0;
    467   for (CacheMap::iterator it = cached_extensions_.begin();
    468        it != cached_extensions_.end(); ++it) {
    469     items.push_back(it);
    470     total_size += it->second.size;
    471   }
    472   std::sort(items.begin(), items.end(), CompareCacheItemsAge);
    473 
    474   for (std::vector<CacheMap::iterator>::iterator it = items.begin();
    475        it != items.end(); ++it) {
    476     if ((*it)->second.last_used < min_cache_age_ ||
    477         (max_cache_size_ && total_size > max_cache_size_)) {
    478       total_size -= (*it)->second.size;
    479       VLOG(1) << "Clean up cached extension id " << (*it)->first;
    480       RemoveExtension((*it)->first);
    481     }
    482   }
    483 }
    484 
    485 LocalExtensionCache::CacheItemInfo::CacheItemInfo(
    486     const std::string& version,
    487     const base::Time& last_used,
    488     uint64 size,
    489     const base::FilePath& file_path)
    490     : version(version), last_used(last_used), size(size), file_path(file_path) {
    491 }
    492 
    493 }  // namespace extensions
    494