diff options
author | rockot <rockot@chromium.org> | 2014-11-06 10:50:01 -0800 |
---|---|---|
committer | Commit bot <commit-bot@chromium.org> | 2014-11-06 18:50:26 +0000 |
commit | 3813023bde420950c60d9d12a8610510b76da337 (patch) | |
tree | c4c1a71be980404150fce8f9848ae4ac831fb0c8 /extensions/browser/updater | |
parent | ba4eca9bf6691e4ed55eee55a0bcac1a932b7688 (diff) | |
download | chromium_src-3813023bde420950c60d9d12a8610510b76da337.zip chromium_src-3813023bde420950c60d9d12a8610510b76da337.tar.gz chromium_src-3813023bde420950c60d9d12a8610510b76da337.tar.bz2 |
Add rudimentary UpdateService to app_shell
It can download a CRX from the web store using an ID given at the
command line.
This wires up utility process support for app_shell and establishes
a base for factoring additional extensions utility IPCs out of
//chrome in the near future.
BUG=398671
TBR=asargent@chromium.org for cloned dependency on omaha_query_params
Review URL: https://codereview.chromium.org/693233003
Cr-Commit-Position: refs/heads/master@{#303055}
Diffstat (limited to 'extensions/browser/updater')
-rw-r--r-- | extensions/browser/updater/update_service.cc | 78 | ||||
-rw-r--r-- | extensions/browser/updater/update_service.h | 71 | ||||
-rw-r--r-- | extensions/browser/updater/update_service_browsertest.cc | 208 | ||||
-rw-r--r-- | extensions/browser/updater/update_service_factory.cc | 42 | ||||
-rw-r--r-- | extensions/browser/updater/update_service_factory.h | 37 |
5 files changed, 436 insertions, 0 deletions
diff --git a/extensions/browser/updater/update_service.cc b/extensions/browser/updater/update_service.cc new file mode 100644 index 0000000..286d137 --- /dev/null +++ b/extensions/browser/updater/update_service.cc @@ -0,0 +1,78 @@ +// 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 "extensions/browser/updater/update_service.h" + +#include "base/message_loop/message_loop.h" +#include "components/omaha_query_params/omaha_query_params.h" +#include "content/public/browser/browser_context.h" +#include "extensions/browser/updater/extension_downloader.h" +#include "extensions/browser/updater/update_service_factory.h" +#include "extensions/common/extension_urls.h" + +using omaha_query_params::OmahaQueryParams; + +namespace extensions { + +// static +UpdateService* UpdateService::Get(content::BrowserContext* context) { + return UpdateServiceFactory::GetForBrowserContext(context); +} + +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); +} + +UpdateService::UpdateService(content::BrowserContext* context) + : browser_context_(context), + downloader_(new ExtensionDownloader(this, context->GetRequestContext())) { + downloader_->set_manifest_query_params( + OmahaQueryParams::Get(OmahaQueryParams::CRX)); +} + +UpdateService::~UpdateService() { +} + +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::OnExtensionDownloadFinished( + const std::string& id, + const base::FilePath& path, + bool file_ownership_passed, + const GURL& download_url, + const std::string& version, + const PingResult& ping, + const std::set<int>& request_id) { + // TODO(rockot): Actually unpack and install the CRX. + auto callback = download_callback_; + download_callback_.Reset(); + callback.Run(true); +} + +bool UpdateService::IsExtensionPending(const std::string& id) { + // TODO(rockot): Implement this. For now all IDs are "pending". + return true; +} + +bool UpdateService::GetExtensionExistingVersion(const std::string& id, + std::string* version) { + // TODO(rockot): Implement this. + return false; +} + +} // namespace extensions diff --git a/extensions/browser/updater/update_service.h b/extensions/browser/updater/update_service.h new file mode 100644 index 0000000..008917d --- /dev/null +++ b/extensions/browser/updater/update_service.h @@ -0,0 +1,71 @@ +// 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. + +#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_ +#define EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_ + +#include <string> + +#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 extensions { + +class ExtensionDownloader; +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. +// TODO(rockot): Replace ExtensionUpdater with this service. +class UpdateService : public KeyedService, public ExtensionDownloaderDelegate { + 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); + + private: + friend class UpdateServiceFactory; + friend class UpdateServiceTest; + + explicit UpdateService(content::BrowserContext* context); + ~UpdateService() override; + + // ExtensionDownloaderDelegate: + void OnExtensionDownloadFailed(const std::string& id, + Error error, + const PingResult& ping, + const std::set<int>& request_ids) override; + void OnExtensionDownloadFinished(const std::string& id, + const base::FilePath& path, + bool file_ownership_passed, + const GURL& download_url, + const std::string& version, + const PingResult& ping, + const std::set<int>& request_id) 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_; + + DISALLOW_COPY_AND_ASSIGN(UpdateService); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_H_ diff --git a/extensions/browser/updater/update_service_browsertest.cc b/extensions/browser/updater/update_service_browsertest.cc new file mode 100644 index 0000000..0ec702a --- /dev/null +++ b/extensions/browser/updater/update_service_browsertest.cc @@ -0,0 +1,208 @@ +// 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/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/omaha_client (which +// would be renamed from omaha_query_params). +FakeResponse CreateFakeOmahaResponse(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 CreateFakeOmahaNotFoundResponse() { + 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 ExtractIdFromOmahaQuery(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() override {} + + void RegisterFakeExtension(const std::string& id, + const std::string& contents) { + CHECK(id.size() == 32); + fake_extensions_.insert(std::make_pair(id, contents)); + } + + // net::URLFetcherFactory: + 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: + 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 (!ExtractIdFromOmahaQuery(url.query(), &extension_id)) { + response = CreateFakeOmahaNotFoundResponse(); + } else { + const auto& iter = fake_extensions_.find(extension_id); + if (iter == fake_extensions_.end()) + response = CreateFakeOmahaNotFoundResponse(); + else + response = CreateFakeOmahaResponse(extension_id, iter->second.size()); + } + return new net::FakeURLFetcher(url, delegate, response.first, + response.second, + net::URLRequestStatus::SUCCESS); + } + + 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); + fetcher->SetResponseFilePath(base::FilePath::FromUTF8Unsafe(url.path())); + return fetcher; + } + + std::map<std::string, std::string> fake_extensions_; +}; + +} // 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 new file mode 100644 index 0000000..a85cb06 --- /dev/null +++ b/extensions/browser/updater/update_service_factory.cc @@ -0,0 +1,42 @@ +// 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 "extensions/browser/updater/update_service_factory.h" + +#include "components/keyed_service/content/browser_context_dependency_manager.h" +#include "extensions/browser/extension_registry_factory.h" +#include "extensions/browser/process_manager_factory.h" +#include "extensions/browser/updater/update_service.h" + +namespace extensions { + +// static +UpdateService* UpdateServiceFactory::GetForBrowserContext( + content::BrowserContext* context) { + return static_cast<UpdateService*>( + GetInstance()->GetServiceForBrowserContext(context, true)); +} + +// static +UpdateServiceFactory* UpdateServiceFactory::GetInstance() { + return Singleton<UpdateServiceFactory>::get(); +} + +UpdateServiceFactory::UpdateServiceFactory() + : BrowserContextKeyedServiceFactory( + "UpdateService", + BrowserContextDependencyManager::GetInstance()) { + DependsOn(extensions::ExtensionRegistryFactory::GetInstance()); + DependsOn(extensions::ProcessManagerFactory::GetInstance()); +} + +UpdateServiceFactory::~UpdateServiceFactory() { +} + +KeyedService* UpdateServiceFactory::BuildServiceInstanceFor( + content::BrowserContext* context) const { + return new UpdateService(context); +} + +} // namespace extensions diff --git a/extensions/browser/updater/update_service_factory.h b/extensions/browser/updater/update_service_factory.h new file mode 100644 index 0000000..7b2726e --- /dev/null +++ b/extensions/browser/updater/update_service_factory.h @@ -0,0 +1,37 @@ +// 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. + +#ifndef EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_FACTORY_H_ +#define EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_FACTORY_H_ + +#include "base/memory/singleton.h" +#include "components/keyed_service/content/browser_context_keyed_service_factory.h" + +namespace extensions { + +class UpdateService; + +// Service factory to construct UpdateService instances per BrowserContext. +// Note that OTR browser contexts do not get an UpdateService. +class UpdateServiceFactory : public BrowserContextKeyedServiceFactory { + public: + static UpdateService* GetForBrowserContext(content::BrowserContext* context); + static UpdateServiceFactory* GetInstance(); + + private: + friend struct DefaultSingletonTraits<UpdateServiceFactory>; + + UpdateServiceFactory(); + ~UpdateServiceFactory() override; + + // BrowserContextKeyedServiceFactory: + KeyedService* BuildServiceInstanceFor( + content::BrowserContext* context) const override; + + DISALLOW_COPY_AND_ASSIGN(UpdateServiceFactory); +}; + +} // namespace extensions + +#endif // EXTENSIONS_BROWSER_UPDATER_UPDATE_SERVICE_FACTORY_H_ |