diff options
author | asargent <asargent@chromium.org> | 2015-10-15 14:51:48 -0700 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2015-10-15 21:52:40 +0000 |
commit | 631a99a61cbe3e7e03e372b3d37c6a37fa700392 (patch) | |
tree | 95c4fc66df8e20ba8744159f0ae63913e1750a4a /extensions/browser/updater | |
parent | a33cc7d19a2c878e2a7d9b4a540d06f3e0c3fc16 (diff) | |
download | chromium_src-631a99a61cbe3e7e03e372b3d37c6a37fa700392.zip chromium_src-631a99a61cbe3e7e03e372b3d37c6a37fa700392.tar.gz chromium_src-631a99a61cbe3e7e03e372b3d37c6a37fa700392.tar.bz2 |
Add extensions code to use common updater in components/update_client/
This is another piece of work towards implementing differential
extensions update and share autoupdate code with the chrome components
system. It adds code to the extensions/browser directory that makes use
of the common autoupdate code via the update_client::UpdateClient class
to send update checks to the server, download a full .crx or
differential update, and hand back an unpacked directory with the
contents of the new extension version.
This CL does not yet add any code in chrome/ that actually uses these
new capabilities; that will be in a forthcoming CL.
BUG=490418
Review URL: https://codereview.chromium.org/1362043005
Cr-Commit-Position: refs/heads/master@{#354370}
Diffstat (limited to 'extensions/browser/updater')
-rw-r--r-- | extensions/browser/updater/update_client_config.cc | 29 | ||||
-rw-r--r-- | extensions/browser/updater/update_client_config.h | 40 | ||||
-rw-r--r-- | extensions/browser/updater/update_data_provider.cc | 72 | ||||
-rw-r--r-- | extensions/browser/updater/update_data_provider.h | 69 | ||||
-rw-r--r-- | extensions/browser/updater/update_install_shim.cc | 83 | ||||
-rw-r--r-- | extensions/browser/updater/update_install_shim.h | 76 | ||||
-rw-r--r-- | extensions/browser/updater/update_service.cc | 96 | ||||
-rw-r--r-- | extensions/browser/updater/update_service.h | 55 | ||||
-rw-r--r-- | extensions/browser/updater/update_service_browsertest.cc | 218 | ||||
-rw-r--r-- | extensions/browser/updater/update_service_factory.cc | 7 | ||||
-rw-r--r-- | extensions/browser/updater/update_service_unittest.cc | 248 |
11 files changed, 683 insertions, 310 deletions
diff --git a/extensions/browser/updater/update_client_config.cc b/extensions/browser/updater/update_client_config.cc new file mode 100644 index 0000000..9f1d0d1 --- /dev/null +++ b/extensions/browser/updater/update_client_config.cc @@ -0,0 +1,29 @@ +// Copyright 2015 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 "extensions/browser/updater/update_client_config.h" + +#include "content/public/browser/browser_thread.h" + +namespace extensions { + +UpdateClientConfig::UpdateClientConfig() {} + +scoped_refptr<base::SequencedTaskRunner> +UpdateClientConfig::GetSequencedTaskRunner() const { + return content::BrowserThread::GetBlockingPool() + ->GetSequencedTaskRunnerWithShutdownBehavior( + content::BrowserThread::GetBlockingPool()->GetSequenceToken(), + base::SequencedWorkerPool::SKIP_ON_SHUTDOWN); +} + +scoped_refptr<base::SingleThreadTaskRunner> +UpdateClientConfig::GetSingleThreadTaskRunner() const { + return content::BrowserThread::GetMessageLoopProxyForThread( + content::BrowserThread::FILE); +} + +UpdateClientConfig::~UpdateClientConfig() {} + +} // namespace extensions diff --git a/extensions/browser/updater/update_client_config.h b/extensions/browser/updater/update_client_config.h new file mode 100644 index 0000000..32e7811 --- /dev/null +++ b/extensions/browser/updater/update_client_config.h @@ -0,0 +1,40 @@ +// Copyright 2015 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. + +#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_CLIENT_CONFIG_H_ +#define EXTENSIONS_BROWSER_UPDATER_UPDATE_CLIENT_CONFIG_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "components/update_client/configurator.h" + +namespace base { +class SequencedTaskRunner; +class SingleThreadTaskRunner; +} + +namespace extensions { + +// Used to provide configuration settings to the UpdateClient. +class UpdateClientConfig : public update_client::Configurator { + public: + UpdateClientConfig(); + + scoped_refptr<base::SequencedTaskRunner> GetSequencedTaskRunner() + const override; + scoped_refptr<base::SingleThreadTaskRunner> GetSingleThreadTaskRunner() + const override; + + protected: + friend class base::RefCountedThreadSafe<UpdateClientConfig>; + ~UpdateClientConfig() override; + + private: + DISALLOW_COPY_AND_ASSIGN(UpdateClientConfig); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_CLIENT_CONFIG_H_ diff --git a/extensions/browser/updater/update_data_provider.cc b/extensions/browser/updater/update_data_provider.cc new file mode 100644 index 0000000..e0ad403 --- /dev/null +++ b/extensions/browser/updater/update_data_provider.cc @@ -0,0 +1,72 @@ +// Copyright 2015 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 "extensions/browser/updater/update_data_provider.h" + +#include "base/base64.h" +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/stl_util.h" +#include "base/strings/string_util.h" +#include "components/update_client/update_client.h" +#include "content/public/browser/browser_thread.h" +#include "crypto/sha2.h" +#include "extensions/browser/content_verifier.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/updater/update_install_shim.h" +#include "extensions/common/extension.h" + +namespace extensions { + +UpdateDataProvider::UpdateDataProvider(content::BrowserContext* context, + const InstallCallback& callback) + : context_(context), callback_(callback) {} + +UpdateDataProvider::~UpdateDataProvider() {} + +void UpdateDataProvider::Shutdown() { + context_ = nullptr; +} + +void UpdateDataProvider::GetData( + const std::vector<std::string>& ids, + std::vector<update_client::CrxComponent>* data) { + if (!context_) + return; + const ExtensionRegistry* registry = ExtensionRegistry::Get(context_); + for (const auto& id : ids) { + const Extension* extension = registry->GetInstalledExtension(id); + if (!extension) + continue; + data->push_back(update_client::CrxComponent()); + update_client::CrxComponent* info = &data->back(); + std::string pubkey_bytes; + base::Base64Decode(extension->public_key(), &pubkey_bytes); + info->pk_hash.resize(crypto::kSHA256Length, 0); + crypto::SHA256HashString(pubkey_bytes, vector_as_array(&info->pk_hash), + info->pk_hash.size()); + info->version = *extension->version(); + info->allow_background_download = false; + + info->installer = new UpdateInstallShim( + id, extension->path(), + base::Bind(&UpdateDataProvider::RunInstallCallback, this)); + } +} + +void UpdateDataProvider::RunInstallCallback(const std::string& extension_id, + const base::FilePath& temp_dir) { + if (!context_) { + content::BrowserThread::PostBlockingPoolTask( + FROM_HERE, + base::Bind(base::IgnoreResult(&base::DeleteFile), temp_dir, false)); + return; + } else { + callback_.Run(context_, extension_id, temp_dir); + } +} + +} // namespace extensions diff --git a/extensions/browser/updater/update_data_provider.h b/extensions/browser/updater/update_data_provider.h new file mode 100644 index 0000000..95cea59 --- /dev/null +++ b/extensions/browser/updater/update_data_provider.h @@ -0,0 +1,69 @@ +// Copyright 2015 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. + +#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_DATA_PROVIDER_H_ +#define EXTENSIONS_BROWSER_UPDATER_UPDATE_DATA_PROVIDER_H_ + +#include <string> +#include <vector> + +#include "base/callback.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" + +namespace base { +class FilePath; +} + +namespace content { +class BrowserContext; +} + +namespace update_client { +struct CrxComponent; +} + +namespace extensions { + +// This class exists to let an UpdateClient retrieve information about a set of +// extensions it is doing an update check for. +class UpdateDataProvider : public base::RefCounted<UpdateDataProvider> { + public: + typedef base::Callback<void(content::BrowserContext* context, + const std::string& /* extension_id */, + const base::FilePath& /* temp_dir */)> + InstallCallback; + + // We need a browser context to use when retrieving data for a set of + // extension ids, as well as a callback for proceeding with installation + // steps once the UpdateClient has downloaded and unpacked an update for an + // extension. + UpdateDataProvider(content::BrowserContext* context, + const InstallCallback& callback); + + // Notify this object that the associated browser context is being shut down + // the pointer to the context should be dropped and no more work should be + // done. + void Shutdown(); + + // Matches update_client::UpdateClient::CrxDataCallback + void GetData(const std::vector<std::string>& ids, + std::vector<update_client::CrxComponent>* data); + + private: + friend class base::RefCounted<UpdateDataProvider>; + ~UpdateDataProvider(); + + void RunInstallCallback(const std::string& extension_id, + const base::FilePath& temp_dir); + + content::BrowserContext* context_; + InstallCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(UpdateDataProvider); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_DATA_PROVIDER_H_ diff --git a/extensions/browser/updater/update_install_shim.cc b/extensions/browser/updater/update_install_shim.cc new file mode 100644 index 0000000..4b6f582 --- /dev/null +++ b/extensions/browser/updater/update_install_shim.cc @@ -0,0 +1,83 @@ +// Copyright 2015 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 "extensions/browser/updater/update_install_shim.h" + +#include "base/files/file_enumerator.h" +#include "base/files/file_path.h" +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/logging.h" +#include "base/strings/string_number_conversions.h" +#include "base/values.h" +#include "content/public/browser/browser_thread.h" + +namespace extensions { + +UpdateInstallShim::UpdateInstallShim(std::string extension_id, + const base::FilePath& extension_root, + const UpdateInstallShimCallback& callback) + : extension_id_(extension_id), + extension_root_(extension_root), + callback_(callback) {} + +void UpdateInstallShim::OnUpdateError(int error) { + VLOG(1) << "OnUpdateError (" << extension_id_ << ") " << error; +} + +bool UpdateInstallShim::Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) { + base::ScopedTempDir temp_dir; + if (!temp_dir.CreateUniqueTempDir()) + return false; + + // The UpdateClient code will delete unpack_path if it still exists after + // this method is done, so we rename it on top of our temp dir. + if (!base::DeleteFile(temp_dir.path(), true) || + !base::Move(unpack_path, temp_dir.path())) { + LOG(ERROR) << "Trying to install update for " << extension_id_ + << "and failed to move " << unpack_path.value() << " to " + << temp_dir.path().value(); + return false; + } + content::BrowserThread::PostTask( + content::BrowserThread::UI, FROM_HERE, + base::Bind(&UpdateInstallShim::RunCallbackOnUIThread, this, + temp_dir.Take())); + return true; +} + +bool UpdateInstallShim::GetInstalledFile(const std::string& file, + base::FilePath* installed_file) { + base::FilePath relative_path = base::FilePath::FromUTF8Unsafe(file); + if (relative_path.IsAbsolute() || relative_path.ReferencesParent()) + return false; + *installed_file = extension_root_.Append(relative_path); + if (!extension_root_.IsParent(*installed_file) || + !base::PathExists(*installed_file)) { + VLOG(1) << "GetInstalledFile failed to find " << installed_file->value(); + installed_file->clear(); + return false; + } + return true; +} + +bool UpdateInstallShim::Uninstall() { + NOTREACHED(); + return false; +} + +UpdateInstallShim::~UpdateInstallShim() {} + +void UpdateInstallShim::RunCallbackOnUIThread(const base::FilePath& temp_dir) { + if (callback_.is_null()) { + content::BrowserThread::PostBlockingPoolTask( + FROM_HERE, base::Bind(base::IgnoreResult(&base::DeleteFile), temp_dir, + true /*recursive */)); + return; + } + callback_.Run(extension_id_, temp_dir); +} + +} // namespace extensions diff --git a/extensions/browser/updater/update_install_shim.h b/extensions/browser/updater/update_install_shim.h new file mode 100644 index 0000000..586e94e --- /dev/null +++ b/extensions/browser/updater/update_install_shim.h @@ -0,0 +1,76 @@ +// Copyright 2015 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. + +#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_INSTALL_SHIM_H_ +#define EXTENSIONS_BROWSER_UPDATER_UPDATE_INSTALL_SHIM_H_ + +#include <string> + +#include "base/callback.h" +#include "base/files/file_path.h" +#include "components/update_client/update_client.h" + +namespace base { +class DictionaryValue; +} + +namespace extensions { + +// A callback to implement the install of a new version of the extension. +// Takes ownership of the directory at |temp_dir|. +using UpdateInstallShimCallback = + base::Callback<void(const std::string& extension_id, + const base::FilePath& temp_dir)>; + +// This class is used as a shim between the components::update_client and +// extensions code, to help the generic update_client code prepare and then +// install an updated version of an extension. Because the update_client code +// doesn't have the notion of extension ids, we use instances of this class to +// map an install request back to the original update check for a given +// extension. +class UpdateInstallShim : public update_client::CrxInstaller { + public: + // This method takes the id and root directory for an extension we're doing + // an update check for, as well as a callback to call if we get a new version + // of it to install. + UpdateInstallShim(std::string extension_id, + const base::FilePath& extension_root, + const UpdateInstallShimCallback& callback); + + // Called when an update attempt failed. + void OnUpdateError(int error) override; + + // This is called when a new version of an extension is unpacked at + // |unpack_path| and is ready for install. + bool Install(const base::DictionaryValue& manifest, + const base::FilePath& unpack_path) override; + + // This is called by the generic differential update code in the + // update_client to provide the path to an existing file in the current + // version of the extension, so that it can be copied (or serve as the input + // to diff-patching) with output going to the directory with the new version + // being staged on disk for install. + bool GetInstalledFile(const std::string& file, + base::FilePath* installed_file) override; + + // This method is not relevant to extension updating. + bool Uninstall() override; + + private: + friend class base::RefCountedThreadSafe<UpdateInstallShim>; + ~UpdateInstallShim() override; + + // Takes ownership of the directory at path |temp_dir|. + void RunCallbackOnUIThread(const base::FilePath& temp_dir); + + std::string extension_id_; + base::FilePath extension_root_; + UpdateInstallShimCallback callback_; + + DISALLOW_COPY_AND_ASSIGN(UpdateInstallShim); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_INSTALL_SHIM_H_ diff --git a/extensions/browser/updater/update_service.cc b/extensions/browser/updater/update_service.cc index 0cb055c..ad40abd 100644 --- a/extensions/browser/updater/update_service.cc +++ b/extensions/browser/updater/update_service.cc @@ -4,79 +4,61 @@ #include "extensions/browser/updater/update_service.h" -#include <set> - -#include "base/message_loop/message_loop.h" -#include "components/update_client/update_query_params.h" +#include "base/bind.h" +#include "base/files/file_util.h" +#include "components/update_client/update_client.h" #include "content/public/browser/browser_context.h" -#include "extensions/browser/updater/extension_downloader.h" +#include "extensions/browser/extension_system.h" +#include "extensions/browser/extensions_browser_client.h" +#include "extensions/browser/updater/update_data_provider.h" #include "extensions/browser/updater/update_service_factory.h" -#include "extensions/common/extension_urls.h" -using update_client::UpdateQueryParams; +namespace { -namespace extensions { +void UpdateCheckCompleteCallback(int error) {} -// static -UpdateService* UpdateService::Get(content::BrowserContext* context) { - return UpdateServiceFactory::GetForBrowserContext(context); +void InstallUpdateCallback(content::BrowserContext* context, + const std::string& extension_id, + const base::FilePath& temp_dir) { + extensions::ExtensionSystem::Get(context) + ->InstallUpdate(extension_id, temp_dir); } -void UpdateService::DownloadAndInstall( - const std::string& id, - const base::Callback<void(bool)>& callback) { - DCHECK(download_callback_.is_null()); - download_callback_ = callback; - downloader_->AddPendingExtension(id, extension_urls::GetWebstoreUpdateUrl(), - 0); - downloader_->StartAllPending(nullptr); -} +} // namespace -UpdateService::UpdateService(content::BrowserContext* context) - : browser_context_(context), - downloader_(new ExtensionDownloader(this, context->GetRequestContext())) { - downloader_->set_manifest_query_params( - UpdateQueryParams::Get(UpdateQueryParams::CRX)); -} +namespace extensions { -UpdateService::~UpdateService() { +// static +UpdateService* UpdateService::Get(content::BrowserContext* context) { + return UpdateServiceFactory::GetForBrowserContext(context); } -void UpdateService::OnExtensionDownloadFailed( - const std::string& id, - Error error, - const PingResult& ping, - const std::set<int>& request_ids) { - auto callback = download_callback_; - download_callback_.Reset(); - callback.Run(false); +void UpdateService::Shutdown() { + if (update_data_provider_) { + update_data_provider_->Shutdown(); + update_data_provider_ = nullptr; + } + update_client_ = nullptr; + context_ = nullptr; } -void UpdateService::OnExtensionDownloadFinished( - const CRXFileInfo& file, - bool file_ownership_passed, - const GURL& download_url, - const std::string& version, - const PingResult& ping, - const std::set<int>& request_id, - const InstallCallback& install_callback) { - // TODO(rockot): Actually unpack and install the CRX. - auto callback = download_callback_; - download_callback_.Reset(); - callback.Run(true); - if (!install_callback.is_null()) - install_callback.Run(true); +void UpdateService::StartUpdateCheck(std::vector<std::string> extension_ids) { + if (!update_client_) + return; + update_client_->Update(extension_ids, base::Bind(&UpdateDataProvider::GetData, + update_data_provider_), + base::Bind(&UpdateCheckCompleteCallback)); } -bool UpdateService::IsExtensionPending(const std::string& id) { - // TODO(rockot): Implement this. For now all IDs are "pending". - return true; +UpdateService::UpdateService( + content::BrowserContext* context, + scoped_refptr<update_client::UpdateClient> update_client) + : context_(context), update_client_(update_client) { + CHECK(update_client_); + update_data_provider_ = + new UpdateDataProvider(context_, base::Bind(&InstallUpdateCallback)); } -bool UpdateService::GetExtensionExistingVersion(const std::string& id, - std::string* version) { - // TODO(rockot): Implement this. - return false; -} +UpdateService::~UpdateService() {} } // namespace extensions diff --git a/extensions/browser/updater/update_service.h b/extensions/browser/updater/update_service.h index 4d19204..0ee321c 100644 --- a/extensions/browser/updater/update_service.h +++ b/extensions/browser/updater/update_service.h @@ -6,62 +6,53 @@ #define EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_ #include <string> +#include <vector> #include "base/callback.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "components/keyed_service/core/keyed_service.h" -#include "extensions/browser/updater/extension_downloader_delegate.h" namespace content { class BrowserContext; } +namespace update_client { +class UpdateClient; +} + namespace extensions { -class ExtensionDownloader; +class UpdateDataProvider; +class UpdateService; class UpdateServiceFactory; -class UpdateServiceTest; -// This service manages the download, update, and installation of extensions. -// It is currently only used by app_shell, but should eventually replace -// ExtensionUpdater in Chrome. +// This service manages the autoupdate of extensions. It should eventually +// replace ExtensionUpdater in Chrome. // TODO(rockot): Replace ExtensionUpdater with this service. -class UpdateService : public KeyedService, public ExtensionDownloaderDelegate { +class UpdateService : public KeyedService { public: static UpdateService* Get(content::BrowserContext* context); - // TODO(rockot): Remove this. It's a placeholder for a real service interface. - // Downloads and (TODO) installs a CRX within the current browser context. - void DownloadAndInstall(const std::string& id, - const base::Callback<void(bool)>& callback); + void Shutdown() override; + + // Starts an update check for each of |extension_ids|. If there are any + // updates available, they will be downloaded, checked for integrity, + // unpacked, and then passed off to the ExtensionSystem::InstallUpdate method + // for install completion. + void StartUpdateCheck(std::vector<std::string> extension_ids); private: friend class UpdateServiceFactory; - friend class UpdateServiceTest; - explicit UpdateService(content::BrowserContext* context); + UpdateService(content::BrowserContext* context, + scoped_refptr<update_client::UpdateClient> update_client); ~UpdateService() override; - // ExtensionDownloaderDelegate: - void OnExtensionDownloadFailed(const std::string& id, - Error error, - const PingResult& ping, - const std::set<int>& request_ids) override; - void OnExtensionDownloadFinished(const CRXFileInfo& file, - bool file_ownership_passed, - const GURL& download_url, - const std::string& version, - const PingResult& ping, - const std::set<int>& request_id, - const InstallCallback& callback) override; - bool IsExtensionPending(const std::string& id) override; - bool GetExtensionExistingVersion(const std::string& id, - std::string* version) override; - - content::BrowserContext* browser_context_; - scoped_ptr<ExtensionDownloader> downloader_; - base::Callback<void(bool)> download_callback_; + content::BrowserContext* context_; + + scoped_refptr<update_client::UpdateClient> update_client_; + scoped_refptr<UpdateDataProvider> update_data_provider_; DISALLOW_COPY_AND_ASSIGN(UpdateService); }; diff --git a/extensions/browser/updater/update_service_browsertest.cc b/extensions/browser/updater/update_service_browsertest.cc deleted file mode 100644 index 097de35..0000000 --- a/extensions/browser/updater/update_service_browsertest.cc +++ /dev/null @@ -1,218 +0,0 @@ -// Copyright 2014 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 <map> -#include <string> -#include <utility> - -#include "base/bind.h" -#include "base/files/scoped_temp_dir.h" -#include "base/run_loop.h" -#include "base/strings/stringprintf.h" -#include "content/public/test/test_browser_thread_bundle.h" -#include "content/public/test/test_utils.h" -#include "extensions/browser/api/extensions_api_client.h" -#include "extensions/browser/extensions_browser_client.h" -#include "extensions/browser/updater/update_service.h" -#include "extensions/common/extension_urls.h" -#include "extensions/shell/test/shell_test.h" -#include "net/base/escape.h" -#include "net/http/http_status_code.h" -#include "net/url_request/test_url_fetcher_factory.h" - -namespace extensions { - -namespace { - -using FakeResponse = std::pair<std::string, net::HttpStatusCode>; - -// TODO(rockot): In general there's enough mock-Omaha-noise that this might be -// better placed into some test library code in //components/update_client. -FakeResponse CreateFakeUpdateResponse(const std::string& id, - size_t crx_length) { - std::string response_text = base::StringPrintf( - "<gupdate xmlns=\"http://www.google.com/update2/response\" " - " protocol=\"2.0\" server=\"prod\">\n" - " <daystart elapsed_days=\"2860\" elapsed_seconds=\"42042\"/>\n" - " <app appid=\"%s\" status=\"ok\">\n" - " <updatecheck codebase=\"%s\" fp=\"0\" hash=\"\" hash_sha256=\"\" " - " size=\"%d\" status=\"ok\" version=\"1\"/>\n" - " </app>\n" - "</gupdate>\n", - id.c_str(), - base::StringPrintf("https://fake-omaha-hostname/%s.crx", - id.c_str()).c_str(), - static_cast<int>(crx_length)); - return std::make_pair(response_text, net::HTTP_OK); -} - -FakeResponse CreateFakeUpdateNotFoundResponse() { - return std::make_pair( - std::string( - "<gupdate xmlns=\"http://www.google.com/update2/response\" " - " protocol=\"2.0\" server=\"prod\">\n" - " <daystart elapsed_days=\"4242\" elapsed_seconds=\"42042\"/>\n" - " <app appid=\"\" status=\"error-invalidAppId\">\n" - "</gupdate>"), - net::HTTP_OK); -} - -bool ExtractKeyValueFromComponent(const std::string& component_str, - const std::string& target_key, - std::string* extracted_value) { - url::Component component(0, static_cast<int>(component_str.length())); - url::Component key, value; - while (url::ExtractQueryKeyValue(component_str.c_str(), &component, &key, - &value)) { - if (target_key == component_str.substr(key.begin, key.len)) { - *extracted_value = component_str.substr(value.begin, value.len); - return true; - } - } - return false; -} - -// This extracts an extension ID from an Omaha update query. Queries have the -// form https://foo/bar/update?x=id%3Dabcdefghijklmnopqrstuvwxyzaaaaaa%26... -// This function extracts the 'x' query parameter (e.g. "id%3Dabcdef...."), -// unescapes its value (to become e.g., "id=abcdef...", and then extracts the -// 'id' value from the result (e.g. "abcdef..."). -bool ExtractIdFromUpdateQuery(const std::string& query_str, std::string* id) { - std::string data_string; - if (!ExtractKeyValueFromComponent(query_str, "x", &data_string)) - return false; - data_string = net::UnescapeURLComponent(data_string, - net::UnescapeRule::URL_SPECIAL_CHARS); - if (!ExtractKeyValueFromComponent(data_string, "id", id)) - return false; - EXPECT_EQ(32u, id->size()); - return true; -} - -void ExpectDownloadSuccess(const base::Closure& continuation, bool success) { - EXPECT_TRUE(success) << "Download failed."; - continuation.Run(); -} - -class FakeUpdateURLFetcherFactory : public net::URLFetcherFactory { - public: - FakeUpdateURLFetcherFactory() { EXPECT_TRUE(dir_.CreateUniqueTempDir()); } - - ~FakeUpdateURLFetcherFactory() override {} - - void RegisterFakeExtension(const std::string& id, - const std::string& contents) { - CHECK_EQ(32u, id.size()); - fake_extensions_.insert(std::make_pair(id, contents)); - } - - // net::URLFetcherFactory: - scoped_ptr<net::URLFetcher> CreateURLFetcher( - int id, - const GURL& url, - net::URLFetcher::RequestType request_type, - net::URLFetcherDelegate* delegate) override { - if (url.spec().find(extension_urls::GetWebstoreUpdateUrl().spec()) == 0) { - // Handle fake Omaha requests. - return CreateUpdateManifestFetcher(url, delegate); - } else if (url.spec().find("https://fake-omaha-hostname") == 0) { - // Handle a fake CRX request. - return CreateCrxFetcher(url, delegate); - } - NOTREACHED(); - return nullptr; - } - - private: - scoped_ptr<net::URLFetcher> CreateUpdateManifestFetcher( - const GURL& url, - net::URLFetcherDelegate* delegate) { - // If we have a fake CRX for the ID, return a fake update blob for it. - // Otherwise return an invalid-ID response. - FakeResponse response; - std::string extension_id; - if (!ExtractIdFromUpdateQuery(url.query(), &extension_id)) { - response = CreateFakeUpdateNotFoundResponse(); - } else { - const auto& iter = fake_extensions_.find(extension_id); - if (iter == fake_extensions_.end()) - response = CreateFakeUpdateNotFoundResponse(); - else - response = CreateFakeUpdateResponse(extension_id, iter->second.size()); - } - return scoped_ptr<net::URLFetcher>( - new net::FakeURLFetcher(url, delegate, response.first, response.second, - net::URLRequestStatus::SUCCESS)); - } - - scoped_ptr<net::URLFetcher> CreateCrxFetcher( - const GURL& url, - net::URLFetcherDelegate* delegate) { - FakeResponse response; - std::string extension_id = url.path().substr(1, 32); - const auto& iter = fake_extensions_.find(extension_id); - if (iter == fake_extensions_.end()) - response = std::make_pair(std::string(), net::HTTP_NOT_FOUND); - else - response = std::make_pair(iter->second, net::HTTP_OK); - net::TestURLFetcher* fetcher = - new net::FakeURLFetcher(url, delegate, response.first, response.second, - net::URLRequestStatus::SUCCESS); - base::FilePath path = dir_.path().Append( - base::FilePath::FromUTF8Unsafe(url.path().substr(1))); - fetcher->SetResponseFilePath(path); - return scoped_ptr<net::URLFetcher>(fetcher); - } - - base::ScopedTempDir dir_; - - std::map<std::string, std::string> fake_extensions_; - - DISALLOW_COPY_AND_ASSIGN(FakeUpdateURLFetcherFactory); -}; - -} // namespace - -class UpdateServiceTest : public AppShellTest { - public: - UpdateServiceTest() {} - ~UpdateServiceTest() override {} - - void SetUpOnMainThread() override { - AppShellTest::SetUpOnMainThread(); - - update_service_ = UpdateService::Get(browser_context()); - - default_url_fetcher_factory_.reset(new FakeUpdateURLFetcherFactory()); - url_fetcher_factory_.reset( - new net::FakeURLFetcherFactory(default_url_fetcher_factory_.get())); - } - - protected: - void RegisterFakeExtension(const std::string& id, - const std::string& crx_contents) { - default_url_fetcher_factory_->RegisterFakeExtension(id, crx_contents); - } - - UpdateService* update_service() const { return update_service_; } - - private: - scoped_ptr<FakeUpdateURLFetcherFactory> default_url_fetcher_factory_; - scoped_ptr<net::FakeURLFetcherFactory> url_fetcher_factory_; - - UpdateService* update_service_; -}; - -IN_PROC_BROWSER_TEST_F(UpdateServiceTest, DownloadAndInstall) { - const char kCrxId[] = "abcdefghijklmnopqrstuvwxyzaaaaaa"; - const char kCrxContents[] = "Hello, world!"; - RegisterFakeExtension(kCrxId, kCrxContents); - - base::RunLoop run_loop; - update_service()->DownloadAndInstall( - kCrxId, base::Bind(ExpectDownloadSuccess, run_loop.QuitClosure())); - run_loop.Run(); -} - -} // namespace extensions diff --git a/extensions/browser/updater/update_service_factory.cc b/extensions/browser/updater/update_service_factory.cc index a87c88b..e441dc3 100644 --- a/extensions/browser/updater/update_service_factory.cc +++ b/extensions/browser/updater/update_service_factory.cc @@ -5,7 +5,9 @@ #include "extensions/browser/updater/update_service_factory.h" #include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "components/update_client/update_client.h" #include "extensions/browser/extension_registry_factory.h" +#include "extensions/browser/extensions_browser_client.h" #include "extensions/browser/process_manager_factory.h" #include "extensions/browser/updater/update_service.h" @@ -27,8 +29,6 @@ UpdateServiceFactory::UpdateServiceFactory() : BrowserContextKeyedServiceFactory( "UpdateService", BrowserContextDependencyManager::GetInstance()) { - DependsOn(extensions::ExtensionRegistryFactory::GetInstance()); - DependsOn(extensions::ProcessManagerFactory::GetInstance()); } UpdateServiceFactory::~UpdateServiceFactory() { @@ -36,7 +36,8 @@ UpdateServiceFactory::~UpdateServiceFactory() { KeyedService* UpdateServiceFactory::BuildServiceInstanceFor( content::BrowserContext* context) const { - return new UpdateService(context); + return new UpdateService( + context, ExtensionsBrowserClient::Get()->CreateUpdateClient(context)); } } // namespace extensions diff --git a/extensions/browser/updater/update_service_unittest.cc b/extensions/browser/updater/update_service_unittest.cc new file mode 100644 index 0000000..f570303 --- /dev/null +++ b/extensions/browser/updater/update_service_unittest.cc @@ -0,0 +1,248 @@ +// Copyright 2014 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 <vector> + +#include "base/files/file_util.h" +#include "base/files/scoped_temp_dir.h" +#include "base/run_loop.h" +#include "base/values.h" +#include "components/crx_file/id_util.h" +#include "components/update_client/update_client.h" +#include "content/public/test/test_browser_thread_bundle.h" +#include "content/public/test/test_utils.h" +#include "extensions/browser/extension_registry.h" +#include "extensions/browser/extensions_test.h" +#include "extensions/browser/mock_extension_system.h" +#include "extensions/browser/test_extensions_browser_client.h" +#include "extensions/browser/updater/update_service.h" +#include "extensions/common/extension_builder.h" +#include "extensions/common/value_builder.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace { + +class FakeUpdateClient : public update_client::UpdateClient { + public: + FakeUpdateClient(); + + // Returns the data we've gotten from the CrxDataCallback for ids passed to + // the Update function. + std::vector<update_client::CrxComponent>* data() { return &data_; } + + // update_client::UpdateClient + void AddObserver(Observer* observer) override {} + void RemoveObserver(Observer* observer) override {} + void Install(const std::string& id, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) override {} + void Update(const std::vector<std::string>& ids, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) override; + bool GetCrxUpdateState( + const std::string& id, + update_client::CrxUpdateItem* update_item) const override { + return false; + } + bool IsUpdating(const std::string& id) const override { return false; } + + protected: + friend class base::RefCounted<FakeUpdateClient>; + ~FakeUpdateClient() override {} + + std::vector<update_client::CrxComponent> data_; +}; + +FakeUpdateClient::FakeUpdateClient() {} + +void FakeUpdateClient::Update(const std::vector<std::string>& ids, + const CrxDataCallback& crx_data_callback, + const CompletionCallback& completion_callback) { + crx_data_callback.Run(ids, &data_); +} + +} // namespace + +namespace extensions { + +namespace { + +// A fake ExtensionSystem that lets us intercept calls to install new +// versions of an extension. +class FakeExtensionSystem : public MockExtensionSystem { + public: + explicit FakeExtensionSystem(content::BrowserContext* context) + : MockExtensionSystem(context) {} + ~FakeExtensionSystem() override {} + + struct InstallUpdateRequest { + std::string extension_id; + base::FilePath temp_dir; + }; + + std::vector<InstallUpdateRequest>* install_requests() { + return &install_requests_; + } + + void set_install_callback(const base::Closure& callback) { + next_install_callback_ = callback; + } + + // ExtensionSystem override + void InstallUpdate(const std::string& extension_id, + const base::FilePath& temp_dir) override { + base::DeleteFile(temp_dir, true /*recursive*/); + InstallUpdateRequest request; + request.extension_id = extension_id; + request.temp_dir = temp_dir; + install_requests_.push_back(request); + if (!next_install_callback_.is_null()) { + base::Closure tmp = next_install_callback_; + next_install_callback_.Reset(); + tmp.Run(); + } + } + + private: + std::vector<InstallUpdateRequest> install_requests_; + base::Closure next_install_callback_; +}; + +class UpdateServiceTest : public ExtensionsTest { + public: + UpdateServiceTest() { + extensions_browser_client()->set_extension_system_factory( + &fake_extension_system_factory_); + } + ~UpdateServiceTest() override {} + + void SetUp() override { + ExtensionsTest::SetUp(); + browser_threads_.reset(new content::TestBrowserThreadBundle( + content::TestBrowserThreadBundle::DEFAULT)); + + extensions_browser_client()->SetUpdateClientFactory(base::Bind( + &UpdateServiceTest::CreateUpdateClient, base::Unretained(this))); + + update_service_ = UpdateService::Get(browser_context()); + } + + protected: + UpdateService* update_service() const { return update_service_; } + FakeUpdateClient* update_client() const { return update_client_.get(); } + + update_client::UpdateClient* CreateUpdateClient() { + // We only expect that this will get called once, so consider it an error + // if our update_client_ is already non-null. + EXPECT_EQ(nullptr, update_client_.get()); + update_client_ = new FakeUpdateClient(); + return update_client_.get(); + } + + // Helper function that creates a file at |relative_path| within |directory| + // and fills it with |content|. + bool AddFileToDirectory(const base::FilePath& directory, + const base::FilePath& relative_path, + const std::string& content) { + base::FilePath full_path = directory.Append(relative_path); + if (!CreateDirectory(full_path.DirName())) + return false; + int result = base::WriteFile(full_path, content.data(), content.size()); + return (static_cast<size_t>(result) == content.size()); + } + + FakeExtensionSystem* extension_system() { + return static_cast<FakeExtensionSystem*>( + fake_extension_system_factory_.GetForBrowserContext(browser_context())); + } + + private: + UpdateService* update_service_; + scoped_refptr<FakeUpdateClient> update_client_; + scoped_ptr<content::TestBrowserThreadBundle> browser_threads_; + MockExtensionSystemFactory<FakeExtensionSystem> + fake_extension_system_factory_; +}; + +TEST_F(UpdateServiceTest, BasicUpdateOperations) { + // Create a temporary directory that a fake extension will live in and fill + // it with some test files. + base::ScopedTempDir temp_dir; + ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); + base::FilePath foo_js(FILE_PATH_LITERAL("foo.js")); + base::FilePath bar_html(FILE_PATH_LITERAL("bar/bar.html")); + ASSERT_TRUE(AddFileToDirectory(temp_dir.path(), foo_js, "hello")) + << "Failed to write " << temp_dir.path().value() << "/" << foo_js.value(); + ASSERT_TRUE(AddFileToDirectory(temp_dir.path(), bar_html, "world")); + + ExtensionBuilder builder; + builder.SetManifest(DictionaryBuilder() + .Set("name", "Foo") + .Set("version", "1.0") + .Set("manifest_version", 2)); + builder.SetID(crx_file::id_util::GenerateId("whatever")); + builder.SetPath(temp_dir.path()); + + scoped_refptr<Extension> extension1(builder.Build()); + + ExtensionRegistry::Get(browser_context())->AddEnabled(extension1); + std::vector<std::string> ids; + ids.push_back(extension1->id()); + + // Start an update check and verify that the UpdateClient was sent the right + // data. + update_service()->StartUpdateCheck(ids); + std::vector<update_client::CrxComponent>* data = update_client()->data(); + ASSERT_NE(nullptr, data); + ASSERT_EQ(1u, data->size()); + + ASSERT_TRUE(data->at(0).version.Equals(*extension1->version())); + update_client::CrxInstaller* installer = data->at(0).installer.get(); + ASSERT_NE(installer, nullptr); + + // The GetInstalledFile method is used when processing differential updates + // to get a path to an existing file in an extension. We want to test a + // number of scenarios to be user we handle invalid relative paths, don't + // accidentally return paths outside the extension's dir, etc. + base::FilePath tmp; + EXPECT_TRUE(installer->GetInstalledFile(foo_js.MaybeAsASCII(), &tmp)); + EXPECT_EQ(temp_dir.path().Append(foo_js), tmp) << tmp.value(); + + EXPECT_TRUE(installer->GetInstalledFile(bar_html.MaybeAsASCII(), &tmp)); + EXPECT_EQ(temp_dir.path().Append(bar_html), tmp) << tmp.value(); + + EXPECT_FALSE(installer->GetInstalledFile("does_not_exist", &tmp)); + EXPECT_FALSE(installer->GetInstalledFile("does/not/exist", &tmp)); + EXPECT_FALSE(installer->GetInstalledFile("/does/not/exist", &tmp)); + EXPECT_FALSE(installer->GetInstalledFile("C:\\tmp", &tmp)); + + base::FilePath system_temp_dir; + ASSERT_TRUE(base::GetTempDir(&system_temp_dir)); + EXPECT_FALSE( + installer->GetInstalledFile(system_temp_dir.MaybeAsASCII(), &tmp)); + + // Test the install callback. + base::ScopedTempDir new_version_dir; + ASSERT_TRUE(new_version_dir.CreateUniqueTempDir()); + scoped_ptr<base::DictionaryValue> new_manifest( + extension1->manifest()->value()->DeepCopy()); + new_manifest->SetString("version", "2.0"); + + installer->Install(*new_manifest, new_version_dir.path()); + + scoped_refptr<content::MessageLoopRunner> loop_runner = + new content::MessageLoopRunner(); + extension_system()->set_install_callback(loop_runner->QuitClosure()); + loop_runner->Run(); + + std::vector<FakeExtensionSystem::InstallUpdateRequest>* requests = + extension_system()->install_requests(); + ASSERT_EQ(1u, requests->size()); + EXPECT_EQ(requests->at(0).extension_id, extension1->id()); + EXPECT_NE(requests->at(0).temp_dir.value(), new_version_dir.path().value()); +} + +} // namespace + +} // namespace extensions |