普通文本  |  564行  |  19.29 KB

// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "chrome/browser/chromeos/extensions/external_cache.h"

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/file_util.h"
#include "base/files/file_enumerator.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/values.h"
#include "base/version.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/extensions/crx_installer.h"
#include "chrome/browser/extensions/external_provider_impl.h"
#include "chrome/browser/extensions/updater/extension_downloader.h"
#include "chrome/common/extensions/extension_constants.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_service.h"
#include "content/public/browser/notification_source.h"
#include "extensions/common/extension.h"

namespace chromeos {

namespace {

// File name extension for CRX files (not case sensitive).
const char kCRXFileExtension[] = ".crx";

// Delay between checks for flag file presence when waiting for the cache to
// become ready.
const int64_t kCacheStatusPollingDelayMs = 1000;

}  // namespace

const char ExternalCache::kCacheReadyFlagFileName[] = ".initialized";

ExternalCache::ExternalCache(const base::FilePath& cache_dir,
                             net::URLRequestContextGetter* request_context,
                             const scoped_refptr<base::SequencedTaskRunner>&
                                 backend_task_runner,
                             Delegate* delegate,
                             bool always_check_updates,
                             bool wait_for_cache_initialization)
    : cache_dir_(cache_dir),
      request_context_(request_context),
      delegate_(delegate),
      shutdown_(false),
      always_check_updates_(always_check_updates),
      wait_for_cache_initialization_(wait_for_cache_initialization),
      cached_extensions_(new base::DictionaryValue()),
      backend_task_runner_(backend_task_runner),
      weak_ptr_factory_(this) {
  notification_registrar_.Add(
      this,
      chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR,
      content::NotificationService::AllBrowserContextsAndSources());
}

ExternalCache::~ExternalCache() {
}

void ExternalCache::Shutdown(const base::Closure& callback) {
  DCHECK(!shutdown_);
  shutdown_ = true;
  backend_task_runner_->PostTask(FROM_HERE,
                                 base::Bind(&ExternalCache::BackendShudown,
                                            callback));
}

void ExternalCache::UpdateExtensionsList(
    scoped_ptr<base::DictionaryValue> prefs) {
  if (shutdown_)
    return;

  extensions_ = prefs.Pass();
  if (extensions_->empty()) {
    // Don't check cache and clear it if there are no extensions in the list.
    // It is important case because login to supervised user shouldn't clear
    // cache for normal users.
    // TODO(dpolukhin): introduce reference counting to preserve cache elements
    // when they are not needed for current user but needed for other users.
    cached_extensions_->Clear();
    UpdateExtensionLoader();
  } else {
    CheckCache();
  }
}

void ExternalCache::OnDamagedFileDetected(const base::FilePath& path) {
  if (shutdown_)
    return;

  for (base::DictionaryValue::Iterator it(*cached_extensions_.get());
       !it.IsAtEnd(); it.Advance()) {
    const base::DictionaryValue* entry = NULL;
    if (!it.value().GetAsDictionary(&entry)) {
      NOTREACHED() << "ExternalCache found bad entry with type "
                   << it.value().GetType();
      continue;
    }

    std::string external_crx;
    if (entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
                         &external_crx) &&
        external_crx == path.value()) {

      LOG(ERROR) << "ExternalCache extension at " << path.value()
                 << " failed to install, deleting it.";
      cached_extensions_->Remove(it.key(), NULL);
      UpdateExtensionLoader();

      // The file will be downloaded again on the next restart.
      if (cache_dir_.IsParent(path)) {
        backend_task_runner_->PostTask(
            FROM_HERE,
            base::Bind(base::IgnoreResult(base::DeleteFile),
            path,
            true));
      }

      // Don't try to DownloadMissingExtensions() from here,
      // since it can cause a fail/retry loop.
      return;
    }
  }
  LOG(ERROR) << "ExternalCache cannot find external_crx " << path.value();
}

void ExternalCache::Observe(int type,
                            const content::NotificationSource& source,
                            const content::NotificationDetails& details) {
  if (shutdown_)
    return;

  switch (type) {
    case chrome::NOTIFICATION_EXTENSION_INSTALL_ERROR: {
      extensions::CrxInstaller* installer =
          content::Source<extensions::CrxInstaller>(source).ptr();
      OnDamagedFileDetected(installer->source_file());
      break;
    }

    default:
      NOTREACHED();
  }
}

