summaryrefslogtreecommitdiffstats
path: root/extensions/browser/updater
diff options
context:
space:
mode:
authorrockot <rockot@chromium.org>2014-11-06 10:50:01 -0800
committerCommit bot <commit-bot@chromium.org>2014-11-06 18:50:26 +0000
commit3813023bde420950c60d9d12a8610510b76da337 (patch)
treec4c1a71be980404150fce8f9848ae4ac831fb0c8 /extensions/browser/updater
parentba4eca9bf6691e4ed55eee55a0bcac1a932b7688 (diff)
downloadchromium_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.cc78
-rw-r--r--extensions/browser/updater/update_service.h71
-rw-r--r--extensions/browser/updater/update_service_browsertest.cc208
-rw-r--r--extensions/browser/updater/update_service_factory.cc42
-rw-r--r--extensions/browser/updater/update_service_factory.h37
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_