void ExternalCache::OnExtensionDownloadFailed(
    const std::string& id,
    extensions::ExtensionDownloaderDelegate::Error error,
    const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
    const std::set<int>& request_ids) {
  if (shutdown_)
    return;

  if (error == NO_UPDATE_AVAILABLE) {
    if (!cached_extensions_->HasKey(id)) {
      LOG(ERROR) << "ExternalCache extension " << id
                 << " not found on update server";
    }
  } else {
    LOG(ERROR) << "ExternalCache failed to download extension " << id
               << ", error " << error;
  }
}

void ExternalCache::OnExtensionDownloadFinished(
    const std::string& id,
    const base::FilePath& path,
    const GURL& download_url,
    const std::string& version,
    const extensions::ExtensionDownloaderDelegate::PingResult& ping_result,
    const std::set<int>& request_ids) {
  if (shutdown_)
    return;

  backend_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&ExternalCache::BackendInstallCacheEntry,
                 weak_ptr_factory_.GetWeakPtr(),
                 cache_dir_,
                 id,
                 path,
                 version));
}

bool ExternalCache::IsExtensionPending(const std::string& id) {
  // Pending means that there is no installed version yet.
  return extensions_->HasKey(id) && !cached_extensions_->HasKey(id);
}

bool ExternalCache::GetExtensionExistingVersion(const std::string& id,
                                                std::string* version) {
  DictionaryValue* extension_dictionary = NULL;
  if (cached_extensions_->GetDictionary(id, &extension_dictionary)) {
    if (extension_dictionary->GetString(
            extensions::ExternalProviderImpl::kExternalVersion, version)) {
      return true;
    }
    *version = delegate_->GetInstalledExtensionVersion(id);
    return !version->empty();
  }
  return false;
}

void ExternalCache::UpdateExtensionLoader() {
  if (shutdown_)
    return;

  VLOG(1) << "Notify ExternalCache delegate about cache update";
  if (delegate_)
    delegate_->OnExtensionListsUpdated(cached_extensions_.get());
}

void ExternalCache::CheckCache() {
  if (wait_for_cache_initialization_) {
    backend_task_runner_->PostTask(
        FROM_HERE,
        base::Bind(&ExternalCache::BackendCheckCacheStatus,
                   weak_ptr_factory_.GetWeakPtr(),
                   cache_dir_));
  } else {
    CheckCacheContents();
  }
}

// static
void ExternalCache::BackendCheckCacheStatus(
    base::WeakPtr<ExternalCache> external_cache,
    const base::FilePath& cache_dir) {
  content::BrowserThread::PostTask(
      content::BrowserThread::UI,
      FROM_HERE,
      base::Bind(&ExternalCache::OnCacheStatusChecked,
          external_cache,
          base::PathExists(cache_dir.AppendASCII(kCacheReadyFlagFileName))));
}

void ExternalCache::OnCacheStatusChecked(bool ready) {
  if (shutdown_)
    return;

  if (ready) {
    CheckCacheContents();
  } else {
    content::BrowserThread::PostDelayedTask(
        content::BrowserThread::UI,
        FROM_HERE,
        base::Bind(&ExternalCache::CheckCache,
                   weak_ptr_factory_.GetWeakPtr()),
        base::TimeDelta::FromMilliseconds(kCacheStatusPollingDelayMs));
  }
}

void ExternalCache::CheckCacheContents() {
  if (shutdown_)
    return;

  backend_task_runner_->PostTask(
      FROM_HERE,
      base::Bind(&ExternalCache::BackendCheckCacheContents,
                 weak_ptr_factory_.GetWeakPtr(),
                 cache_dir_,
                 base::Passed(make_scoped_ptr(extensions_->DeepCopy()))));
}

// static
void ExternalCache::BackendCheckCacheContents(
    base::WeakPtr<ExternalCache> external_cache,
    const base::FilePath& cache_dir,
    scoped_ptr<base::DictionaryValue> prefs) {
  BackendCheckCacheContentsInternal(cache_dir, prefs.get());
  content::BrowserThread::PostTask(content::BrowserThread::UI,
                                   FROM_HERE,
                                   base::Bind(&ExternalCache::OnCacheUpdated,
                                              external_cache,
                                              base::Passed(&prefs)));
}

// static
void ExternalCache::BackendCheckCacheContentsInternal(
    const base::FilePath& cache_dir,
    base::DictionaryValue* prefs) {
  // Start by verifying that the cache_dir exists.
  if (!base::DirectoryExists(cache_dir)) {
    // Create it now.
    if (!base::CreateDirectory(cache_dir)) {
      LOG(ERROR) << "Failed to create ExternalCache directory at "
                 << cache_dir.value();
    }

    // Nothing else to do. Cache won't be used.
    return;
  }

  // Enumerate all the files in the cache |cache_dir|, including directories
  // and symlinks. Each unrecognized file will be erased.
  int types = base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES |
      base::FileEnumerator::SHOW_SYM_LINKS;
  base::FileEnumerator enumerator(cache_dir, false /* recursive */, types);
  for (base::FilePath path = enumerator.Next();
       !path.empty(); path = enumerator.Next()) {
    base::FileEnumerator::FileInfo info = enumerator.GetInfo();
    std::string basename = path.BaseName().value();

    if (info.IsDirectory() || base::IsLink(info.GetName())) {
      LOG(ERROR) << "Erasing bad file in ExternalCache directory: " << basename;
      base::DeleteFile(path, true /* recursive */);
      continue;
    }

    // Skip flag file that indicates that cache is ready.
    if (basename == kCacheReadyFlagFileName)
      continue;

    // crx files in the cache are named <extension-id>-<version>.crx.
    std::string id;
    std::string version;
    if (EndsWith(basename, kCRXFileExtension, false /* case-sensitive */)) {
      size_t n = basename.find('-');
      if (n != std::string::npos && n + 1 < basename.size() - 4) {
        id = basename.substr(0, n);
        // Size of |version| = total size - "<id>" - "-" - ".crx"
        version = basename.substr(n + 1, basename.size() - 5 - id.size());
      }
    }

    base::DictionaryValue* entry = NULL;
    if (!extensions::Extension::IdIsValid(id)) {
      LOG(ERROR) << "Bad extension id in ExternalCache: " << id;
      id.clear();
    } else if (!prefs->GetDictionary(id, &entry)) {
      LOG(WARNING) << basename << " is in the cache but is not configured by "
                   << "the ExternalCache source, and will be erased.";
      id.clear();
    }

    if (!Version(version).IsValid()) {
      LOG(ERROR) << "Bad extension version in ExternalCache: " << version;
      version.clear();
    }

    if (id.empty() || version.empty()) {
      LOG(ERROR) << "Invalid file in ExternalCache, erasing: " << basename;
      base::DeleteFile(path, true /* recursive */);
      continue;
    }

    // Enforce a lower-case id.
    id = StringToLowerASCII(id);

    std::string update_url;
    std::string prev_version_string;
    std::string prev_crx;
    if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
                         &update_url)) {
      VLOG(1) << "ExternalCache found cached version " << version
              << " for extension id: " << id;
      entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
      entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
                       version);
      entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
                       path.value());
      if (extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
        entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore,
                          true);
      }
    } else if (
        entry->GetString(extensions::ExternalProviderImpl::kExternalVersion,
                         &prev_version_string) &&
        entry->GetString(extensions::ExternalProviderImpl::kExternalCrx,
                         &prev_crx)) {
      Version prev_version(prev_version_string);
      Version curr_version(version);
      DCHECK(prev_version.IsValid());
      DCHECK(curr_version.IsValid());
      if (prev_version.CompareTo(curr_version) <= 0) {
        VLOG(1) << "ExternalCache found old cached version "
                << prev_version_string << " path: " << prev_crx;
        base::FilePath prev_crx_file(prev_crx);
        if (cache_dir.IsParent(prev_crx_file)) {
          // Only delete old cached files under cache_dir_ folder.
          base::DeleteFile(base::FilePath(prev_crx), true /* recursive */);
        }
        entry->SetString(extensions::ExternalProviderImpl::kExternalVersion,
                         version);
        entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
                         path.value());
      } else {
        VLOG(1) << "ExternalCache found old cached version "
                << version << " path: " << path.value();
        base::DeleteFile(path, true /* recursive */);
      }
    } else {
      NOTREACHED() << "ExternalCache found bad entry for extension id: " << id
                   << " file path: " << path.value();
    }
  }
}

void ExternalCache::OnCacheUpdated(scoped_ptr<base::DictionaryValue> prefs) {
  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
  if (shutdown_)
    return;

  // If request_context_ is missing we can't download anything.
  if (!downloader_ && request_context_) {
    downloader_.reset(
        new extensions::ExtensionDownloader(this, request_context_));
  }

  cached_extensions_->Clear();
  for (base::DictionaryValue::Iterator it(*extensions_.get());
       !it.IsAtEnd(); it.Advance()) {
    const base::DictionaryValue* entry = NULL;
    if (!it.value().GetAsDictionary(&entry)) {
      LOG(ERROR) << "ExternalCache found bad entry with type "
                 << it.value().GetType();
      continue;
    }

    bool keep_if_present =
        entry->HasKey(extensions::ExternalProviderImpl::kKeepIfPresent);
    // Check for updates for all extensions configured except for extensions
    // marked as keep_if_present.
    if (downloader_ && !keep_if_present) {
      GURL update_url;
      std::string external_update_url;
      if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
                           &external_update_url)) {
        update_url = GURL(external_update_url);
      } else if (always_check_updates_) {
        update_url = extension_urls::GetWebstoreUpdateUrl();
      }
      if (update_url.is_valid())
        downloader_->AddPendingExtension(it.key(), update_url, 0);
    }

    base::DictionaryValue* cached_entry = NULL;
    if (prefs->GetDictionary(it.key(), &cached_entry)) {
      bool has_external_crx = cached_entry->HasKey(
          extensions::ExternalProviderImpl::kExternalCrx);
      bool is_already_installed =
          !delegate_->GetInstalledExtensionVersion(it.key()).empty();
      if (!downloader_ || keep_if_present || has_external_crx ||
          is_already_installed) {
        scoped_ptr<base::Value> value;
        prefs->Remove(it.key(), &value);
        cached_extensions_->Set(it.key(), value.release());
      }
    }
  }
  if (downloader_)
    downloader_->StartAllPending();

  VLOG(1) << "Updated ExternalCache, there are "
          << cached_extensions_->size() << " extensions cached";

  UpdateExtensionLoader();
}

// static
void ExternalCache::BackendInstallCacheEntry(
    base::WeakPtr<ExternalCache> external_cache,
    const base::FilePath& cache_dir,
    const std::string& id,
    const base::FilePath& path,
    const std::string& version) {
  Version version_validator(version);
  if (!version_validator.IsValid()) {
    LOG(ERROR) << "ExternalCache downloaded extension " << id << " but got bad "
               << "version: " << version;
    base::DeleteFile(path, true /* recursive */);
    return;
  }

  std::string basename = id + "-" + version + kCRXFileExtension;
  base::FilePath cached_crx_path = cache_dir.Append(basename);

  if (base::PathExists(cached_crx_path)) {
    LOG(WARNING) << "AppPack downloaded a crx whose filename will overwrite "
                 << "an existing cached crx.";
    base::DeleteFile(cached_crx_path, true /* recursive */);
  }

  if (!base::DirectoryExists(cache_dir)) {
    LOG(ERROR) << "AppPack cache directory does not exist, creating now: "
               << cache_dir.value();
    if (!base::CreateDirectory(cache_dir)) {
      LOG(ERROR) << "Failed to create the AppPack cache dir!";
      base::DeleteFile(path, true /* recursive */);
      return;
    }
  }

  if (!base::Move(path, cached_crx_path)) {
    LOG(ERROR) << "Failed to move AppPack crx from " << path.value()
               << " to " << cached_crx_path.value();
    base::DeleteFile(path, true /* recursive */);
    return;
  }

  content::BrowserThread::PostTask(
      content::BrowserThread::UI,
      FROM_HERE,
      base::Bind(&ExternalCache::OnCacheEntryInstalled,
                 external_cache,
                 id,
                 cached_crx_path,
                 version));
}

void ExternalCache::OnCacheEntryInstalled(const std::string& id,
                                          const base::FilePath& path,
                                          const std::string& version) {
  if (shutdown_)
    return;

  VLOG(1) << "AppPack installed a new extension in the cache: " << path.value();

  base::DictionaryValue* entry = NULL;
  if (!extensions_->GetDictionary(id, &entry)) {
    LOG(ERROR) << "ExternalCache cannot find entry for extension " << id;
    return;
  }

  // Copy entry to don't modify it inside extensions_.
  entry = entry->DeepCopy();

  std::string update_url;
  if (entry->GetString(extensions::ExternalProviderImpl::kExternalUpdateUrl,
                       &update_url) &&
      extension_urls::IsWebstoreUpdateUrl(GURL(update_url))) {
    entry->SetBoolean(extensions::ExternalProviderImpl::kIsFromWebstore, true);
  }
  entry->Remove(extensions::ExternalProviderImpl::kExternalUpdateUrl, NULL);
  entry->SetString(extensions::ExternalProviderImpl::kExternalVersion, version);
  entry->SetString(extensions::ExternalProviderImpl::kExternalCrx,
                   path.value());

  cached_extensions_->Set(id, entry);
  UpdateExtensionLoader();
}

// static
void ExternalCache::BackendShudown(const base::Closure& callback) {
  content::BrowserThread::PostTask(content::BrowserThread::UI,
                                   FROM_HERE,
                                   callback);
}

std::string ExternalCache::Delegate::GetInstalledExtensionVersion(
    const std::string& id) {
  return std::string();
}

}  // namespace chromeos