Import Cobalt 21.master.0.260628
diff --git a/src/components/update_client/BUILD.gn b/src/components/update_client/BUILD.gn
new file mode 100644
index 0000000..8ec4b59
--- /dev/null
+++ b/src/components/update_client/BUILD.gn
@@ -0,0 +1,241 @@
+# 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.
+
+import("//net/features.gni")
+
+source_set("network_impl") {
+ sources = [
+ "net/network_chromium.h",
+ "net/network_impl.cc",
+ "net/network_impl.h",
+ ]
+
+ deps = [
+ ":update_client",
+ "//base",
+ "//net",
+ "//services/network/public/cpp:cpp",
+ "//url",
+ ]
+}
+
+source_set("unzip_impl") {
+ sources = [
+ "unzip/unzip_impl.cc",
+ "unzip/unzip_impl.h",
+ ]
+ deps = [
+ ":update_client",
+ "//components/services/unzip/public/cpp",
+ ]
+}
+
+source_set("patch_impl") {
+ sources = [
+ "patch/patch_impl.cc",
+ "patch/patch_impl.h",
+ ]
+ deps = [
+ ":update_client",
+ "//components/services/patch/public/cpp",
+ "//components/services/patch/public/mojom",
+ "//mojo/public/cpp/bindings",
+ ]
+}
+
+group("common_impl") {
+ public_deps = [
+ ":network_impl",
+ ":patch_impl",
+ ":unzip_impl",
+ ]
+}
+
+static_library("update_client") {
+ sources = [
+ "action_runner.cc",
+ "action_runner.h",
+ "action_runner_win.cc",
+ "activity_data_service.h",
+ "background_downloader_win.cc",
+ "background_downloader_win.h",
+ "command_line_config_policy.cc",
+ "command_line_config_policy.h",
+ "component.cc",
+ "component.h",
+ "component_patcher.cc",
+ "component_patcher.h",
+ "component_patcher_operation.cc",
+ "component_patcher_operation.h",
+ "component_unpacker.cc",
+ "component_unpacker.h",
+ "configurator.h",
+ "crx_downloader.cc",
+ "crx_downloader.h",
+ "crx_update_item.h",
+ "network.cc",
+ "network.h",
+ "patcher.h",
+ "persisted_data.cc",
+ "persisted_data.h",
+ "ping_manager.cc",
+ "ping_manager.h",
+ "protocol_definition.cc",
+ "protocol_definition.h",
+ "protocol_handler.cc",
+ "protocol_handler.h",
+ "protocol_parser.cc",
+ "protocol_parser.h",
+ "protocol_parser_json.cc",
+ "protocol_parser_json.h",
+ "protocol_serializer.cc",
+ "protocol_serializer.h",
+ "protocol_serializer_json.cc",
+ "protocol_serializer_json.h",
+ "request_sender.cc",
+ "request_sender.h",
+ "task.h",
+ "task_send_uninstall_ping.cc",
+ "task_send_uninstall_ping.h",
+ "task_traits.h",
+ "task_update.cc",
+ "task_update.h",
+ "unzipper.h",
+ "update_checker.cc",
+ "update_checker.h",
+ "update_client.cc",
+ "update_client.h",
+ "update_client_errors.h",
+ "update_client_internal.h",
+ "update_engine.cc",
+ "update_engine.h",
+ "update_query_params.cc",
+ "update_query_params.h",
+ "update_query_params_delegate.cc",
+ "update_query_params_delegate.h",
+ "updater_state.cc",
+ "updater_state.h",
+ "updater_state_mac.mm",
+ "updater_state_win.cc",
+ "url_fetcher_downloader.cc",
+ "url_fetcher_downloader.h",
+ "utils.cc",
+ "utils.h",
+ ]
+
+ deps = [
+ "//base",
+ "//components/client_update_protocol",
+ "//components/crx_file",
+ "//components/prefs",
+ "//components/version_info:version_info",
+ "//courgette:courgette_lib",
+ "//crypto",
+ "//url",
+ ]
+}
+
+static_library("test_support") {
+ testonly = true
+ sources = [
+ "net/url_loader_post_interceptor.cc",
+ "net/url_loader_post_interceptor.h",
+ "test_configurator.cc",
+ "test_configurator.h",
+ "test_installer.cc",
+ "test_installer.h",
+ ]
+
+ public_deps = [
+ ":update_client",
+ ]
+
+ deps = [
+ ":network_impl",
+ ":patch_impl",
+ ":unzip_impl",
+ "//base",
+ "//components/prefs",
+ "//components/services/patch:in_process",
+ "//components/services/unzip:in_process",
+ "//mojo/public/cpp/bindings",
+ "//net:test_support",
+ "//services/network:test_support",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//url",
+ ]
+}
+
+bundle_data("unit_tests_bundle_data") {
+ visibility = [ ":unit_tests" ]
+ testonly = true
+ sources = [
+ "//components/test/data/update_client/binary_bsdiff_patch.bin",
+ "//components/test/data/update_client/binary_courgette_patch.bin",
+ "//components/test/data/update_client/binary_input.bin",
+ "//components/test/data/update_client/binary_output.bin",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx",
+ "//components/test/data/update_client/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx",
+ "//components/test/data/update_client/jebgalgnebhfojomionfpkfelancnnkf.crx",
+ "//components/test/data/update_client/runaction_test_win.crx3",
+ "//components/test/data/update_client/updatecheck_reply_1.json",
+ "//components/test/data/update_client/updatecheck_reply_4.json",
+ "//components/test/data/update_client/updatecheck_reply_noupdate.json",
+ "//components/test/data/update_client/updatecheck_reply_parse_error.json",
+ "//components/test/data/update_client/updatecheck_reply_unknownapp.json",
+ ]
+ outputs = [
+ "{{bundle_resources_dir}}/" +
+ "{{source_root_relative_dir}}/{{source_file_part}}",
+ ]
+}
+
+source_set("unit_tests") {
+ testonly = true
+ sources = [
+ "component_patcher_unittest.cc",
+ "component_patcher_unittest.h",
+ "component_unpacker_unittest.cc",
+ "persisted_data_unittest.cc",
+ "ping_manager_unittest.cc",
+ "protocol_parser_json_unittest.cc",
+ "protocol_serializer_json_unittest.cc",
+ "protocol_serializer_unittest.cc",
+ "request_sender_unittest.cc",
+ "update_checker_unittest.cc",
+ "update_client_unittest.cc",
+ "update_query_params_unittest.cc",
+ "updater_state_unittest.cc",
+ "utils_unittest.cc",
+ ]
+
+ if (!disable_file_support) {
+ sources += [ "crx_downloader_unittest.cc" ]
+ }
+
+ deps = [
+ ":network_impl",
+ ":patch_impl",
+ ":test_support",
+ ":unit_tests_bundle_data",
+ ":unzip_impl",
+ ":update_client",
+ "//base",
+ "//components/crx_file",
+ "//components/prefs",
+ "//components/prefs:test_support",
+ "//components/services/patch:in_process",
+ "//components/version_info:version_info",
+ "//courgette:courgette_lib",
+ "//net:test_support",
+ "//services/network:test_support",
+ "//services/network/public/cpp:cpp",
+ "//services/network/public/cpp:cpp_base",
+ "//testing/gmock",
+ "//testing/gtest",
+ "//third_party/re2",
+ ]
+}
diff --git a/src/components/update_client/action_runner.cc b/src/components/update_client/action_runner.cc
new file mode 100644
index 0000000..490aeeb
--- /dev/null
+++ b/src/components/update_client/action_runner.cc
@@ -0,0 +1,142 @@
+// Copyright 2017 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 "components/update_client/action_runner.h"
+
+#include <iterator>
+#include <stack>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/command_line.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_engine.h"
+
+namespace {
+
+#if defined(OS_STARBOARD)
+void CleanupDirectory(base::FilePath& dir) {
+ std::stack<std::string> directories;
+ base::FileEnumerator file_enumerator(
+ dir, true,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ for (auto path = file_enumerator.Next(); !path.value().empty();
+ path = file_enumerator.Next()) {
+ base::FileEnumerator::FileInfo info(file_enumerator.GetInfo());
+
+ if (info.IsDirectory()) {
+ directories.push(path.value());
+ } else {
+ SbFileDelete(path.value().c_str());
+ }
+ }
+ while (!directories.empty()) {
+ SbFileDelete(directories.top().c_str());
+ directories.pop();
+ }
+}
+#endif
+
+} // namespace
+
+namespace update_client {
+
+ActionRunner::ActionRunner(const Component& component)
+ : is_per_user_install_(component.config()->IsPerUserInstall()),
+ component_(component),
+ main_task_runner_(base::ThreadTaskRunnerHandle::Get()) {}
+
+ActionRunner::~ActionRunner() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void ActionRunner::Run(Callback run_complete) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ run_complete_ = std::move(run_complete);
+
+ base::CreateSequencedTaskRunnerWithTraits(kTaskTraits)
+ ->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ActionRunner::RunOnTaskRunner, base::Unretained(this),
+ component_.config()->GetUnzipperFactory()->Create(),
+ component_.config()->GetPatcherFactory()->Create()));
+}
+
+void ActionRunner::RunOnTaskRunner(std::unique_ptr<Unzipper> unzip,
+ scoped_refptr<Patcher> patch) {
+ const auto installer = component_.crx_component()->installer;
+
+ base::FilePath crx_path;
+ installer->GetInstalledFile(component_.action_run(), &crx_path);
+
+ if (!is_per_user_install_) {
+ RunRecoveryCRXElevated(std::move(crx_path));
+ return;
+ }
+
+ const auto config = component_.config();
+ auto unpacker = base::MakeRefCounted<ComponentUnpacker>(
+ config->GetRunActionKeyHash(), crx_path, installer, std::move(unzip),
+ std::move(patch), component_.crx_component()->crx_format_requirement);
+ unpacker->Unpack(
+ base::BindOnce(&ActionRunner::UnpackComplete, base::Unretained(this)));
+}
+
+void ActionRunner::UnpackComplete(const ComponentUnpacker::Result& result) {
+ if (result.error != UnpackerError::kNone) {
+ DCHECK(!base::DirectoryExists(result.unpack_path));
+
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(run_complete_), false,
+ static_cast<int>(result.error), result.extended_error));
+ return;
+ }
+
+ unpack_path_ = result.unpack_path;
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ActionRunner::RunCommand, base::Unretained(this),
+ MakeCommandLine(result.unpack_path)));
+}
+
+#if !defined(OS_WIN)
+
+void ActionRunner::RunRecoveryCRXElevated(const base::FilePath& crx_path) {
+ NOTREACHED();
+}
+
+void ActionRunner::RunCommand(const base::CommandLine& cmdline) {
+#if defined(OS_STARBOARD)
+ CleanupDirectory(unpack_path_);
+#else
+ base::DeleteFile(unpack_path_, true);
+#endif
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(run_complete_), false, -1, 0));
+}
+
+base::CommandLine ActionRunner::MakeCommandLine(
+ const base::FilePath& unpack_path) const {
+ return base::CommandLine(base::CommandLine::NO_PROGRAM);
+}
+
+#endif // OS_WIN
+
+} // namespace update_client
diff --git a/src/components/update_client/action_runner.h b/src/components/update_client/action_runner.h
new file mode 100644
index 0000000..8638fc2
--- /dev/null
+++ b/src/components/update_client/action_runner.h
@@ -0,0 +1,75 @@
+// Copyright 2017 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 COMPONENTS_UPDATE_CLIENT_ACTION_RUNNER_H_
+#define COMPONENTS_UPDATE_CLIENT_ACTION_RUNNER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "build/build_config.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class CommandLine;
+class Process;
+class SingleThreadTaskRunner;
+} // namespace base
+
+namespace update_client {
+
+class Component;
+
+class ActionRunner {
+ public:
+ using Callback =
+ base::OnceCallback<void(bool succeeded, int error_code, int extra_code1)>;
+
+ explicit ActionRunner(const Component& component);
+ ~ActionRunner();
+
+ void Run(Callback run_complete);
+
+ private:
+ void RunOnTaskRunner(std::unique_ptr<Unzipper> unzipper,
+ scoped_refptr<Patcher> patcher);
+ void UnpackComplete(const ComponentUnpacker::Result& result);
+
+ void RunCommand(const base::CommandLine& cmdline);
+ void RunRecoveryCRXElevated(const base::FilePath& crx_path);
+
+ base::CommandLine MakeCommandLine(const base::FilePath& unpack_path) const;
+
+ void WaitForCommand(base::Process process);
+
+#if defined(OS_WIN)
+ void RunRecoveryCRXElevatedInSTA(const base::FilePath& crx_path);
+#endif
+
+ bool is_per_user_install_ = false;
+ const Component& component_;
+
+ // Used to post callbacks to the main thread.
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ // Contains the unpack path for the component associated with the run action.
+ base::FilePath unpack_path_;
+
+ Callback run_complete_;
+
+ THREAD_CHECKER(thread_checker_);
+ DISALLOW_COPY_AND_ASSIGN(ActionRunner);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_ACTION_RUNNER_H_
diff --git a/src/components/update_client/action_runner_win.cc b/src/components/update_client/action_runner_win.cc
new file mode 100644
index 0000000..6985ee6
--- /dev/null
+++ b/src/components/update_client/action_runner_win.cc
@@ -0,0 +1,91 @@
+// Copyright 2017 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 "components/update_client/action_runner.h"
+
+#include <tuple>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/command_line.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/process/launch.h"
+#include "base/process/process.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/task_traits.h"
+
+namespace {
+
+const base::FilePath::CharType kRecoveryFileName[] =
+ FILE_PATH_LITERAL("ChromeRecovery.exe");
+
+} // namespace
+
+namespace update_client {
+
+void ActionRunner::RunCommand(const base::CommandLine& cmdline) {
+ base::LaunchOptions options;
+ options.start_hidden = true;
+ base::Process process = base::LaunchProcess(cmdline, options);
+
+ base::PostTaskWithTraits(
+ FROM_HERE, kTaskTraitsRunCommand,
+ base::BindOnce(&ActionRunner::WaitForCommand, base::Unretained(this),
+ std::move(process)));
+}
+
+void ActionRunner::WaitForCommand(base::Process process) {
+ int exit_code = 0;
+ const base::TimeDelta kMaxWaitTime = base::TimeDelta::FromSeconds(600);
+ const bool succeeded =
+ process.WaitForExitWithTimeout(kMaxWaitTime, &exit_code);
+ base::DeleteFile(unpack_path_, true);
+ main_task_runner_->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(run_complete_), succeeded, exit_code, 0));
+}
+
+base::CommandLine ActionRunner::MakeCommandLine(
+ const base::FilePath& unpack_path) const {
+ base::CommandLine command_line(unpack_path.Append(kRecoveryFileName));
+ if (!is_per_user_install_)
+ command_line.AppendSwitch("system");
+ command_line.AppendSwitchASCII(
+ "browser-version", component_.config()->GetBrowserVersion().GetString());
+ command_line.AppendSwitchASCII("sessionid", component_.session_id());
+ const auto app_guid = component_.config()->GetAppGuid();
+ if (!app_guid.empty())
+ command_line.AppendSwitchASCII("appguid", app_guid);
+ VLOG(1) << "run action: " << command_line.GetCommandLineString();
+ return command_line;
+}
+
+void ActionRunner::RunRecoveryCRXElevated(const base::FilePath& crx_path) {
+ base::CreateCOMSTATaskRunnerWithTraits(
+ kTaskTraitsRunCommand, base::SingleThreadTaskRunnerThreadMode::DEDICATED)
+ ->PostTask(FROM_HERE,
+ base::BindOnce(&ActionRunner::RunRecoveryCRXElevatedInSTA,
+ base::Unretained(this), crx_path));
+}
+
+void ActionRunner::RunRecoveryCRXElevatedInSTA(const base::FilePath& crx_path) {
+ bool succeeded = false;
+ int error_code = 0;
+ int extra_code = 0;
+ const auto config = component_.config();
+ std::tie(succeeded, error_code, extra_code) =
+ component_.config()->GetRecoveryCRXElevator().Run(
+ crx_path, config->GetAppGuid(),
+ config->GetBrowserVersion().GetString(), component_.session_id());
+ main_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(std::move(run_complete_), succeeded, error_code,
+ extra_code));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/activity_data_service.h b/src/components/update_client/activity_data_service.h
new file mode 100644
index 0000000..5d3744c
--- /dev/null
+++ b/src/components/update_client/activity_data_service.h
@@ -0,0 +1,42 @@
+// Copyright 2017 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 COMPONENTS_UPDATE_CLIENT_ACTIVITY_DATA_SERVICE_H_
+#define COMPONENTS_UPDATE_CLIENT_ACTIVITY_DATA_SERVICE_H_
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+const int kDateFirstTime = -1;
+const int kDaysFirstTime = -1;
+const int kDateUnknown = -2;
+const int kDaysUnknown = -2;
+
+// This is an interface that injects certain update information (active, days
+// since ...) into the update engine of the update client.
+// GetDaysSinceLastActive and GetDaysSinceLastRollCall are used for backward
+// compatibility.
+class ActivityDataService {
+ public:
+ // Returns the current state of the active bit of the specified |id|.
+ virtual bool GetActiveBit(const std::string& id) const = 0;
+
+ // Clears the active bit of the specified |id|.
+ virtual void ClearActiveBit(const std::string& id) = 0;
+
+ // The following 2 functions return the number of days since last
+ // active/roll call.
+ virtual int GetDaysSinceLastActive(const std::string& id) const = 0;
+ virtual int GetDaysSinceLastRollCall(const std::string& id) const = 0;
+
+ virtual ~ActivityDataService() {}
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_ACTIVITY_DATA_SERVICE_H_
diff --git a/src/components/update_client/background_downloader_win.cc b/src/components/update_client/background_downloader_win.cc
new file mode 100644
index 0000000..860c494
--- /dev/null
+++ b/src/components/update_client/background_downloader_win.cc
@@ -0,0 +1,901 @@
+// 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 "components/update_client/background_downloader_win.h"
+
+#include <objbase.h>
+#include <winerror.h>
+
+#include <stddef.h>
+#include <stdint.h>
+#include <functional>
+#include <iomanip>
+#include <limits>
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/metrics/histogram_macros.h"
+#include "base/sequenced_task_runner.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/win/atl.h"
+#include "base/win/scoped_co_mem.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+using Microsoft::WRL::ComPtr;
+using base::win::ScopedCoMem;
+
+// The class BackgroundDownloader in this module is an adapter between
+// the CrxDownloader interface and the BITS service interfaces.
+// The interface exposed on the CrxDownloader code runs on the main thread,
+// while the BITS specific code runs on a separate thread passed in by the
+// client. For every url to download, a BITS job is created, unless there is
+// already an existing job for that url, in which case, the downloader
+// connects to it. Once a job is associated with the url, the code looks for
+// changes in the BITS job state. The checks are triggered by a timer.
+// The BITS job contains just one file to download. There could only be one
+// download in progress at a time. If Chrome closes down before the download is
+// complete, the BITS job remains active and finishes in the background, without
+// any intervention. The job can be completed next time the code runs, if the
+// file is still needed, otherwise it will be cleaned up on a periodic basis.
+//
+// To list the BITS jobs for a user, use the |bitsadmin| tool. The command line
+// to do that is: "bitsadmin /list /verbose". Another useful command is
+// "bitsadmin /info" and provide the job id returned by the previous /list
+// command.
+//
+// Ignoring the suspend/resume issues since this code is not using them, the
+// job state machine implemented by BITS is something like this:
+//
+// Suspended--->Queued--->Connecting---->Transferring--->Transferred
+// | ^ | | |
+// | | V V | (complete)
+// +----------|---------+-----------------+-----+ V
+// | | | | Acknowledged
+// | V V |
+// | Transient Error------->Error |
+// | | | |(cancel)
+// | +-------+---------+--->-+
+// | V |
+// | (resume) | |
+// +------<----------+ +---->Cancelled
+//
+// The job is created in the "suspended" state. Once |Resume| is called,
+// BITS queues up the job, then tries to connect, begins transferring the
+// job bytes, and moves the job to the "transferred state, after the job files
+// have been transferred. When calling |Complete| for a job, the job files are
+// made available to the caller, and the job is moved to the "acknowledged"
+// state.
+// At any point, the job can be cancelled, in which case, the job is moved
+// to the "cancelled state" and the job object is removed from the BITS queue.
+// Along the way, the job can encounter recoverable and non-recoverable errors.
+// BITS moves the job to "transient error" or "error", depending on which kind
+// of error has occured.
+// If the job has reached the "transient error" state, BITS retries the
+// job after a certain programmable delay. If the job can't be completed in a
+// certain time interval, BITS stops retrying and errors the job out. This time
+// interval is also programmable.
+// If the job is in either of the error states, the job parameters can be
+// adjusted to handle the error, after which the job can be resumed, and the
+// whole cycle starts again.
+// Jobs that are not touched in 90 days (or a value set by group policy) are
+// automatically disposed off by BITS. This concludes the brief description of
+// a job lifetime, according to BITS.
+//
+// In addition to how BITS is managing the life time of the job, there are a
+// couple of special cases defined by the BackgroundDownloader.
+// First, if the job encounters any of the 5xx HTTP responses, the job is
+// not retried, in order to avoid DDOS-ing the servers.
+// Second, there is a simple mechanism to detect stuck jobs, and allow the rest
+// of the code to move on to trying other urls or trying other components.
+// Last, after completing a job, irrespective of the outcome, the jobs older
+// than a week are proactively cleaned up.
+
+namespace update_client {
+
+namespace {
+
+// All jobs created by this module have a specific description so they can
+// be found at run-time or by using system administration tools.
+const base::char16 kJobName[] = L"Chrome Component Updater";
+
+// How often the code looks for changes in the BITS job state.
+const int kJobPollingIntervalSec = 4;
+
+// How long BITS waits before retrying a job after the job encountered
+// a transient error. If this value is not set, the BITS default is 10 minutes.
+const int kMinimumRetryDelayMin = 1;
+
+// How long to wait for stuck jobs. Stuck jobs could be queued for too long,
+// have trouble connecting, could be suspended for any reason, or they have
+// encountered some transient error.
+const int kJobStuckTimeoutMin = 15;
+
+// How long BITS waits before giving up on a job that could not be completed
+// since the job has encountered its first transient error. If this value is
+// not set, the BITS default is 14 days.
+const int kSetNoProgressTimeoutDays = 1;
+
+// How often the jobs which were started but not completed for any reason
+// are cleaned up. Reasons for jobs to be left behind include browser restarts,
+// system restarts, etc. Also, the check to purge stale jobs only happens
+// at most once a day. If the job clean up code is not running, the BITS
+// default policy is to cancel jobs after 90 days of inactivity.
+const int kPurgeStaleJobsAfterDays = 3;
+const int kPurgeStaleJobsIntervalBetweenChecksDays = 1;
+
+// Number of maximum BITS jobs this downloader can create and queue up.
+const int kMaxQueuedJobs = 10;
+
+// Retrieves the singleton instance of GIT for this process.
+HRESULT GetGit(ComPtr<IGlobalInterfaceTable>* git) {
+ return ::CoCreateInstance(CLSID_StdGlobalInterfaceTable, nullptr,
+ CLSCTX_INPROC_SERVER,
+ IID_PPV_ARGS(git->GetAddressOf()));
+}
+
+// Retrieves an interface pointer from the process GIT for a given |cookie|.
+HRESULT GetInterfaceFromGit(const ComPtr<IGlobalInterfaceTable>& git,
+ DWORD cookie,
+ REFIID riid,
+ void** ppv) {
+ return git->GetInterfaceFromGlobal(cookie, riid, ppv);
+}
+
+// Registers an interface pointer in GIT and returns its corresponding |cookie|.
+template <typename T>
+HRESULT RegisterInterfaceInGit(const ComPtr<IGlobalInterfaceTable>& git,
+ const ComPtr<T>& p,
+ DWORD* cookie) {
+ return git->RegisterInterfaceInGlobal(p.Get(), __uuidof(T), cookie);
+}
+
+// Returns the status code from a given BITS error.
+int GetHttpStatusFromBitsError(HRESULT error) {
+ // BITS errors are defined in bitsmsg.h. Although not documented, it is
+ // clear that all errors corresponding to http status code have the high
+ // word equal to 0x8019 and the low word equal to the http status code.
+ const int kHttpStatusFirst = 100; // Continue.
+ const int kHttpStatusLast = 505; // Version not supported.
+ bool is_valid = HIWORD(error) == 0x8019 &&
+ LOWORD(error) >= kHttpStatusFirst &&
+ LOWORD(error) <= kHttpStatusLast;
+ return is_valid ? LOWORD(error) : 0;
+}
+
+// Returns the files in a BITS job.
+HRESULT GetFilesInJob(const ComPtr<IBackgroundCopyJob>& job,
+ std::vector<ComPtr<IBackgroundCopyFile>>* files) {
+ ComPtr<IEnumBackgroundCopyFiles> enum_files;
+ HRESULT hr = job->EnumFiles(enum_files.GetAddressOf());
+ if (FAILED(hr))
+ return hr;
+
+ ULONG num_files = 0;
+ hr = enum_files->GetCount(&num_files);
+ if (FAILED(hr))
+ return hr;
+
+ for (ULONG i = 0; i != num_files; ++i) {
+ ComPtr<IBackgroundCopyFile> file;
+ if (enum_files->Next(1, file.GetAddressOf(), nullptr) == S_OK && file.Get())
+ files->push_back(file);
+ }
+
+ return S_OK;
+}
+
+// Returns the file name, the url, and some per-file progress information.
+// The function out parameters can be NULL if that data is not requested.
+HRESULT GetJobFileProperties(const ComPtr<IBackgroundCopyFile>& file,
+ base::string16* local_name,
+ base::string16* remote_name,
+ BG_FILE_PROGRESS* progress) {
+ if (!file)
+ return E_FAIL;
+
+ HRESULT hr = S_OK;
+
+ if (local_name) {
+ ScopedCoMem<base::char16> name;
+ hr = file->GetLocalName(&name);
+ if (FAILED(hr))
+ return hr;
+ local_name->assign(name);
+ }
+
+ if (remote_name) {
+ ScopedCoMem<base::char16> name;
+ hr = file->GetRemoteName(&name);
+ if (FAILED(hr))
+ return hr;
+ remote_name->assign(name);
+ }
+
+ if (progress) {
+ BG_FILE_PROGRESS bg_file_progress = {};
+ hr = file->GetProgress(&bg_file_progress);
+ if (FAILED(hr))
+ return hr;
+ *progress = bg_file_progress;
+ }
+
+ return hr;
+}
+
+// Returns the number of bytes downloaded and bytes to download for all files
+// in the job. If the values are not known or if an error has occurred,
+// a value of -1 is reported.
+HRESULT GetJobByteCount(const ComPtr<IBackgroundCopyJob>& job,
+ int64_t* downloaded_bytes,
+ int64_t* total_bytes) {
+ *downloaded_bytes = -1;
+ *total_bytes = -1;
+
+ if (!job)
+ return E_FAIL;
+
+ BG_JOB_PROGRESS job_progress = {};
+ HRESULT hr = job->GetProgress(&job_progress);
+ if (FAILED(hr))
+ return hr;
+
+ const uint64_t kMaxNumBytes =
+ static_cast<uint64_t>(std::numeric_limits<int64_t>::max());
+ if (job_progress.BytesTransferred <= kMaxNumBytes)
+ *downloaded_bytes = job_progress.BytesTransferred;
+
+ if (job_progress.BytesTotal <= kMaxNumBytes &&
+ job_progress.BytesTotal != BG_SIZE_UNKNOWN)
+ *total_bytes = job_progress.BytesTotal;
+
+ return S_OK;
+}
+
+HRESULT GetJobDisplayName(const ComPtr<IBackgroundCopyJob>& job,
+ base::string16* name) {
+ ScopedCoMem<base::char16> local_name;
+ const HRESULT hr = job->GetDisplayName(&local_name);
+ if (FAILED(hr))
+ return hr;
+ *name = local_name.get();
+ return S_OK;
+}
+
+// Returns the job error code in |error_code| if the job is in the transient
+// or the final error state. Otherwise, the job error is not available and
+// the function fails.
+HRESULT GetJobError(const ComPtr<IBackgroundCopyJob>& job,
+ HRESULT* error_code_out) {
+ *error_code_out = S_OK;
+ ComPtr<IBackgroundCopyError> copy_error;
+ HRESULT hr = job->GetError(copy_error.GetAddressOf());
+ if (FAILED(hr))
+ return hr;
+
+ BG_ERROR_CONTEXT error_context = BG_ERROR_CONTEXT_NONE;
+ HRESULT error_code = S_OK;
+ hr = copy_error->GetError(&error_context, &error_code);
+ if (FAILED(hr))
+ return hr;
+
+ *error_code_out = FAILED(error_code) ? error_code : E_FAIL;
+ return S_OK;
+}
+
+// Finds the component updater jobs matching the given predicate.
+// Returns S_OK if the function has found at least one job, returns S_FALSE if
+// no job was found, and it returns an error otherwise.
+template <class Predicate>
+HRESULT FindBitsJobIf(Predicate pred,
+ const ComPtr<IBackgroundCopyManager>& bits_manager,
+ std::vector<ComPtr<IBackgroundCopyJob>>* jobs) {
+ ComPtr<IEnumBackgroundCopyJobs> enum_jobs;
+ HRESULT hr = bits_manager->EnumJobs(0, enum_jobs.GetAddressOf());
+ if (FAILED(hr))
+ return hr;
+
+ ULONG job_count = 0;
+ hr = enum_jobs->GetCount(&job_count);
+ if (FAILED(hr))
+ return hr;
+
+ // Iterate over jobs, run the predicate, and select the job only if
+ // the job description matches the component updater jobs.
+ for (ULONG i = 0; i != job_count; ++i) {
+ ComPtr<IBackgroundCopyJob> current_job;
+ if (enum_jobs->Next(1, current_job.GetAddressOf(), nullptr) == S_OK &&
+ pred(current_job)) {
+ base::string16 job_name;
+ hr = GetJobDisplayName(current_job, &job_name);
+ if (job_name.compare(kJobName) == 0)
+ jobs->push_back(current_job);
+ }
+ }
+
+ return jobs->empty() ? S_FALSE : S_OK;
+}
+
+bool JobCreationOlderThanDaysPredicate(ComPtr<IBackgroundCopyJob> job,
+ int num_days) {
+ BG_JOB_TIMES times = {};
+ HRESULT hr = job->GetTimes(×);
+ if (FAILED(hr))
+ return false;
+
+ const base::TimeDelta time_delta(base::TimeDelta::FromDays(num_days));
+ const base::Time creation_time(base::Time::FromFileTime(times.CreationTime));
+
+ return creation_time + time_delta < base::Time::Now();
+}
+
+bool JobFileUrlEqualPredicate(ComPtr<IBackgroundCopyJob> job, const GURL& url) {
+ std::vector<ComPtr<IBackgroundCopyFile>> files;
+ HRESULT hr = GetFilesInJob(job, &files);
+ if (FAILED(hr))
+ return false;
+
+ for (size_t i = 0; i != files.size(); ++i) {
+ ScopedCoMem<base::char16> remote_name;
+ if (SUCCEEDED(files[i]->GetRemoteName(&remote_name)) &&
+ url == GURL(base::StringPiece16(remote_name)))
+ return true;
+ }
+
+ return false;
+}
+
+// Creates an instance of the BITS manager.
+HRESULT CreateBitsManager(ComPtr<IBackgroundCopyManager>* bits_manager) {
+ ComPtr<IBackgroundCopyManager> local_bits_manager;
+ HRESULT hr =
+ ::CoCreateInstance(__uuidof(BackgroundCopyManager), nullptr, CLSCTX_ALL,
+ IID_PPV_ARGS(&local_bits_manager));
+ if (FAILED(hr)) {
+ return hr;
+ }
+ *bits_manager = local_bits_manager;
+ return S_OK;
+}
+
+void CleanupJob(const ComPtr<IBackgroundCopyJob>& job) {
+ if (!job)
+ return;
+
+ // Get the file paths associated with this job before canceling the job.
+ // Canceling the job removes it from the BITS queue right away. It appears
+ // that it is still possible to query for the properties of the job after
+ // the job has been canceled. It seems safer though to get the paths first.
+ std::vector<ComPtr<IBackgroundCopyFile>> files;
+ GetFilesInJob(job, &files);
+
+ std::vector<base::FilePath> paths;
+ for (const auto& file : files) {
+ base::string16 local_name;
+ HRESULT hr = GetJobFileProperties(file, &local_name, nullptr, nullptr);
+ if (SUCCEEDED(hr))
+ paths.push_back(base::FilePath(local_name));
+ }
+
+ job->Cancel();
+
+ for (const auto& path : paths)
+ DeleteFileAndEmptyParentDirectory(path);
+}
+
+} // namespace
+
+BackgroundDownloader::BackgroundDownloader(
+ std::unique_ptr<CrxDownloader> successor)
+ : CrxDownloader(std::move(successor)),
+ com_task_runner_(base::CreateCOMSTATaskRunnerWithTraits(
+ kTaskTraitsBackgroundDownloader)),
+ git_cookie_bits_manager_(0),
+ git_cookie_job_(0) {}
+
+BackgroundDownloader::~BackgroundDownloader() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ timer_.reset();
+}
+
+void BackgroundDownloader::StartTimer() {
+ timer_.reset(new base::OneShotTimer);
+ timer_->Start(FROM_HERE, base::TimeDelta::FromSeconds(kJobPollingIntervalSec),
+ this, &BackgroundDownloader::OnTimer);
+}
+
+void BackgroundDownloader::OnTimer() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ com_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloading,
+ base::Unretained(this)));
+}
+
+void BackgroundDownloader::DoStartDownload(const GURL& url) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ com_task_runner_->PostTask(
+ FROM_HERE, base::BindOnce(&BackgroundDownloader::BeginDownload,
+ base::Unretained(this), url));
+}
+
+// Called one time when this class is asked to do a download.
+void BackgroundDownloader::BeginDownload(const GURL& url) {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+ download_start_time_ = base::TimeTicks::Now();
+ job_stuck_begin_time_ = download_start_time_;
+
+ HRESULT hr = BeginDownloadHelper(url);
+ if (FAILED(hr)) {
+ EndDownload(hr);
+ return;
+ }
+
+ VLOG(1) << "Starting BITS download for: " << url.spec();
+
+ ResetInterfacePointers();
+ main_task_runner()->PostTask(FROM_HERE,
+ base::BindOnce(&BackgroundDownloader::StartTimer,
+ base::Unretained(this)));
+}
+
+// Creates or opens an existing BITS job to download the |url|, and handles
+// the marshalling of the interfaces in GIT.
+HRESULT BackgroundDownloader::BeginDownloadHelper(const GURL& url) {
+ ComPtr<IGlobalInterfaceTable> git;
+ HRESULT hr = GetGit(&git);
+ if (FAILED(hr))
+ return hr;
+
+ hr = CreateBitsManager(&bits_manager_);
+ if (FAILED(hr))
+ return hr;
+
+ hr = QueueBitsJob(url, &job_);
+ if (FAILED(hr))
+ return hr;
+
+ hr = RegisterInterfaceInGit(git, bits_manager_, &git_cookie_bits_manager_);
+ if (FAILED(hr))
+ return hr;
+
+ hr = RegisterInterfaceInGit(git, job_, &git_cookie_job_);
+ if (FAILED(hr))
+ return hr;
+
+ return S_OK;
+}
+
+// Called any time the timer fires.
+void BackgroundDownloader::OnDownloading() {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+ HRESULT hr = UpdateInterfacePointers();
+ if (FAILED(hr)) {
+ EndDownload(hr);
+ return;
+ }
+
+ BG_JOB_STATE job_state = BG_JOB_STATE_ERROR;
+ hr = job_->GetState(&job_state);
+ if (FAILED(hr)) {
+ EndDownload(hr);
+ return;
+ }
+
+ bool is_handled = false;
+ switch (job_state) {
+ case BG_JOB_STATE_TRANSFERRED:
+ is_handled = OnStateTransferred();
+ break;
+
+ case BG_JOB_STATE_ERROR:
+ is_handled = OnStateError();
+ break;
+
+ case BG_JOB_STATE_CANCELLED:
+ is_handled = OnStateCancelled();
+ break;
+
+ case BG_JOB_STATE_ACKNOWLEDGED:
+ is_handled = OnStateAcknowledged();
+ break;
+
+ case BG_JOB_STATE_QUEUED:
+ // Fall through.
+ case BG_JOB_STATE_CONNECTING:
+ // Fall through.
+ case BG_JOB_STATE_SUSPENDED:
+ is_handled = OnStateQueued();
+ break;
+
+ case BG_JOB_STATE_TRANSIENT_ERROR:
+ is_handled = OnStateTransientError();
+ break;
+
+ case BG_JOB_STATE_TRANSFERRING:
+ is_handled = OnStateTransferring();
+ break;
+
+ default:
+ break;
+ }
+
+ if (is_handled)
+ return;
+
+ ResetInterfacePointers();
+ main_task_runner()->PostTask(FROM_HERE,
+ base::BindOnce(&BackgroundDownloader::StartTimer,
+ base::Unretained(this)));
+}
+
+// Completes the BITS download, picks up the file path of the response, and
+// notifies the CrxDownloader. The function should be called only once.
+void BackgroundDownloader::EndDownload(HRESULT error) {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+ const base::TimeTicks download_end_time(base::TimeTicks::Now());
+ const base::TimeDelta download_time =
+ download_end_time >= download_start_time_
+ ? download_end_time - download_start_time_
+ : base::TimeDelta();
+
+ int64_t downloaded_bytes = -1;
+ int64_t total_bytes = -1;
+ GetJobByteCount(job_, &downloaded_bytes, &total_bytes);
+
+ if (FAILED(error))
+ CleanupJob(job_);
+
+ ClearGit();
+
+ // Consider the url handled if it has been successfully downloaded or a
+ // 5xx has been received.
+ const bool is_handled =
+ SUCCEEDED(error) || IsHttpServerError(GetHttpStatusFromBitsError(error));
+
+ const int error_to_report = SUCCEEDED(error) ? 0 : error;
+
+ DCHECK(static_cast<bool>(error_to_report) == !base::PathExists(response_));
+
+ DownloadMetrics download_metrics;
+ download_metrics.url = url();
+ download_metrics.downloader = DownloadMetrics::kBits;
+ download_metrics.error = error_to_report;
+ download_metrics.downloaded_bytes = downloaded_bytes;
+ download_metrics.total_bytes = total_bytes;
+ download_metrics.download_time_ms = download_time.InMilliseconds();
+
+ Result result;
+ result.error = error_to_report;
+ if (!result.error)
+ result.response = response_;
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadComplete,
+ base::Unretained(this), is_handled, result,
+ download_metrics));
+
+ // Once the task is posted to the the main thread, this object may be deleted
+ // by its owner. It is not safe to access members of this object on this task
+ // runner from now on.
+}
+
+// Called when the BITS job has been transferred successfully. Completes the
+// BITS job by removing it from the BITS queue and making the download
+// available to the caller.
+bool BackgroundDownloader::OnStateTransferred() {
+ EndDownload(CompleteJob());
+ return true;
+}
+
+// Called when the job has encountered an error and no further progress can
+// be made. Cancels this job and removes it from the BITS queue.
+bool BackgroundDownloader::OnStateError() {
+ HRESULT error_code = S_OK;
+ HRESULT hr = GetJobError(job_, &error_code);
+ if (FAILED(hr))
+ error_code = hr;
+
+ DCHECK(FAILED(error_code));
+ EndDownload(error_code);
+ return true;
+}
+
+// Called when the download was completed. This notification is not seen
+// in the current implementation but provided here as a defensive programming
+// measure.
+bool BackgroundDownloader::OnStateAcknowledged() {
+ EndDownload(E_UNEXPECTED);
+ return true;
+}
+
+// Called when the download was cancelled. Same as above.
+bool BackgroundDownloader::OnStateCancelled() {
+ EndDownload(E_UNEXPECTED);
+ return true;
+}
+
+// Called when the job has encountered a transient error, such as a
+// network disconnect, a server error, or some other recoverable error.
+bool BackgroundDownloader::OnStateTransientError() {
+ // If the job appears to be stuck, handle the transient error as if
+ // it were a final error. This causes the job to be cancelled and a specific
+ // error be returned, if the error was available.
+ if (IsStuck()) {
+ return OnStateError();
+ }
+
+ // Don't retry at all if the transient error was a 5xx.
+ HRESULT error_code = S_OK;
+ HRESULT hr = GetJobError(job_, &error_code);
+ if (SUCCEEDED(hr) &&
+ IsHttpServerError(GetHttpStatusFromBitsError(error_code))) {
+ return OnStateError();
+ }
+
+ return false;
+}
+
+bool BackgroundDownloader::OnStateQueued() {
+ if (!IsStuck())
+ return false;
+
+ // Terminate the download if the job has not made progress in a while.
+ EndDownload(E_ABORT);
+ return true;
+}
+
+bool BackgroundDownloader::OnStateTransferring() {
+ // Resets the baseline for detecting a stuck job since the job is transferring
+ // data and it is making progress.
+ job_stuck_begin_time_ = base::TimeTicks::Now();
+
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&BackgroundDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+ return false;
+}
+
+HRESULT BackgroundDownloader::QueueBitsJob(const GURL& url,
+ ComPtr<IBackgroundCopyJob>* job) {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+ size_t num_jobs = std::numeric_limits<size_t>::max();
+ HRESULT hr = GetBackgroundDownloaderJobCount(&num_jobs);
+
+ // The metric records a large number if the job count can't be read.
+ UMA_HISTOGRAM_COUNTS_100("UpdateClient.BackgroundDownloaderJobs", num_jobs);
+
+ // Remove some old jobs from the BITS queue before creating new jobs.
+ CleanupStaleJobs();
+
+ if (FAILED(hr) || num_jobs >= kMaxQueuedJobs)
+ return MAKE_HRESULT(SEVERITY_ERROR, FACILITY_ITF,
+ CrxDownloaderError::BITS_TOO_MANY_JOBS);
+
+ ComPtr<IBackgroundCopyJob> local_job;
+ hr = CreateOrOpenJob(url, &local_job);
+ if (FAILED(hr)) {
+ CleanupJob(local_job);
+ return hr;
+ }
+
+ const bool is_new_job = hr == S_OK;
+ UMA_HISTOGRAM_BOOLEAN("UpdateClient.BackgroundDownloaderNewJob", is_new_job);
+
+ hr = local_job->Resume();
+ if (FAILED(hr)) {
+ CleanupJob(local_job);
+ return hr;
+ }
+
+ *job = local_job;
+ return S_OK;
+}
+
+HRESULT BackgroundDownloader::CreateOrOpenJob(const GURL& url,
+ ComPtr<IBackgroundCopyJob>* job) {
+ std::vector<ComPtr<IBackgroundCopyJob>> jobs;
+ HRESULT hr = FindBitsJobIf(
+ [&url](ComPtr<IBackgroundCopyJob> job) {
+ return JobFileUrlEqualPredicate(job, url);
+ },
+ bits_manager_, &jobs);
+ if (SUCCEEDED(hr) && !jobs.empty()) {
+ *job = jobs.front();
+ return S_FALSE;
+ }
+
+ ComPtr<IBackgroundCopyJob> local_job;
+
+ GUID guid = {0};
+ hr = bits_manager_->CreateJob(kJobName, BG_JOB_TYPE_DOWNLOAD, &guid,
+ local_job.GetAddressOf());
+ if (FAILED(hr)) {
+ CleanupJob(local_job);
+ return hr;
+ }
+
+ hr = InitializeNewJob(local_job, url);
+ if (FAILED(hr)) {
+ CleanupJob(local_job);
+ return hr;
+ }
+
+ *job = local_job;
+ return S_OK;
+}
+
+HRESULT BackgroundDownloader::InitializeNewJob(
+ const ComPtr<IBackgroundCopyJob>& job,
+ const GURL& url) {
+ base::FilePath tempdir;
+ if (!base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_BITS_"),
+ &tempdir))
+ return E_FAIL;
+
+ const base::string16 filename(base::SysUTF8ToWide(url.ExtractFileName()));
+ HRESULT hr = job->AddFile(base::SysUTF8ToWide(url.spec()).c_str(),
+ tempdir.Append(filename).AsUTF16Unsafe().c_str());
+ if (FAILED(hr))
+ return hr;
+
+ hr = job->SetDescription(filename.c_str());
+ if (FAILED(hr))
+ return hr;
+
+ hr = job->SetPriority(BG_JOB_PRIORITY_NORMAL);
+ if (FAILED(hr))
+ return hr;
+
+ hr = job->SetMinimumRetryDelay(60 * kMinimumRetryDelayMin);
+ if (FAILED(hr))
+ return hr;
+
+ const int kSecondsDay = 60 * 60 * 24;
+ hr = job->SetNoProgressTimeout(kSecondsDay * kSetNoProgressTimeoutDays);
+ if (FAILED(hr))
+ return hr;
+
+ return S_OK;
+}
+
+bool BackgroundDownloader::IsStuck() {
+ const base::TimeDelta job_stuck_timeout(
+ base::TimeDelta::FromMinutes(kJobStuckTimeoutMin));
+ return job_stuck_begin_time_ + job_stuck_timeout < base::TimeTicks::Now();
+}
+
+HRESULT BackgroundDownloader::CompleteJob() {
+ HRESULT hr = job_->Complete();
+ if (FAILED(hr) && hr != BG_S_UNABLE_TO_DELETE_FILES)
+ return hr;
+
+ std::vector<ComPtr<IBackgroundCopyFile>> files;
+ hr = GetFilesInJob(job_, &files);
+ if (FAILED(hr))
+ return hr;
+
+ if (files.empty())
+ return E_UNEXPECTED;
+
+ base::string16 local_name;
+ BG_FILE_PROGRESS progress = {0};
+ hr = GetJobFileProperties(files.front(), &local_name, nullptr, &progress);
+ if (FAILED(hr))
+ return hr;
+
+ // Sanity check the post-conditions of a successful download, including
+ // the file and job invariants. The byte counts for a job and its file
+ // must match as a job only contains one file.
+ DCHECK(progress.Completed);
+ DCHECK_EQ(progress.BytesTotal, progress.BytesTransferred);
+
+ response_ = base::FilePath(local_name);
+
+ return S_OK;
+}
+
+HRESULT BackgroundDownloader::UpdateInterfacePointers() {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+ bits_manager_ = nullptr;
+ job_ = nullptr;
+
+ ComPtr<IGlobalInterfaceTable> git;
+ HRESULT hr = GetGit(&git);
+ if (FAILED(hr))
+ return hr;
+
+ hr = GetInterfaceFromGit(git, git_cookie_bits_manager_,
+ IID_PPV_ARGS(bits_manager_.GetAddressOf()));
+ if (FAILED(hr))
+ return hr;
+
+ hr = GetInterfaceFromGit(git, git_cookie_job_,
+ IID_PPV_ARGS(job_.GetAddressOf()));
+ if (FAILED(hr))
+ return hr;
+
+ return S_OK;
+}
+
+void BackgroundDownloader::ResetInterfacePointers() {
+ job_ = nullptr;
+ bits_manager_ = nullptr;
+}
+
+HRESULT BackgroundDownloader::ClearGit() {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+
+ ResetInterfacePointers();
+
+ ComPtr<IGlobalInterfaceTable> git;
+ HRESULT hr = GetGit(&git);
+ if (FAILED(hr))
+ return hr;
+
+ const DWORD cookies[] = {git_cookie_job_, git_cookie_bits_manager_};
+
+ for (auto cookie : cookies) {
+ // TODO(sorin): check the result of the call, see crbug.com/644857.
+ git->RevokeInterfaceFromGlobal(cookie);
+ }
+
+ return S_OK;
+}
+
+HRESULT BackgroundDownloader::GetBackgroundDownloaderJobCount(
+ size_t* num_jobs) {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(bits_manager_);
+
+ std::vector<ComPtr<IBackgroundCopyJob>> jobs;
+ const HRESULT hr =
+ FindBitsJobIf([](const ComPtr<IBackgroundCopyJob>&) { return true; },
+ bits_manager_, &jobs);
+ if (FAILED(hr))
+ return hr;
+
+ *num_jobs = jobs.size();
+ return S_OK;
+}
+
+void BackgroundDownloader::CleanupStaleJobs() {
+ DCHECK(com_task_runner_->RunsTasksInCurrentSequence());
+ DCHECK(bits_manager_);
+
+ static base::Time last_sweep;
+
+ const base::TimeDelta time_delta(
+ base::TimeDelta::FromDays(kPurgeStaleJobsIntervalBetweenChecksDays));
+ const base::Time current_time(base::Time::Now());
+ if (last_sweep + time_delta > current_time)
+ return;
+
+ last_sweep = current_time;
+
+ std::vector<ComPtr<IBackgroundCopyJob>> jobs;
+ FindBitsJobIf(
+ [](ComPtr<IBackgroundCopyJob> job) {
+ return JobCreationOlderThanDaysPredicate(job, kPurgeStaleJobsAfterDays);
+ },
+ bits_manager_, &jobs);
+
+ for (const auto& job : jobs)
+ CleanupJob(job);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/background_downloader_win.h b/src/components/update_client/background_downloader_win.h
new file mode 100644
index 0000000..2817ba7
--- /dev/null
+++ b/src/components/update_client/background_downloader_win.h
@@ -0,0 +1,157 @@
+// 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 COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_
+#define COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_
+
+#include <bits.h>
+#include <windows.h>
+#include <wrl/client.h>
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/strings/string16.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/timer/timer.h"
+#include "components/update_client/crx_downloader.h"
+
+namespace base {
+class FilePath;
+class SequencedTaskRunner;
+} // namespace base
+
+namespace update_client {
+
+// Implements a downloader in terms of the BITS service. The public interface
+// of this class and the CrxDownloader overrides are expected to be called
+// from the main thread. The rest of the class code runs on a sequenced
+// task runner, usually associated with a blocking thread pool. The task runner
+// must initialize COM.
+//
+// This class manages a COM client for Windows BITS. The client uses polling,
+// triggered by an one-shot timer, to get state updates from BITS. Since the
+// timer has thread afinity, the callbacks from the timer are delegated to
+// a sequenced task runner, which handles all client COM interaction with
+// the BITS service.
+class BackgroundDownloader : public CrxDownloader {
+ public:
+ explicit BackgroundDownloader(std::unique_ptr<CrxDownloader> successor);
+ ~BackgroundDownloader() override;
+
+ private:
+ // Overrides for CrxDownloader.
+ void DoStartDownload(const GURL& url) override;
+
+ // Called asynchronously on the |com_task_runner_| at different stages during
+ // the download. |OnDownloading| can be called multiple times.
+ // |EndDownload| switches the execution flow from the |com_task_runner_| to
+ // the main thread. Accessing any data members of this object from the
+ // |com_task_runner_| after calling |EndDownload| is unsafe.
+ void BeginDownload(const GURL& url);
+ void OnDownloading();
+ void EndDownload(HRESULT hr);
+
+ HRESULT BeginDownloadHelper(const GURL& url);
+
+ // Handles the job state transitions to a final state. Returns true always
+ // since the download has reached a final state and no further processing for
+ // this download is needed.
+ bool OnStateTransferred();
+ bool OnStateError();
+ bool OnStateCancelled();
+ bool OnStateAcknowledged();
+
+ // Handles the transition to a transient state where the job is in the
+ // queue but not actively transferring data. Returns true if the download has
+ // been in this state for too long and it will be abandoned, or false, if
+ // further processing for this download is needed.
+ bool OnStateQueued();
+
+ // Handles the job state transition to a transient error state, which may or
+ // may not be considered final, depending on the error. Returns true if
+ // the state is final, or false, if the download is allowed to continue.
+ bool OnStateTransientError();
+
+ // Handles the job state corresponding to transferring data. Returns false
+ // always since this is never a final state.
+ bool OnStateTransferring();
+
+ void StartTimer();
+ void OnTimer();
+
+ // Creates or opens a job for the given url and queues it up. Returns S_OK if
+ // a new job was created or S_FALSE if an existing job for the |url| was found
+ // in the BITS queue.
+ HRESULT QueueBitsJob(const GURL& url,
+ Microsoft::WRL::ComPtr<IBackgroundCopyJob>* job);
+ HRESULT CreateOrOpenJob(const GURL& url,
+ Microsoft::WRL::ComPtr<IBackgroundCopyJob>* job);
+ HRESULT InitializeNewJob(
+ const Microsoft::WRL::ComPtr<IBackgroundCopyJob>& job,
+ const GURL& url);
+
+ // Returns true if at the time of the call, it appears that the job
+ // has not been making progress toward completion.
+ bool IsStuck();
+
+ // Makes the downloaded file available to the caller by renaming the
+ // temporary file to its destination and removing it from the BITS queue.
+ HRESULT CompleteJob();
+
+ // Revokes the interface pointers from GIT.
+ HRESULT ClearGit();
+
+ // Updates the BITS interface pointers so that they can be used by the
+ // thread calling the function. Call this function to get valid COM interface
+ // pointers when a thread from the thread pool enters the object.
+ HRESULT UpdateInterfacePointers();
+
+ // Resets the BITS interface pointers. Call this function when a thread
+ // from the thread pool leaves the object to release the interface pointers.
+ void ResetInterfacePointers();
+
+ // Returns the number of jobs in the BITS queue which were created by this
+ // downloader.
+ HRESULT GetBackgroundDownloaderJobCount(size_t* num_jobs);
+
+ // Cleans up incompleted jobs that are too old.
+ void CleanupStaleJobs();
+
+ // Ensures that we are running on the same thread we created the object on.
+ base::ThreadChecker thread_checker_;
+
+ // Executes blocking COM calls to BITS.
+ scoped_refptr<base::SequencedTaskRunner> com_task_runner_;
+
+ // The timer has thread affinity. This member is initialized and destroyed
+ // on the main task runner.
+ std::unique_ptr<base::OneShotTimer> timer_;
+
+ DWORD git_cookie_bits_manager_;
+ DWORD git_cookie_job_;
+
+ // COM interface pointers are valid for the thread that called
+ // |UpdateInterfacePointers| to get pointers to COM proxies, which are valid
+ // for that thread only.
+ Microsoft::WRL::ComPtr<IBackgroundCopyManager> bits_manager_;
+ Microsoft::WRL::ComPtr<IBackgroundCopyJob> job_;
+
+ // Contains the time when the download of the current url has started.
+ base::TimeTicks download_start_time_;
+
+ // Contains the time when the BITS job is last seen making progress.
+ base::TimeTicks job_stuck_begin_time_;
+
+ // Contains the path of the downloaded file if the download was successful.
+ base::FilePath response_;
+
+ DISALLOW_COPY_AND_ASSIGN(BackgroundDownloader);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_BACKGROUND_DOWNLOADER_WIN_H_
diff --git a/src/components/update_client/command_line_config_policy.cc b/src/components/update_client/command_line_config_policy.cc
new file mode 100644
index 0000000..899d8ac
--- /dev/null
+++ b/src/components/update_client/command_line_config_policy.cc
@@ -0,0 +1,44 @@
+// Copyright 2018 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 "components/update_client/command_line_config_policy.h"
+
+#include "build/build_config.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+bool CommandLineConfigPolicy::BackgroundDownloadsEnabled() const {
+#if defined(OS_WIN)
+ return true;
+#else
+ return false;
+#endif
+}
+
+bool CommandLineConfigPolicy::DeltaUpdatesEnabled() const {
+ return true;
+}
+
+bool CommandLineConfigPolicy::FastUpdate() const {
+ return false;
+}
+
+bool CommandLineConfigPolicy::PingsEnabled() const {
+ return true;
+}
+
+bool CommandLineConfigPolicy::TestRequest() const {
+ return false;
+}
+
+GURL CommandLineConfigPolicy::UrlSourceOverride() const {
+ return GURL();
+}
+
+int CommandLineConfigPolicy::InitialDelay() const {
+ return 0;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/command_line_config_policy.h b/src/components/update_client/command_line_config_policy.h
new file mode 100644
index 0000000..2d201a5
--- /dev/null
+++ b/src/components/update_client/command_line_config_policy.h
@@ -0,0 +1,44 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_COMMAND_LINE_CONFIG_POLICY_H_
+#define COMPONENTS_UPDATE_CLIENT_COMMAND_LINE_CONFIG_POLICY_H_
+
+class GURL;
+
+namespace update_client {
+
+// This class provides additional settings from command line switches to the
+// main configurator.
+class CommandLineConfigPolicy {
+ public:
+ // If true, background downloads are enabled.
+ virtual bool BackgroundDownloadsEnabled() const;
+
+ // If true, differential updates are enabled.
+ virtual bool DeltaUpdatesEnabled() const;
+
+ // If true, speed up the initial update checking.
+ virtual bool FastUpdate() const;
+
+ // If true, pings are enabled. Pings are the requests sent to the update
+ // server that report the success or failure of installs or update attempts.
+ virtual bool PingsEnabled() const;
+
+ // If true, add "testrequest" attribute to update check requests.
+ virtual bool TestRequest() const;
+
+ // The override URL for updates. Can be empty.
+ virtual GURL UrlSourceOverride() const;
+
+ // If non-zero, time interval in seconds until the first component
+ // update check.
+ virtual int InitialDelay() const;
+
+ virtual ~CommandLineConfigPolicy() {}
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_COMMAND_LINE_CONFIG_POLICY_H_
diff --git a/src/components/update_client/component.cc b/src/components/update_client/component.cc
new file mode 100644
index 0000000..73f36cd
--- /dev/null
+++ b/src/components/update_client/component.cc
@@ -0,0 +1,1042 @@
+// Copyright 2017 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 "components/update_client/component.h"
+
+#include <algorithm>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/single_thread_task_runner.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "components/update_client/action_runner.h"
+#include "components/update_client/component_unpacker.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/network.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/update_engine.h"
+#include "components/update_client/utils.h"
+
+// The state machine representing how a CRX component changes during an update.
+//
+// +------------------------- kNew
+// | |
+// | V
+// | kChecking
+// | |
+// V error V no no
+// kUpdateError <------------- [update?] -> [action?] -> kUpToDate kUpdated
+// ^ | | ^ ^
+// | yes | | yes | |
+// | V | | |
+// | kCanUpdate +--------> kRun |
+// | | |
+// | no V |
+// | +-<- [differential update?] |
+// | | | |
+// | | yes | |
+// | | error V |
+// | +-<----- kDownloadingDiff kRun---->-+
+// | | | ^ |
+// | | | yes | |
+// | | error V | |
+// | +-<----- kUpdatingDiff ---------> [action?] ->-+
+// | | ^ no
+// | error V |
+// +-<-------- kDownloading |
+// | | |
+// | | |
+// | error V |
+// +-<-------- kUpdating --------------------------------+
+
+namespace update_client {
+
+namespace {
+
+using InstallOnBlockingTaskRunnerCompleteCallback = base::OnceCallback<
+ void(ErrorCategory error_category, int error_code, int extra_code1)>;
+
+void InstallComplete(
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ InstallOnBlockingTaskRunnerCompleteCallback callback,
+ const base::FilePath& unpack_path,
+ const CrxInstaller::Result& result) {
+ base::PostTaskWithTraits(
+ FROM_HERE, {base::TaskPriority::BEST_EFFORT, base::MayBlock()},
+ base::BindOnce(
+ [](scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ InstallOnBlockingTaskRunnerCompleteCallback callback,
+ const base::FilePath& unpack_path,
+ const CrxInstaller::Result& result) {
+
+// For Cobalt, don't delete the unpack_path, which is not a temp directory.
+// Cobalt uses a dedicated installation slot obtained from the Installation
+// Manager.
+#if !defined(OS_STARBOARD)
+ base::DeleteFile(unpack_path, true);
+#endif
+ const ErrorCategory error_category =
+ result.error ? ErrorCategory::kInstall : ErrorCategory::kNone;
+ main_task_runner->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), error_category,
+ static_cast<int>(result.error),
+ result.extended_error));
+ },
+ main_task_runner, std::move(callback), unpack_path, result));
+}
+
+void InstallOnBlockingTaskRunner(
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ const base::FilePath& unpack_path,
+ const std::string& public_key,
+#if defined(OS_STARBOARD)
+ const int installation_index,
+ const bool is_channel_changed,
+#endif
+ const std::string& fingerprint,
+ scoped_refptr<CrxInstaller> installer,
+ InstallOnBlockingTaskRunnerCompleteCallback callback) {
+ DCHECK(base::DirectoryExists(unpack_path));
+
+#if !defined(OS_STARBOARD)
+ // Acquire the ownership of the |unpack_path|.
+ base::ScopedTempDir unpack_path_owner;
+ ignore_result(unpack_path_owner.Set(unpack_path));
+#endif
+
+ if (static_cast<int>(fingerprint.size()) !=
+ base::WriteFile(
+ unpack_path.Append(FILE_PATH_LITERAL("manifest.fingerprint")),
+ fingerprint.c_str(), base::checked_cast<int>(fingerprint.size()))) {
+ const CrxInstaller::Result result(InstallError::FINGERPRINT_WRITE_FAILED);
+ main_task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback), ErrorCategory::kInstall,
+ static_cast<int>(result.error), result.extended_error));
+ return;
+ }
+
+#if defined(OS_STARBOARD)
+ InstallError install_error = InstallError::NONE;
+ const CobaltExtensionInstallationManagerApi* installation_api =
+ static_cast<const CobaltExtensionInstallationManagerApi*>(
+ SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+ if (!installation_api) {
+ SB_LOG(ERROR) << "Failed to get installation manager api.";
+ // TODO: add correct error code.
+ install_error = InstallError::GENERIC_ERROR;
+ } else if (installation_index == IM_EXT_INVALID_INDEX) {
+ SB_LOG(ERROR) << "Installation index is invalid.";
+ // TODO: add correct error code.
+ install_error = InstallError::GENERIC_ERROR;
+ } else {
+ int ret =
+ installation_api->RequestRollForwardToInstallation(installation_index);
+ if (ret == IM_EXT_ERROR) {
+ SB_LOG(ERROR) << "Failed to request roll forward.";
+ // TODO: add correct error code.
+ install_error = InstallError::GENERIC_ERROR;
+ }
+ }
+
+ CrxInstaller::Result result(install_error);
+ InstallComplete(main_task_runner, std::move(callback), unpack_path, result);
+
+ // Restart the app if installation is successful after the web app sets a new
+ // channel.
+ if (install_error == InstallError::NONE && is_channel_changed) {
+ SbSystemRequestStop(0);
+ }
+#else
+ installer->Install(
+ unpack_path, public_key,
+ base::BindOnce(&InstallComplete, main_task_runner, std::move(callback),
+ unpack_path_owner.Take()));
+#endif
+}
+
+void UnpackCompleteOnBlockingTaskRunner(
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ const base::FilePath& crx_path,
+#if defined(OS_STARBOARD)
+ const int installation_index,
+ const bool is_channel_changed,
+#endif
+ const std::string& fingerprint,
+ scoped_refptr<CrxInstaller> installer,
+ InstallOnBlockingTaskRunnerCompleteCallback callback,
+ const ComponentUnpacker::Result& result) {
+
+#if defined(OS_STARBOARD)
+ base::DeleteFile(crx_path, false);
+#else
+ update_client::DeleteFileAndEmptyParentDirectory(crx_path);
+#endif
+
+ if (result.error != UnpackerError::kNone) {
+ main_task_runner->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback), ErrorCategory::kUnpack,
+ static_cast<int>(result.error), result.extended_error));
+ return;
+ }
+
+ base::PostTaskWithTraits(
+ FROM_HERE, kTaskTraits,
+ base::BindOnce(&InstallOnBlockingTaskRunner, main_task_runner,
+ result.unpack_path, result.public_key,
+#if defined(OS_STARBOARD)
+ installation_index, is_channel_changed,
+#endif
+ fingerprint, installer, std::move(callback)));
+}
+
+void StartInstallOnBlockingTaskRunner(
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner,
+ const std::vector<uint8_t>& pk_hash,
+ const base::FilePath& crx_path,
+#if defined(OS_STARBOARD)
+ const int installation_index,
+ PersistedData* metadata,
+ const std::string& id,
+ const std::string& version,
+ const bool is_channel_changed,
+#endif
+ const std::string& fingerprint,
+ scoped_refptr<CrxInstaller> installer,
+ std::unique_ptr<Unzipper> unzipper_,
+ scoped_refptr<Patcher> patcher_,
+ crx_file::VerifierFormat crx_format,
+ InstallOnBlockingTaskRunnerCompleteCallback callback) {
+ auto unpacker = base::MakeRefCounted<ComponentUnpacker>(
+ pk_hash, crx_path, installer, std::move(unzipper_), std::move(patcher_),
+ crx_format, metadata, id, version);
+
+ unpacker->Unpack(base::BindOnce(&UnpackCompleteOnBlockingTaskRunner,
+ main_task_runner, crx_path,
+#if defined(OS_STARBOARD)
+ installation_index, is_channel_changed,
+#endif
+ fingerprint, installer, std::move(callback)));
+}
+
+// Returns a string literal corresponding to the value of the downloader |d|.
+const char* DownloaderToString(CrxDownloader::DownloadMetrics::Downloader d) {
+ switch (d) {
+ case CrxDownloader::DownloadMetrics::kUrlFetcher:
+ return "direct";
+ case CrxDownloader::DownloadMetrics::kBits:
+ return "bits";
+ default:
+ return "unknown";
+ }
+}
+
+} // namespace
+
+Component::Component(const UpdateContext& update_context, const std::string& id)
+ : id_(id),
+ state_(std::make_unique<StateNew>(this)),
+ update_context_(update_context) {}
+
+Component::~Component() {}
+
+scoped_refptr<Configurator> Component::config() const {
+ return update_context_.config;
+}
+
+std::string Component::session_id() const {
+ return update_context_.session_id;
+}
+
+bool Component::is_foreground() const {
+ return update_context_.is_foreground;
+}
+
+void Component::Handle(CallbackHandleComplete callback_handle_complete) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(state_);
+
+ callback_handle_complete_ = std::move(callback_handle_complete);
+
+ state_->Handle(
+ base::BindOnce(&Component::ChangeState, base::Unretained(this)));
+}
+
+void Component::ChangeState(std::unique_ptr<State> next_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ previous_state_ = state();
+ if (next_state)
+ state_ = std::move(next_state);
+ else
+ is_handled_ = true;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, std::move(callback_handle_complete_));
+}
+
+CrxUpdateItem Component::GetCrxUpdateItem() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ CrxUpdateItem crx_update_item;
+ crx_update_item.state = state_->state();
+ crx_update_item.id = id_;
+ if (crx_component_)
+ crx_update_item.component = *crx_component_;
+ crx_update_item.last_check = last_check_;
+ crx_update_item.next_version = next_version_;
+ crx_update_item.next_fp = next_fp_;
+ crx_update_item.error_category = error_category_;
+ crx_update_item.error_code = error_code_;
+ crx_update_item.extra_code1 = extra_code1_;
+
+ return crx_update_item;
+}
+
+void Component::SetParseResult(const ProtocolParser::Result& result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK_EQ(0, update_check_error_);
+
+ status_ = result.status;
+ action_run_ = result.action_run;
+
+ if (result.manifest.packages.empty())
+ return;
+
+ next_version_ = base::Version(result.manifest.version);
+ const auto& package = result.manifest.packages.front();
+ next_fp_ = package.fingerprint;
+
+ // Resolve the urls by combining the base urls with the package names.
+ for (const auto& crx_url : result.crx_urls) {
+ const GURL url = crx_url.Resolve(package.name);
+ if (url.is_valid())
+ crx_urls_.push_back(url);
+ }
+ for (const auto& crx_diffurl : result.crx_diffurls) {
+ const GURL url = crx_diffurl.Resolve(package.namediff);
+ if (url.is_valid())
+ crx_diffurls_.push_back(url);
+ }
+
+ hash_sha256_ = package.hash_sha256;
+ hashdiff_sha256_ = package.hashdiff_sha256;
+}
+
+void Component::Uninstall(const base::Version& version, int reason) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK_EQ(ComponentState::kNew, state());
+
+ crx_component_ = CrxComponent();
+ crx_component_->version = version;
+
+ previous_version_ = version;
+ next_version_ = base::Version("0");
+ extra_code1_ = reason;
+
+ state_ = std::make_unique<StateUninstalled>(this);
+}
+
+void Component::SetUpdateCheckResult(
+ const base::Optional<ProtocolParser::Result>& result,
+ ErrorCategory error_category,
+ int error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_EQ(ComponentState::kChecking, state());
+
+ error_category_ = error_category;
+ error_code_ = error;
+ if (result)
+ SetParseResult(result.value());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, std::move(update_check_complete_));
+}
+
+bool Component::CanDoBackgroundDownload() const {
+ // Foreground component updates are always downloaded in foreground.
+ return !is_foreground() &&
+ (crx_component() && crx_component()->allows_background_download) &&
+ update_context_.config->EnabledBackgroundDownloader();
+}
+
+void Component::AppendEvent(base::Value event) {
+ events_.push_back(std::move(event));
+}
+
+void Component::NotifyObservers(UpdateClient::Observer::Events event) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ update_context_.notify_observers_callback.Run(event, id_);
+}
+
+base::TimeDelta Component::GetUpdateDuration() const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (update_begin_.is_null())
+ return base::TimeDelta();
+
+ const base::TimeDelta update_cost(base::TimeTicks::Now() - update_begin_);
+ DCHECK_GE(update_cost, base::TimeDelta());
+ const base::TimeDelta max_update_delay =
+ base::TimeDelta::FromSeconds(update_context_.config->UpdateDelay());
+ return std::min(update_cost, max_update_delay);
+}
+
+base::Value Component::MakeEventUpdateComplete() const {
+ base::Value event(base::Value::Type::DICTIONARY);
+ event.SetKey("eventtype", base::Value(3));
+ event.SetKey(
+ "eventresult",
+ base::Value(static_cast<int>(state() == ComponentState::kUpdated)));
+ if (error_category() != ErrorCategory::kNone)
+ event.SetKey("errorcat", base::Value(static_cast<int>(error_category())));
+ if (error_code())
+ event.SetKey("errorcode", base::Value(error_code()));
+ if (extra_code1())
+ event.SetKey("extracode1", base::Value(extra_code1()));
+ if (HasDiffUpdate(*this)) {
+ const int diffresult = static_cast<int>(!diff_update_failed());
+ event.SetKey("diffresult", base::Value(diffresult));
+ }
+ if (diff_error_category() != ErrorCategory::kNone) {
+ const int differrorcat = static_cast<int>(diff_error_category());
+ event.SetKey("differrorcat", base::Value(differrorcat));
+ }
+ if (diff_error_code())
+ event.SetKey("differrorcode", base::Value(diff_error_code()));
+ if (diff_extra_code1())
+ event.SetKey("diffextracode1", base::Value(diff_extra_code1()));
+ if (!previous_fp().empty())
+ event.SetKey("previousfp", base::Value(previous_fp()));
+ if (!next_fp().empty())
+ event.SetKey("nextfp", base::Value(next_fp()));
+ DCHECK(previous_version().IsValid());
+ event.SetKey("previousversion", base::Value(previous_version().GetString()));
+ if (next_version().IsValid())
+ event.SetKey("nextversion", base::Value(next_version().GetString()));
+ return event;
+}
+
+base::Value Component::MakeEventDownloadMetrics(
+ const CrxDownloader::DownloadMetrics& dm) const {
+ base::Value event(base::Value::Type::DICTIONARY);
+ event.SetKey("eventtype", base::Value(14));
+ event.SetKey("eventresult", base::Value(static_cast<int>(dm.error == 0)));
+ event.SetKey("downloader", base::Value(DownloaderToString(dm.downloader)));
+ if (dm.error)
+ event.SetKey("errorcode", base::Value(dm.error));
+ event.SetKey("url", base::Value(dm.url.spec()));
+
+ // -1 means that the byte counts are not known.
+ if (dm.total_bytes != -1 && dm.total_bytes < kProtocolMaxInt)
+ event.SetKey("total", base::Value(static_cast<double>(dm.total_bytes)));
+ if (dm.downloaded_bytes != -1 && dm.total_bytes < kProtocolMaxInt) {
+ event.SetKey("downloaded",
+ base::Value(static_cast<double>(dm.downloaded_bytes)));
+ }
+ if (dm.download_time_ms && dm.total_bytes < kProtocolMaxInt) {
+ event.SetKey("download_time_ms",
+ base::Value(static_cast<double>(dm.download_time_ms)));
+ }
+ DCHECK(previous_version().IsValid());
+ event.SetKey("previousversion", base::Value(previous_version().GetString()));
+ if (next_version().IsValid())
+ event.SetKey("nextversion", base::Value(next_version().GetString()));
+ return event;
+}
+
+base::Value Component::MakeEventUninstalled() const {
+ DCHECK(state() == ComponentState::kUninstalled);
+ base::Value event(base::Value::Type::DICTIONARY);
+ event.SetKey("eventtype", base::Value(4));
+ event.SetKey("eventresult", base::Value(1));
+ if (extra_code1())
+ event.SetKey("extracode1", base::Value(extra_code1()));
+ DCHECK(previous_version().IsValid());
+ event.SetKey("previousversion", base::Value(previous_version().GetString()));
+ DCHECK(next_version().IsValid());
+ event.SetKey("nextversion", base::Value(next_version().GetString()));
+ return event;
+}
+
+base::Value Component::MakeEventActionRun(bool succeeded,
+ int error_code,
+ int extra_code1) const {
+ base::Value event(base::Value::Type::DICTIONARY);
+ event.SetKey("eventtype", base::Value(42));
+ event.SetKey("eventresult", base::Value(static_cast<int>(succeeded)));
+ if (error_code)
+ event.SetKey("errorcode", base::Value(error_code));
+ if (extra_code1)
+ event.SetKey("extracode1", base::Value(extra_code1));
+ return event;
+}
+
+std::vector<base::Value> Component::GetEvents() const {
+ std::vector<base::Value> events;
+ for (const auto& event : events_)
+ events.push_back(event.Clone());
+ return events;
+}
+
+Component::State::State(Component* component, ComponentState state)
+ : state_(state), component_(*component) {}
+
+Component::State::~State() {}
+
+void Component::State::Handle(CallbackNextState callback_next_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ callback_next_state_ = std::move(callback_next_state);
+
+ DoHandle();
+}
+
+void Component::State::TransitionState(std::unique_ptr<State> next_state) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(next_state);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback_next_state_), std::move(next_state)));
+}
+
+void Component::State::EndState() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_next_state_), nullptr));
+}
+
+Component::StateNew::StateNew(Component* component)
+ : State(component, ComponentState::kNew) {}
+
+Component::StateNew::~StateNew() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateNew::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+ if (component.crx_component()) {
+ TransitionState(std::make_unique<StateChecking>(&component));
+ } else {
+ component.error_code_ = static_cast<int>(Error::CRX_NOT_FOUND);
+ component.error_category_ = ErrorCategory::kService;
+ TransitionState(std::make_unique<StateUpdateError>(&component));
+ }
+}
+
+Component::StateChecking::StateChecking(Component* component)
+ : State(component, ComponentState::kChecking) {}
+
+Component::StateChecking::~StateChecking() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+// Unlike how other states are handled, this function does not change the
+// state right away. The state transition happens when the UpdateChecker
+// calls Component::UpdateCheckComplete and |update_check_complete_| is invoked.
+// This is an artifact of how multiple components must be checked for updates
+// together but the state machine defines the transitions for one component
+// at a time.
+void Component::StateChecking::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+ DCHECK(component.crx_component());
+
+ component.last_check_ = base::TimeTicks::Now();
+ component.update_check_complete_ = base::BindOnce(
+ &Component::StateChecking::UpdateCheckComplete, base::Unretained(this));
+
+#if defined(OS_STARBOARD)
+ // Set component.is_channel_changed_ here so that it's synced with the
+ // "updaterchannelchanged" attribute in the update check request sent to
+ // Omaha.
+ component.is_channel_changed_ =
+ component.update_context_.config->IsChannelChanged();
+#endif
+
+ component.NotifyObservers(Events::COMPONENT_CHECKING_FOR_UPDATES);
+}
+
+void Component::StateChecking::UpdateCheckComplete() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ auto& component = State::component();
+ if (!component.error_code_) {
+ if (component.status_ == "ok") {
+ TransitionState(std::make_unique<StateCanUpdate>(&component));
+ return;
+ }
+
+ if (component.status_ == "noupdate") {
+ if (component.action_run_.empty())
+ TransitionState(std::make_unique<StateUpToDate>(&component));
+ else
+ TransitionState(std::make_unique<StateRun>(&component));
+ return;
+ }
+ }
+
+ TransitionState(std::make_unique<StateUpdateError>(&component));
+}
+
+Component::StateUpdateError::StateUpdateError(Component* component)
+ : State(component, ComponentState::kUpdateError) {}
+
+Component::StateUpdateError::~StateUpdateError() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdateError::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+
+ DCHECK_NE(ErrorCategory::kNone, component.error_category_);
+ DCHECK_NE(0, component.error_code_);
+
+ // Create an event only when the server response included an update.
+ if (component.IsUpdateAvailable())
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ EndState();
+ component.NotifyObservers(Events::COMPONENT_UPDATE_ERROR);
+}
+
+Component::StateCanUpdate::StateCanUpdate(Component* component)
+ : State(component, ComponentState::kCanUpdate) {}
+
+Component::StateCanUpdate::~StateCanUpdate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateCanUpdate::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+ DCHECK(component.crx_component());
+
+ component.is_update_available_ = true;
+ component.NotifyObservers(Events::COMPONENT_UPDATE_FOUND);
+
+ if (component.crx_component()
+ ->supports_group_policy_enable_component_updates &&
+ !component.update_context_.enabled_component_updates) {
+ component.error_category_ = ErrorCategory::kService;
+ component.error_code_ = static_cast<int>(ServiceError::UPDATE_DISABLED);
+ component.extra_code1_ = 0;
+ TransitionState(std::make_unique<StateUpdateError>(&component));
+ return;
+ }
+
+ // Start computing the cost of the this update from here on.
+ component.update_begin_ = base::TimeTicks::Now();
+
+ if (CanTryDiffUpdate())
+ TransitionState(std::make_unique<StateDownloadingDiff>(&component));
+ else
+ TransitionState(std::make_unique<StateDownloading>(&component));
+}
+
+// Returns true if a differential update is available, it has not failed yet,
+// and the configuration allows this update.
+bool Component::StateCanUpdate::CanTryDiffUpdate() const {
+ const auto& component = Component::State::component();
+ return HasDiffUpdate(component) && !component.diff_error_code_ &&
+ component.update_context_.config->EnabledDeltas();
+}
+
+Component::StateUpToDate::StateUpToDate(Component* component)
+ : State(component, ComponentState::kUpToDate) {}
+
+Component::StateUpToDate::~StateUpToDate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpToDate::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+ DCHECK(component.crx_component());
+
+ component.NotifyObservers(Events::COMPONENT_NOT_UPDATED);
+ EndState();
+}
+
+Component::StateDownloadingDiff::StateDownloadingDiff(Component* component)
+ : State(component, ComponentState::kDownloadingDiff) {}
+
+Component::StateDownloadingDiff::~StateDownloadingDiff() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateDownloadingDiff::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const auto& component = Component::State::component();
+ const auto& update_context = component.update_context_;
+
+ DCHECK(component.crx_component());
+
+ crx_downloader_ = update_context.crx_downloader_factory(
+ component.CanDoBackgroundDownload(),
+ update_context.config->GetNetworkFetcherFactory());
+
+ const auto& id = component.id_;
+ crx_downloader_->set_progress_callback(
+ base::Bind(&Component::StateDownloadingDiff::DownloadProgress,
+ base::Unretained(this), id));
+ crx_downloader_->StartDownload(
+ component.crx_diffurls_, component.hashdiff_sha256_,
+ base::BindOnce(&Component::StateDownloadingDiff::DownloadComplete,
+ base::Unretained(this), id));
+
+ component.NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+// Called when progress is being made downloading a CRX. Can be called multiple
+// times due to how the CRX downloader switches between different downloaders
+// and fallback urls.
+void Component::StateDownloadingDiff::DownloadProgress(const std::string& id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ component().NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+void Component::StateDownloadingDiff::DownloadComplete(
+ const std::string& id,
+ const CrxDownloader::Result& download_result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = Component::State::component();
+ for (const auto& download_metrics : crx_downloader_->download_metrics())
+ component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+ crx_downloader_.reset();
+
+ if (download_result.error) {
+ DCHECK(download_result.response.empty());
+ component.diff_error_category_ = ErrorCategory::kDownload;
+ component.diff_error_code_ = download_result.error;
+
+ TransitionState(std::make_unique<StateDownloading>(&component));
+ return;
+ }
+
+ component.crx_path_ = download_result.response;
+
+#if defined(OS_STARBOARD)
+ component.installation_index_ = download_result.installation_index;
+#endif
+
+ TransitionState(std::make_unique<StateUpdatingDiff>(&component));
+}
+
+Component::StateDownloading::StateDownloading(Component* component)
+ : State(component, ComponentState::kDownloading) {}
+
+Component::StateDownloading::~StateDownloading() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateDownloading::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const auto& component = Component::State::component();
+ const auto& update_context = component.update_context_;
+
+ DCHECK(component.crx_component());
+
+ crx_downloader_ = update_context.crx_downloader_factory(
+ component.CanDoBackgroundDownload(),
+ update_context.config->GetNetworkFetcherFactory());
+
+ const auto& id = component.id_;
+ crx_downloader_->set_progress_callback(
+ base::Bind(&Component::StateDownloading::DownloadProgress,
+ base::Unretained(this), id));
+ crx_downloader_->StartDownload(
+ component.crx_urls_, component.hash_sha256_,
+ base::BindOnce(&Component::StateDownloading::DownloadComplete,
+ base::Unretained(this), id));
+
+ component.NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+// Called when progress is being made downloading a CRX. Can be called multiple
+// times due to how the CRX downloader switches between different downloaders
+// and fallback urls.
+void Component::StateDownloading::DownloadProgress(const std::string& id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ component().NotifyObservers(Events::COMPONENT_UPDATE_DOWNLOADING);
+}
+
+void Component::StateDownloading::DownloadComplete(
+ const std::string& id,
+ const CrxDownloader::Result& download_result) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = Component::State::component();
+
+ for (const auto& download_metrics : crx_downloader_->download_metrics())
+ component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+ crx_downloader_.reset();
+
+ if (download_result.error) {
+ DCHECK(download_result.response.empty());
+ component.error_category_ = ErrorCategory::kDownload;
+ component.error_code_ = download_result.error;
+
+ TransitionState(std::make_unique<StateUpdateError>(&component));
+ return;
+ }
+
+ component.crx_path_ = download_result.response;
+
+#if defined(OS_STARBOARD)
+ component.installation_index_ = download_result.installation_index;
+#endif
+
+ TransitionState(std::make_unique<StateUpdating>(&component));
+}
+
+Component::StateUpdatingDiff::StateUpdatingDiff(Component* component)
+ : State(component, ComponentState::kUpdatingDiff) {}
+
+Component::StateUpdatingDiff::~StateUpdatingDiff() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdatingDiff::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const auto& component = Component::State::component();
+ const auto& update_context = component.update_context_;
+
+ DCHECK(component.crx_component());
+
+ component.NotifyObservers(Events::COMPONENT_UPDATE_READY);
+
+ base::CreateSequencedTaskRunnerWithTraits(kTaskTraits)
+ ->PostTask(
+ FROM_HERE,
+ base::BindOnce(
+ &update_client::StartInstallOnBlockingTaskRunner,
+ base::ThreadTaskRunnerHandle::Get(),
+ component.crx_component()->pk_hash, component.crx_path_,
+#if defined(OS_STARBOARD)
+ component.installation_index_,
+ update_context.update_checker->GetPersistedData(), component.id_,
+ component.next_version_.GetString(),
+ component.is_channel_changed_,
+#endif
+ component.next_fp_, component.crx_component()->installer,
+ update_context.config->GetUnzipperFactory()->Create(),
+ update_context.config->GetPatcherFactory()->Create(),
+ component.crx_component()->crx_format_requirement,
+ base::BindOnce(&Component::StateUpdatingDiff::InstallComplete,
+ base::Unretained(this))));
+}
+
+void Component::StateUpdatingDiff::InstallComplete(ErrorCategory error_category,
+ int error_code,
+ int extra_code1) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = Component::State::component();
+
+ component.diff_error_category_ = error_category;
+ component.diff_error_code_ = error_code;
+ component.diff_extra_code1_ = extra_code1;
+
+ if (component.diff_error_code_ != 0) {
+ TransitionState(std::make_unique<StateDownloading>(&component));
+ return;
+ }
+
+ DCHECK_EQ(ErrorCategory::kNone, component.diff_error_category_);
+ DCHECK_EQ(0, component.diff_error_code_);
+ DCHECK_EQ(0, component.diff_extra_code1_);
+
+ DCHECK_EQ(ErrorCategory::kNone, component.error_category_);
+ DCHECK_EQ(0, component.error_code_);
+ DCHECK_EQ(0, component.extra_code1_);
+
+ if (component.action_run_.empty())
+ TransitionState(std::make_unique<StateUpdated>(&component));
+ else
+ TransitionState(std::make_unique<StateRun>(&component));
+}
+
+Component::StateUpdating::StateUpdating(Component* component)
+ : State(component, ComponentState::kUpdating) {}
+
+Component::StateUpdating::~StateUpdating() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdating::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const auto& component = Component::State::component();
+ const auto& update_context = component.update_context_;
+
+ DCHECK(component.crx_component());
+
+ component.NotifyObservers(Events::COMPONENT_UPDATE_READY);
+
+ base::CreateSequencedTaskRunnerWithTraits(kTaskTraits)
+ ->PostTask(FROM_HERE,
+ base::BindOnce(
+ &update_client::StartInstallOnBlockingTaskRunner,
+ base::ThreadTaskRunnerHandle::Get(),
+ component.crx_component()->pk_hash, component.crx_path_,
+#if defined(OS_STARBOARD)
+ component.installation_index_,
+ update_context.update_checker->GetPersistedData(),
+ component.id_, component.next_version_.GetString(),
+ component.is_channel_changed_,
+#endif
+ component.next_fp_, component.crx_component()->installer,
+ update_context.config->GetUnzipperFactory()->Create(),
+ update_context.config->GetPatcherFactory()->Create(),
+ component.crx_component()->crx_format_requirement,
+ base::BindOnce(&Component::StateUpdating::InstallComplete,
+ base::Unretained(this))));
+}
+
+void Component::StateUpdating::InstallComplete(ErrorCategory error_category,
+ int error_code,
+ int extra_code1) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = Component::State::component();
+
+ component.error_category_ = error_category;
+ component.error_code_ = error_code;
+ component.extra_code1_ = extra_code1;
+
+ if (component.error_code_ != 0) {
+ TransitionState(std::make_unique<StateUpdateError>(&component));
+ return;
+ }
+
+ DCHECK_EQ(ErrorCategory::kNone, component.error_category_);
+ DCHECK_EQ(0, component.error_code_);
+ DCHECK_EQ(0, component.extra_code1_);
+
+ if (component.action_run_.empty())
+ TransitionState(std::make_unique<StateUpdated>(&component));
+ else
+ TransitionState(std::make_unique<StateRun>(&component));
+}
+
+Component::StateUpdated::StateUpdated(Component* component)
+ : State(component, ComponentState::kUpdated) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+Component::StateUpdated::~StateUpdated() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUpdated::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+ DCHECK(component.crx_component());
+
+ component.crx_component_->version = component.next_version_;
+ component.crx_component_->fingerprint = component.next_fp_;
+
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ component.NotifyObservers(Events::COMPONENT_UPDATED);
+ EndState();
+}
+
+Component::StateUninstalled::StateUninstalled(Component* component)
+ : State(component, ComponentState::kUninstalled) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+Component::StateUninstalled::~StateUninstalled() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateUninstalled::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+ DCHECK(component.crx_component());
+
+ component.AppendEvent(component.MakeEventUninstalled());
+
+ EndState();
+}
+
+Component::StateRun::StateRun(Component* component)
+ : State(component, ComponentState::kRun) {}
+
+Component::StateRun::~StateRun() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void Component::StateRun::DoHandle() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const auto& component = State::component();
+ DCHECK(component.crx_component());
+
+ action_runner_ = std::make_unique<ActionRunner>(component);
+ action_runner_->Run(
+ base::BindOnce(&StateRun::ActionRunComplete, base::Unretained(this)));
+}
+
+void Component::StateRun::ActionRunComplete(bool succeeded,
+ int error_code,
+ int extra_code1) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto& component = State::component();
+
+ component.AppendEvent(
+ component.MakeEventActionRun(succeeded, error_code, extra_code1));
+ switch (component.previous_state_) {
+ case ComponentState::kChecking:
+ TransitionState(std::make_unique<StateUpToDate>(&component));
+ return;
+ case ComponentState::kUpdating:
+ case ComponentState::kUpdatingDiff:
+ TransitionState(std::make_unique<StateUpdated>(&component));
+ return;
+ default:
+ break;
+ }
+ NOTREACHED();
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/component.h b/src/components/update_client/component.h
new file mode 100644
index 0000000..b674852
--- /dev/null
+++ b/src/components/update_client/component.h
@@ -0,0 +1,471 @@
+// Copyright 2017 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 COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/gtest_prod_util.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/protocol_parser.h"
+#include "components/update_client/update_client.h"
+#include "url/gurl.h"
+
+#if defined(OS_STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
+namespace base {
+class Value;
+} // namespace base
+
+namespace update_client {
+
+class ActionRunner;
+class Configurator;
+struct CrxUpdateItem;
+struct UpdateContext;
+
+// Describes a CRX component managed by the UpdateEngine. Each |Component| is
+// associated with an UpdateContext.
+class Component {
+ public:
+ using Events = UpdateClient::Observer::Events;
+
+ using CallbackHandleComplete = base::OnceCallback<void()>;
+
+ Component(const UpdateContext& update_context, const std::string& id);
+ ~Component();
+
+ // Handles the current state of the component and makes it transition
+ // to the next component state before |callback_handle_complete_| is invoked.
+ void Handle(CallbackHandleComplete callback_handle_complete);
+
+ CrxUpdateItem GetCrxUpdateItem() const;
+
+ // Sets the uninstall state for this component.
+ void Uninstall(const base::Version& cur_version, int reason);
+
+ // Called by the UpdateEngine when an update check for this component is done.
+ void SetUpdateCheckResult(
+ const base::Optional<ProtocolParser::Result>& result,
+ ErrorCategory error_category,
+ int error);
+
+ // Returns true if the component has reached a final state and no further
+ // handling and state transitions are possible.
+ bool IsHandled() const { return is_handled_; }
+
+ // Returns true if an update is available for this component, meaning that
+ // the update server has return a response containing an update.
+ bool IsUpdateAvailable() const { return is_update_available_; }
+
+ base::TimeDelta GetUpdateDuration() const;
+
+ ComponentState state() const { return state_->state(); }
+
+ std::string id() const { return id_; }
+
+ const base::Optional<CrxComponent>& crx_component() const {
+ return crx_component_;
+ }
+ void set_crx_component(const CrxComponent& crx_component) {
+ crx_component_ = crx_component;
+ }
+
+ const base::Version& previous_version() const { return previous_version_; }
+ void set_previous_version(const base::Version& previous_version) {
+ previous_version_ = previous_version;
+ }
+
+ const base::Version& next_version() const { return next_version_; }
+
+ std::string previous_fp() const { return previous_fp_; }
+ void set_previous_fp(const std::string& previous_fp) {
+ previous_fp_ = previous_fp;
+ }
+
+ std::string next_fp() const { return next_fp_; }
+ void set_next_fp(const std::string& next_fp) { next_fp_ = next_fp; }
+
+ bool is_foreground() const;
+
+ const std::vector<GURL>& crx_diffurls() const { return crx_diffurls_; }
+
+ bool diff_update_failed() const { return !!diff_error_code_; }
+
+ ErrorCategory error_category() const { return error_category_; }
+ int error_code() const { return error_code_; }
+ int extra_code1() const { return extra_code1_; }
+ ErrorCategory diff_error_category() const { return diff_error_category_; }
+ int diff_error_code() const { return diff_error_code_; }
+ int diff_extra_code1() const { return diff_extra_code1_; }
+
+ std::string action_run() const { return action_run_; }
+
+ scoped_refptr<Configurator> config() const;
+
+ std::string session_id() const;
+
+ const std::vector<base::Value>& events() const { return events_; }
+
+ // Returns a clone of the component events.
+ std::vector<base::Value> GetEvents() const;
+
+ private:
+ friend class MockPingManagerImpl;
+ friend class UpdateCheckerTest;
+
+ FRIEND_TEST_ALL_PREFIXES(PingManagerTest, SendPing);
+ FRIEND_TEST_ALL_PREFIXES(PingManagerTest, RequiresEncryption);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, NoUpdateActionRun);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckCupError);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckError);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckInvalidAp);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest,
+ UpdateCheckRequiresEncryptionError);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckSuccess);
+ FRIEND_TEST_ALL_PREFIXES(UpdateCheckerTest, UpdateCheckUpdateDisabled);
+
+ // Describes an abstraction for implementing the behavior of a component and
+ // the transition from one state to another.
+ class State {
+ public:
+ using CallbackNextState =
+ base::OnceCallback<void(std::unique_ptr<State> next_state)>;
+
+ State(Component* component, ComponentState state);
+ virtual ~State();
+
+ // Handles the current state and initiates a transition to a new state.
+ // The transition to the new state is non-blocking and it is completed
+ // by the outer component, after the current state is fully handled.
+ void Handle(CallbackNextState callback);
+
+ ComponentState state() const { return state_; }
+
+ protected:
+ // Initiates the transition to the new state.
+ void TransitionState(std::unique_ptr<State> new_state);
+
+ // Makes the current state a final state where no other state transition
+ // can further occur.
+ void EndState();
+
+ Component& component() { return component_; }
+ const Component& component() const { return component_; }
+
+ base::ThreadChecker thread_checker_;
+
+ const ComponentState state_;
+
+ private:
+ virtual void DoHandle() = 0;
+
+ Component& component_;
+ CallbackNextState callback_next_state_;
+ };
+
+ class StateNew : public State {
+ public:
+ explicit StateNew(Component* component);
+ ~StateNew() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ DISALLOW_COPY_AND_ASSIGN(StateNew);
+ };
+
+ class StateChecking : public State {
+ public:
+ explicit StateChecking(Component* component);
+ ~StateChecking() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ void UpdateCheckComplete();
+
+ DISALLOW_COPY_AND_ASSIGN(StateChecking);
+ };
+
+ class StateUpdateError : public State {
+ public:
+ explicit StateUpdateError(Component* component);
+ ~StateUpdateError() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ DISALLOW_COPY_AND_ASSIGN(StateUpdateError);
+ };
+
+ class StateCanUpdate : public State {
+ public:
+ explicit StateCanUpdate(Component* component);
+ ~StateCanUpdate() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+ bool CanTryDiffUpdate() const;
+
+ DISALLOW_COPY_AND_ASSIGN(StateCanUpdate);
+ };
+
+ class StateUpToDate : public State {
+ public:
+ explicit StateUpToDate(Component* component);
+ ~StateUpToDate() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ DISALLOW_COPY_AND_ASSIGN(StateUpToDate);
+ };
+
+ class StateDownloadingDiff : public State {
+ public:
+ explicit StateDownloadingDiff(Component* component);
+ ~StateDownloadingDiff() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ // Called when progress is being made downloading a CRX. Can be called
+ // multiple times due to how the CRX downloader switches between
+ // different downloaders and fallback urls.
+ void DownloadProgress(const std::string& id);
+
+ void DownloadComplete(const std::string& id,
+ const CrxDownloader::Result& download_result);
+
+ // Downloads updates for one CRX id only.
+ std::unique_ptr<CrxDownloader> crx_downloader_;
+
+ DISALLOW_COPY_AND_ASSIGN(StateDownloadingDiff);
+ };
+
+ class StateDownloading : public State {
+ public:
+ explicit StateDownloading(Component* component);
+ ~StateDownloading() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ // Called when progress is being made downloading a CRX. Can be called
+ // multiple times due to how the CRX downloader switches between
+ // different downloaders and fallback urls.
+ void DownloadProgress(const std::string& id);
+
+ void DownloadComplete(const std::string& id,
+ const CrxDownloader::Result& download_result);
+
+ // Downloads updates for one CRX id only.
+ std::unique_ptr<CrxDownloader> crx_downloader_;
+
+ DISALLOW_COPY_AND_ASSIGN(StateDownloading);
+ };
+
+ class StateUpdatingDiff : public State {
+ public:
+ explicit StateUpdatingDiff(Component* component);
+ ~StateUpdatingDiff() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ void InstallComplete(ErrorCategory error_category,
+ int error_code,
+ int extra_code1);
+
+ DISALLOW_COPY_AND_ASSIGN(StateUpdatingDiff);
+ };
+
+ class StateUpdating : public State {
+ public:
+ explicit StateUpdating(Component* component);
+ ~StateUpdating() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ void InstallComplete(ErrorCategory error_category,
+ int error_code,
+ int extra_code1);
+
+ DISALLOW_COPY_AND_ASSIGN(StateUpdating);
+ };
+
+ class StateUpdated : public State {
+ public:
+ explicit StateUpdated(Component* component);
+ ~StateUpdated() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ DISALLOW_COPY_AND_ASSIGN(StateUpdated);
+ };
+
+ class StateUninstalled : public State {
+ public:
+ explicit StateUninstalled(Component* component);
+ ~StateUninstalled() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ DISALLOW_COPY_AND_ASSIGN(StateUninstalled);
+ };
+
+ class StateRun : public State {
+ public:
+ explicit StateRun(Component* component);
+ ~StateRun() override;
+
+ private:
+ // State overrides.
+ void DoHandle() override;
+
+ void ActionRunComplete(bool succeeded, int error_code, int extra_code1);
+
+ // Runs the action referred by the |action_run_| member of the Component
+ // class.
+ std::unique_ptr<ActionRunner> action_runner_;
+
+ DISALLOW_COPY_AND_ASSIGN(StateRun);
+ };
+
+ // Returns true is the update payload for this component can be downloaded
+ // by a downloader which can do bandwidth throttling on the client side.
+ bool CanDoBackgroundDownload() const;
+
+ void AppendEvent(base::Value event);
+
+ // Changes the component state and notifies the caller of the |Handle|
+ // function that the handling of this component state is complete.
+ void ChangeState(std::unique_ptr<State> next_state);
+
+ // Notifies registered observers about changes in the state of the component.
+ void NotifyObservers(Events event) const;
+
+ void SetParseResult(const ProtocolParser::Result& result);
+
+ // These functions return a specific event. Each data member of the event is
+ // represented as a key-value pair in a dictionary value.
+ base::Value MakeEventUpdateComplete() const;
+ base::Value MakeEventDownloadMetrics(
+ const CrxDownloader::DownloadMetrics& download_metrics) const;
+ base::Value MakeEventUninstalled() const;
+ base::Value MakeEventActionRun(bool succeeded,
+ int error_code,
+ int extra_code1) const;
+
+ base::ThreadChecker thread_checker_;
+
+ const std::string id_;
+ base::Optional<CrxComponent> crx_component_;
+
+ // The status of the updatecheck response.
+ std::string status_;
+
+ // Time when an update check for this CRX has happened.
+ base::TimeTicks last_check_;
+
+ // Time when the update of this CRX has begun.
+ base::TimeTicks update_begin_;
+
+ // A component can be made available for download from several urls.
+ std::vector<GURL> crx_urls_;
+ std::vector<GURL> crx_diffurls_;
+
+ // The cryptographic hash values for the component payload.
+ std::string hash_sha256_;
+ std::string hashdiff_sha256_;
+
+ // The from/to version and fingerprint values.
+ base::Version previous_version_;
+ base::Version next_version_;
+ std::string previous_fp_;
+ std::string next_fp_;
+
+ // Contains the file name of the payload to run. This member is set by
+ // the update response parser, when the update response includes a run action.
+ std::string action_run_;
+
+ // True if the update check response for this component includes an update.
+ bool is_update_available_ = false;
+
+ // The error reported by the update checker.
+ int update_check_error_ = 0;
+
+ base::FilePath crx_path_;
+
+#if defined(OS_STARBOARD)
+ int installation_index_ = IM_EXT_INVALID_INDEX;
+ bool is_channel_changed_ = false;
+#endif
+
+ // The error information for full and differential updates.
+ // The |error_category| contains a hint about which module in the component
+ // updater generated the error. The |error_code| constains the error and
+ // the |extra_code1| usually contains a system error, but it can contain
+ // any extended information that is relevant to either the category or the
+ // error itself.
+ ErrorCategory error_category_ = ErrorCategory::kNone;
+ int error_code_ = 0;
+ int extra_code1_ = 0;
+ ErrorCategory diff_error_category_ = ErrorCategory::kNone;
+ int diff_error_code_ = 0;
+ int diff_extra_code1_ = 0;
+
+ // Contains the events which are therefore serialized in the requests.
+ std::vector<base::Value> events_;
+
+ CallbackHandleComplete callback_handle_complete_;
+ std::unique_ptr<State> state_;
+ const UpdateContext& update_context_;
+
+ base::OnceClosure update_check_complete_;
+
+ ComponentState previous_state_ = ComponentState::kLastStatus;
+
+ // True if this component has reached a final state because all its states
+ // have been handled.
+ bool is_handled_ = false;
+
+ DISALLOW_COPY_AND_ASSIGN(Component);
+};
+
+using IdToComponentPtrMap = std::map<std::string, std::unique_ptr<Component>>;
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_H_
diff --git a/src/components/update_client/component_patcher.cc b/src/components/update_client/component_patcher.cc
new file mode 100644
index 0000000..9a8feaa
--- /dev/null
+++ b/src/components/update_client/component_patcher.cc
@@ -0,0 +1,117 @@
+// 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 "components/update_client/component_patcher.h"
+
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/location.h"
+#include "base/memory/weak_ptr.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "base/values.h"
+#include "components/update_client/component_patcher_operation.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+namespace {
+
+// Deserialize the commands file (present in delta update packages). The top
+// level must be a list.
+base::ListValue* ReadCommands(const base::FilePath& unpack_path) {
+ const base::FilePath commands =
+ unpack_path.Append(FILE_PATH_LITERAL("commands.json"));
+ if (!base::PathExists(commands))
+ return nullptr;
+
+ JSONFileValueDeserializer deserializer(commands);
+ std::unique_ptr<base::Value> root =
+ deserializer.Deserialize(nullptr, nullptr);
+
+ return (root.get() && root->is_list())
+ ? static_cast<base::ListValue*>(root.release())
+ : nullptr;
+}
+
+} // namespace
+
+ComponentPatcher::ComponentPatcher(const base::FilePath& input_dir,
+ const base::FilePath& unpack_dir,
+ scoped_refptr<CrxInstaller> installer,
+ scoped_refptr<Patcher> patcher)
+ : input_dir_(input_dir),
+ unpack_dir_(unpack_dir),
+ installer_(installer),
+ patcher_(patcher) {}
+
+ComponentPatcher::~ComponentPatcher() {}
+
+void ComponentPatcher::Start(Callback callback) {
+ callback_ = std::move(callback);
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ComponentPatcher::StartPatching,
+ scoped_refptr<ComponentPatcher>(this)));
+}
+
+void ComponentPatcher::StartPatching() {
+ commands_.reset(ReadCommands(input_dir_));
+ if (!commands_) {
+ DonePatching(UnpackerError::kDeltaBadCommands, 0);
+ } else {
+ next_command_ = commands_->begin();
+ PatchNextFile();
+ }
+}
+
+void ComponentPatcher::PatchNextFile() {
+ if (next_command_ == commands_->end()) {
+ DonePatching(UnpackerError::kNone, 0);
+ return;
+ }
+ const base::DictionaryValue* command_args;
+ if (!next_command_->GetAsDictionary(&command_args)) {
+ DonePatching(UnpackerError::kDeltaBadCommands, 0);
+ return;
+ }
+
+ std::string operation;
+ if (command_args->GetString(kOp, &operation)) {
+ current_operation_ = CreateDeltaUpdateOp(operation, patcher_);
+ }
+
+ if (!current_operation_) {
+ DonePatching(UnpackerError::kDeltaUnsupportedCommand, 0);
+ return;
+ }
+ current_operation_->Run(
+ command_args, input_dir_, unpack_dir_, installer_,
+ base::BindOnce(&ComponentPatcher::DonePatchingFile,
+ scoped_refptr<ComponentPatcher>(this)));
+}
+
+void ComponentPatcher::DonePatchingFile(UnpackerError error,
+ int extended_error) {
+ if (error != UnpackerError::kNone) {
+ DonePatching(error, extended_error);
+ } else {
+ ++next_command_;
+ PatchNextFile();
+ }
+}
+
+void ComponentPatcher::DonePatching(UnpackerError error, int extended_error) {
+ current_operation_ = nullptr;
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), error, extended_error));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/component_patcher.h b/src/components/update_client/component_patcher.h
new file mode 100644
index 0000000..e31a812
--- /dev/null
+++ b/src/components/update_client/component_patcher.h
@@ -0,0 +1,103 @@
+// 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.
+
+// Component updates can be either differential updates or full updates.
+// Full updates come in CRX format; differential updates come in CRX-style
+// archives, but have a different magic number. They contain "commands.json", a
+// list of commands for the patcher to follow. The patcher uses these commands,
+// the other files in the archive, and the files from the existing installation
+// of the component to create the contents of a full update, which is then
+// installed normally.
+// Component updates are specified by the 'codebasediff' attribute of an
+// updatecheck response:
+// <updatecheck codebase="http://example.com/extension_1.2.3.4.crx"
+// hash="12345" size="9854" status="ok" version="1.2.3.4"
+// prodversionmin="2.0.143.0"
+// codebasediff="http://example.com/diff_1.2.3.4.crx"
+// hashdiff="123" sizediff="101"
+// fp="1.123"/>
+// The component updater will attempt a differential update if it is available
+// and allowed to, and fall back to a full update if it fails.
+//
+// After installation (diff or full), the component updater records "fp", the
+// fingerprint of the installed files, to later identify the existing files to
+// the server so that a proper differential update can be provided next cycle.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_
+
+#include <memory>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/values.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class FilePath;
+}
+
+namespace update_client {
+
+class CrxInstaller;
+class DeltaUpdateOp;
+enum class UnpackerError;
+
+// The type of a patch file.
+enum PatchType {
+ kPatchTypeUnknown,
+ kPatchTypeCourgette,
+ kPatchTypeBsdiff,
+};
+
+// Encapsulates a task for applying a differential update to a component.
+class ComponentPatcher : public base::RefCountedThreadSafe<ComponentPatcher> {
+ public:
+ using Callback = base::OnceCallback<void(UnpackerError, int)>;
+
+ // Takes an unpacked differential CRX (|input_dir|) and a component installer,
+ // and sets up the class to create a new (non-differential) unpacked CRX.
+ // If |in_process| is true, patching will be done completely within the
+ // existing process. Otherwise, some steps of patching may be done
+ // out-of-process.
+ ComponentPatcher(const base::FilePath& input_dir,
+ const base::FilePath& unpack_dir,
+ scoped_refptr<CrxInstaller> installer,
+ scoped_refptr<Patcher> patcher);
+
+ // Starts patching files. This member function returns immediately, after
+ // posting a task to do the patching. When patching has been completed,
+ // |callback| will be called with the error codes if any error codes were
+ // encountered.
+ void Start(Callback callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<ComponentPatcher>;
+
+ virtual ~ComponentPatcher();
+
+ void StartPatching();
+
+ void PatchNextFile();
+
+ void DonePatchingFile(UnpackerError error, int extended_error);
+
+ void DonePatching(UnpackerError error, int extended_error);
+
+ const base::FilePath input_dir_;
+ const base::FilePath unpack_dir_;
+ scoped_refptr<CrxInstaller> installer_;
+ scoped_refptr<Patcher> patcher_;
+ Callback callback_;
+ std::unique_ptr<base::ListValue> commands_;
+ base::ListValue::const_iterator next_command_;
+ scoped_refptr<DeltaUpdateOp> current_operation_;
+
+ DISALLOW_COPY_AND_ASSIGN(ComponentPatcher);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_H_
diff --git a/src/components/update_client/component_patcher_operation.cc b/src/components/update_client/component_patcher_operation.cc
new file mode 100644
index 0000000..e07a216
--- /dev/null
+++ b/src/components/update_client/component_patcher_operation.cc
@@ -0,0 +1,223 @@
+// 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 "components/update_client/component_patcher_operation.h"
+
+#include <stdint.h>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/location.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/task/post_task.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/courgette/courgette.h"
+#include "components/courgette/third_party/bsdiff/bsdiff.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+namespace {
+
+const char kOutput[] = "output";
+const char kSha256[] = "sha256";
+
+// The integer offset disambiguates between overlapping error ranges.
+const int kCourgetteErrorOffset = 300;
+const int kBsdiffErrorOffset = 600;
+
+} // namespace
+
+const char kOp[] = "op";
+const char kBsdiff[] = "bsdiff";
+const char kCourgette[] = "courgette";
+const char kInput[] = "input";
+const char kPatch[] = "patch";
+
+DeltaUpdateOp* CreateDeltaUpdateOp(const std::string& operation,
+ scoped_refptr<Patcher> patcher) {
+ if (operation == "copy") {
+ return new DeltaUpdateOpCopy();
+ } else if (operation == "create") {
+ return new DeltaUpdateOpCreate();
+ } else if (operation == "bsdiff" || operation == "courgette") {
+ return new DeltaUpdateOpPatch(operation, patcher);
+ }
+ return nullptr;
+}
+
+DeltaUpdateOp::DeltaUpdateOp() {}
+
+DeltaUpdateOp::~DeltaUpdateOp() {}
+
+void DeltaUpdateOp::Run(const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ const base::FilePath& unpack_dir,
+ scoped_refptr<CrxInstaller> installer,
+ ComponentPatcher::Callback callback) {
+ callback_ = std::move(callback);
+ std::string output_rel_path;
+ if (!command_args->GetString(kOutput, &output_rel_path) ||
+ !command_args->GetString(kSha256, &output_sha256_)) {
+ DoneRunning(UnpackerError::kDeltaBadCommands, 0);
+ return;
+ }
+
+ output_abs_path_ =
+ unpack_dir.Append(base::FilePath::FromUTF8Unsafe(output_rel_path));
+ UnpackerError parse_result =
+ DoParseArguments(command_args, input_dir, installer);
+ if (parse_result != UnpackerError::kNone) {
+ DoneRunning(parse_result, 0);
+ return;
+ }
+
+ const base::FilePath parent = output_abs_path_.DirName();
+ if (!base::DirectoryExists(parent)) {
+ if (!base::CreateDirectory(parent)) {
+ DoneRunning(UnpackerError::kIoError, 0);
+ return;
+ }
+ }
+
+ DoRun(base::BindOnce(&DeltaUpdateOp::DoneRunning,
+ scoped_refptr<DeltaUpdateOp>(this)));
+}
+
+void DeltaUpdateOp::DoneRunning(UnpackerError error, int extended_error) {
+ if (error == UnpackerError::kNone)
+ error = CheckHash();
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), error, extended_error));
+}
+
+// Uses the hash as a checksum to confirm that the file now residing in the
+// output directory probably has the contents it should.
+UnpackerError DeltaUpdateOp::CheckHash() {
+ return VerifyFileHash256(output_abs_path_, output_sha256_)
+ ? UnpackerError::kNone
+ : UnpackerError::kDeltaVerificationFailure;
+}
+
+DeltaUpdateOpCopy::DeltaUpdateOpCopy() {}
+
+DeltaUpdateOpCopy::~DeltaUpdateOpCopy() {}
+
+UnpackerError DeltaUpdateOpCopy::DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) {
+ std::string input_rel_path;
+ if (!command_args->GetString(kInput, &input_rel_path))
+ return UnpackerError::kDeltaBadCommands;
+
+ if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_))
+ return UnpackerError::kDeltaMissingExistingFile;
+
+ return UnpackerError::kNone;
+}
+
+void DeltaUpdateOpCopy::DoRun(ComponentPatcher::Callback callback) {
+ if (!base::CopyFile(input_abs_path_, output_abs_path_))
+ std::move(callback).Run(UnpackerError::kDeltaOperationFailure, 0);
+ else
+ std::move(callback).Run(UnpackerError::kNone, 0);
+}
+
+DeltaUpdateOpCreate::DeltaUpdateOpCreate() {}
+
+DeltaUpdateOpCreate::~DeltaUpdateOpCreate() {}
+
+UnpackerError DeltaUpdateOpCreate::DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) {
+ std::string patch_rel_path;
+ if (!command_args->GetString(kPatch, &patch_rel_path))
+ return UnpackerError::kDeltaBadCommands;
+
+ patch_abs_path_ =
+ input_dir.Append(base::FilePath::FromUTF8Unsafe(patch_rel_path));
+
+ return UnpackerError::kNone;
+}
+
+void DeltaUpdateOpCreate::DoRun(ComponentPatcher::Callback callback) {
+#if !defined(OS_STARBOARD)
+ if (!base::Move(patch_abs_path_, output_abs_path_))
+ std::move(callback).Run(UnpackerError::kDeltaOperationFailure, 0);
+ else
+ std::move(callback).Run(UnpackerError::kNone, 0);
+#else
+ std::move(callback).Run(UnpackerError::kDeltaOperationFailure, 0);
+#endif
+}
+
+DeltaUpdateOpPatch::DeltaUpdateOpPatch(const std::string& operation,
+ scoped_refptr<Patcher> patcher)
+ : operation_(operation), patcher_(patcher) {
+ DCHECK(operation == kBsdiff || operation == kCourgette);
+}
+
+DeltaUpdateOpPatch::~DeltaUpdateOpPatch() {}
+
+UnpackerError DeltaUpdateOpPatch::DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) {
+ std::string patch_rel_path;
+ std::string input_rel_path;
+ if (!command_args->GetString(kPatch, &patch_rel_path) ||
+ !command_args->GetString(kInput, &input_rel_path))
+ return UnpackerError::kDeltaBadCommands;
+
+ if (!installer->GetInstalledFile(input_rel_path, &input_abs_path_))
+ return UnpackerError::kDeltaMissingExistingFile;
+
+ patch_abs_path_ =
+ input_dir.Append(base::FilePath::FromUTF8Unsafe(patch_rel_path));
+
+ return UnpackerError::kNone;
+}
+
+void DeltaUpdateOpPatch::DoRun(ComponentPatcher::Callback callback) {
+ if (operation_ == kBsdiff) {
+ patcher_->PatchBsdiff(input_abs_path_, patch_abs_path_, output_abs_path_,
+ base::BindOnce(&DeltaUpdateOpPatch::DonePatching,
+ this, std::move(callback)));
+ } else {
+ patcher_->PatchCourgette(input_abs_path_, patch_abs_path_, output_abs_path_,
+ base::BindOnce(&DeltaUpdateOpPatch::DonePatching,
+ this, std::move(callback)));
+ }
+}
+
+void DeltaUpdateOpPatch::DonePatching(ComponentPatcher::Callback callback,
+ int result) {
+ if (operation_ == kBsdiff) {
+ if (result == bsdiff::OK) {
+ std::move(callback).Run(UnpackerError::kNone, 0);
+ } else {
+ std::move(callback).Run(UnpackerError::kDeltaOperationFailure,
+ result + kBsdiffErrorOffset);
+ }
+ } else if (operation_ == kCourgette) {
+ if (result == courgette::C_OK) {
+ std::move(callback).Run(UnpackerError::kNone, 0);
+ } else {
+ std::move(callback).Run(UnpackerError::kDeltaOperationFailure,
+ result + kCourgetteErrorOffset);
+ }
+ } else {
+ NOTREACHED();
+ }
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/component_patcher_operation.h b/src/components/update_client/component_patcher_operation.h
new file mode 100644
index 0000000..0649ff9
--- /dev/null
+++ b/src/components/update_client/component_patcher_operation.h
@@ -0,0 +1,163 @@
+// 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 COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_
+
+#include <string>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/component_patcher.h"
+#include "components/update_client/component_unpacker.h"
+
+namespace base {
+class DictionaryValue;
+} // namespace base
+
+namespace update_client {
+
+extern const char kOp[];
+extern const char kBsdiff[];
+extern const char kCourgette[];
+extern const char kInput[];
+extern const char kPatch[];
+
+class CrxInstaller;
+class Patcher;
+enum class UnpackerError;
+
+class DeltaUpdateOp : public base::RefCountedThreadSafe<DeltaUpdateOp> {
+ public:
+ DeltaUpdateOp();
+
+ // Parses, runs, and verifies the operation. Calls |callback| with the
+ // result of the operation. The callback is called using |task_runner|.
+ void Run(const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ const base::FilePath& unpack_dir,
+ scoped_refptr<CrxInstaller> installer,
+ ComponentPatcher::Callback callback);
+
+ protected:
+ virtual ~DeltaUpdateOp();
+
+ std::string output_sha256_;
+ base::FilePath output_abs_path_;
+
+ private:
+ friend class base::RefCountedThreadSafe<DeltaUpdateOp>;
+
+ UnpackerError CheckHash();
+
+ // Subclasses must override DoParseArguments to parse operation-specific
+ // arguments. DoParseArguments returns DELTA_OK on success; any other code
+ // represents failure.
+ virtual UnpackerError DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) = 0;
+
+ // Subclasses must override DoRun to actually perform the patching operation.
+ // They must call the provided callback when they have completed their
+ // operations. In practice, the provided callback is always for "DoneRunning".
+ virtual void DoRun(ComponentPatcher::Callback callback) = 0;
+
+ // Callback given to subclasses for when they complete their operation.
+ // Validates the output, and posts a task to the patching operation's
+ // callback.
+ void DoneRunning(UnpackerError error, int extended_error);
+
+ ComponentPatcher::Callback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOp);
+};
+
+// A 'copy' operation takes a file currently residing on the disk and moves it
+// into the unpacking directory: this represents "no change" in the file being
+// installed.
+class DeltaUpdateOpCopy : public DeltaUpdateOp {
+ public:
+ DeltaUpdateOpCopy();
+
+ private:
+ ~DeltaUpdateOpCopy() override;
+
+ // Overrides of DeltaUpdateOp.
+ UnpackerError DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) override;
+
+ void DoRun(ComponentPatcher::Callback callback) override;
+
+ base::FilePath input_abs_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCopy);
+};
+
+// A 'create' operation takes a full file that was sent in the delta update
+// archive and moves it into the unpacking directory: this represents the
+// addition of a new file, or a file so different that no bandwidth could be
+// saved by transmitting a differential update.
+class DeltaUpdateOpCreate : public DeltaUpdateOp {
+ public:
+ DeltaUpdateOpCreate();
+
+ private:
+ ~DeltaUpdateOpCreate() override;
+
+ // Overrides of DeltaUpdateOp.
+ UnpackerError DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) override;
+
+ void DoRun(ComponentPatcher::Callback callback) override;
+
+ base::FilePath patch_abs_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpCreate);
+};
+
+// Both 'bsdiff' and 'courgette' operations take an existing file on disk,
+// and a bsdiff- or Courgette-format patch file provided in the delta update
+// package, and run bsdiff or Courgette to construct an output file in the
+// unpacking directory.
+class DeltaUpdateOpPatch : public DeltaUpdateOp {
+ public:
+ DeltaUpdateOpPatch(const std::string& operation,
+ scoped_refptr<Patcher> patcher);
+
+ private:
+ ~DeltaUpdateOpPatch() override;
+
+ // Overrides of DeltaUpdateOp.
+ UnpackerError DoParseArguments(
+ const base::DictionaryValue* command_args,
+ const base::FilePath& input_dir,
+ scoped_refptr<CrxInstaller> installer) override;
+
+ void DoRun(ComponentPatcher::Callback callback) override;
+
+ // |success_code| is the code that indicates a successful patch.
+ // |result| is the code the patching operation returned.
+ void DonePatching(ComponentPatcher::Callback callback, int result);
+
+ std::string operation_;
+ scoped_refptr<Patcher> patcher_;
+ base::FilePath patch_abs_path_;
+ base::FilePath input_abs_path_;
+
+ DISALLOW_COPY_AND_ASSIGN(DeltaUpdateOpPatch);
+};
+
+DeltaUpdateOp* CreateDeltaUpdateOp(const std::string& operation,
+ scoped_refptr<Patcher> patcher);
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_OPERATION_H_
diff --git a/src/components/update_client/component_patcher_unittest.cc b/src/components/update_client/component_patcher_unittest.cc
new file mode 100644
index 0000000..0995703
--- /dev/null
+++ b/src/components/update_client/component_patcher_unittest.cc
@@ -0,0 +1,209 @@
+// Copyright 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 "components/update_client/component_patcher.h"
+#include "base/base_paths.h"
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/values.h"
+#include "components/courgette/courgette.h"
+#include "components/courgette/third_party/bsdiff/bsdiff.h"
+#include "components/services/patch/in_process_file_patcher.h"
+#include "components/update_client/component_patcher_operation.h"
+#include "components/update_client/component_patcher_unittest.h"
+#include "components/update_client/patch/patch_impl.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/update_client_errors.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestCallback {
+ public:
+ TestCallback();
+ virtual ~TestCallback() {}
+ void Set(update_client::UnpackerError error, int extra_code);
+
+ update_client::UnpackerError error_;
+ int extra_code_;
+ bool called_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestCallback);
+};
+
+TestCallback::TestCallback()
+ : error_(update_client::UnpackerError::kNone),
+ extra_code_(-1),
+ called_(false) {}
+
+void TestCallback::Set(update_client::UnpackerError error, int extra_code) {
+ error_ = error;
+ extra_code_ = extra_code;
+ called_ = true;
+}
+
+base::FilePath test_file(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("update_client")
+ .AppendASCII(file);
+}
+
+} // namespace
+
+namespace update_client {
+
+ComponentPatcherOperationTest::ComponentPatcherOperationTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO) {
+ EXPECT_TRUE(unpack_dir_.CreateUniqueTempDir());
+ EXPECT_TRUE(input_dir_.CreateUniqueTempDir());
+ EXPECT_TRUE(installed_dir_.CreateUniqueTempDir());
+ installer_ =
+ base::MakeRefCounted<ReadOnlyTestInstaller>(installed_dir_.GetPath());
+}
+
+ComponentPatcherOperationTest::~ComponentPatcherOperationTest() {}
+
+// Verify that a 'create' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckCreateOperation) {
+ EXPECT_TRUE(base::CopyFile(
+ test_file("binary_output.bin"),
+ input_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_output.bin"))));
+
+ std::unique_ptr<base::DictionaryValue> command_args =
+ std::make_unique<base::DictionaryValue>();
+ command_args->SetString("output", "output.bin");
+ command_args->SetString("sha256", binary_output_hash);
+ command_args->SetString("op", "create");
+ command_args->SetString("patch", "binary_output.bin");
+
+ TestCallback callback;
+ scoped_refptr<DeltaUpdateOp> op = base::MakeRefCounted<DeltaUpdateOpCreate>();
+ op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+ nullptr,
+ base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+ scoped_task_environment_.RunUntilIdle();
+
+ EXPECT_EQ(true, callback.called_);
+ EXPECT_EQ(UnpackerError::kNone, callback.error_);
+ EXPECT_EQ(0, callback.extra_code_);
+ EXPECT_TRUE(base::ContentsEqual(
+ unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+ test_file("binary_output.bin")));
+}
+
+// Verify that a 'copy' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckCopyOperation) {
+ EXPECT_TRUE(base::CopyFile(
+ test_file("binary_output.bin"),
+ installed_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_output.bin"))));
+
+ std::unique_ptr<base::DictionaryValue> command_args =
+ std::make_unique<base::DictionaryValue>();
+ command_args->SetString("output", "output.bin");
+ command_args->SetString("sha256", binary_output_hash);
+ command_args->SetString("op", "copy");
+ command_args->SetString("input", "binary_output.bin");
+
+ TestCallback callback;
+ scoped_refptr<DeltaUpdateOp> op = base::MakeRefCounted<DeltaUpdateOpCopy>();
+ op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+ installer_.get(),
+ base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+ scoped_task_environment_.RunUntilIdle();
+
+ EXPECT_EQ(true, callback.called_);
+ EXPECT_EQ(UnpackerError::kNone, callback.error_);
+ EXPECT_EQ(0, callback.extra_code_);
+ EXPECT_TRUE(base::ContentsEqual(
+ unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+ test_file("binary_output.bin")));
+}
+
+// Verify that a 'courgette' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckCourgetteOperation) {
+ EXPECT_TRUE(base::CopyFile(
+ test_file("binary_input.bin"),
+ installed_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_input.bin"))));
+ EXPECT_TRUE(base::CopyFile(test_file("binary_courgette_patch.bin"),
+ input_dir_.GetPath().Append(FILE_PATH_LITERAL(
+ "binary_courgette_patch.bin"))));
+
+ std::unique_ptr<base::DictionaryValue> command_args =
+ std::make_unique<base::DictionaryValue>();
+ command_args->SetString("output", "output.bin");
+ command_args->SetString("sha256", binary_output_hash);
+ command_args->SetString("op", "courgette");
+ command_args->SetString("input", "binary_input.bin");
+ command_args->SetString("patch", "binary_courgette_patch.bin");
+
+ scoped_refptr<Patcher> patcher =
+ base::MakeRefCounted<PatchChromiumFactory>(
+ base::BindRepeating(&patch::LaunchInProcessFilePatcher))
+ ->Create();
+
+ TestCallback callback;
+ scoped_refptr<DeltaUpdateOp> op = CreateDeltaUpdateOp("courgette", patcher);
+ op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+ installer_.get(),
+ base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+ scoped_task_environment_.RunUntilIdle();
+
+ EXPECT_EQ(true, callback.called_);
+ EXPECT_EQ(UnpackerError::kNone, callback.error_);
+ EXPECT_EQ(0, callback.extra_code_);
+ EXPECT_TRUE(base::ContentsEqual(
+ unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+ test_file("binary_output.bin")));
+}
+
+// Verify that a 'bsdiff' delta update operation works correctly.
+TEST_F(ComponentPatcherOperationTest, CheckBsdiffOperation) {
+ EXPECT_TRUE(base::CopyFile(
+ test_file("binary_input.bin"),
+ installed_dir_.GetPath().Append(FILE_PATH_LITERAL("binary_input.bin"))));
+ EXPECT_TRUE(base::CopyFile(test_file("binary_bsdiff_patch.bin"),
+ input_dir_.GetPath().Append(FILE_PATH_LITERAL(
+ "binary_bsdiff_patch.bin"))));
+
+ std::unique_ptr<base::DictionaryValue> command_args =
+ std::make_unique<base::DictionaryValue>();
+ command_args->SetString("output", "output.bin");
+ command_args->SetString("sha256", binary_output_hash);
+ command_args->SetString("op", "courgette");
+ command_args->SetString("input", "binary_input.bin");
+ command_args->SetString("patch", "binary_bsdiff_patch.bin");
+
+ // The operation needs a Patcher to access the PatchService.
+ scoped_refptr<Patcher> patcher =
+ base::MakeRefCounted<PatchChromiumFactory>(
+ base::BindRepeating(&patch::LaunchInProcessFilePatcher))
+ ->Create();
+
+ TestCallback callback;
+ scoped_refptr<DeltaUpdateOp> op = CreateDeltaUpdateOp("bsdiff", patcher);
+ op->Run(command_args.get(), input_dir_.GetPath(), unpack_dir_.GetPath(),
+ installer_.get(),
+ base::BindOnce(&TestCallback::Set, base::Unretained(&callback)));
+ scoped_task_environment_.RunUntilIdle();
+
+ EXPECT_EQ(true, callback.called_);
+ EXPECT_EQ(UnpackerError::kNone, callback.error_);
+ EXPECT_EQ(0, callback.extra_code_);
+ EXPECT_TRUE(base::ContentsEqual(
+ unpack_dir_.GetPath().Append(FILE_PATH_LITERAL("output.bin")),
+ test_file("binary_output.bin")));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/component_patcher_unittest.h b/src/components/update_client/component_patcher_unittest.h
new file mode 100644
index 0000000..ec5b849
--- /dev/null
+++ b/src/components/update_client/component_patcher_unittest.h
@@ -0,0 +1,41 @@
+// Copyright 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.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_UNITTEST_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_UNITTEST_H_
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/memory/ref_counted.h"
+#include "base/sequenced_task_runner.h"
+#include "base/test/scoped_task_environment.h"
+#include "components/courgette/courgette.h"
+#include "components/courgette/third_party/bsdiff/bsdiff.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+class ReadOnlyTestInstaller;
+
+const char binary_output_hash[] =
+ "599aba6d15a7da390621ef1bacb66601ed6aed04dadc1f9b445dcfe31296142a";
+
+class ComponentPatcherOperationTest : public testing::Test {
+ public:
+ ComponentPatcherOperationTest();
+ ~ComponentPatcherOperationTest() override;
+
+ protected:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::ScopedTempDir input_dir_;
+ base::ScopedTempDir installed_dir_;
+ base::ScopedTempDir unpack_dir_;
+ scoped_refptr<ReadOnlyTestInstaller> installer_;
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_PATCHER_UNITTEST_H_
diff --git a/src/components/update_client/component_unpacker.cc b/src/components/update_client/component_unpacker.cc
new file mode 100644
index 0000000..c80919e
--- /dev/null
+++ b/src/components/update_client/component_unpacker.cc
@@ -0,0 +1,205 @@
+// 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 "components/update_client/component_unpacker.h"
+
+#include <stdint.h>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_file.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/sequenced_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/component_patcher.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+ComponentUnpacker::Result::Result() {}
+
+ComponentUnpacker::ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+ const base::FilePath& path,
+ scoped_refptr<CrxInstaller> installer,
+ std::unique_ptr<Unzipper> unzipper,
+ scoped_refptr<Patcher> patcher,
+ crx_file::VerifierFormat crx_format)
+ : pk_hash_(pk_hash),
+ path_(path),
+ is_delta_(false),
+ installer_(installer),
+ unzipper_(std::move(unzipper)),
+ patcher_tool_(patcher),
+ crx_format_(crx_format),
+ error_(UnpackerError::kNone),
+#if defined(OS_STARBOARD)
+ extended_error_(0),
+ metadata_(nullptr),
+ id_(""),
+ update_version_("") {}
+#else
+ extended_error_(0) {}
+#endif
+
+#if defined(OS_STARBOARD)
+ComponentUnpacker::ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+ const base::FilePath& path,
+ scoped_refptr<CrxInstaller> installer,
+ std::unique_ptr<Unzipper> unzipper,
+ scoped_refptr<Patcher> patcher,
+ crx_file::VerifierFormat crx_format,
+ PersistedData* metadata,
+ const std::string& id,
+ const std::string& update_version)
+ : pk_hash_(pk_hash),
+ path_(path),
+ is_delta_(false),
+ installer_(installer),
+ unzipper_(std::move(unzipper)),
+ patcher_tool_(patcher),
+ crx_format_(crx_format),
+ error_(UnpackerError::kNone),
+ extended_error_(0),
+ metadata_(metadata),
+ id_(id),
+ update_version_(update_version) {}
+#endif
+
+ComponentUnpacker::~ComponentUnpacker() {}
+
+void ComponentUnpacker::Unpack(Callback callback) {
+ callback_ = std::move(callback);
+ if (!Verify() || !BeginUnzipping())
+ EndUnpacking();
+}
+
+bool ComponentUnpacker::Verify() {
+ VLOG(1) << "Verifying component: " << path_.value();
+
+ if (path_.empty()) {
+ error_ = UnpackerError::kInvalidParams;
+ return false;
+ }
+ std::vector<std::vector<uint8_t>> required_keys;
+ if (!pk_hash_.empty())
+ required_keys.push_back(pk_hash_);
+ const crx_file::VerifierResult result =
+ crx_file::Verify(path_, crx_format_, required_keys,
+ std::vector<uint8_t>(), &public_key_, nullptr);
+ if (result != crx_file::VerifierResult::OK_FULL &&
+ result != crx_file::VerifierResult::OK_DELTA) {
+ error_ = UnpackerError::kInvalidFile;
+ extended_error_ = static_cast<int>(result);
+ SB_LOG(INFO) << "Verification failed. Verifier error = " << extended_error_;
+ return false;
+ }
+ is_delta_ = result == crx_file::VerifierResult::OK_DELTA;
+ VLOG(1) << "Verification successful: " << path_.value();
+ return true;
+}
+
+bool ComponentUnpacker::BeginUnzipping() {
+ // Mind the reference to non-const type, passed as an argument below.
+ base::FilePath& destination = is_delta_ ? unpack_diff_path_ : unpack_path_;
+
+#if !defined(OS_STARBOARD)
+ if (!base::CreateNewTempDirectory(base::FilePath::StringType(),
+ &destination)) {
+ VLOG(1) << "Unable to create temporary directory for unpacking.";
+ error_ = UnpackerError::kUnzipPathError;
+ return false;
+ }
+#else
+ // The directory of path_ is the installation slot.
+ destination = path_.DirName();
+
+#endif
+ VLOG(1) << "Unpacking in: " << destination.value();
+ unzipper_->Unzip(path_, destination,
+ base::BindOnce(&ComponentUnpacker::EndUnzipping, this));
+
+ return true;
+}
+
+void ComponentUnpacker::EndUnzipping(bool result) {
+ if (!result) {
+ VLOG(1) << "Unzipping failed.";
+ error_ = UnpackerError::kUnzipFailed;
+ EndUnpacking();
+ return;
+ }
+ VLOG(1) << "Unpacked successfully";
+ BeginPatching();
+}
+
+bool ComponentUnpacker::BeginPatching() {
+ if (is_delta_) { // Package is a diff package.
+ // Use a different temp directory for the patch output files.
+ if (!base::CreateNewTempDirectory(base::FilePath::StringType(),
+ &unpack_path_)) {
+ error_ = UnpackerError::kUnzipPathError;
+ return false;
+ }
+ patcher_ = base::MakeRefCounted<ComponentPatcher>(
+ unpack_diff_path_, unpack_path_, installer_, patcher_tool_);
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&ComponentPatcher::Start, patcher_,
+ base::BindOnce(&ComponentUnpacker::EndPatching,
+ scoped_refptr<ComponentUnpacker>(this))));
+ } else {
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&ComponentUnpacker::EndPatching,
+ scoped_refptr<ComponentUnpacker>(this),
+ UnpackerError::kNone, 0));
+ }
+ return true;
+}
+
+void ComponentUnpacker::EndPatching(UnpackerError error, int extended_error) {
+ error_ = error;
+ extended_error_ = extended_error;
+ patcher_ = nullptr;
+
+ EndUnpacking();
+}
+
+void ComponentUnpacker::EndUnpacking() {
+#if !defined(OS_STARBOARD)
+ if (!unpack_diff_path_.empty())
+ base::DeleteFile(unpack_diff_path_, true);
+ if (error_ != UnpackerError::kNone && !unpack_path_.empty())
+ base::DeleteFile(unpack_path_, true);
+#else
+ // Write the version of the unpacked update package to the persisted data.
+ if (error_ == UnpackerError::kNone && metadata_ != nullptr) {
+ metadata_->SetLastUnpackedVersion(id_, update_version_);
+ }
+#endif
+
+ Result result;
+ result.error = error_;
+ result.extended_error = extended_error_;
+ if (error_ == UnpackerError::kNone) {
+ result.unpack_path = unpack_path_;
+ result.public_key = public_key_;
+ }
+
+ base::SequencedTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_), result));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/component_unpacker.h b/src/components/update_client/component_unpacker.h
new file mode 100644
index 0000000..af967f0
--- /dev/null
+++ b/src/components/update_client/component_unpacker.h
@@ -0,0 +1,172 @@
+// 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 COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_
+#define COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace crx_file {
+enum class VerifierFormat;
+}
+
+namespace update_client {
+
+class CrxInstaller;
+class ComponentPatcher;
+class Patcher;
+class Unzipper;
+
+// In charge of unpacking the component CRX package and verifying that it is
+// well formed and the cryptographic signature is correct.
+//
+// This class should be used only by the component updater. It is inspired by
+// and overlaps with code in the extension's SandboxedUnpacker.
+// The main differences are:
+// - The public key hash is full SHA256.
+// - Does not use a sandboxed unpacker. A valid component is fully trusted.
+// - The manifest can have different attributes and resources are not
+// transcoded.
+//
+// If the CRX is a delta CRX, the flow is:
+// [ComponentUpdater] [ComponentPatcher]
+// Unpack
+// \_ Verify
+// \_ Unzip
+// \_ BeginPatching ---> DifferentialUpdatePatch
+// ...
+// EndPatching <------------ ...
+// \_ EndUnpacking
+//
+// For a full CRX, the flow is:
+// [ComponentUpdater]
+// Unpack
+// \_ Verify
+// \_ Unzip
+// \_ BeginPatching
+// |
+// V
+// EndPatching
+// \_ EndUnpacking
+//
+// In both cases, if there is an error at any point, the remaining steps will
+// be skipped and EndUnpacking will be called.
+class ComponentUnpacker : public base::RefCountedThreadSafe<ComponentUnpacker> {
+ public:
+ // Contains the result of the unpacking.
+ struct Result {
+ Result();
+
+ // Unpack error: 0 indicates success.
+ UnpackerError error = UnpackerError::kNone;
+
+ // Additional error information, such as errno or last error.
+ int extended_error = 0;
+
+ // Path of the unpacked files if the unpacking was successful.
+ base::FilePath unpack_path;
+
+ // The extracted public key of the package if the unpacking was successful.
+ std::string public_key;
+ };
+
+ using Callback = base::OnceCallback<void(const Result& result)>;
+
+ // Constructs an unpacker for a specific component unpacking operation.
+ // |pk_hash| is the expected public developer key's SHA256 hash. If empty,
+ // the unpacker accepts any developer key. |path| is the current location
+ // of the CRX.
+ ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+ const base::FilePath& path,
+ scoped_refptr<CrxInstaller> installer,
+ std::unique_ptr<Unzipper> unzipper,
+ scoped_refptr<Patcher> patcher,
+ crx_file::VerifierFormat crx_format);
+
+#if defined(OS_STARBOARD)
+ // Constructs an unpacker for a specific component unpacking operation.
+ // |pk_hash| is the expected public developer key's SHA256 hash. If empty,
+ // the unpacker accepts any developer key. |path| is the current location
+ // of the CRX. This unpacker write the update package version to persisted
+ // data.
+ ComponentUnpacker(const std::vector<uint8_t>& pk_hash,
+ const base::FilePath& path,
+ scoped_refptr<CrxInstaller> installer,
+ std::unique_ptr<Unzipper> unzipper,
+ scoped_refptr<Patcher> patcher,
+ crx_file::VerifierFormat crx_format,
+ PersistedData* metadata,
+ const std::string& id,
+ const std::string& update_version);
+#endif
+
+ // Begins the actual unpacking of the files. May invoke a patcher and the
+ // component installer if the package is a differential update.
+ // Calls |callback| with the result.
+ void Unpack(Callback callback);
+
+ private:
+ friend class base::RefCountedThreadSafe<ComponentUnpacker>;
+
+ virtual ~ComponentUnpacker();
+
+ // The first step of unpacking is to verify the file. Returns false if an
+ // error is encountered, the file is malformed, or the file is incorrectly
+ // signed.
+ bool Verify();
+
+ // The second step of unpacking is to unzip. Returns false if an early error
+ // is encountered.
+ bool BeginUnzipping();
+ void EndUnzipping(bool error);
+
+ // The third step is to optionally patch files - this is a no-op for full
+ // (non-differential) updates. This step is asynchronous. Returns false if an
+ // error is encountered.
+ bool BeginPatching();
+ void EndPatching(UnpackerError error, int extended_error);
+
+ // The final step is to do clean-up for things that can't be tidied as we go.
+ // If there is an error at any step, the remaining steps are skipped and
+ // EndUnpacking is called. EndUnpacking is responsible for calling the
+ // callback provided in Unpack().
+ void EndUnpacking();
+
+ std::vector<uint8_t> pk_hash_;
+ base::FilePath path_;
+ base::FilePath unpack_path_;
+ base::FilePath unpack_diff_path_;
+ bool is_delta_;
+ scoped_refptr<ComponentPatcher> patcher_;
+ scoped_refptr<CrxInstaller> installer_;
+ Callback callback_;
+ std::unique_ptr<Unzipper> unzipper_;
+ scoped_refptr<Patcher> patcher_tool_;
+ crx_file::VerifierFormat crx_format_;
+ UnpackerError error_;
+ int extended_error_;
+ std::string public_key_;
+#if defined(OS_STARBOARD)
+ PersistedData* metadata_;
+ std::string id_;
+ std::string update_version_;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(ComponentUnpacker);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_COMPONENT_UNPACKER_H_
diff --git a/src/components/update_client/component_unpacker_unittest.cc b/src/components/update_client/component_unpacker_unittest.cc
new file mode 100644
index 0000000..617abcb
--- /dev/null
+++ b/src/components/update_client/component_unpacker_unittest.cc
@@ -0,0 +1,175 @@
+// Copyright 2016 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 <iterator>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/single_thread_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/update_client/component_unpacker.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/unzipper.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+class TestCallback {
+ public:
+ TestCallback();
+ virtual ~TestCallback() {}
+ void Set(update_client::UnpackerError error, int extra_code);
+
+ update_client::UnpackerError error_;
+ int extra_code_;
+ bool called_;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(TestCallback);
+};
+
+TestCallback::TestCallback()
+ : error_(update_client::UnpackerError::kNone),
+ extra_code_(-1),
+ called_(false) {}
+
+void TestCallback::Set(update_client::UnpackerError error, int extra_code) {
+ error_ = error;
+ extra_code_ = extra_code;
+ called_ = true;
+}
+
+base::FilePath test_file(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("update_client")
+ .AppendASCII(file);
+}
+
+} // namespace
+
+namespace update_client {
+
+class ComponentUnpackerTest : public testing::Test {
+ public:
+ ComponentUnpackerTest();
+ ~ComponentUnpackerTest() override;
+
+ void UnpackComplete(const ComponentUnpacker::Result& result);
+
+ protected:
+ void RunThreads();
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ const scoped_refptr<base::SingleThreadTaskRunner> main_thread_task_runner_ =
+ base::ThreadTaskRunnerHandle::Get();
+ base::RunLoop runloop_;
+ base::OnceClosure quit_closure_ = runloop_.QuitClosure();
+
+ ComponentUnpacker::Result result_;
+};
+
+ComponentUnpackerTest::ComponentUnpackerTest() = default;
+
+ComponentUnpackerTest::~ComponentUnpackerTest() = default;
+
+void ComponentUnpackerTest::RunThreads() {
+ runloop_.Run();
+}
+
+void ComponentUnpackerTest::UnpackComplete(
+ const ComponentUnpacker::Result& result) {
+ result_ = result;
+ main_thread_task_runner_->PostTask(FROM_HERE, std::move(quit_closure_));
+}
+
+TEST_F(ComponentUnpackerTest, UnpackFullCrx) {
+ auto config = base::MakeRefCounted<TestConfigurator>();
+ scoped_refptr<ComponentUnpacker> component_unpacker =
+ base::MakeRefCounted<ComponentUnpacker>(
+ std::vector<uint8_t>(std::begin(jebg_hash), std::end(jebg_hash)),
+ test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"), nullptr,
+ config->GetUnzipperFactory()->Create(),
+ config->GetPatcherFactory()->Create(),
+ crx_file::VerifierFormat::CRX2_OR_CRX3);
+ component_unpacker->Unpack(base::BindOnce(
+ &ComponentUnpackerTest::UnpackComplete, base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(UnpackerError::kNone, result_.error);
+ EXPECT_EQ(0, result_.extended_error);
+
+ base::FilePath unpack_path = result_.unpack_path;
+ EXPECT_FALSE(unpack_path.empty());
+ EXPECT_TRUE(base::DirectoryExists(unpack_path));
+ EXPECT_EQ(jebg_public_key, result_.public_key);
+
+ int64_t file_size = 0;
+ EXPECT_TRUE(
+ base::GetFileSize(unpack_path.AppendASCII("component1.dll"), &file_size));
+ EXPECT_EQ(1024, file_size);
+ EXPECT_TRUE(
+ base::GetFileSize(unpack_path.AppendASCII("flashtest.pem"), &file_size));
+ EXPECT_EQ(911, file_size);
+ EXPECT_TRUE(
+ base::GetFileSize(unpack_path.AppendASCII("manifest.json"), &file_size));
+ EXPECT_EQ(144, file_size);
+
+ EXPECT_TRUE(base::DeleteFile(unpack_path, true));
+}
+
+TEST_F(ComponentUnpackerTest, UnpackFileNotFound) {
+ scoped_refptr<ComponentUnpacker> component_unpacker =
+ base::MakeRefCounted<ComponentUnpacker>(
+ std::vector<uint8_t>(std::begin(jebg_hash), std::end(jebg_hash)),
+ test_file("file-not-found.crx"), nullptr, nullptr, nullptr,
+ crx_file::VerifierFormat::CRX2_OR_CRX3);
+ component_unpacker->Unpack(base::BindOnce(
+ &ComponentUnpackerTest::UnpackComplete, base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(UnpackerError::kInvalidFile, result_.error);
+ EXPECT_EQ(static_cast<int>(crx_file::VerifierResult::ERROR_FILE_NOT_READABLE),
+ result_.extended_error);
+
+ EXPECT_TRUE(result_.unpack_path.empty());
+}
+
+// Tests a mismatch between the public key hash and the id of the component.
+TEST_F(ComponentUnpackerTest, UnpackFileHashMismatch) {
+ scoped_refptr<ComponentUnpacker> component_unpacker =
+ base::MakeRefCounted<ComponentUnpacker>(
+ std::vector<uint8_t>(std::begin(abag_hash), std::end(abag_hash)),
+ test_file("jebgalgnebhfojomionfpkfelancnnkf.crx"), nullptr, nullptr,
+ nullptr, crx_file::VerifierFormat::CRX2_OR_CRX3);
+ component_unpacker->Unpack(base::BindOnce(
+ &ComponentUnpackerTest::UnpackComplete, base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(UnpackerError::kInvalidFile, result_.error);
+ EXPECT_EQ(
+ static_cast<int>(crx_file::VerifierResult::ERROR_REQUIRED_PROOF_MISSING),
+ result_.extended_error);
+
+ EXPECT_TRUE(result_.unpack_path.empty());
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/configurator.h b/src/components/update_client/configurator.h
new file mode 100644
index 0000000..5baa3cc
--- /dev/null
+++ b/src/components/update_client/configurator.h
@@ -0,0 +1,185 @@
+// 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 COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_
+#define COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/memory/ref_counted.h"
+
+class GURL;
+class PrefService;
+
+namespace base {
+class FilePath;
+class Version;
+} // namespace base
+
+namespace update_client {
+
+class ActivityDataService;
+class NetworkFetcherFactory;
+class PatcherFactory;
+class ProtocolHandlerFactory;
+class UnzipperFactory;
+
+using RecoveryCRXElevator = base::OnceCallback<std::tuple<bool, int, int>(
+ const base::FilePath& crx_path,
+ const std::string& browser_appid,
+ const std::string& browser_version,
+ const std::string& session_id)>;
+
+// Controls the component updater behavior.
+// TODO(sorin): this class will be split soon in two. One class controls
+// the behavior of the update client, and the other class controls the
+// behavior of the component updater.
+class Configurator : public base::RefCountedThreadSafe<Configurator> {
+ public:
+ // Delay in seconds from calling Start() to the first update check.
+ virtual int InitialDelay() const = 0;
+
+ // Delay in seconds to every subsequent update check. 0 means don't check.
+ virtual int NextCheckDelay() const = 0;
+
+ // Minimum delta time in seconds before an on-demand check is allowed
+ // for the same component.
+ virtual int OnDemandDelay() const = 0;
+
+ // The time delay in seconds between applying updates for different
+ // components.
+ virtual int UpdateDelay() const = 0;
+
+ // The URLs for the update checks. The URLs are tried in order, the first one
+ // that succeeds wins.
+ virtual std::vector<GURL> UpdateUrl() const = 0;
+
+ // The URLs for pings. Returns an empty vector if and only if pings are
+ // disabled. Similarly, these URLs have a fall back behavior too.
+ virtual std::vector<GURL> PingUrl() const = 0;
+
+ // The ProdId is used as a prefix in some of the version strings which appear
+ // in the protocol requests. Possible values include "chrome", "chromecrx",
+ // "chromiumcrx", and "unknown".
+ virtual std::string GetProdId() const = 0;
+
+ // Version of the application. Used to compare the component manifests.
+ virtual base::Version GetBrowserVersion() const = 0;
+
+ // Returns the value we use for the "updaterchannel=" and "prodchannel="
+ // parameters. Possible return values include: "canary", "dev", "beta", and
+ // "stable".
+ virtual std::string GetChannel() const = 0;
+
+ // Returns the brand code or distribution tag that has been assigned to
+ // a partner. A brand code is a 4-character string used to identify
+ // installations that took place as a result of partner deals or website
+ // promotions.
+ virtual std::string GetBrand() const = 0;
+
+ // Returns the language for the present locale. Possible return values are
+ // standard tags for languages, such as "en", "en-US", "de", "fr", "af", etc.
+ virtual std::string GetLang() const = 0;
+
+ // Returns the OS's long name like "Windows", "Mac OS X", etc.
+ virtual std::string GetOSLongName() const = 0;
+
+ // Parameters added to each url request. It can be empty if none are needed.
+ // Returns a map of name-value pairs that match ^[-_a-zA-Z0-9]$ regex.
+ virtual base::flat_map<std::string, std::string> ExtraRequestParams()
+ const = 0;
+
+ // Provides a hint for the server to control the order in which multiple
+ // download urls are returned. The hint may or may not be honored in the
+ // response returned by the server.
+ // Returns an empty string if no policy is in effect.
+ virtual std::string GetDownloadPreference() const = 0;
+
+ virtual scoped_refptr<NetworkFetcherFactory> GetNetworkFetcherFactory() = 0;
+
+ virtual scoped_refptr<UnzipperFactory> GetUnzipperFactory() = 0;
+
+ virtual scoped_refptr<PatcherFactory> GetPatcherFactory() = 0;
+
+ // True means that this client can handle delta updates.
+ virtual bool EnabledDeltas() const = 0;
+
+ // True if component updates are enabled. Updates for all components are
+ // enabled by default. This method allows enabling or disabling
+ // updates for certain components such as the plugins. Updates for some
+ // components are always enabled and can't be disabled programatically.
+ virtual bool EnabledComponentUpdates() const = 0;
+
+ // True means that the background downloader can be used for downloading
+ // non on-demand components.
+ virtual bool EnabledBackgroundDownloader() const = 0;
+
+ // True if signing of update checks is enabled.
+ virtual bool EnabledCupSigning() const = 0;
+
+ // Returns a PrefService that the update_client can use to store persistent
+ // update information. The PrefService must outlive the entire update_client,
+ // and be safe to access from the thread the update_client is constructed
+ // on.
+ // Returning null is safe and will disable any functionality that requires
+ // persistent storage.
+ virtual PrefService* GetPrefService() const = 0;
+
+ // Returns an ActivityDataService that the update_client can use to access
+ // to update information (namely active bit, last active/rollcall days)
+ // normally stored in the user extension profile.
+ // Similar to PrefService, ActivityDataService must outlive the entire
+ // update_client, and be safe to access from the thread the update_client
+ // is constructed on.
+ // Returning null is safe and will disable any functionality that requires
+ // accessing to the information provided by ActivityDataService.
+ virtual ActivityDataService* GetActivityDataService() const = 0;
+
+ // Returns true if the Chrome is installed for the current user only, or false
+ // if Chrome is installed for all users on the machine. This function must be
+ // called only from a blocking pool thread, as it may access the file system.
+ virtual bool IsPerUserInstall() const = 0;
+
+ // Returns the key hash corresponding to a CRX trusted by ActionRun. The
+ // CRX payloads are signed with this key, and their integrity is verified
+ // during the unpacking by the action runner. This is a dependency injection
+ // feature to support testing.
+ virtual std::vector<uint8_t> GetRunActionKeyHash() const = 0;
+
+ // Returns the app GUID with which Chrome is registered with Google Update, or
+ // an empty string if this brand does not integrate with Google Update.
+ virtual std::string GetAppGuid() const = 0;
+
+ // Returns the class factory to create protocol parser and protocol
+ // serializer object instances.
+ virtual std::unique_ptr<ProtocolHandlerFactory> GetProtocolHandlerFactory()
+ const = 0;
+
+ // Returns a callback which can elevate and run the CRX payload associated
+ // with the improved recovery component. Running this payload repairs the
+ // Chrome update functionality.
+ virtual RecoveryCRXElevator GetRecoveryCRXElevator() const = 0;
+
+#if defined(OS_STARBOARD)
+ // Sets the value we use for the "updaterchannel=" and "prodchannel="
+ // parameters.
+ virtual void SetChannel(const std::string& channel) = 0;
+
+ virtual bool IsChannelChanged() const = 0;
+#endif
+
+ protected:
+ friend class base::RefCountedThreadSafe<Configurator>;
+
+ virtual ~Configurator() {}
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_CONFIGURATOR_H_
diff --git a/src/components/update_client/crx_downloader.cc b/src/components/update_client/crx_downloader.cc
new file mode 100644
index 0000000..35cac8e
--- /dev/null
+++ b/src/components/update_client/crx_downloader.cc
@@ -0,0 +1,217 @@
+// 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 "components/update_client/crx_downloader.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#if defined(OS_WIN)
+#include "components/update_client/background_downloader_win.h"
+#endif
+#include "components/update_client/network.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/url_fetcher_downloader.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+CrxDownloader::DownloadMetrics::DownloadMetrics()
+ : downloader(kNone),
+ error(0),
+ downloaded_bytes(-1),
+ total_bytes(-1),
+ download_time_ms(0) {}
+
+// On Windows, the first downloader in the chain is a background downloader,
+// which uses the BITS service.
+std::unique_ptr<CrxDownloader> CrxDownloader::Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ std::unique_ptr<CrxDownloader> url_fetcher_downloader =
+ std::make_unique<UrlFetcherDownloader>(nullptr, network_fetcher_factory);
+
+#if defined(OS_WIN)
+ if (is_background_download) {
+ return std::make_unique<BackgroundDownloader>(
+ std::move(url_fetcher_downloader));
+ }
+#endif
+
+ return url_fetcher_downloader;
+}
+
+CrxDownloader::CrxDownloader(std::unique_ptr<CrxDownloader> successor)
+ : main_task_runner_(base::ThreadTaskRunnerHandle::Get()),
+ successor_(std::move(successor)) {}
+
+CrxDownloader::~CrxDownloader() {}
+
+void CrxDownloader::set_progress_callback(
+ const ProgressCallback& progress_callback) {
+ progress_callback_ = progress_callback;
+}
+
+GURL CrxDownloader::url() const {
+ return current_url_ != urls_.end() ? *current_url_ : GURL();
+}
+
+const std::vector<CrxDownloader::DownloadMetrics>
+CrxDownloader::download_metrics() const {
+ if (!successor_)
+ return download_metrics_;
+
+ std::vector<DownloadMetrics> retval(successor_->download_metrics());
+ retval.insert(retval.begin(), download_metrics_.begin(),
+ download_metrics_.end());
+ return retval;
+}
+
+void CrxDownloader::StartDownloadFromUrl(const GURL& url,
+ const std::string& expected_hash,
+ DownloadCallback download_callback) {
+ std::vector<GURL> urls;
+ urls.push_back(url);
+ StartDownload(urls, expected_hash, std::move(download_callback));
+}
+
+void CrxDownloader::StartDownload(const std::vector<GURL>& urls,
+ const std::string& expected_hash,
+ DownloadCallback download_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto error = CrxDownloaderError::NONE;
+ if (urls.empty()) {
+ error = CrxDownloaderError::NO_URL;
+ } else if (expected_hash.empty()) {
+ error = CrxDownloaderError::NO_HASH;
+ }
+
+ if (error != CrxDownloaderError::NONE) {
+ Result result;
+ result.error = static_cast<int>(error);
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(download_callback), result));
+ return;
+ }
+
+ urls_ = urls;
+ expected_hash_ = expected_hash;
+ current_url_ = urls_.begin();
+ download_callback_ = std::move(download_callback);
+
+ DoStartDownload(*current_url_);
+}
+
+void CrxDownloader::OnDownloadComplete(
+ bool is_handled,
+ const Result& result,
+ const DownloadMetrics& download_metrics) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (!result.error)
+ base::PostTaskWithTraits(
+ FROM_HERE, kTaskTraits,
+ base::BindOnce(&CrxDownloader::VerifyResponse, base::Unretained(this),
+ is_handled, result, download_metrics));
+ else
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&CrxDownloader::HandleDownloadError,
+ base::Unretained(this), is_handled, result,
+ download_metrics));
+}
+
+void CrxDownloader::OnDownloadProgress() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (progress_callback_.is_null())
+ return;
+
+ progress_callback_.Run();
+}
+
+// The function mutates the values of the parameters |result| and
+// |download_metrics|.
+void CrxDownloader::VerifyResponse(bool is_handled,
+ Result result,
+ DownloadMetrics download_metrics) {
+ DCHECK_EQ(0, result.error);
+ DCHECK_EQ(0, download_metrics.error);
+ DCHECK(is_handled);
+
+ if (VerifyFileHash256(result.response, expected_hash_)) {
+ download_metrics_.push_back(download_metrics);
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(download_callback_), result));
+ return;
+ }
+
+ // The download was successful but the response is not trusted. Clean up
+ // the download, mutate the result, and try the remaining fallbacks when
+ // handling the error.
+ result.error = static_cast<int>(CrxDownloaderError::BAD_HASH);
+ download_metrics.error = result.error;
+#if defined(OS_STARBOARD)
+ base::DeleteFile(result.response, false);
+#else
+ DeleteFileAndEmptyParentDirectory(result.response);
+#endif
+ result.response.clear();
+
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&CrxDownloader::HandleDownloadError,
+ base::Unretained(this), is_handled, result,
+ download_metrics));
+}
+
+void CrxDownloader::HandleDownloadError(
+ bool is_handled,
+ const Result& result,
+ const DownloadMetrics& download_metrics) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(0, result.error);
+ DCHECK(result.response.empty());
+ DCHECK_NE(0, download_metrics.error);
+
+ download_metrics_.push_back(download_metrics);
+
+ // If an error has occured, try the next url if there is any,
+ // or try the successor in the chain if there is any successor.
+ // If this downloader has received a 5xx error for the current url,
+ // as indicated by the |is_handled| flag, remove that url from the list of
+ // urls so the url is never tried again down the chain.
+ if (is_handled) {
+ current_url_ = urls_.erase(current_url_);
+ } else {
+ ++current_url_;
+ }
+
+ // Try downloading from another url from the list.
+ if (current_url_ != urls_.end()) {
+ DoStartDownload(*current_url_);
+ return;
+ }
+
+ // Try downloading using the next downloader.
+ if (successor_ && !urls_.empty()) {
+ successor_->StartDownload(urls_, expected_hash_,
+ std::move(download_callback_));
+ return;
+ }
+
+ // The download ends here since there is no url nor downloader to handle this
+ // download request further.
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(download_callback_), result));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/crx_downloader.h b/src/components/update_client/crx_downloader.h
new file mode 100644
index 0000000..966ee82
--- /dev/null
+++ b/src/components/update_client/crx_downloader.h
@@ -0,0 +1,172 @@
+// 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 COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_
+#define COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/single_thread_task_runner.h"
+#include "base/threading/thread_checker.h"
+#include "url/gurl.h"
+
+#if defined(OS_STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
+namespace update_client {
+
+class NetworkFetcherFactory;
+
+// Defines a download interface for downloading components, with retrying on
+// fallback urls in case of errors. This class implements a chain of
+// responsibility design pattern. It can give successors in the chain a chance
+// to handle a download request, until one of them succeeds, or there are no
+// more urls or successors to try. A callback is always called at the end of
+// the download, one time only.
+// When multiple urls and downloaders exists, first all the urls are tried, in
+// the order they are provided in the StartDownload function argument. After
+// that, the download request is routed to the next downloader in the chain.
+// The members of this class expect to be called from the main thread only.
+class CrxDownloader {
+ public:
+ struct DownloadMetrics {
+ enum Downloader { kNone = 0, kUrlFetcher, kBits };
+
+ DownloadMetrics();
+
+ GURL url;
+
+ Downloader downloader;
+
+ int error;
+
+ int64_t downloaded_bytes; // -1 means that the byte count is unknown.
+ int64_t total_bytes;
+
+ uint64_t download_time_ms;
+ };
+
+ // Contains the progress or the outcome of the download.
+ struct Result {
+ // Download error: 0 indicates success.
+ int error = 0;
+
+#if defined(OS_STARBOARD)
+ int installation_index = IM_EXT_INVALID_INDEX;
+#endif
+
+ // Path of the downloaded file if the download was successful.
+ base::FilePath response;
+ };
+
+ // The callback fires only once, regardless of how many urls are tried, and
+ // how many successors in the chain of downloaders have handled the
+ // download. The callback interface can be extended if needed to provide
+ // more visibility into how the download has been handled, including
+ // specific error codes and download metrics.
+ using DownloadCallback = base::OnceCallback<void(const Result& result)>;
+
+ // The callback may fire 0 or once during a download. Since this
+ // class implements a chain of responsibility, the callback can fire for
+ // different urls and different downloaders.
+ using ProgressCallback = base::RepeatingCallback<void()>;
+
+ using Factory =
+ std::unique_ptr<CrxDownloader> (*)(bool,
+ scoped_refptr<NetworkFetcherFactory>);
+
+ // Factory method to create an instance of this class and build the
+ // chain of responsibility. |is_background_download| specifies that a
+ // background downloader be used, if the platform supports it.
+ // |task_runner| should be a task runner able to run blocking
+ // code such as file IO operations.
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory);
+ virtual ~CrxDownloader();
+
+ void set_progress_callback(const ProgressCallback& progress_callback);
+
+ // Starts the download. One instance of the class handles one download only.
+ // One instance of CrxDownloader can only be started once, otherwise the
+ // behavior is undefined. The callback gets invoked if the download can't
+ // be started. |expected_hash| represents the SHA256 cryptographic hash of
+ // the download payload, represented as a hexadecimal string.
+ void StartDownloadFromUrl(const GURL& url,
+ const std::string& expected_hash,
+ DownloadCallback download_callback);
+ void StartDownload(const std::vector<GURL>& urls,
+ const std::string& expected_hash,
+ DownloadCallback download_callback);
+
+ const std::vector<DownloadMetrics> download_metrics() const;
+
+ protected:
+ explicit CrxDownloader(std::unique_ptr<CrxDownloader> successor);
+
+ // Handles the fallback in the case of multiple urls and routing of the
+ // download to the following successor in the chain. Derived classes must call
+ // this function after each attempt at downloading the urls provided
+ // in the StartDownload function.
+ // In case of errors, |is_handled| indicates that a server side error has
+ // occured for the current url and the url should not be retried down
+ // the chain to avoid DDOS of the server. This url will be removed from the
+ // list of url and never tried again.
+ void OnDownloadComplete(bool is_handled,
+ const Result& result,
+ const DownloadMetrics& download_metrics);
+
+ // Calls the callback when progress is made.
+ void OnDownloadProgress();
+
+ // Returns the url which is currently being downloaded from.
+ GURL url() const;
+
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner() const {
+ return main_task_runner_;
+ }
+
+ private:
+ virtual void DoStartDownload(const GURL& url) = 0;
+
+ void VerifyResponse(bool is_handled,
+ Result result,
+ DownloadMetrics download_metrics);
+
+ void HandleDownloadError(bool is_handled,
+ const Result& result,
+ const DownloadMetrics& download_metrics);
+
+ base::ThreadChecker thread_checker_;
+
+ // Used to post callbacks to the main thread.
+ scoped_refptr<base::SingleThreadTaskRunner> main_task_runner_;
+
+ std::vector<GURL> urls_;
+
+ // The SHA256 hash of the download payload in hexadecimal format.
+ std::string expected_hash_;
+ std::unique_ptr<CrxDownloader> successor_;
+ DownloadCallback download_callback_;
+ ProgressCallback progress_callback_;
+
+ std::vector<GURL>::iterator current_url_;
+
+ std::vector<DownloadMetrics> download_metrics_;
+
+ DISALLOW_COPY_AND_ASSIGN(CrxDownloader);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_CRX_DOWNLOADER_H_
diff --git a/src/components/update_client/crx_downloader_unittest.cc b/src/components/update_client/crx_downloader_unittest.cc
new file mode 100644
index 0000000..03c9002
--- /dev/null
+++ b/src/components/update_client/crx_downloader_unittest.cc
@@ -0,0 +1,418 @@
+// Copyright 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 "components/update_client/crx_downloader.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/update_client/net/network_chromium.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+#include "net/base/net_errors.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::ContentsEqual;
+
+namespace update_client {
+
+namespace {
+
+const char kTestFileName[] = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+
+const char hash_jebg[] =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+base::FilePath MakeTestFilePath(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components/test/data/update_client")
+ .AppendASCII(file);
+}
+
+} // namespace
+
+class CrxDownloaderTest : public testing::Test {
+ public:
+ CrxDownloaderTest();
+ ~CrxDownloaderTest() override;
+
+ // Overrides from testing::Test.
+ void SetUp() override;
+ void TearDown() override;
+
+ void Quit();
+ void RunThreads();
+ void RunThreadsUntilIdle();
+
+ void DownloadComplete(int crx_context, const CrxDownloader::Result& result);
+
+ void DownloadProgress(int crx_context);
+
+ int GetInterceptorCount() { return interceptor_count_; }
+
+ void AddResponse(const GURL& url,
+ const base::FilePath& file_path,
+ int net_error);
+
+ protected:
+ std::unique_ptr<CrxDownloader> crx_downloader_;
+
+ network::TestURLLoaderFactory test_url_loader_factory_;
+
+ CrxDownloader::DownloadCallback callback_;
+ CrxDownloader::ProgressCallback progress_callback_;
+
+ int crx_context_;
+
+ int num_download_complete_calls_;
+ CrxDownloader::Result download_complete_result_;
+
+ // These members are updated by DownloadProgress.
+ int num_progress_calls_;
+
+ // Accumulates the number of loads triggered.
+ int interceptor_count_ = 0;
+
+ // A magic value for the context to be used in the tests.
+ static const int kExpectedContext = 0xaabb;
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ scoped_refptr<network::SharedURLLoaderFactory>
+ test_shared_url_loader_factory_;
+ base::OnceClosure quit_closure_;
+};
+
+const int CrxDownloaderTest::kExpectedContext;
+
+CrxDownloaderTest::CrxDownloaderTest()
+ : callback_(base::BindOnce(&CrxDownloaderTest::DownloadComplete,
+ base::Unretained(this),
+ kExpectedContext)),
+ progress_callback_(base::Bind(&CrxDownloaderTest::DownloadProgress,
+ base::Unretained(this),
+ kExpectedContext)),
+ crx_context_(0),
+ num_download_complete_calls_(0),
+ num_progress_calls_(0),
+ scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO),
+ test_shared_url_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)) {}
+
+CrxDownloaderTest::~CrxDownloaderTest() {}
+
+void CrxDownloaderTest::SetUp() {
+ num_download_complete_calls_ = 0;
+ download_complete_result_ = CrxDownloader::Result();
+ num_progress_calls_ = 0;
+
+ // Do not use the background downloader in these tests.
+ crx_downloader_ = CrxDownloader::Create(
+ false, base::MakeRefCounted<NetworkFetcherChromiumFactory>(
+ test_shared_url_loader_factory_));
+ crx_downloader_->set_progress_callback(progress_callback_);
+
+ test_url_loader_factory_.SetInterceptor(base::BindLambdaForTesting(
+ [&](const network::ResourceRequest& request) { interceptor_count_++; }));
+}
+
+void CrxDownloaderTest::TearDown() {
+ crx_downloader_.reset();
+}
+
+void CrxDownloaderTest::Quit() {
+ if (!quit_closure_.is_null())
+ std::move(quit_closure_).Run();
+}
+
+void CrxDownloaderTest::DownloadComplete(int crx_context,
+ const CrxDownloader::Result& result) {
+ ++num_download_complete_calls_;
+ crx_context_ = crx_context;
+ download_complete_result_ = result;
+ Quit();
+}
+
+void CrxDownloaderTest::DownloadProgress(int crx_context) {
+ ++num_progress_calls_;
+}
+
+void CrxDownloaderTest::AddResponse(const GURL& url,
+ const base::FilePath& file_path,
+ int net_error) {
+ if (net_error == net::OK) {
+ std::string data;
+ EXPECT_TRUE(base::ReadFileToString(file_path, &data));
+ network::ResourceResponseHead head;
+ head.content_length = data.size();
+ network::URLLoaderCompletionStatus status(net_error);
+ status.decoded_body_length = data.size();
+ test_url_loader_factory_.AddResponse(url, head, data, status);
+ return;
+ }
+
+ EXPECT_NE(net_error, net::OK);
+ test_url_loader_factory_.AddResponse(
+ url, network::ResourceResponseHead(), std::string(),
+ network::URLLoaderCompletionStatus(net_error));
+}
+
+void CrxDownloaderTest::RunThreads() {
+ base::RunLoop runloop;
+ quit_closure_ = runloop.QuitClosure();
+ runloop.Run();
+
+ // Since some tests need to drain currently enqueued tasks such as network
+ // intercepts on the IO thread, run the threads until they are
+ // idle. The component updater service won't loop again until the loop count
+ // is set and the service is started.
+ RunThreadsUntilIdle();
+}
+
+void CrxDownloaderTest::RunThreadsUntilIdle() {
+ scoped_task_environment_.RunUntilIdle();
+ base::RunLoop().RunUntilIdle();
+}
+
+// Tests that starting a download without a url results in an error.
+TEST_F(CrxDownloaderTest, NoUrl) {
+ std::vector<GURL> urls;
+ crx_downloader_->StartDownload(urls, std::string("abcd"),
+ std::move(callback_));
+ RunThreadsUntilIdle();
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_URL),
+ download_complete_result_.error);
+ EXPECT_TRUE(download_complete_result_.response.empty());
+ EXPECT_EQ(0, num_progress_calls_);
+}
+
+// Tests that starting a download without providing a hash results in an error.
+TEST_F(CrxDownloaderTest, NoHash) {
+ std::vector<GURL> urls(1, GURL("http://somehost/somefile"));
+
+ crx_downloader_->StartDownload(urls, std::string(), std::move(callback_));
+ RunThreadsUntilIdle();
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(static_cast<int>(CrxDownloaderError::NO_HASH),
+ download_complete_result_.error);
+ EXPECT_TRUE(download_complete_result_.response.empty());
+ EXPECT_EQ(0, num_progress_calls_);
+}
+
+// Tests that downloading from one url is successful.
+TEST_F(CrxDownloaderTest, OneUrl) {
+ const GURL expected_crx_url =
+ GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+ const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+ AddResponse(expected_crx_url, test_file, net::OK);
+
+ crx_downloader_->StartDownloadFromUrl(
+ expected_crx_url, std::string(hash_jebg), std::move(callback_));
+ RunThreads();
+
+ EXPECT_EQ(1, GetInterceptorCount());
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(0, download_complete_result_.error);
+ EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+ EXPECT_TRUE(
+ DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+ EXPECT_LE(1, num_progress_calls_);
+}
+
+// Tests that downloading from one url fails if the actual hash of the file
+// does not match the expected hash.
+TEST_F(CrxDownloaderTest, OneUrlBadHash) {
+ const GURL expected_crx_url =
+ GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+ const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+ AddResponse(expected_crx_url, test_file, net::OK);
+
+ crx_downloader_->StartDownloadFromUrl(
+ expected_crx_url,
+ std::string(
+ "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9"),
+ std::move(callback_));
+ RunThreads();
+
+ EXPECT_EQ(1, GetInterceptorCount());
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(static_cast<int>(CrxDownloaderError::BAD_HASH),
+ download_complete_result_.error);
+ EXPECT_TRUE(download_complete_result_.response.empty());
+
+ EXPECT_LE(1, num_progress_calls_);
+}
+
+// Tests that specifying two urls has no side effects. Expect a successful
+// download, and only one download request be made.
+TEST_F(CrxDownloaderTest, TwoUrls) {
+ const GURL expected_crx_url =
+ GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+ const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+ AddResponse(expected_crx_url, test_file, net::OK);
+
+ std::vector<GURL> urls;
+ urls.push_back(expected_crx_url);
+ urls.push_back(expected_crx_url);
+
+ crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+ std::move(callback_));
+ RunThreads();
+
+ EXPECT_EQ(1, GetInterceptorCount());
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(0, download_complete_result_.error);
+ EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+ EXPECT_TRUE(
+ DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+ EXPECT_LE(1, num_progress_calls_);
+}
+
+// Tests that the fallback to a valid url is successful.
+TEST_F(CrxDownloaderTest, TwoUrls_FirstInvalid) {
+ const GURL expected_crx_url =
+ GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+ const GURL no_file_url =
+ GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
+
+ const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+ AddResponse(expected_crx_url, test_file, net::OK);
+ AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
+
+ std::vector<GURL> urls;
+ urls.push_back(no_file_url);
+ urls.push_back(expected_crx_url);
+
+ crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+ std::move(callback_));
+ RunThreads();
+
+ EXPECT_EQ(2, GetInterceptorCount());
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(0, download_complete_result_.error);
+ EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+ EXPECT_TRUE(
+ DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+ // Expect at least some progress reported by the loader.
+ EXPECT_LE(1, num_progress_calls_);
+
+ const auto download_metrics = crx_downloader_->download_metrics();
+ ASSERT_EQ(2u, download_metrics.size());
+ EXPECT_EQ(no_file_url, download_metrics[0].url);
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
+ EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
+ EXPECT_EQ(-1, download_metrics[0].total_bytes);
+ EXPECT_EQ(expected_crx_url, download_metrics[1].url);
+ EXPECT_EQ(0, download_metrics[1].error);
+ EXPECT_EQ(1843, download_metrics[1].downloaded_bytes);
+ EXPECT_EQ(1843, download_metrics[1].total_bytes);
+}
+
+// Tests that the download succeeds if the first url is correct and the
+// second bad url does not have a side-effect.
+TEST_F(CrxDownloaderTest, TwoUrls_SecondInvalid) {
+ const GURL expected_crx_url =
+ GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+ const GURL no_file_url =
+ GURL("http://localhost/download/ihfokbkgjpifnbbojhneepfflplebdkc.crx");
+
+ const base::FilePath test_file(MakeTestFilePath(kTestFileName));
+ AddResponse(expected_crx_url, test_file, net::OK);
+ AddResponse(no_file_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
+
+ std::vector<GURL> urls;
+ urls.push_back(expected_crx_url);
+ urls.push_back(no_file_url);
+
+ crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+ std::move(callback_));
+ RunThreads();
+
+ EXPECT_EQ(1, GetInterceptorCount());
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_EQ(0, download_complete_result_.error);
+ EXPECT_TRUE(ContentsEqual(download_complete_result_.response, test_file));
+
+ EXPECT_TRUE(
+ DeleteFileAndEmptyParentDirectory(download_complete_result_.response));
+
+ EXPECT_LE(1, num_progress_calls_);
+
+ EXPECT_EQ(1u, crx_downloader_->download_metrics().size());
+}
+
+// Tests that the download fails if both urls don't serve content.
+TEST_F(CrxDownloaderTest, TwoUrls_BothInvalid) {
+ const GURL expected_crx_url =
+ GURL("http://localhost/download/jebgalgnebhfojomionfpkfelancnnkf.crx");
+
+ AddResponse(expected_crx_url, base::FilePath(), net::ERR_FILE_NOT_FOUND);
+
+ std::vector<GURL> urls;
+ urls.push_back(expected_crx_url);
+ urls.push_back(expected_crx_url);
+
+ crx_downloader_->StartDownload(urls, std::string(hash_jebg),
+ std::move(callback_));
+ RunThreads();
+
+ EXPECT_EQ(2, GetInterceptorCount());
+
+ EXPECT_EQ(1, num_download_complete_calls_);
+ EXPECT_EQ(kExpectedContext, crx_context_);
+ EXPECT_NE(0, download_complete_result_.error);
+ EXPECT_TRUE(download_complete_result_.response.empty());
+
+ const auto download_metrics = crx_downloader_->download_metrics();
+ ASSERT_EQ(2u, download_metrics.size());
+ EXPECT_EQ(expected_crx_url, download_metrics[0].url);
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[0].error);
+ EXPECT_EQ(-1, download_metrics[0].downloaded_bytes);
+ EXPECT_EQ(-1, download_metrics[0].total_bytes);
+ EXPECT_EQ(expected_crx_url, download_metrics[1].url);
+ EXPECT_EQ(net::ERR_FILE_NOT_FOUND, download_metrics[1].error);
+ EXPECT_EQ(-1, download_metrics[1].downloaded_bytes);
+ EXPECT_EQ(-1, download_metrics[1].total_bytes);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/crx_update_item.h b/src/components/update_client/crx_update_item.h
new file mode 100644
index 0000000..ac645b5
--- /dev/null
+++ b/src/components/update_client/crx_update_item.h
@@ -0,0 +1,51 @@
+// 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 COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_
+#define COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/memory/weak_ptr.h"
+#include "base/optional.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+
+namespace update_client {
+
+struct CrxUpdateItem {
+ CrxUpdateItem();
+ CrxUpdateItem(const CrxUpdateItem& other);
+ ~CrxUpdateItem();
+
+ ComponentState state;
+
+ std::string id;
+
+ // The value of this data member is provided to the |UpdateClient| by the
+ // caller by responding to the |CrxDataCallback|. If the caller can't
+ // provide this value, for instance, in cases where the CRX was uninstalled,
+ // then the |component| member will not be present.
+ base::Optional<CrxComponent> component;
+
+ // Time when an update check for this CRX has happened.
+ base::TimeTicks last_check;
+
+ base::Version next_version;
+ std::string next_fp;
+
+ ErrorCategory error_category = ErrorCategory::kNone;
+ int error_code = 0;
+ int extra_code1 = 0;
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_CRX_UPDATE_ITEM_H_
diff --git a/src/components/update_client/net/network_chromium.h b/src/components/update_client/net/network_chromium.h
new file mode 100644
index 0000000..e8ac9bb
--- /dev/null
+++ b/src/components/update_client/net/network_chromium.h
@@ -0,0 +1,39 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_NET_NETWORK_CHROMIUM_H_
+#define COMPONENTS_UPDATE_CLIENT_NET_NETWORK_CHROMIUM_H_
+
+#include <memory>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/network.h"
+
+namespace network {
+class SharedURLLoaderFactory;
+}
+
+namespace update_client {
+
+class NetworkFetcherChromiumFactory : public NetworkFetcherFactory {
+ public:
+ explicit NetworkFetcherChromiumFactory(
+ scoped_refptr<network::SharedURLLoaderFactory>
+ shared_url_network_factory);
+
+ std::unique_ptr<NetworkFetcher> Create() const override;
+
+ protected:
+ ~NetworkFetcherChromiumFactory() override;
+
+ private:
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkFetcherChromiumFactory);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_NET_NETWORK_CHROMIUM_H_
diff --git a/src/components/update_client/net/network_impl.cc b/src/components/update_client/net/network_impl.cc
new file mode 100644
index 0000000..c78853e
--- /dev/null
+++ b/src/components/update_client/net/network_impl.cc
@@ -0,0 +1,207 @@
+// Copyright 2019 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 "components/update_client/net/network_impl.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/numerics/safe_conversions.h"
+#include "components/update_client/net/network_chromium.h"
+#include "net/base/load_flags.h"
+#include "net/http/http_response_headers.h"
+#include "net/traffic_annotation/network_traffic_annotation.h"
+#include "services/network/public/cpp/resource_response.h"
+#include "services/network/public/cpp/shared_url_loader_factory.h"
+#include "services/network/public/cpp/simple_url_loader.h"
+#include "url/gurl.h"
+
+namespace {
+
+const net::NetworkTrafficAnnotationTag traffic_annotation =
+ net::DefineNetworkTrafficAnnotation("update_client", R"(
+ semantics {
+ sender: "Component Updater and Extension Updater"
+ description:
+ "This network module is used by both the component and the "
+ "extension updaters in Chrome. "
+ "The component updater is responsible for updating code and data "
+ "modules such as Flash, CrlSet, Origin Trials, etc. These modules "
+ "are updated on cycles independent of the Chrome release tracks. "
+ "It runs in the browser process and communicates with a set of "
+ "servers using the Omaha protocol to find the latest versions of "
+ "components, download them, and register them with the rest of "
+ "Chrome. "
+ "The extension updater works similarly, but it updates user "
+ "extensions instead of Chrome components. "
+ trigger: "Manual or automatic software updates."
+ data:
+ "Various OS and Chrome parameters such as version, bitness, "
+ "release tracks, etc. The component and the extension ids are also "
+ "present in both the request and the response from the servers. "
+ "The URL that refers to a component CRX payload is obfuscated for "
+ "most components."
+ destination: GOOGLE_OWNED_SERVICE
+ }
+ policy {
+ cookies_allowed: NO
+ setting: "This feature cannot be disabled."
+ chrome_policy {
+ ComponentUpdatesEnabled {
+ policy_options {mode: MANDATORY}
+ ComponentUpdatesEnabled: false
+ }
+ }
+ })");
+
+// Returns the string value of a header of the server response or an empty
+// string if the header is not available. Only the first header is returned
+// if multiple instances of the same header are present.
+std::string GetStringHeader(const network::SimpleURLLoader* simple_url_loader,
+ const char* header_name) {
+ DCHECK(simple_url_loader);
+
+ const auto* response_info = simple_url_loader->ResponseInfo();
+ if (!response_info || !response_info->headers)
+ return {};
+
+ std::string header_value;
+ return response_info->headers->EnumerateHeader(nullptr, header_name,
+ &header_value)
+ ? header_value
+ : std::string{};
+}
+
+// Returns the integral value of a header of the server response or -1 if
+// if the header is not available or a conversion error has occured.
+int64_t GetInt64Header(const network::SimpleURLLoader* simple_url_loader,
+ const char* header_name) {
+ DCHECK(simple_url_loader);
+
+ const auto* response_info = simple_url_loader->ResponseInfo();
+ if (!response_info || !response_info->headers)
+ return -1;
+
+ return response_info->headers->GetInt64HeaderValue(header_name);
+}
+
+} // namespace
+
+namespace update_client {
+
+NetworkFetcherImpl::NetworkFetcherImpl(
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory)
+ : shared_url_network_factory_(shared_url_network_factory) {}
+NetworkFetcherImpl::~NetworkFetcherImpl() = default;
+
+void NetworkFetcherImpl::PostRequest(
+ const GURL& url,
+ const std::string& post_data,
+ const base::flat_map<std::string, std::string>& post_additional_headers,
+ ResponseStartedCallback response_started_callback,
+ ProgressCallback progress_callback,
+ PostRequestCompleteCallback post_request_complete_callback) {
+ DCHECK(!simple_url_loader_);
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+ resource_request->method = "POST";
+ resource_request->load_flags = net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DISABLE_CACHE;
+ for (const auto& header : post_additional_headers)
+ resource_request->headers.SetHeader(header.first, header.second);
+ simple_url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), traffic_annotation);
+ simple_url_loader_->SetRetryOptions(
+ kMaxRetriesOnNetworkChange,
+ network::SimpleURLLoader::RETRY_ON_NETWORK_CHANGE);
+ simple_url_loader_->AttachStringForUpload(post_data, "application/json");
+ simple_url_loader_->SetOnResponseStartedCallback(base::BindOnce(
+ &NetworkFetcherImpl::OnResponseStartedCallback, base::Unretained(this),
+ std::move(response_started_callback)));
+ simple_url_loader_->SetOnDownloadProgressCallback(base::BindRepeating(
+ &NetworkFetcherImpl::OnProgressCallback, base::Unretained(this),
+ std::move(progress_callback)));
+ constexpr size_t kMaxResponseSize = 1024 * 1024;
+ simple_url_loader_->DownloadToString(
+ shared_url_network_factory_.get(),
+ base::BindOnce(
+ [](const network::SimpleURLLoader* simple_url_loader,
+ PostRequestCompleteCallback post_request_complete_callback,
+ std::unique_ptr<std::string> response_body) {
+ std::move(post_request_complete_callback)
+ .Run(std::move(response_body), simple_url_loader->NetError(),
+ GetStringHeader(simple_url_loader, kHeaderEtag),
+ GetInt64Header(simple_url_loader, kHeaderXRetryAfter));
+ },
+ simple_url_loader_.get(), std::move(post_request_complete_callback)),
+ kMaxResponseSize);
+}
+
+void NetworkFetcherImpl::DownloadToFile(
+ const GURL& url,
+ const base::FilePath& file_path,
+ ResponseStartedCallback response_started_callback,
+ ProgressCallback progress_callback,
+ DownloadToFileCompleteCallback download_to_file_complete_callback) {
+ DCHECK(!simple_url_loader_);
+ auto resource_request = std::make_unique<network::ResourceRequest>();
+ resource_request->url = url;
+ resource_request->method = "GET";
+ resource_request->load_flags = net::LOAD_DO_NOT_SEND_COOKIES |
+ net::LOAD_DO_NOT_SAVE_COOKIES |
+ net::LOAD_DISABLE_CACHE;
+ simple_url_loader_ = network::SimpleURLLoader::Create(
+ std::move(resource_request), traffic_annotation);
+ simple_url_loader_->SetRetryOptions(
+ kMaxRetriesOnNetworkChange,
+ network::SimpleURLLoader::RetryMode::RETRY_ON_NETWORK_CHANGE);
+ simple_url_loader_->SetAllowPartialResults(true);
+ simple_url_loader_->SetOnResponseStartedCallback(base::BindOnce(
+ &NetworkFetcherImpl::OnResponseStartedCallback, base::Unretained(this),
+ std::move(response_started_callback)));
+ simple_url_loader_->SetOnDownloadProgressCallback(base::BindRepeating(
+ &NetworkFetcherImpl::OnProgressCallback, base::Unretained(this),
+ std::move(progress_callback)));
+ simple_url_loader_->DownloadToFile(
+ shared_url_network_factory_.get(),
+ base::BindOnce(
+ [](const network::SimpleURLLoader* simple_url_loader,
+ DownloadToFileCompleteCallback download_to_file_complete_callback,
+ base::FilePath file_path) {
+ std::move(download_to_file_complete_callback)
+ .Run(file_path, simple_url_loader->NetError(),
+ simple_url_loader->GetContentSize());
+ },
+ simple_url_loader_.get(),
+ std::move(download_to_file_complete_callback)),
+ file_path);
+}
+
+void NetworkFetcherImpl::OnResponseStartedCallback(
+ ResponseStartedCallback response_started_callback,
+ const GURL& final_url,
+ const network::ResourceResponseHead& response_head) {
+ std::move(response_started_callback)
+ .Run(final_url,
+ response_head.headers ? response_head.headers->response_code() : -1,
+ response_head.content_length);
+}
+
+void NetworkFetcherImpl::OnProgressCallback(ProgressCallback progress_callback,
+ uint64_t current) {
+ progress_callback.Run(base::saturated_cast<int64_t>(current));
+}
+
+NetworkFetcherChromiumFactory::NetworkFetcherChromiumFactory(
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory)
+ : shared_url_network_factory_(shared_url_network_factory) {}
+
+NetworkFetcherChromiumFactory::~NetworkFetcherChromiumFactory() = default;
+
+std::unique_ptr<NetworkFetcher> NetworkFetcherChromiumFactory::Create() const {
+ return std::make_unique<NetworkFetcherImpl>(shared_url_network_factory_);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/net/network_impl.h b/src/components/update_client/net/network_impl.h
new file mode 100644
index 0000000..f28cf2f
--- /dev/null
+++ b/src/components/update_client/net/network_impl.h
@@ -0,0 +1,65 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_NET_NETWORK_IMPL_H_
+#define COMPONENTS_UPDATE_CLIENT_NET_NETWORK_IMPL_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/network.h"
+
+namespace network {
+struct ResourceResponseHead;
+class SharedURLLoaderFactory;
+class SimpleURLLoader;
+} // namespace network
+
+namespace update_client {
+
+class NetworkFetcherImpl : public NetworkFetcher {
+ public:
+ explicit NetworkFetcherImpl(scoped_refptr<network::SharedURLLoaderFactory>
+ shared_url_network_factory);
+ ~NetworkFetcherImpl() override;
+
+ // NetworkFetcher overrides.
+ void PostRequest(
+ const GURL& url,
+ const std::string& post_data,
+ const base::flat_map<std::string, std::string>& post_additional_headers,
+ ResponseStartedCallback response_started_callback,
+ ProgressCallback progress_callback,
+ PostRequestCompleteCallback post_request_complete_callback) override;
+ void DownloadToFile(const GURL& url,
+ const base::FilePath& file_path,
+ ResponseStartedCallback response_started_callback,
+ ProgressCallback progress_callback,
+ DownloadToFileCompleteCallback
+ download_to_file_complete_callback) override;
+
+ private:
+ void OnResponseStartedCallback(
+ ResponseStartedCallback response_started_callback,
+ const GURL& final_url,
+ const network::ResourceResponseHead& response_head);
+
+ void OnProgressCallback(ProgressCallback response_started_callback,
+ uint64_t current);
+
+ static constexpr int kMaxRetriesOnNetworkChange = 3;
+
+ scoped_refptr<network::SharedURLLoaderFactory> shared_url_network_factory_;
+ std::unique_ptr<network::SimpleURLLoader> simple_url_loader_;
+
+ DISALLOW_COPY_AND_ASSIGN(NetworkFetcherImpl);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_NET_NETWORK_IMPL_H_
diff --git a/src/components/update_client/net/url_loader_post_interceptor.cc b/src/components/update_client/net/url_loader_post_interceptor.cc
new file mode 100644
index 0000000..11a2af8
--- /dev/null
+++ b/src/components/update_client/net/url_loader_post_interceptor.cc
@@ -0,0 +1,260 @@
+// Copyright 2018 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 "components/update_client/net/url_loader_post_interceptor.h"
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/macros.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/test/bind_test_util.h"
+#include "components/update_client/test_configurator.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "services/network/public/cpp/resource_request.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "services/network/test/test_utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+URLLoaderPostInterceptor::URLLoaderPostInterceptor(
+ network::TestURLLoaderFactory* url_loader_factory)
+ : url_loader_factory_(url_loader_factory) {
+ filtered_urls_.push_back(
+ GURL(base::StringPrintf("%s://%s%s", POST_INTERCEPT_SCHEME,
+ POST_INTERCEPT_HOSTNAME, POST_INTERCEPT_PATH)));
+ InitializeWithInterceptor();
+}
+
+URLLoaderPostInterceptor::URLLoaderPostInterceptor(
+ std::vector<GURL> supported_urls,
+ network::TestURLLoaderFactory* url_loader_factory)
+ : url_loader_factory_(url_loader_factory) {
+ DCHECK_LT(0u, supported_urls.size());
+ filtered_urls_.swap(supported_urls);
+ InitializeWithInterceptor();
+}
+
+URLLoaderPostInterceptor::URLLoaderPostInterceptor(
+ std::vector<GURL> supported_urls,
+ net::test_server::EmbeddedTestServer* embedded_test_server)
+ : embedded_test_server_(embedded_test_server) {
+ DCHECK_LT(0u, supported_urls.size());
+ filtered_urls_.swap(supported_urls);
+ InitializeWithRequestHandler();
+}
+
+URLLoaderPostInterceptor::~URLLoaderPostInterceptor() {}
+
+bool URLLoaderPostInterceptor::ExpectRequest(
+ std::unique_ptr<RequestMatcher> request_matcher) {
+ return ExpectRequest(std::move(request_matcher), net::HTTP_OK);
+}
+
+bool URLLoaderPostInterceptor::ExpectRequest(
+ std::unique_ptr<RequestMatcher> request_matcher,
+ net::HttpStatusCode response_code) {
+ expectations_.push(
+ {std::move(request_matcher), ExpectationResponse(response_code, "")});
+ return true;
+}
+
+bool URLLoaderPostInterceptor::ExpectRequest(
+ std::unique_ptr<RequestMatcher> request_matcher,
+ const base::FilePath& filepath) {
+ std::string response;
+ if (filepath.empty() || !base::ReadFileToString(filepath, &response))
+ return false;
+
+ expectations_.push({std::move(request_matcher),
+ ExpectationResponse(net::HTTP_OK, response)});
+ return true;
+}
+
+// Returns how many requests have been intercepted and matched by
+// an expectation. One expectation can only be matched by one request.
+int URLLoaderPostInterceptor::GetHitCount() const {
+ return hit_count_;
+}
+
+// Returns how many requests in total have been captured by the interceptor.
+int URLLoaderPostInterceptor::GetCount() const {
+ return static_cast<int>(requests_.size());
+}
+
+// Returns all requests that have been intercepted, matched or not.
+std::vector<URLLoaderPostInterceptor::InterceptedRequest>
+URLLoaderPostInterceptor::GetRequests() const {
+ return requests_;
+}
+
+// Return the body of the n-th request, zero-based.
+std::string URLLoaderPostInterceptor::GetRequestBody(size_t n) const {
+ return std::get<0>(requests_[n]);
+}
+
+// Returns the joined bodies of all requests for debugging purposes.
+std::string URLLoaderPostInterceptor::GetRequestsAsString() const {
+ const std::vector<InterceptedRequest> requests = GetRequests();
+ std::string s = "Requests are:";
+ int i = 0;
+ for (auto it = requests.cbegin(); it != requests.cend(); ++it)
+ s.append(base::StringPrintf("\n [%d]: %s", ++i, std::get<0>(*it).c_str()));
+ return s;
+}
+
+// Resets the state of the interceptor so that new expectations can be set.
+void URLLoaderPostInterceptor::Reset() {
+ hit_count_ = 0;
+ requests_.clear();
+ base::queue<Expectation>().swap(expectations_);
+}
+
+void URLLoaderPostInterceptor::Pause() {
+ is_paused_ = true;
+}
+
+void URLLoaderPostInterceptor::Resume() {
+ is_paused_ = false;
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindLambdaForTesting([&]() {
+ if (!pending_expectations_.size())
+ return;
+
+ PendingExpectation expectation =
+ std::move(pending_expectations_.front());
+ pending_expectations_.pop();
+ url_loader_factory_->AddResponse(expectation.first.spec(),
+ expectation.second.response_body,
+ expectation.second.response_code);
+ }));
+}
+
+void URLLoaderPostInterceptor::url_job_request_ready_callback(
+ UrlJobRequestReadyCallback url_job_request_ready_callback) {
+ url_job_request_ready_callback_ = std::move(url_job_request_ready_callback);
+}
+
+int URLLoaderPostInterceptor::GetHitCountForURL(const GURL& url) {
+ int hit_count = 0;
+ const std::vector<InterceptedRequest> requests = GetRequests();
+ for (auto it = requests.cbegin(); it != requests.cend(); ++it) {
+ GURL url_no_query = std::get<2>(*it);
+ if (url_no_query.has_query()) {
+ GURL::Replacements replacements;
+ replacements.ClearQuery();
+ url_no_query = url_no_query.ReplaceComponents(replacements);
+ }
+ if (url_no_query == url)
+ hit_count++;
+ }
+ return hit_count;
+}
+
+void URLLoaderPostInterceptor::InitializeWithInterceptor() {
+ DCHECK(url_loader_factory_);
+ url_loader_factory_->SetInterceptor(
+ base::BindLambdaForTesting([&](const network::ResourceRequest& request) {
+ GURL url = request.url;
+ if (url.has_query()) {
+ GURL::Replacements replacements;
+ replacements.ClearQuery();
+ url = url.ReplaceComponents(replacements);
+ }
+ auto it = std::find_if(
+ filtered_urls_.begin(), filtered_urls_.end(),
+ [url](const GURL& filtered_url) { return filtered_url == url; });
+ if (it == filtered_urls_.end())
+ return;
+
+ std::string request_body = network::GetUploadData(request);
+ requests_.push_back({request_body, request.headers, request.url});
+ if (expectations_.empty())
+ return;
+ const auto& expectation = expectations_.front();
+ if (expectation.first->Match(request_body)) {
+ const net::HttpStatusCode response_code(
+ expectation.second.response_code);
+ const std::string response_body(expectation.second.response_body);
+
+ if (url_job_request_ready_callback_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, std::move(url_job_request_ready_callback_));
+ }
+
+ if (!is_paused_) {
+ url_loader_factory_->AddResponse(request.url.spec(), response_body,
+ response_code);
+ } else {
+ pending_expectations_.push({request.url, expectation.second});
+ }
+ expectations_.pop();
+ ++hit_count_;
+ }
+ }));
+}
+
+void URLLoaderPostInterceptor::InitializeWithRequestHandler() {
+ DCHECK(embedded_test_server_);
+ DCHECK(!url_loader_factory_);
+ embedded_test_server_->RegisterRequestHandler(base::BindRepeating(
+ &URLLoaderPostInterceptor::RequestHandler, base::Unretained(this)));
+}
+
+std::unique_ptr<net::test_server::HttpResponse>
+URLLoaderPostInterceptor::RequestHandler(
+ const net::test_server::HttpRequest& request) {
+ // Only intercepts POST.
+ if (request.method != net::test_server::METHOD_POST)
+ return nullptr;
+
+ GURL url = request.GetURL();
+ if (url.has_query()) {
+ GURL::Replacements replacements;
+ replacements.ClearQuery();
+ url = url.ReplaceComponents(replacements);
+ }
+ auto it = std::find_if(
+ filtered_urls_.begin(), filtered_urls_.end(),
+ [url](const GURL& filtered_url) { return filtered_url == url; });
+ if (it == filtered_urls_.end())
+ return nullptr;
+
+ std::string request_body = request.content;
+ net::HttpRequestHeaders headers;
+ for (auto it : request.headers)
+ headers.SetHeader(it.first, it.second);
+ requests_.push_back({request_body, headers, url});
+ if (expectations_.empty())
+ return nullptr;
+
+ const auto& expectation = expectations_.front();
+ if (expectation.first->Match(request_body)) {
+ const net::HttpStatusCode response_code(expectation.second.response_code);
+ const std::string response_body(expectation.second.response_body);
+ expectations_.pop();
+ ++hit_count_;
+
+ std::unique_ptr<net::test_server::BasicHttpResponse> http_response(
+ new net::test_server::BasicHttpResponse);
+ http_response->set_code(response_code);
+ http_response->set_content(response_body);
+ return http_response;
+ }
+
+ return nullptr;
+}
+
+bool PartialMatch::Match(const std::string& actual) const {
+ return actual.find(expected_) != std::string::npos;
+}
+
+bool AnyMatch::Match(const std::string& actual) const {
+ return true;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/net/url_loader_post_interceptor.h b/src/components/update_client/net/url_loader_post_interceptor.h
new file mode 100644
index 0000000..9907f49
--- /dev/null
+++ b/src/components/update_client/net/url_loader_post_interceptor.h
@@ -0,0 +1,181 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_NET_URL_LOADER_POST_INTERCEPTOR_H_
+#define COMPONENTS_UPDATE_CLIENT_NET_URL_LOADER_POST_INTERCEPTOR_H_
+
+#include <memory>
+#include <string>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "net/http/http_request_headers.h"
+#include "net/http/http_status_code.h"
+#include "url/gurl.h"
+
+namespace network {
+class TestURLLoaderFactory;
+}
+
+namespace net {
+namespace test_server {
+class EmbeddedTestServer;
+class HttpResponse;
+struct HttpRequest;
+} // namespace test_server
+} // namespace net
+
+namespace update_client {
+
+// Intercepts requests to a file path, counts them, and captures the body of
+// the requests. Optionally, for each request, it can return a canned response
+// from a given file. The class maintains a queue of expectations, and returns
+// one and only one response for each request that matches the expectation.
+// Then, the expectation is removed from the queue.
+class URLLoaderPostInterceptor {
+ public:
+ using InterceptedRequest =
+ std::tuple<std::string, net::HttpRequestHeaders, GURL>;
+
+ // Called when the load associated with the url request is intercepted
+ // by this object:.
+ using UrlJobRequestReadyCallback = base::OnceCallback<void()>;
+
+ // Allows a generic string maching interface when setting up expectations.
+ class RequestMatcher {
+ public:
+ virtual bool Match(const std::string& actual) const = 0;
+ virtual ~RequestMatcher() {}
+ };
+
+ explicit URLLoaderPostInterceptor(
+ network::TestURLLoaderFactory* url_loader_factory);
+ URLLoaderPostInterceptor(std::vector<GURL> supported_urls,
+ network::TestURLLoaderFactory* url_loader_factory);
+ URLLoaderPostInterceptor(std::vector<GURL> supported_urls,
+ net::test_server::EmbeddedTestServer*);
+
+ ~URLLoaderPostInterceptor();
+
+ // Sets an expection for the body of the POST request and optionally,
+ // provides a canned response identified by a |file_path| to be returned when
+ // the expectation is met. If no |file_path| is provided, then an empty
+ // response body is served. If |response_code| is provided, then an empty
+ // response body with that response code is returned.
+ // Returns |true| if the expectation was set.
+ bool ExpectRequest(std::unique_ptr<RequestMatcher> request_matcher);
+
+ bool ExpectRequest(std::unique_ptr<RequestMatcher> request_matcher,
+ net::HttpStatusCode response_code);
+
+ bool ExpectRequest(std::unique_ptr<RequestMatcher> request_matcher,
+ const base::FilePath& filepath);
+
+ // Returns how many requests have been intercepted and matched by
+ // an expectation. One expectation can only be matched by one request.
+ int GetHitCount() const;
+
+ // Returns how many requests in total have been captured by the interceptor.
+ int GetCount() const;
+
+ // Returns all requests that have been intercepted, matched or not.
+ std::vector<InterceptedRequest> GetRequests() const;
+
+ // Return the body of the n-th request, zero-based.
+ std::string GetRequestBody(size_t n) const;
+
+ // Returns the joined bodies of all requests for debugging purposes.
+ std::string GetRequestsAsString() const;
+
+ // Resets the state of the interceptor so that new expectations can be set.
+ void Reset();
+
+ // Prevents the intercepted request from starting, as a way to simulate
+ // the effects of a very slow network. Call this function before the actual
+ // network request occurs.
+ void Pause();
+
+ // Allows a previously paused request to continue.
+ void Resume();
+
+ // Sets a callback to be invoked when the request job associated with
+ // an intercepted request is created. This allows the test execution to
+ // synchronize with network tasks running on the IO thread and avoid polling
+ // using idle run loops. A paused request can be resumed after this callback
+ // has been invoked.
+ void url_job_request_ready_callback(
+ UrlJobRequestReadyCallback url_job_request_ready_callback);
+
+ int GetHitCountForURL(const GURL& url);
+
+ private:
+ void InitializeWithInterceptor();
+ void InitializeWithRequestHandler();
+
+ std::unique_ptr<net::test_server::HttpResponse> RequestHandler(
+ const net::test_server::HttpRequest& request);
+
+ struct ExpectationResponse {
+ ExpectationResponse(net::HttpStatusCode code, const std::string& body)
+ : response_code(code), response_body(body) {}
+ const net::HttpStatusCode response_code;
+ const std::string response_body;
+ };
+ using Expectation =
+ std::pair<std::unique_ptr<RequestMatcher>, ExpectationResponse>;
+
+ using PendingExpectation = std::pair<GURL, ExpectationResponse>;
+
+ // Contains the count of the request matching expectations.
+ int hit_count_ = 0;
+
+ // Contains the request body and the extra headers of the intercepted
+ // requests.
+ std::vector<InterceptedRequest> requests_;
+
+ // Contains the expectations which this interceptor tries to match.
+ base::queue<Expectation> expectations_;
+
+ base::queue<PendingExpectation> pending_expectations_;
+
+ network::TestURLLoaderFactory* url_loader_factory_ = nullptr;
+ net::test_server::EmbeddedTestServer* embedded_test_server_ = nullptr;
+
+ bool is_paused_ = false;
+
+ std::vector<GURL> filtered_urls_;
+
+ UrlJobRequestReadyCallback url_job_request_ready_callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(URLLoaderPostInterceptor);
+};
+
+class PartialMatch : public URLLoaderPostInterceptor::RequestMatcher {
+ public:
+ explicit PartialMatch(const std::string& expected) : expected_(expected) {}
+ bool Match(const std::string& actual) const override;
+
+ private:
+ const std::string expected_;
+
+ DISALLOW_COPY_AND_ASSIGN(PartialMatch);
+};
+
+class AnyMatch : public URLLoaderPostInterceptor::RequestMatcher {
+ public:
+ AnyMatch() = default;
+ bool Match(const std::string& actual) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AnyMatch);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_NET_URL_LOADER_POST_INTERCEPTOR_H_
diff --git a/src/components/update_client/network.cc b/src/components/update_client/network.cc
new file mode 100644
index 0000000..cb8e151
--- /dev/null
+++ b/src/components/update_client/network.cc
@@ -0,0 +1,12 @@
+// Copyright 2019 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 "components/update_client/network.h"
+
+namespace update_client {
+
+constexpr char NetworkFetcher::kHeaderEtag[];
+constexpr char NetworkFetcher::kHeaderXRetryAfter[];
+
+} // namespace update_client
diff --git a/src/components/update_client/network.h b/src/components/update_client/network.h
new file mode 100644
index 0000000..daed162
--- /dev/null
+++ b/src/components/update_client/network.h
@@ -0,0 +1,89 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_NETWORK_H_
+#define COMPONENTS_UPDATE_CLIENT_NETWORK_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+class GURL;
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace update_client {
+
+class NetworkFetcher {
+ public:
+ using PostRequestCompleteCallback =
+ base::OnceCallback<void(std::unique_ptr<std::string> response_body,
+ int net_error,
+ const std::string& header_etag,
+ int64_t xheader_retry_after_sec)>;
+ using DownloadToFileCompleteCallback = base::OnceCallback<
+ void(base::FilePath path, int net_error, int64_t content_size)>;
+ using ResponseStartedCallback = base::OnceCallback<
+ void(const GURL& final_url, int response_code, int64_t content_length)>;
+ using ProgressCallback = base::RepeatingCallback<void(int64_t current)>;
+
+ // The ETag header carries the ECSDA signature of the POST response, if
+ // signing has been used.
+ static constexpr char kHeaderEtag[] = "ETag";
+
+ // The server uses the optional X-Retry-After header to indicate that the
+ // current request should not be attempted again.
+ //
+ // The value of the header is the number of seconds to wait before trying to
+ // do a subsequent update check. Only the values retrieved over HTTPS are
+ // trusted.
+ static constexpr char kHeaderXRetryAfter[] = "X-Retry-After";
+
+ virtual ~NetworkFetcher() = default;
+
+ virtual void PostRequest(
+ const GURL& url,
+ const std::string& post_data,
+ const base::flat_map<std::string, std::string>& post_additional_headers,
+ ResponseStartedCallback response_started_callback,
+ ProgressCallback progress_callback,
+ PostRequestCompleteCallback post_request_complete_callback) = 0;
+ virtual void DownloadToFile(
+ const GURL& url,
+ const base::FilePath& file_path,
+ ResponseStartedCallback response_started_callback,
+ ProgressCallback progress_callback,
+ DownloadToFileCompleteCallback download_to_file_complete_callback) = 0;
+
+ protected:
+ NetworkFetcher() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkFetcher);
+};
+
+class NetworkFetcherFactory : public base::RefCounted<NetworkFetcherFactory> {
+ public:
+ virtual std::unique_ptr<NetworkFetcher> Create() const = 0;
+
+ protected:
+ friend class base::RefCounted<NetworkFetcherFactory>;
+ NetworkFetcherFactory() = default;
+ virtual ~NetworkFetcherFactory() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NetworkFetcherFactory);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_NETWORK_H_
diff --git a/src/components/update_client/patch/patch_impl.cc b/src/components/update_client/patch/patch_impl.cc
new file mode 100644
index 0000000..599101f
--- /dev/null
+++ b/src/components/update_client/patch/patch_impl.cc
@@ -0,0 +1,53 @@
+// Copyright 2019 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 "components/update_client/patch/patch_impl.h"
+
+#include "components/services/patch/public/cpp/patch.h"
+#include "components/update_client/component_patcher_operation.h"
+
+namespace update_client {
+
+namespace {
+
+class PatcherImpl : public Patcher {
+ public:
+ explicit PatcherImpl(PatchChromiumFactory::Callback callback)
+ : callback_(std::move(callback)) {}
+
+ void PatchBsdiff(const base::FilePath& old_file,
+ const base::FilePath& patch_file,
+ const base::FilePath& destination,
+ PatchCompleteCallback callback) const override {
+ patch::Patch(callback_.Run(), update_client::kBsdiff, old_file, patch_file,
+ destination, std::move(callback));
+ }
+
+ void PatchCourgette(const base::FilePath& old_file,
+ const base::FilePath& patch_file,
+ const base::FilePath& destination,
+ PatchCompleteCallback callback) const override {
+ patch::Patch(callback_.Run(), update_client::kCourgette, old_file,
+ patch_file, destination, std::move(callback));
+ }
+
+ protected:
+ ~PatcherImpl() override = default;
+
+ private:
+ const PatchChromiumFactory::Callback callback_;
+};
+
+} // namespace
+
+PatchChromiumFactory::PatchChromiumFactory(Callback callback)
+ : callback_(std::move(callback)) {}
+
+scoped_refptr<Patcher> PatchChromiumFactory::Create() const {
+ return base::MakeRefCounted<PatcherImpl>(callback_);
+}
+
+PatchChromiumFactory::~PatchChromiumFactory() = default;
+
+} // namespace update_client
diff --git a/src/components/update_client/patch/patch_impl.h b/src/components/update_client/patch/patch_impl.h
new file mode 100644
index 0000000..e76666d
--- /dev/null
+++ b/src/components/update_client/patch/patch_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_PATCH_PATCH_IMPL_H_
+#define COMPONENTS_UPDATE_CLIENT_PATCH_PATCH_IMPL_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/services/patch/public/mojom/file_patcher.mojom.h"
+#include "components/update_client/patcher.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace update_client {
+
+class PatchChromiumFactory : public PatcherFactory {
+ public:
+ using Callback =
+ base::RepeatingCallback<mojo::PendingRemote<patch::mojom::FilePatcher>()>;
+ explicit PatchChromiumFactory(Callback callback);
+
+ scoped_refptr<Patcher> Create() const override;
+
+ protected:
+ ~PatchChromiumFactory() override;
+
+ private:
+ const Callback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(PatchChromiumFactory);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PATCH_PATCH_IMPL_H_
diff --git a/src/components/update_client/patcher.h b/src/components/update_client/patcher.h
new file mode 100644
index 0000000..1316d26
--- /dev/null
+++ b/src/components/update_client/patcher.h
@@ -0,0 +1,56 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_PATCHER_H_
+#define COMPONENTS_UPDATE_CLIENT_PATCHER_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace update_client {
+
+class Patcher : public base::RefCountedThreadSafe<Patcher> {
+ public:
+ using PatchCompleteCallback = base::OnceCallback<void(int result)>;
+
+ virtual void PatchBsdiff(const base::FilePath& input_file,
+ const base::FilePath& patch_file,
+ const base::FilePath& destination,
+ PatchCompleteCallback callback) const = 0;
+
+ virtual void PatchCourgette(const base::FilePath& input_file,
+ const base::FilePath& patch_file,
+ const base::FilePath& destination,
+ PatchCompleteCallback callback) const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<Patcher>;
+ Patcher() = default;
+ virtual ~Patcher() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Patcher);
+};
+
+class PatcherFactory : public base::RefCountedThreadSafe<PatcherFactory> {
+ public:
+ virtual scoped_refptr<Patcher> Create() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<PatcherFactory>;
+ PatcherFactory() = default;
+ virtual ~PatcherFactory() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(PatcherFactory);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PATCHER_H_
diff --git a/src/components/update_client/persisted_data.cc b/src/components/update_client/persisted_data.cc
new file mode 100644
index 0000000..c14bb03
--- /dev/null
+++ b/src/components/update_client/persisted_data.cc
@@ -0,0 +1,195 @@
+// Copyright 2016 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 "components/update_client/persisted_data.h"
+
+#include <string>
+#include <vector>
+
+#include "base/guid.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_checker.h"
+#include "base/values.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/prefs/pref_service.h"
+#include "components/prefs/scoped_user_pref_update.h"
+#include "components/update_client/activity_data_service.h"
+
+const char kPersistedDataPreference[] = "updateclientdata";
+
+namespace update_client {
+
+PersistedData::PersistedData(PrefService* pref_service,
+ ActivityDataService* activity_data_service)
+ : pref_service_(pref_service),
+ activity_data_service_(activity_data_service) {}
+
+PersistedData::~PersistedData() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+int PersistedData::GetInt(const std::string& id,
+ const std::string& key,
+ int fallback) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // We assume ids do not contain '.' characters.
+ DCHECK_EQ(std::string::npos, id.find('.'));
+ if (!pref_service_)
+ return fallback;
+ const base::DictionaryValue* dict =
+ pref_service_->GetDictionary(kPersistedDataPreference);
+ if (!dict)
+ return fallback;
+ int result = 0;
+ return dict->GetInteger(
+ base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()), &result)
+ ? result
+ : fallback;
+}
+
+std::string PersistedData::GetString(const std::string& id,
+ const std::string& key) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ // We assume ids do not contain '.' characters.
+ DCHECK_EQ(std::string::npos, id.find('.'));
+ if (!pref_service_)
+ return std::string();
+ const base::DictionaryValue* dict =
+ pref_service_->GetDictionary(kPersistedDataPreference);
+ if (!dict)
+ return std::string();
+ std::string result;
+ return dict->GetString(
+ base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()), &result)
+ ? result
+ : std::string();
+}
+
+int PersistedData::GetDateLastRollCall(const std::string& id) const {
+ return GetInt(id, "dlrc", kDateUnknown);
+}
+
+int PersistedData::GetDateLastActive(const std::string& id) const {
+ return GetInt(id, "dla", kDateUnknown);
+}
+
+std::string PersistedData::GetPingFreshness(const std::string& id) const {
+ std::string result = GetString(id, "pf");
+ return !result.empty() ? base::StringPrintf("{%s}", result.c_str()) : result;
+}
+
+#if defined(OS_STARBOARD)
+std::string PersistedData::GetLastUnpackedVersion(const std::string& id) const {
+ return GetString(id, "version");
+}
+std::string PersistedData::GetUpdaterChannel(const std::string& id) const {
+ return GetString(id, "updaterchannel");
+}
+#endif
+
+std::string PersistedData::GetCohort(const std::string& id) const {
+ return GetString(id, "cohort");
+}
+
+std::string PersistedData::GetCohortName(const std::string& id) const {
+ return GetString(id, "cohortname");
+}
+
+std::string PersistedData::GetCohortHint(const std::string& id) const {
+ return GetString(id, "cohorthint");
+}
+
+void PersistedData::SetDateLastRollCall(const std::vector<std::string>& ids,
+ int datenum) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!pref_service_ || datenum < 0)
+ return;
+ DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+ for (const auto& id : ids) {
+ // We assume ids do not contain '.' characters.
+ DCHECK_EQ(std::string::npos, id.find('.'));
+ update->SetInteger(base::StringPrintf("apps.%s.dlrc", id.c_str()), datenum);
+ update->SetString(base::StringPrintf("apps.%s.pf", id.c_str()),
+ base::GenerateGUID());
+ }
+}
+
+void PersistedData::SetDateLastActive(const std::vector<std::string>& ids,
+ int datenum) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!pref_service_ || datenum < 0)
+ return;
+ DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+ for (const auto& id : ids) {
+ if (GetActiveBit(id)) {
+ // We assume ids do not contain '.' characters.
+ DCHECK_EQ(std::string::npos, id.find('.'));
+ update->SetInteger(base::StringPrintf("apps.%s.dla", id.c_str()),
+ datenum);
+ activity_data_service_->ClearActiveBit(id);
+ }
+ }
+}
+
+void PersistedData::SetString(const std::string& id,
+ const std::string& key,
+ const std::string& value) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ if (!pref_service_)
+ return;
+ DictionaryPrefUpdate update(pref_service_, kPersistedDataPreference);
+ update->SetString(base::StringPrintf("apps.%s.%s", id.c_str(), key.c_str()),
+ value);
+}
+
+#if defined(OS_STARBOARD)
+void PersistedData::SetLastUnpackedVersion(const std::string& id,
+ const std::string& version) {
+ SetString(id, "version", version);
+}
+void PersistedData::SetUpdaterChannel(const std::string& id,
+ const std::string& channel) {
+ SetString(id, "updaterchannel", channel);
+}
+#endif
+
+void PersistedData::SetCohort(const std::string& id,
+ const std::string& cohort) {
+ SetString(id, "cohort", cohort);
+}
+
+void PersistedData::SetCohortName(const std::string& id,
+ const std::string& cohort_name) {
+ SetString(id, "cohortname", cohort_name);
+}
+
+void PersistedData::SetCohortHint(const std::string& id,
+ const std::string& cohort_hint) {
+ SetString(id, "cohorthint", cohort_hint);
+}
+
+bool PersistedData::GetActiveBit(const std::string& id) const {
+ return activity_data_service_ && activity_data_service_->GetActiveBit(id);
+}
+
+int PersistedData::GetDaysSinceLastRollCall(const std::string& id) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return activity_data_service_
+ ? activity_data_service_->GetDaysSinceLastRollCall(id)
+ : kDaysUnknown;
+}
+
+int PersistedData::GetDaysSinceLastActive(const std::string& id) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ return activity_data_service_
+ ? activity_data_service_->GetDaysSinceLastActive(id)
+ : kDaysUnknown;
+}
+
+void PersistedData::RegisterPrefs(PrefRegistrySimple* registry) {
+ registry->RegisterDictionaryPref(kPersistedDataPreference);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/persisted_data.h b/src/components/update_client/persisted_data.h
new file mode 100644
index 0000000..4853d39
--- /dev/null
+++ b/src/components/update_client/persisted_data.h
@@ -0,0 +1,139 @@
+// Copyright 2016 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 COMPONENTS_UPDATE_CLIENT_PERSISTED_DATA_H_
+#define COMPONENTS_UPDATE_CLIENT_PERSISTED_DATA_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/threading/thread_checker.h"
+#include "base/values.h"
+
+class PrefRegistrySimple;
+class PrefService;
+
+namespace update_client {
+
+class ActivityDataService;
+
+// A PersistedData is a wrapper layer around a PrefService, designed to maintain
+// update data that outlives the browser process and isn't exposed outside of
+// update_client.
+//
+// The public methods of this class should be called only on the thread that
+// initializes it - which also has to match the thread the PrefService has been
+// initialized on.
+class PersistedData {
+ public:
+ // Constructs a provider using the specified |pref_service| and
+ // |activity_data_service|.
+ // The associated preferences are assumed to already be registered.
+ // The |pref_service| and |activity_data_service| must outlive the entire
+ // update_client.
+ PersistedData(PrefService* pref_service,
+ ActivityDataService* activity_data_service);
+
+ ~PersistedData();
+
+ // Returns the DateLastRollCall (the server-localized calendar date number the
+ // |id| was last checked by this client on) for the specified |id|.
+ // -2 indicates that there is no recorded date number for the |id|.
+ int GetDateLastRollCall(const std::string& id) const;
+
+ // Returns the DateLastActive (the server-localized calendar date number the
+ // |id| was last active by this client on) for the specified |id|.
+ // -1 indicates that there is no recorded date for the |id| (i.e. this is the
+ // first time the |id| is active).
+ // -2 indicates that the |id| has an unknown value of last active date.
+ int GetDateLastActive(const std::string& id) const;
+
+#if defined(OS_STARBOARD)
+ // Returns the version of the update that was last successfully unpacked for
+ // the specified |id|. "" indicates that there is no recorded version value
+ // for the |id|.
+ std::string GetLastUnpackedVersion(const std::string& id) const;
+
+ // Returns the updater channel that is set for the specified |id|. ""
+ // indicates that there is no recorded updater channel value for the |id|.
+ std::string GetUpdaterChannel(const std::string& id) const;
+#endif
+
+ // Returns the PingFreshness (a random token that is written into the profile
+ // data whenever the DateLastRollCall it is modified) for the specified |id|.
+ // "" indicates that there is no recorded freshness value for the |id|.
+ std::string GetPingFreshness(const std::string& id) const;
+
+ // Records the DateLastRollCall for the specified |ids|. |datenum| must be a
+ // non-negative integer: calls with a negative |datenum| are simply ignored.
+ // Calls to SetDateLastRollCall that occur prior to the persisted data store
+ // has been fully initialized are ignored. Also sets the PingFreshness.
+ void SetDateLastRollCall(const std::vector<std::string>& ids, int datenum);
+
+ // Records the DateLastActive for the specified |ids|. |datenum| must be a
+ // non-negative integer: calls with a negative |datenum| are simply ignored.
+ // Calls to SetDateLastActive that occur prior to the persisted data store
+ // has been fully initialized or the active bit of the |ids| are not set
+ // are ignored.
+ // This function also clears the active bits of the specified |ids| if they
+ // are set.
+ void SetDateLastActive(const std::vector<std::string>& ids, int datenum);
+
+#if defined(OS_STARBOARD)
+ // Records the version of the update that is successfully unpacked for
+ // the specified |id|.
+ void SetLastUnpackedVersion(const std::string& id,
+ const std::string& version);
+
+ // Records the updater channel that is set for the specified |id|.
+ void SetUpdaterChannel(const std::string& id, const std::string& channel);
+#endif
+
+ // This is called only via update_client's RegisterUpdateClientPreferences.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ // These functions return cohort data for the specified |id|. "Cohort"
+ // indicates the membership of the client in any release channels components
+ // have set up in a machine-readable format, while "CohortName" does so in a
+ // human-readable form. "CohortHint" indicates the client's channel selection
+ // preference.
+ std::string GetCohort(const std::string& id) const;
+ std::string GetCohortHint(const std::string& id) const;
+ std::string GetCohortName(const std::string& id) const;
+
+ // These functions set cohort data for the specified |id|.
+ void SetCohort(const std::string& id, const std::string& cohort);
+ void SetCohortHint(const std::string& id, const std::string& cohort_hint);
+ void SetCohortName(const std::string& id, const std::string& cohort_name);
+
+ // Returns true if the active bit of the specified |id| is set.
+ bool GetActiveBit(const std::string& id) const;
+
+ // The following two functions returns the number of days since the last
+ // time the client checked for update/was active.
+ // -1 indicates that this is the first time the client reports
+ // an update check/active for the specified |id|.
+ // -2 indicates that the client has no information about the
+ // update check/last active of the specified |id|.
+ int GetDaysSinceLastRollCall(const std::string& id) const;
+ int GetDaysSinceLastActive(const std::string& id) const;
+
+ private:
+ int GetInt(const std::string& id, const std::string& key, int fallback) const;
+ std::string GetString(const std::string& id, const std::string& key) const;
+ void SetString(const std::string& id,
+ const std::string& key,
+ const std::string& value);
+
+ base::ThreadChecker thread_checker_;
+ PrefService* pref_service_;
+ ActivityDataService* activity_data_service_;
+
+ DISALLOW_COPY_AND_ASSIGN(PersistedData);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PERSISTED_DATA_H_
diff --git a/src/components/update_client/persisted_data_unittest.cc b/src/components/update_client/persisted_data_unittest.cc
new file mode 100644
index 0000000..7f7ae6e
--- /dev/null
+++ b/src/components/update_client/persisted_data_unittest.cc
@@ -0,0 +1,250 @@
+// Copyright 2016 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 <memory>
+#include <string>
+#include <vector>
+
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+TEST(PersistedDataTest, Simple) {
+ auto pref = std::make_unique<TestingPrefServiceSimple>();
+ PersistedData::RegisterPrefs(pref->registry());
+ auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+ EXPECT_EQ(-2, metadata->GetDateLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+ std::vector<std::string> items;
+ items.push_back("someappid");
+ metadata->SetDateLastRollCall(items, 3383);
+ metadata->SetDateLastActive(items, 3383);
+ EXPECT_EQ(3383, metadata->GetDateLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someotherappid"));
+ const std::string pf1 = metadata->GetPingFreshness("someappid");
+ EXPECT_FALSE(pf1.empty());
+ metadata->SetDateLastRollCall(items, 3386);
+ metadata->SetDateLastActive(items, 3386);
+ EXPECT_EQ(3386, metadata->GetDateLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someotherappid"));
+ const std::string pf2 = metadata->GetPingFreshness("someappid");
+ EXPECT_FALSE(pf2.empty());
+ // The following has a 1 / 2^128 chance of being flaky.
+ EXPECT_NE(pf1, pf2);
+}
+
+TEST(PersistedDataTest, SharedPref) {
+ auto pref = std::make_unique<TestingPrefServiceSimple>();
+ PersistedData::RegisterPrefs(pref->registry());
+ auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+ EXPECT_EQ(-2, metadata->GetDateLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+ std::vector<std::string> items;
+ items.push_back("someappid");
+ metadata->SetDateLastRollCall(items, 3383);
+ metadata->SetDateLastActive(items, 3383);
+
+ // Now, create a new PersistedData reading from the same path, verify
+ // that it loads the value.
+ metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+ EXPECT_EQ(3383, metadata->GetDateLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastRollCall("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastRollCall("someotherappid"));
+ EXPECT_EQ(-2, metadata->GetDaysSinceLastActive("someotherappid"));
+}
+
+TEST(PersistedDataTest, SimpleCohort) {
+ auto pref = std::make_unique<TestingPrefServiceSimple>();
+ PersistedData::RegisterPrefs(pref->registry());
+ auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+ EXPECT_EQ("", metadata->GetCohort("someappid"));
+ EXPECT_EQ("", metadata->GetCohort("someotherappid"));
+ EXPECT_EQ("", metadata->GetCohortHint("someappid"));
+ EXPECT_EQ("", metadata->GetCohortHint("someotherappid"));
+ EXPECT_EQ("", metadata->GetCohortName("someappid"));
+ EXPECT_EQ("", metadata->GetCohortName("someotherappid"));
+ metadata->SetCohort("someappid", "c1");
+ metadata->SetCohort("someotherappid", "c2");
+ metadata->SetCohortHint("someappid", "ch1");
+ metadata->SetCohortHint("someotherappid", "ch2");
+ metadata->SetCohortName("someappid", "cn1");
+ metadata->SetCohortName("someotherappid", "cn2");
+ EXPECT_EQ("c1", metadata->GetCohort("someappid"));
+ EXPECT_EQ("c2", metadata->GetCohort("someotherappid"));
+ EXPECT_EQ("ch1", metadata->GetCohortHint("someappid"));
+ EXPECT_EQ("ch2", metadata->GetCohortHint("someotherappid"));
+ EXPECT_EQ("cn1", metadata->GetCohortName("someappid"));
+ EXPECT_EQ("cn2", metadata->GetCohortName("someotherappid"));
+ metadata->SetCohort("someappid", "oc1");
+ metadata->SetCohort("someotherappid", "oc2");
+ metadata->SetCohortHint("someappid", "och1");
+ metadata->SetCohortHint("someotherappid", "och2");
+ metadata->SetCohortName("someappid", "ocn1");
+ metadata->SetCohortName("someotherappid", "ocn2");
+ EXPECT_EQ("oc1", metadata->GetCohort("someappid"));
+ EXPECT_EQ("oc2", metadata->GetCohort("someotherappid"));
+ EXPECT_EQ("och1", metadata->GetCohortHint("someappid"));
+ EXPECT_EQ("och2", metadata->GetCohortHint("someotherappid"));
+ EXPECT_EQ("ocn1", metadata->GetCohortName("someappid"));
+ EXPECT_EQ("ocn2", metadata->GetCohortName("someotherappid"));
+}
+
+TEST(PersistedDataTest, ActivityData) {
+ class TestActivityDataService : public ActivityDataService {
+ public:
+ bool GetActiveBit(const std::string& id) const override {
+ auto it = active_.find(id);
+ return it != active_.end() ? it->second : false;
+ }
+
+ int GetDaysSinceLastActive(const std::string& id) const override {
+ auto it = days_last_active_.find(id);
+ return it != days_last_active_.end() ? it->second : -1;
+ }
+
+ int GetDaysSinceLastRollCall(const std::string& id) const override {
+ auto it = days_last_roll_call_.find(id);
+ return it != days_last_roll_call_.end() ? it->second : -1;
+ }
+
+ void ClearActiveBit(const std::string& id) override { active_[id] = false; }
+
+ void SetDaysSinceLastActive(const std::string& id, int numdays) {
+ days_last_active_[id] = numdays;
+ }
+
+ void SetDaysSinceLastRollCall(const std::string& id, int numdays) {
+ days_last_roll_call_[id] = numdays;
+ }
+
+ void SetActiveBit(const std::string& id) { active_[id] = true; }
+
+ private:
+ std::map<std::string, bool> active_;
+ std::map<std::string, int> days_last_active_;
+ std::map<std::string, int> days_last_roll_call_;
+ };
+
+ auto pref = std::make_unique<TestingPrefServiceSimple>();
+ auto activity_service = std::make_unique<TestActivityDataService>();
+ PersistedData::RegisterPrefs(pref->registry());
+ auto metadata =
+ std::make_unique<PersistedData>(pref.get(), activity_service.get());
+
+ std::vector<std::string> items({"id1", "id2", "id3"});
+
+ for (const auto& item : items) {
+ EXPECT_EQ(-2, metadata->GetDateLastActive(item));
+ EXPECT_EQ(-2, metadata->GetDateLastRollCall(item));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive(item));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastRollCall(item));
+ EXPECT_EQ(false, metadata->GetActiveBit(item));
+ }
+
+ metadata->SetDateLastActive(items, 1234);
+ metadata->SetDateLastRollCall(items, 1234);
+ for (const auto& item : items) {
+ EXPECT_EQ(false, metadata->GetActiveBit(item));
+ EXPECT_EQ(-2, metadata->GetDateLastActive(item));
+ EXPECT_EQ(1234, metadata->GetDateLastRollCall(item));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive(item));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastRollCall(item));
+ }
+
+ activity_service->SetActiveBit("id1");
+ activity_service->SetDaysSinceLastActive("id1", 3);
+ activity_service->SetDaysSinceLastRollCall("id1", 2);
+ activity_service->SetDaysSinceLastRollCall("id2", 3);
+ activity_service->SetDaysSinceLastRollCall("id3", 4);
+ EXPECT_EQ(true, metadata->GetActiveBit("id1"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+ EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+ EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+
+ metadata->SetDateLastActive(items, 3384);
+ metadata->SetDateLastRollCall(items, 3383);
+ EXPECT_EQ(false, metadata->GetActiveBit("id1"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id3"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+ EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+ EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+ EXPECT_EQ(3384, metadata->GetDateLastActive("id1"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("id2"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("id3"));
+ EXPECT_EQ(3383, metadata->GetDateLastRollCall("id1"));
+ EXPECT_EQ(3383, metadata->GetDateLastRollCall("id2"));
+ EXPECT_EQ(3383, metadata->GetDateLastRollCall("id3"));
+
+ metadata->SetDateLastActive(items, 5000);
+ metadata->SetDateLastRollCall(items, 3390);
+ EXPECT_EQ(false, metadata->GetActiveBit("id1"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id3"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+ EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+ EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+ EXPECT_EQ(3384, metadata->GetDateLastActive("id1"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("id2"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("id3"));
+ EXPECT_EQ(3390, metadata->GetDateLastRollCall("id1"));
+ EXPECT_EQ(3390, metadata->GetDateLastRollCall("id2"));
+ EXPECT_EQ(3390, metadata->GetDateLastRollCall("id3"));
+
+ activity_service->SetActiveBit("id2");
+ metadata->SetDateLastActive(items, 5678);
+ metadata->SetDateLastRollCall(items, 6789);
+ EXPECT_EQ(false, metadata->GetActiveBit("id1"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id2"));
+ EXPECT_EQ(false, metadata->GetActiveBit("id3"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastActive("id1"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id2"));
+ EXPECT_EQ(-1, metadata->GetDaysSinceLastActive("id3"));
+ EXPECT_EQ(2, metadata->GetDaysSinceLastRollCall("id1"));
+ EXPECT_EQ(3, metadata->GetDaysSinceLastRollCall("id2"));
+ EXPECT_EQ(4, metadata->GetDaysSinceLastRollCall("id3"));
+ EXPECT_EQ(3384, metadata->GetDateLastActive("id1"));
+ EXPECT_EQ(5678, metadata->GetDateLastActive("id2"));
+ EXPECT_EQ(-2, metadata->GetDateLastActive("id3"));
+ EXPECT_EQ(6789, metadata->GetDateLastRollCall("id1"));
+ EXPECT_EQ(6789, metadata->GetDateLastRollCall("id2"));
+ EXPECT_EQ(6789, metadata->GetDateLastRollCall("id3"));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/ping_manager.cc b/src/components/update_client/ping_manager.cc
new file mode 100644
index 0000000..3c2a60c
--- /dev/null
+++ b/src/components/update_client/ping_manager.cc
@@ -0,0 +1,129 @@
+// 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 "components/update_client/ping_manager.h"
+
+#include <stddef.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/request_sender.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+const int kErrorNoEvents = -1;
+const int kErrorNoUrl = -2;
+
+// An instance of this class can send only one ping.
+class PingSender : public base::RefCountedThreadSafe<PingSender> {
+ public:
+ using Callback = PingManager::Callback;
+ explicit PingSender(scoped_refptr<Configurator> config);
+ void SendPing(const Component& component, Callback callback);
+
+ protected:
+ virtual ~PingSender();
+
+ private:
+ friend class base::RefCountedThreadSafe<PingSender>;
+ void SendPingComplete(int error,
+ const std::string& response,
+ int retry_after_sec);
+
+ THREAD_CHECKER(thread_checker_);
+
+ const scoped_refptr<Configurator> config_;
+ Callback callback_;
+ std::unique_ptr<RequestSender> request_sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(PingSender);
+};
+
+PingSender::PingSender(scoped_refptr<Configurator> config) : config_(config) {}
+
+PingSender::~PingSender() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void PingSender::SendPing(const Component& component, Callback callback) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ if (component.events().empty()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), kErrorNoEvents, ""));
+ return;
+ }
+
+ DCHECK(component.crx_component());
+
+ auto urls(config_->PingUrl());
+ if (component.crx_component()->requires_network_encryption)
+ RemoveUnsecureUrls(&urls);
+
+ if (urls.empty()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), kErrorNoUrl, ""));
+ return;
+ }
+
+ callback_ = std::move(callback);
+
+ std::vector<protocol_request::App> apps;
+ apps.push_back(MakeProtocolApp(component.id(),
+ component.crx_component()->version,
+ component.GetEvents()));
+ request_sender_ = std::make_unique<RequestSender>(config_);
+ request_sender_->Send(
+ urls, {},
+ config_->GetProtocolHandlerFactory()->CreateSerializer()->Serialize(
+ MakeProtocolRequest(
+ component.session_id(), config_->GetProdId(),
+ config_->GetBrowserVersion().GetString(), config_->GetLang(),
+ config_->GetChannel(), config_->GetOSLongName(),
+ config_->GetDownloadPreference(), config_->ExtraRequestParams(),
+ nullptr, std::move(apps))),
+ false, base::BindOnce(&PingSender::SendPingComplete, this));
+}
+
+void PingSender::SendPingComplete(int error,
+ const std::string& response,
+ int retry_after_sec) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ std::move(callback_).Run(error, response);
+}
+
+} // namespace
+
+PingManager::PingManager(scoped_refptr<Configurator> config)
+ : config_(config) {}
+
+PingManager::~PingManager() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void PingManager::SendPing(const Component& component, Callback callback) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ auto ping_sender = base::MakeRefCounted<PingSender>(config_);
+ ping_sender->SendPing(component, std::move(callback));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/ping_manager.h b/src/components/update_client/ping_manager.h
new file mode 100644
index 0000000..24040e6
--- /dev/null
+++ b/src/components/update_client/ping_manager.h
@@ -0,0 +1,48 @@
+// 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 COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_
+#define COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_
+
+#include <string>
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+
+namespace update_client {
+
+class Configurator;
+class Component;
+
+class PingManager : public base::RefCountedThreadSafe<PingManager> {
+ public:
+ // |error| is 0 if the ping was sent successfully, otherwise |error| contains
+ // a value with no particular meaning for the caller.
+ using Callback =
+ base::OnceCallback<void(int error, const std::string& response)>;
+
+ explicit PingManager(scoped_refptr<Configurator> config);
+
+ // Sends a ping for the |item|. |callback| is invoked after the ping is sent
+ // or an error has occured. The ping itself is not persisted and it will
+ // be discarded if it has not been sent for any reason.
+ virtual void SendPing(const Component& component, Callback callback);
+
+ protected:
+ virtual ~PingManager();
+
+ private:
+ friend class base::RefCountedThreadSafe<PingManager>;
+
+ THREAD_CHECKER(thread_checker_);
+ const scoped_refptr<Configurator> config_;
+
+ DISALLOW_COPY_AND_ASSIGN(PingManager);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PING_MANAGER_H_
diff --git a/src/components/update_client/ping_manager_unittest.cc b/src/components/update_client/ping_manager_unittest.cc
new file mode 100644
index 0000000..34583d7
--- /dev/null
+++ b/src/components/update_client/ping_manager_unittest.cc
@@ -0,0 +1,443 @@
+// Copyright 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 "components/update_client/ping_manager.h"
+
+#include <stdint.h>
+
+#include <limits>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/json/json_reader.h"
+#include "base/memory/ref_counted.h"
+#include "base/run_loop.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "components/update_client/component.h"
+#include "components/update_client/net/url_loader_post_interceptor.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/update_engine.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using std::string;
+
+namespace update_client {
+
+class PingManagerTest : public testing::Test,
+ public testing::WithParamInterface<bool> {
+ public:
+ PingManagerTest();
+ ~PingManagerTest() override {}
+
+ PingManager::Callback MakePingCallback();
+ scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
+
+ // Overrides from testing::Test.
+ void SetUp() override;
+ void TearDown() override;
+
+ void PingSentCallback(int error, const std::string& response);
+
+ protected:
+ void Quit();
+ void RunThreads();
+
+ scoped_refptr<TestConfigurator> config_;
+ scoped_refptr<PingManager> ping_manager_;
+
+ int error_ = -1;
+ std::string response_;
+
+ private:
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::OnceClosure quit_closure_;
+};
+
+PingManagerTest::PingManagerTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO) {
+ config_ = base::MakeRefCounted<TestConfigurator>();
+}
+
+void PingManagerTest::SetUp() {
+ ping_manager_ = base::MakeRefCounted<PingManager>(config_);
+}
+
+void PingManagerTest::TearDown() {
+ // Run the threads until they are idle to allow the clean up
+ // of the network interceptors on the IO thread.
+ scoped_task_environment_.RunUntilIdle();
+ ping_manager_ = nullptr;
+}
+
+void PingManagerTest::RunThreads() {
+ base::RunLoop runloop;
+ quit_closure_ = runloop.QuitClosure();
+ runloop.Run();
+}
+
+void PingManagerTest::Quit() {
+ if (!quit_closure_.is_null())
+ std::move(quit_closure_).Run();
+}
+
+PingManager::Callback PingManagerTest::MakePingCallback() {
+ return base::BindOnce(&PingManagerTest::PingSentCallback,
+ base::Unretained(this));
+}
+
+void PingManagerTest::PingSentCallback(int error, const std::string& response) {
+ error_ = error;
+ response_ = response;
+ Quit();
+}
+
+scoped_refptr<UpdateContext> PingManagerTest::MakeMockUpdateContext() const {
+ return base::MakeRefCounted<UpdateContext>(
+ config_, false, std::vector<std::string>(),
+ UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
+ UpdateEngine::Callback(), nullptr);
+}
+
+// This test is parameterized for using JSON or XML serialization. |true| means
+// JSON serialization is used.
+INSTANTIATE_TEST_SUITE_P(Parameterized, PingManagerTest, testing::Bool());
+
+TEST_P(PingManagerTest, SendPing) {
+ auto interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(interceptor);
+
+ const auto update_context = MakeMockUpdateContext();
+ {
+ // Test eventresult="1" is sent for successful updates.
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.crx_component_->version = base::Version("1.0");
+ component.state_ = std::make_unique<Component::StateUpdated>(&component);
+ component.previous_version_ = base::Version("1.0");
+ component.next_version_ = base::Version("2.0");
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+ const auto msg = interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(msg);
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ ASSERT_TRUE(request);
+ EXPECT_TRUE(request->FindKey("@os"));
+ EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+ EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+ EXPECT_TRUE(request->FindKey("arch"));
+ EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+ EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+ EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+ EXPECT_TRUE(request->FindKey("nacl_arch"));
+ EXPECT_EQ("fake_channel_string",
+ request->FindKey("prodchannel")->GetString());
+ EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+ EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+ EXPECT_TRUE(request->FindKey("requestid"));
+ EXPECT_TRUE(request->FindKey("sessionid"));
+ EXPECT_EQ("fake_channel_string",
+ request->FindKey("updaterchannel")->GetString());
+ EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
+
+ EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+ EXPECT_EQ("Fake Operating System",
+ request->FindPath({"os", "platform"})->GetString());
+ EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+ EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+ const auto& event = app.FindKey("event")->GetList()[0];
+ EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+
+ // Check the ping request does not carry the specific extra request headers.
+ const auto headers = std::get<1>(interceptor->GetRequests()[0]);
+ EXPECT_FALSE(headers.HasHeader("X-Goog-Update-Interactivity"));
+ EXPECT_FALSE(headers.HasHeader("X-Goog-Update-Updater"));
+ EXPECT_FALSE(headers.HasHeader("X-Goog-Update-AppId"));
+ interceptor->Reset();
+ }
+
+ {
+ // Test eventresult="0" is sent for failed updates.
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.crx_component_->version = base::Version("1.0");
+ component.state_ =
+ std::make_unique<Component::StateUpdateError>(&component);
+ component.previous_version_ = base::Version("1.0");
+ component.next_version_ = base::Version("2.0");
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+ const auto msg = interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(msg);
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+ EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+ const auto& event = app.FindKey("event")->GetList()[0];
+ EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ interceptor->Reset();
+ }
+
+ {
+ // Test the error values and the fingerprints.
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.crx_component_->version = base::Version("1.0");
+ component.state_ =
+ std::make_unique<Component::StateUpdateError>(&component);
+ component.previous_version_ = base::Version("1.0");
+ component.next_version_ = base::Version("2.0");
+ component.previous_fp_ = "prev fp";
+ component.next_fp_ = "next fp";
+ component.error_category_ = ErrorCategory::kDownload;
+ component.error_code_ = 2;
+ component.extra_code1_ = -1;
+ component.diff_error_category_ = ErrorCategory::kService;
+ component.diff_error_code_ = 20;
+ component.diff_extra_code1_ = -10;
+ component.crx_diffurls_.push_back(GURL("http://host/path"));
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+ const auto msg = interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(msg);
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+ EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+ const auto& event = app.FindKey("event")->GetList()[0];
+ EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ EXPECT_EQ(4, event.FindKey("differrorcat")->GetInt());
+ EXPECT_EQ(20, event.FindKey("differrorcode")->GetInt());
+ EXPECT_EQ(-10, event.FindKey("diffextracode1")->GetInt());
+ EXPECT_EQ(0, event.FindKey("diffresult")->GetInt());
+ EXPECT_EQ(1, event.FindKey("errorcat")->GetInt());
+ EXPECT_EQ(2, event.FindKey("errorcode")->GetInt());
+ EXPECT_EQ(-1, event.FindKey("extracode1")->GetInt());
+ EXPECT_EQ("next fp", event.FindKey("nextfp")->GetString());
+ EXPECT_EQ("prev fp", event.FindKey("previousfp")->GetString());
+ interceptor->Reset();
+ }
+
+ {
+ // Test an invalid |next_version| is not serialized.
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.crx_component_->version = base::Version("1.0");
+ component.state_ =
+ std::make_unique<Component::StateUpdateError>(&component);
+ component.previous_version_ = base::Version("1.0");
+
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+ const auto msg = interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(msg);
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+ EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+ const auto& event = app.FindKey("event")->GetList()[0];
+ EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ interceptor->Reset();
+ }
+
+ {
+ // Test a valid |previouversion| and |next_version| = base::Version("0")
+ // are serialized correctly under <event...> for uninstall.
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.Uninstall(base::Version("1.2.3.4"), 0);
+ component.AppendEvent(component.MakeEventUninstalled());
+
+ EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+ const auto msg = interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(msg);
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+ EXPECT_EQ("1.2.3.4", app.FindKey("version")->GetString());
+ const auto& event = app.FindKey("event")->GetList()[0];
+ EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(4, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ("1.2.3.4", event.FindKey("previousversion")->GetString());
+ EXPECT_EQ("0", event.FindKey("nextversion")->GetString());
+ interceptor->Reset();
+ }
+
+ {
+ // Test the download metrics.
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.crx_component_->version = base::Version("1.0");
+ component.state_ = std::make_unique<Component::StateUpdated>(&component);
+ component.previous_version_ = base::Version("1.0");
+ component.next_version_ = base::Version("2.0");
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ CrxDownloader::DownloadMetrics download_metrics;
+ download_metrics.url = GURL("http://host1/path1");
+ download_metrics.downloader = CrxDownloader::DownloadMetrics::kUrlFetcher;
+ download_metrics.error = -1;
+ download_metrics.downloaded_bytes = 123;
+ download_metrics.total_bytes = 456;
+ download_metrics.download_time_ms = 987;
+ component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+ download_metrics = CrxDownloader::DownloadMetrics();
+ download_metrics.url = GURL("http://host2/path2");
+ download_metrics.downloader = CrxDownloader::DownloadMetrics::kBits;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1230;
+ download_metrics.total_bytes = 4560;
+ download_metrics.download_time_ms = 9870;
+ component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+ download_metrics = CrxDownloader::DownloadMetrics();
+ download_metrics.url = GURL("http://host3/path3");
+ download_metrics.downloader = CrxDownloader::DownloadMetrics::kBits;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = kProtocolMaxInt;
+ download_metrics.total_bytes = kProtocolMaxInt - 1;
+ download_metrics.download_time_ms = kProtocolMaxInt - 2;
+ component.AppendEvent(component.MakeEventDownloadMetrics(download_metrics));
+
+ EXPECT_TRUE(interceptor->ExpectRequest(std::make_unique<AnyMatch>()));
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(1, interceptor->GetCount()) << interceptor->GetRequestsAsString();
+ const auto msg = interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(msg);
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ("abc", app.FindKey("appid")->GetString());
+ EXPECT_EQ("1.0", app.FindKey("version")->GetString());
+ EXPECT_EQ(4u, app.FindKey("event")->GetList().size());
+ {
+ const auto& event = app.FindKey("event")->GetList()[0];
+ EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(3, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ }
+ {
+ const auto& event = app.FindKey("event")->GetList()[1];
+ EXPECT_EQ(0, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(987, event.FindKey("download_time_ms")->GetDouble());
+ EXPECT_EQ(123, event.FindKey("downloaded")->GetDouble());
+ EXPECT_EQ("direct", event.FindKey("downloader")->GetString());
+ EXPECT_EQ(-1, event.FindKey("errorcode")->GetInt());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ EXPECT_EQ(456, event.FindKey("total")->GetDouble());
+ EXPECT_EQ("http://host1/path1", event.FindKey("url")->GetString());
+ }
+ {
+ const auto& event = app.FindKey("event")->GetList()[2];
+ EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(9870, event.FindKey("download_time_ms")->GetDouble());
+ EXPECT_EQ(1230, event.FindKey("downloaded")->GetDouble());
+ EXPECT_EQ("bits", event.FindKey("downloader")->GetString());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ EXPECT_EQ(4560, event.FindKey("total")->GetDouble());
+ EXPECT_EQ("http://host2/path2", event.FindKey("url")->GetString());
+ }
+ {
+ const auto& event = app.FindKey("event")->GetList()[3];
+ EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(14, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(9007199254740990,
+ event.FindKey("download_time_ms")->GetDouble());
+ EXPECT_EQ(9007199254740992, event.FindKey("downloaded")->GetDouble());
+ EXPECT_EQ("bits", event.FindKey("downloader")->GetString());
+ EXPECT_EQ("2.0", event.FindKey("nextversion")->GetString());
+ EXPECT_EQ("1.0", event.FindKey("previousversion")->GetString());
+ EXPECT_EQ(9007199254740991, event.FindKey("total")->GetDouble());
+ EXPECT_EQ("http://host3/path3", event.FindKey("url")->GetString());
+ }
+ interceptor->Reset();
+ }
+}
+
+// Tests that sending the ping fails when the component requires encryption but
+// the ping URL is unsecure.
+TEST_P(PingManagerTest, RequiresEncryption) {
+ config_->SetPingUrl(GURL("http:\\foo\bar"));
+
+ const auto update_context = MakeMockUpdateContext();
+
+ Component component(*update_context, "abc");
+ component.crx_component_ = CrxComponent();
+ component.crx_component_->version = base::Version("1.0");
+
+ // The default value for |requires_network_encryption| is true.
+ EXPECT_TRUE(component.crx_component_->requires_network_encryption);
+
+ component.state_ = std::make_unique<Component::StateUpdated>(&component);
+ component.previous_version_ = base::Version("1.0");
+ component.next_version_ = base::Version("2.0");
+ component.AppendEvent(component.MakeEventUpdateComplete());
+
+ ping_manager_->SendPing(component, MakePingCallback());
+ RunThreads();
+
+ EXPECT_EQ(-2, error_);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_definition.cc b/src/components/update_client/protocol_definition.cc
new file mode 100644
index 0000000..2dd3cc6
--- /dev/null
+++ b/src/components/update_client/protocol_definition.cc
@@ -0,0 +1,38 @@
+// Copyright (c) 2018 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 "components/update_client/protocol_definition.h"
+
+#include "base/values.h"
+
+namespace update_client {
+
+namespace protocol_request {
+
+OS::OS() = default;
+OS::OS(OS&&) = default;
+OS::~OS() = default;
+
+Updater::Updater() = default;
+Updater::Updater(const Updater&) = default;
+Updater::~Updater() = default;
+
+UpdateCheck::UpdateCheck() = default;
+UpdateCheck::~UpdateCheck() = default;
+
+Ping::Ping() = default;
+Ping::Ping(const Ping&) = default;
+Ping::~Ping() = default;
+
+App::App() = default;
+App::App(App&&) = default;
+App::~App() = default;
+
+Request::Request() = default;
+Request::Request(Request&&) = default;
+Request::~Request() = default;
+
+} // namespace protocol_request
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_definition.h b/src/components/update_client/protocol_definition.h
new file mode 100644
index 0000000..aaacc76
--- /dev/null
+++ b/src/components/update_client/protocol_definition.h
@@ -0,0 +1,177 @@
+// Copyright (c) 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_DEFINITION_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_DEFINITION_H_
+
+#include <stdint.h>
+
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/optional.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+namespace update_client {
+
+// The protocol versions so far are:
+// * Version 3.1: it changes how the run actions are serialized.
+// * Version 3.0: it is the version implemented by the desktop updaters.
+constexpr char kProtocolVersion[] = "3.1";
+
+// Due to implementation constraints of the JSON parser and serializer,
+// precision of integer numbers greater than 2^53 is lost.
+constexpr int64_t kProtocolMaxInt = 1LL << 53;
+
+namespace protocol_request {
+
+struct HW {
+ uint32_t physmemory = 0; // Physical memory rounded down to the closest GB.
+};
+
+struct OS {
+ OS();
+ OS(OS&&);
+ ~OS();
+
+ std::string platform;
+ std::string version;
+ std::string service_pack;
+ std::string arch;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(OS);
+};
+
+struct Updater {
+ Updater();
+ Updater(const Updater&);
+ ~Updater();
+
+ std::string name;
+ std::string version;
+ bool is_machine = false;
+ bool autoupdate_check_enabled = false;
+ base::Optional<int> last_started;
+ base::Optional<int> last_checked;
+ int update_policy = 0;
+};
+
+struct UpdateCheck {
+ UpdateCheck();
+ ~UpdateCheck();
+
+ bool is_update_disabled = false;
+};
+
+// didrun element. The element is named "ping" for legacy reasons.
+struct Ping {
+ Ping();
+ Ping(const Ping&);
+ ~Ping();
+
+ // Preferred user count metrics ("ad" and "rd").
+ base::Optional<int> date_last_active;
+ base::Optional<int> date_last_roll_call;
+
+ // Legacy user count metrics ("a" and "r").
+ base::Optional<int> days_since_last_active_ping;
+ int days_since_last_roll_call = 0;
+
+ std::string ping_freshness;
+};
+
+struct App {
+ App();
+ App(App&&);
+ ~App();
+
+ std::string app_id;
+ std::string version;
+ base::flat_map<std::string, std::string> installer_attributes;
+ std::string lang;
+ std::string brand_code;
+ std::string install_source;
+ std::string install_location;
+ std::string fingerprint;
+
+ std::string cohort; // Opaque string.
+ std::string cohort_hint; // Server may use to move the app to a new cohort.
+ std::string cohort_name; // Human-readable interpretation of the cohort.
+
+ base::Optional<bool> enabled;
+ base::Optional<std::vector<int>> disabled_reasons;
+
+ // Optional update check.
+ base::Optional<UpdateCheck> update_check;
+
+ // Optional 'did run' ping.
+ base::Optional<Ping> ping;
+
+ // Progress/result pings.
+ base::Optional<std::vector<base::Value>> events;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(App);
+};
+
+struct Request {
+ Request();
+ Request(Request&&);
+ ~Request();
+
+ std::string protocol_version;
+
+ // Unique identifier for this session, used to correlate multiple requests
+ // associated with a single update operation.
+ std::string session_id;
+
+ // Unique identifier for this request, used to associate the same request
+ // received multiple times on the server.
+ std::string request_id;
+
+ std::string updatername;
+ std::string updaterversion;
+ std::string prodversion;
+ std::string lang;
+ std::string updaterchannel;
+ std::string prodchannel;
+ std::string operating_system;
+ std::string arch;
+ std::string nacl_arch;
+
+#if defined(OS_WIN)
+ bool is_wow64 = false;
+#endif
+
+ // Provides a hint for what download urls should be returned by server.
+ // This data member is controlled by group policy settings.
+ // The only group policy value supported so far is |cacheable|.
+ std::string dlpref;
+
+ // True if this machine is part of a managed enterprise domain.
+ base::Optional<bool> domain_joined;
+
+ base::flat_map<std::string, std::string> additional_attributes;
+
+ HW hw;
+
+ OS os;
+
+ base::Optional<Updater> updater;
+
+ std::vector<App> apps;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Request);
+};
+
+} // namespace protocol_request
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PROTOCOL_DEFINITION_H_
diff --git a/src/components/update_client/protocol_handler.cc b/src/components/update_client/protocol_handler.cc
new file mode 100644
index 0000000..aa547b4
--- /dev/null
+++ b/src/components/update_client/protocol_handler.cc
@@ -0,0 +1,21 @@
+// Copyright 2018 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 "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_parser_json.h"
+#include "components/update_client/protocol_serializer_json.h"
+
+namespace update_client {
+
+std::unique_ptr<ProtocolParser> ProtocolHandlerFactoryJSON::CreateParser()
+ const {
+ return std::make_unique<ProtocolParserJSON>();
+}
+
+std::unique_ptr<ProtocolSerializer>
+ProtocolHandlerFactoryJSON::CreateSerializer() const {
+ return std::make_unique<ProtocolSerializerJSON>();
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_handler.h b/src/components/update_client/protocol_handler.h
new file mode 100644
index 0000000..57c8cfd
--- /dev/null
+++ b/src/components/update_client/protocol_handler.h
@@ -0,0 +1,39 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
+
+#include <memory>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+class ProtocolParser;
+class ProtocolSerializer;
+
+class ProtocolHandlerFactory {
+ public:
+ virtual ~ProtocolHandlerFactory() = default;
+ virtual std::unique_ptr<ProtocolParser> CreateParser() const = 0;
+ virtual std::unique_ptr<ProtocolSerializer> CreateSerializer() const = 0;
+
+ protected:
+ ProtocolHandlerFactory() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProtocolHandlerFactory);
+};
+
+class ProtocolHandlerFactoryJSON final : public ProtocolHandlerFactory {
+ public:
+ // Overrides for ProtocolHandlerFactory.
+ std::unique_ptr<ProtocolParser> CreateParser() const override;
+ std::unique_ptr<ProtocolSerializer> CreateSerializer() const override;
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PROTOCOL_HANDLER_H_
diff --git a/src/components/update_client/protocol_parser.cc b/src/components/update_client/protocol_parser.cc
new file mode 100644
index 0000000..7f280c5
--- /dev/null
+++ b/src/components/update_client/protocol_parser.cc
@@ -0,0 +1,55 @@
+// 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 "components/update_client/protocol_parser.h"
+#include "base/strings/stringprintf.h"
+
+namespace update_client {
+
+const char ProtocolParser::Result::kCohort[] = "cohort";
+const char ProtocolParser::Result::kCohortHint[] = "cohorthint";
+const char ProtocolParser::Result::kCohortName[] = "cohortname";
+
+ProtocolParser::ProtocolParser() = default;
+ProtocolParser::~ProtocolParser() = default;
+
+ProtocolParser::Results::Results() = default;
+ProtocolParser::Results::Results(const Results& other) = default;
+ProtocolParser::Results::~Results() = default;
+
+ProtocolParser::Result::Result() = default;
+ProtocolParser::Result::Result(const Result& other) = default;
+ProtocolParser::Result::~Result() = default;
+
+ProtocolParser::Result::Manifest::Manifest() = default;
+ProtocolParser::Result::Manifest::Manifest(const Manifest& other) = default;
+ProtocolParser::Result::Manifest::~Manifest() = default;
+
+ProtocolParser::Result::Manifest::Package::Package() = default;
+ProtocolParser::Result::Manifest::Package::Package(const Package& other) =
+ default;
+ProtocolParser::Result::Manifest::Package::~Package() = default;
+
+void ProtocolParser::ParseError(const char* details, ...) {
+ va_list args;
+ va_start(args, details);
+
+ if (!errors_.empty()) {
+ errors_ += "\r\n";
+ }
+
+ base::StringAppendV(&errors_, details, args);
+ va_end(args);
+}
+
+bool ProtocolParser::Parse(const std::string& response) {
+ results_.daystart_elapsed_seconds = kNoDaystart;
+ results_.daystart_elapsed_days = kNoDaystart;
+ results_.list.clear();
+ errors_.clear();
+
+ return DoParse(response, &results_);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_parser.h b/src/components/update_client/protocol_parser.h
new file mode 100644
index 0000000..471fd6d
--- /dev/null
+++ b/src/components/update_client/protocol_parser.h
@@ -0,0 +1,128 @@
+// 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_H_
+
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+class ProtocolParser {
+ public:
+ // The result of parsing one |app| entity in an update check response.
+ struct Result {
+ struct Manifest {
+ struct Package {
+ Package();
+ Package(const Package& other);
+ ~Package();
+
+ // |fingerprint| is optional. It identifies the package, preferably
+ // with a modified sha256 hash of the package in hex format.
+ std::string fingerprint;
+
+ // Attributes for the full update.
+ std::string name;
+ std::string hash_sha256;
+ int64_t size = 0;
+
+ // Attributes for the differential update.
+ std::string namediff;
+ std::string hashdiff_sha256;
+ int64_t sizediff = 0;
+ };
+
+ Manifest();
+ Manifest(const Manifest& other);
+ ~Manifest();
+
+ std::string version;
+ std::string browser_min_version;
+ std::vector<Package> packages;
+ };
+
+ Result();
+ Result(const Result& other);
+ ~Result();
+
+ std::string extension_id;
+
+ // The updatecheck response status.
+ std::string status;
+
+ // The list of fallback urls, for full and diff updates respectively.
+ // These urls are base urls; they don't include the filename.
+ std::vector<GURL> crx_urls;
+ std::vector<GURL> crx_diffurls;
+
+ Manifest manifest;
+
+ // The server has instructed the client to set its [key] to [value] for each
+ // key-value pair in this string.
+ std::map<std::string, std::string> cohort_attrs;
+
+ // The following are the only allowed keys in |cohort_attrs|.
+ static const char kCohort[];
+ static const char kCohortHint[];
+ static const char kCohortName[];
+
+ // Contains the run action returned by the server as part of an update
+ // check response.
+ std::string action_run;
+ };
+
+ static const int kNoDaystart = -1;
+ struct Results {
+ Results();
+ Results(const Results& other);
+ ~Results();
+
+ // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+ int daystart_elapsed_seconds = kNoDaystart;
+
+ // This will be >= 0, or kNoDaystart if the <daystart> tag was not present.
+ int daystart_elapsed_days = kNoDaystart;
+ std::vector<Result> list;
+ };
+
+ static std::unique_ptr<ProtocolParser> Create();
+
+ virtual ~ProtocolParser();
+
+ // Parses an update response string into Result data. Returns a bool
+ // indicating success or failure. On success, the results are available by
+ // calling results(). In case of success, only results corresponding to
+ // the update check status |ok| or |noupdate| are included.
+ // The details for any failures are available by calling errors().
+ bool Parse(const std::string& response);
+
+ const Results& results() const { return results_; }
+ const std::string& errors() const { return errors_; }
+
+ protected:
+ ProtocolParser();
+
+ // Appends parse error details to |errors_| string.
+ void ParseError(const char* details, ...);
+
+ private:
+ virtual bool DoParse(const std::string& response, Results* results) = 0;
+
+ Results results_;
+ std::string errors_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProtocolParser);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_H_
diff --git a/src/components/update_client/protocol_parser_json.cc b/src/components/update_client/protocol_parser_json.cc
new file mode 100644
index 0000000..eb022e4
--- /dev/null
+++ b/src/components/update_client/protocol_parser_json.cc
@@ -0,0 +1,337 @@
+// Copyright 2018 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 "components/update_client/protocol_parser_json.h"
+
+#include <utility>
+
+#include "base/json/json_reader.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/update_client/protocol_definition.h"
+
+namespace update_client {
+
+namespace {
+
+bool ParseManifest(const base::Value& manifest_node,
+ ProtocolParser::Result* result,
+ std::string* error) {
+ if (!manifest_node.is_dict()) {
+ *error = "'manifest' is not a dictionary.";
+ }
+ const auto* version = manifest_node.FindKey("version");
+ if (!version || !version->is_string()) {
+ *error = "Missing version for manifest.";
+ return false;
+ }
+
+ result->manifest.version = version->GetString();
+ if (!base::Version(result->manifest.version).IsValid()) {
+ *error =
+ base::StrCat({"Invalid version: '", result->manifest.version, "'."});
+ return false;
+ }
+
+ // Get the optional minimum browser version.
+ const auto* browser_min_version = manifest_node.FindKey("prodversionmin");
+ if (browser_min_version && browser_min_version->is_string()) {
+ result->manifest.browser_min_version = browser_min_version->GetString();
+ if (!base::Version(result->manifest.browser_min_version).IsValid()) {
+ *error = base::StrCat({"Invalid prodversionmin: '",
+ result->manifest.browser_min_version, "'."});
+ return false;
+ }
+ }
+
+ const auto* packages_node = manifest_node.FindKey("packages");
+ if (!packages_node || !packages_node->is_dict()) {
+ *error = "Missing packages in manifest or 'packages' is not a dictionary.";
+ return false;
+ }
+ const auto* package_node = packages_node->FindKey("package");
+ if (!package_node || !package_node->is_list()) {
+ *error = "Missing package in packages.";
+ return false;
+ }
+
+ for (const auto& package : package_node->GetList()) {
+ if (!package.is_dict()) {
+ *error = "'package' is not a dictionary.";
+ return false;
+ }
+ ProtocolParser::Result::Manifest::Package p;
+ const auto* name = package.FindKey("name");
+ if (!name || !name->is_string()) {
+ *error = "Missing name for package.";
+ return false;
+ }
+ p.name = name->GetString();
+
+ const auto* namediff = package.FindKey("namediff");
+ if (namediff && namediff->is_string())
+ p.namediff = namediff->GetString();
+
+ const auto* fingerprint = package.FindKey("fp");
+ if (fingerprint && fingerprint->is_string())
+ p.fingerprint = fingerprint->GetString();
+
+ const auto* hash_sha256 = package.FindKey("hash_sha256");
+ if (hash_sha256 && hash_sha256->is_string())
+ p.hash_sha256 = hash_sha256->GetString();
+
+ const auto* size = package.FindKey("size");
+ if (size && (size->is_int() || size->is_double())) {
+ const auto val = size->GetDouble();
+ if (0 <= val && val < kProtocolMaxInt)
+ p.size = size->GetDouble();
+ }
+
+ const auto* hashdiff_sha256 = package.FindKey("hashdiff_sha256");
+ if (hashdiff_sha256 && hashdiff_sha256->is_string())
+ p.hashdiff_sha256 = hashdiff_sha256->GetString();
+
+ const auto* sizediff = package.FindKey("sizediff");
+ if (sizediff && (sizediff->is_int() || sizediff->is_double())) {
+ const auto val = sizediff->GetDouble();
+ if (0 <= val && val < kProtocolMaxInt)
+ p.sizediff = sizediff->GetDouble();
+ }
+
+ result->manifest.packages.push_back(std::move(p));
+ }
+
+ return true;
+}
+
+void ParseActions(const base::Value& actions_node,
+ ProtocolParser::Result* result) {
+ if (!actions_node.is_dict())
+ return;
+
+ const auto* action_node = actions_node.FindKey("action");
+ if (!action_node || !action_node->is_list())
+ return;
+
+ const auto& action_list = action_node->GetList();
+ if (action_list.empty() || !action_list[0].is_dict())
+ return;
+
+ const auto* run = action_list[0].FindKey("run");
+ if (run && run->is_string())
+ result->action_run = run->GetString();
+}
+
+bool ParseUrls(const base::Value& urls_node,
+ ProtocolParser::Result* result,
+ std::string* error) {
+ if (!urls_node.is_dict()) {
+ *error = "'urls' is not a dictionary.";
+ return false;
+ }
+ const auto* url_node = urls_node.FindKey("url");
+ if (!url_node || !url_node->is_list()) {
+ *error = "Missing url on urls.";
+ return false;
+ }
+
+ for (const auto& url : url_node->GetList()) {
+ if (!url.is_dict())
+ continue;
+ const auto* codebase = url.FindKey("codebase");
+ if (codebase && codebase->is_string()) {
+ GURL crx_url(codebase->GetString());
+ if (crx_url.is_valid())
+ result->crx_urls.push_back(std::move(crx_url));
+ }
+ const auto* codebasediff = url.FindKey("codebasediff");
+ if (codebasediff && codebasediff->is_string()) {
+ GURL crx_diffurl(codebasediff->GetString());
+ if (crx_diffurl.is_valid())
+ result->crx_diffurls.push_back(std::move(crx_diffurl));
+ }
+ }
+
+ // Expect at least one url for full update.
+ if (result->crx_urls.empty()) {
+ *error = "Missing valid url for full update.";
+ return false;
+ }
+
+ return true;
+}
+
+bool ParseUpdateCheck(const base::Value& updatecheck_node,
+ ProtocolParser::Result* result,
+ std::string* error) {
+ if (!updatecheck_node.is_dict()) {
+ *error = "'updatecheck' is not a dictionary.";
+ return false;
+ }
+ const auto* status = updatecheck_node.FindKey("status");
+ if (!status || !status->is_string()) {
+ *error = "Missing status on updatecheck node";
+ return false;
+ }
+
+ result->status = status->GetString();
+ if (result->status == "noupdate") {
+ const auto* actions_node = updatecheck_node.FindKey("actions");
+ if (actions_node)
+ ParseActions(*actions_node, result);
+ return true;
+ }
+
+ if (result->status == "ok") {
+ const auto* actions_node = updatecheck_node.FindKey("actions");
+ if (actions_node)
+ ParseActions(*actions_node, result);
+
+ const auto* urls_node = updatecheck_node.FindKey("urls");
+ if (!urls_node) {
+ *error = "Missing urls on updatecheck.";
+ return false;
+ }
+
+ if (!ParseUrls(*urls_node, result, error))
+ return false;
+
+ const auto* manifest_node = updatecheck_node.FindKey("manifest");
+ if (!manifest_node) {
+ *error = "Missing manifest on updatecheck.";
+ return false;
+ }
+ return ParseManifest(*manifest_node, result, error);
+ }
+
+ // Return the |updatecheck| element status as a parsing error.
+ *error = result->status;
+ return false;
+}
+
+bool ParseApp(const base::Value& app_node,
+ ProtocolParser::Result* result,
+ std::string* error) {
+ if (!app_node.is_dict()) {
+ *error = "'app' is not a dictionary.";
+ return false;
+ }
+ for (const auto* cohort_key :
+ {ProtocolParser::Result::kCohort, ProtocolParser::Result::kCohortHint,
+ ProtocolParser::Result::kCohortName}) {
+ const auto* cohort_value = app_node.FindKey(cohort_key);
+ if (cohort_value && cohort_value->is_string())
+ result->cohort_attrs[cohort_key] = cohort_value->GetString();
+ }
+ const auto* appid = app_node.FindKey("appid");
+ if (appid && appid->is_string())
+ result->extension_id = appid->GetString();
+ if (result->extension_id.empty()) {
+ *error = "Missing appid on app node";
+ return false;
+ }
+
+ // Read the |status| attribute for the app.
+ // If the status is one of the defined app status error literals, then return
+ // it in the result as if it were an updatecheck status, then stop parsing,
+ // and return success.
+ const auto* status = app_node.FindKey("status");
+ if (status && status->is_string()) {
+ result->status = status->GetString();
+ if (result->status == "restricted" ||
+ result->status == "error-unknownApplication" ||
+ result->status == "error-invalidAppId")
+ return true;
+
+ // If the status was not handled above and the status is not "ok", then
+ // this must be a status literal that that the parser does not know about.
+ if (!result->status.empty() && result->status != "ok") {
+ *error = "Unknown app status";
+ return false;
+ }
+ }
+
+ DCHECK(result->status.empty() || result->status == "ok");
+ const auto* updatecheck_node = app_node.FindKey("updatecheck");
+ if (!updatecheck_node) {
+ *error = "Missing updatecheck on app.";
+ return false;
+ }
+
+ return ParseUpdateCheck(*updatecheck_node, result, error);
+}
+
+} // namespace
+
+bool ProtocolParserJSON::DoParse(const std::string& response_json,
+ Results* results) {
+ DCHECK(results);
+
+ if (response_json.empty()) {
+ ParseError("Empty JSON.");
+ return false;
+ }
+
+ // The JSON response contains a prefix to prevent XSSI.
+ constexpr char kJSONPrefix[] = ")]}'";
+ if (!base::StartsWith(response_json, kJSONPrefix,
+ base::CompareCase::SENSITIVE)) {
+ ParseError("Missing secure JSON prefix.");
+ return false;
+ }
+ const auto doc = base::JSONReader::Read(
+ {response_json.begin() + std::char_traits<char>::length(kJSONPrefix),
+ response_json.end()});
+ if (!doc) {
+ ParseError("JSON read error.");
+ return false;
+ }
+ if (!doc->is_dict()) {
+ ParseError("JSON document is not a dictionary.");
+ return false;
+ }
+ const auto* response_node = doc->FindKey("response");
+ if (!response_node || !response_node->is_dict()) {
+ ParseError("Missing 'response' element or 'response' is not a dictionary.");
+ return false;
+ }
+ const auto* protocol = response_node->FindKey("protocol");
+ if (!protocol || !protocol->is_string()) {
+ ParseError("Missing/non-string protocol.");
+ return false;
+ }
+ if (protocol->GetString() != kProtocolVersion) {
+ ParseError("Incorrect protocol. (expected '%s', found '%s')",
+ kProtocolVersion, protocol->GetString().c_str());
+ return false;
+ }
+
+ const auto* daystart_node = response_node->FindKey("daystart");
+ if (daystart_node && daystart_node->is_dict()) {
+ const auto* elapsed_seconds = daystart_node->FindKey("elapsed_seconds");
+ if (elapsed_seconds && elapsed_seconds->is_int())
+ results->daystart_elapsed_seconds = elapsed_seconds->GetInt();
+ const auto* elapsed_days = daystart_node->FindKey("elapsed_days");
+ if (elapsed_days && elapsed_days->is_int())
+ results->daystart_elapsed_days = elapsed_days->GetInt();
+ }
+
+ const auto* app_node = response_node->FindKey("app");
+ if (app_node && app_node->is_list()) {
+ for (const auto& app : app_node->GetList()) {
+ Result result;
+ std::string error;
+ if (ParseApp(app, &result, &error))
+ results->list.push_back(result);
+ else
+ ParseError("%s", error.c_str());
+ }
+ }
+
+ return true;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_parser_json.h b/src/components/update_client/protocol_parser_json.h
new file mode 100644
index 0000000..71f96f4
--- /dev/null
+++ b/src/components/update_client/protocol_parser_json.h
@@ -0,0 +1,30 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
+
+#include <string>
+
+#include "base/macros.h"
+#include "components/update_client/protocol_parser.h"
+
+namespace update_client {
+
+// Parses responses for the update protocol version 3.
+// (https://github.com/google/omaha/blob/wiki/ServerProtocolV3.md)
+class ProtocolParserJSON final : public ProtocolParser {
+ public:
+ ProtocolParserJSON() = default;
+
+ private:
+ // Overrides for ProtocolParser.
+ bool DoParse(const std::string& response_json, Results* results) override;
+
+ DISALLOW_COPY_AND_ASSIGN(ProtocolParserJSON);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PROTOCOL_PARSER_JSON_H_
diff --git a/src/components/update_client/protocol_parser_json_unittest.cc b/src/components/update_client/protocol_parser_json_unittest.cc
new file mode 100644
index 0000000..5bf114a
--- /dev/null
+++ b/src/components/update_client/protocol_parser_json_unittest.cc
@@ -0,0 +1,506 @@
+// Copyright 2018 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 "components/update_client/protocol_parser_json.h"
+
+#include <memory>
+
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+const char* kJSONValid = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://example.com/"},
+ {"codebasediff":"http://diff.example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONHash = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"extension_1_2_3_4.crx",
+ "hash_sha256":"1234",
+ "hashdiff_sha256":"5678"}]}}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONInvalidSizes = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"1","size":1234},
+ {"name":"2","size":9007199254740991},
+ {"name":"3","size":-1234},
+ {"name":"4"},
+ {"name":"5","size":"-a"},
+ {"name":"6","size":-123467890123456789},
+ {"name":"7","size":123467890123456789},
+ {"name":"8","sizediff":1234},
+ {"name":"9","sizediff":9007199254740991},
+ {"name":"10","sizediff":-1234},
+ {"name":"11","sizediff":"-a"},
+ {"name":"12","sizediff":-123467890123456789},
+ {"name":"13","sizediff":123467890123456789}]}}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONInvalidMissingCodebase = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebasediff":"http://diff.example.com"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"namediff":"extension_1_2_3_4.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONInvalidMissingManifest = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://localhost/download/"}]}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONMissingAppId = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://localhost/download/"}]}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONInvalidCodebase = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"example.com/extension_1.2.3.4.crx",
+ "version":"1.2.3.4"}]}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONMissingVersion = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+ "manifest":{
+ "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+const char* kJSONInvalidVersion = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://localhost/download/"}]},
+ "manifest":{
+ "version":"1.2.3.a",
+ "packages":{"package":[{"name":"jebgalgnebhfojomionfpkfelancnnkf.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+// Includes a <daystart> tag.
+const char* kJSONWithDaystart = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_seconds":456},
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://example.com/"},
+ {"codebasediff":"http://diff.example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+// Indicates no updates available.
+const char* kJSONNoUpdate = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "status":"ok",
+ "updatecheck":{
+ "status":"noupdate"
+ }
+ }
+ ]
+ }})";
+
+// Includes two app objects, one app with an error.
+const char* kJSONTwoAppsOneError = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_seconds":456},
+ "app":[
+ {"appid":"aaaaaaaa",
+ "status":"error-unknownApplication",
+ "updatecheck":{"status":"error-internal"}
+ },
+ {"appid":"bbbbbbbb",
+ "status":"ok",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+// Includes two <app> tags, both of which set the cohort.
+const char* kJSONTwoAppsSetCohort = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "daystart":{"elapsed_seconds":456},
+ "app":[
+ {"appid":"aaaaaaaa",
+ "cohort":"1:2q3/",
+ "updatecheck":{"status":"noupdate"}
+ },
+ {"appid":"bbbbbbbb",
+ "cohort":"1:33z@0.33",
+ "cohortname":"cname",
+ "updatecheck":{
+ "status":"ok",
+ "urls":{"url":[{"codebase":"http://example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+// Includes a run action for an update check with status='ok'.
+const char* kJSONUpdateCheckStatusOkWithRunAction = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "updatecheck":{
+ "status":"ok",
+ "actions":{"action":[{"run":"this"}]},
+ "urls":{"url":[{"codebase":"http://example.com/"},
+ {"codebasediff":"http://diff.example.com/"}]},
+ "manifest":{
+ "version":"1.2.3.4",
+ "prodversionmin":"2.0.143.0",
+ "packages":{"package":[{"name":"extension_1_2_3_4.crx"}]}}
+ }
+ }
+ ]
+ }})";
+
+// Includes a run action for an update check with status='noupdate'.
+const char* kJSONUpdateCheckStatusNoUpdateWithRunAction = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "updatecheck":{
+ "status":"noupdate",
+ "actions":{"action":[{"run":"this"}]}
+ }
+ }
+ ]
+ }})";
+
+// Includes a run action for an update check with status='error'.
+const char* kJSONUpdateCheckStatusErrorWithRunAction = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"12345",
+ "updatecheck":{
+ "status":"error-osnotsupported",
+ "actions":{"action":[{"run":"this"}]}
+ }
+ }
+ ]
+ }})";
+
+// Includes four app objects with status different than 'ok'.
+const char* kJSONAppsStatusError = R"()]}'
+ {"response":{
+ "protocol":"3.1",
+ "app":[
+ {"appid":"aaaaaaaa",
+ "status":"error-unknownApplication",
+ "updatecheck":{"status":"error-internal"}
+ },
+ {"appid":"bbbbbbbb",
+ "status":"restricted",
+ "updatecheck":{"status":"error-internal"}
+ },
+ {"appid":"cccccccc",
+ "status":"error-invalidAppId",
+ "updatecheck":{"status":"error-internal"}
+ },
+ {"appid":"dddddddd",
+ "status":"foobar",
+ "updatecheck":{"status":"error-internal"}
+ }
+ ]
+ }})";
+
+TEST(UpdateClientProtocolParserJSONTest, Parse) {
+ const auto parser = std::make_unique<ProtocolParserJSON>();
+
+ // Test parsing of a number of invalid JSON cases
+ EXPECT_FALSE(parser->Parse(std::string()));
+ EXPECT_FALSE(parser->errors().empty());
+
+ EXPECT_TRUE(parser->Parse(kJSONMissingAppId));
+ EXPECT_TRUE(parser->results().list.empty());
+ EXPECT_FALSE(parser->errors().empty());
+
+ EXPECT_TRUE(parser->Parse(kJSONInvalidCodebase));
+ EXPECT_TRUE(parser->results().list.empty());
+ EXPECT_FALSE(parser->errors().empty());
+
+ EXPECT_TRUE(parser->Parse(kJSONMissingVersion));
+ EXPECT_TRUE(parser->results().list.empty());
+ EXPECT_FALSE(parser->errors().empty());
+
+ EXPECT_TRUE(parser->Parse(kJSONInvalidVersion));
+ EXPECT_TRUE(parser->results().list.empty());
+ EXPECT_FALSE(parser->errors().empty());
+
+ EXPECT_TRUE(parser->Parse(kJSONInvalidMissingCodebase));
+ EXPECT_TRUE(parser->results().list.empty());
+ EXPECT_FALSE(parser->errors().empty());
+
+ EXPECT_TRUE(parser->Parse(kJSONInvalidMissingManifest));
+ EXPECT_TRUE(parser->results().list.empty());
+ EXPECT_FALSE(parser->errors().empty());
+
+ {
+ // Parse some valid XML, and check that all params came out as expected.
+ EXPECT_TRUE(parser->Parse(kJSONValid));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_EQ(1u, parser->results().list.size());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_STREQ("ok", first_result->status.c_str());
+ EXPECT_EQ(1u, first_result->crx_urls.size());
+ EXPECT_EQ(GURL("http://example.com/"), first_result->crx_urls[0]);
+ EXPECT_EQ(GURL("http://diff.example.com/"), first_result->crx_diffurls[0]);
+ EXPECT_EQ("1.2.3.4", first_result->manifest.version);
+ EXPECT_EQ("2.0.143.0", first_result->manifest.browser_min_version);
+ EXPECT_EQ(1u, first_result->manifest.packages.size());
+ EXPECT_EQ("extension_1_2_3_4.crx", first_result->manifest.packages[0].name);
+ }
+ {
+ // Parse xml with hash value.
+ EXPECT_TRUE(parser->Parse(kJSONHash));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_FALSE(parser->results().list.empty());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_FALSE(first_result->manifest.packages.empty());
+ EXPECT_EQ("1234", first_result->manifest.packages[0].hash_sha256);
+ EXPECT_EQ("5678", first_result->manifest.packages[0].hashdiff_sha256);
+ }
+ {
+ // Parse xml with package size value.
+ EXPECT_TRUE(parser->Parse(kJSONInvalidSizes));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_FALSE(parser->results().list.empty());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_FALSE(first_result->manifest.packages.empty());
+ EXPECT_EQ(1234, first_result->manifest.packages[0].size);
+ EXPECT_EQ(9007199254740991, first_result->manifest.packages[1].size);
+ EXPECT_EQ(0, first_result->manifest.packages[2].size);
+ EXPECT_EQ(0, first_result->manifest.packages[3].size);
+ EXPECT_EQ(0, first_result->manifest.packages[4].size);
+ EXPECT_EQ(0, first_result->manifest.packages[5].size);
+ EXPECT_EQ(0, first_result->manifest.packages[6].size);
+ EXPECT_EQ(1234, first_result->manifest.packages[7].sizediff);
+ EXPECT_EQ(9007199254740991, first_result->manifest.packages[8].sizediff);
+ EXPECT_EQ(0, first_result->manifest.packages[9].sizediff);
+ EXPECT_EQ(0, first_result->manifest.packages[10].sizediff);
+ EXPECT_EQ(0, first_result->manifest.packages[11].sizediff);
+ EXPECT_EQ(0, first_result->manifest.packages[12].sizediff);
+ }
+ {
+ // Parse xml with a <daystart> element.
+ EXPECT_TRUE(parser->Parse(kJSONWithDaystart));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_FALSE(parser->results().list.empty());
+ EXPECT_EQ(parser->results().daystart_elapsed_seconds, 456);
+ }
+ {
+ // Parse a no-update response.
+ EXPECT_TRUE(parser->Parse(kJSONNoUpdate));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_FALSE(parser->results().list.empty());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_STREQ("noupdate", first_result->status.c_str());
+ EXPECT_EQ(first_result->extension_id, "12345");
+ EXPECT_EQ(first_result->manifest.version, "");
+ }
+ {
+ // Parse xml with one error and one success <app> tag.
+ EXPECT_TRUE(parser->Parse(kJSONTwoAppsOneError));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_EQ(2u, parser->results().list.size());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+ EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
+ EXPECT_TRUE(first_result->manifest.version.empty());
+ const auto* second_result = &parser->results().list[1];
+ EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+ EXPECT_STREQ("ok", second_result->status.c_str());
+ EXPECT_EQ("1.2.3.4", second_result->manifest.version);
+ }
+ {
+ // Parse xml with two apps setting the cohort info.
+ EXPECT_TRUE(parser->Parse(kJSONTwoAppsSetCohort));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_EQ(2u, parser->results().list.size());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+ EXPECT_NE(first_result->cohort_attrs.find("cohort"),
+ first_result->cohort_attrs.end());
+ EXPECT_EQ(first_result->cohort_attrs.find("cohort")->second, "1:2q3/");
+ EXPECT_EQ(first_result->cohort_attrs.find("cohortname"),
+ first_result->cohort_attrs.end());
+ EXPECT_EQ(first_result->cohort_attrs.find("cohorthint"),
+ first_result->cohort_attrs.end());
+ const auto* second_result = &parser->results().list[1];
+ EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+ EXPECT_NE(second_result->cohort_attrs.find("cohort"),
+ second_result->cohort_attrs.end());
+ EXPECT_EQ(second_result->cohort_attrs.find("cohort")->second, "1:33z@0.33");
+ EXPECT_NE(second_result->cohort_attrs.find("cohortname"),
+ second_result->cohort_attrs.end());
+ EXPECT_EQ(second_result->cohort_attrs.find("cohortname")->second, "cname");
+ EXPECT_EQ(second_result->cohort_attrs.find("cohorthint"),
+ second_result->cohort_attrs.end());
+ }
+ {
+ EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusOkWithRunAction));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_FALSE(parser->results().list.empty());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_STREQ("ok", first_result->status.c_str());
+ EXPECT_EQ(first_result->extension_id, "12345");
+ EXPECT_STREQ("this", first_result->action_run.c_str());
+ }
+ {
+ EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusNoUpdateWithRunAction));
+ EXPECT_TRUE(parser->errors().empty());
+ EXPECT_FALSE(parser->results().list.empty());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_STREQ("noupdate", first_result->status.c_str());
+ EXPECT_EQ(first_result->extension_id, "12345");
+ EXPECT_STREQ("this", first_result->action_run.c_str());
+ }
+ {
+ EXPECT_TRUE(parser->Parse(kJSONUpdateCheckStatusErrorWithRunAction));
+ EXPECT_FALSE(parser->errors().empty());
+ EXPECT_TRUE(parser->results().list.empty());
+ }
+ {
+ EXPECT_TRUE(parser->Parse(kJSONAppsStatusError));
+ EXPECT_STREQ("Unknown app status", parser->errors().c_str());
+ EXPECT_EQ(3u, parser->results().list.size());
+ const auto* first_result = &parser->results().list[0];
+ EXPECT_EQ(first_result->extension_id, "aaaaaaaa");
+ EXPECT_STREQ("error-unknownApplication", first_result->status.c_str());
+ EXPECT_TRUE(first_result->manifest.version.empty());
+ const auto* second_result = &parser->results().list[1];
+ EXPECT_EQ(second_result->extension_id, "bbbbbbbb");
+ EXPECT_STREQ("restricted", second_result->status.c_str());
+ EXPECT_TRUE(second_result->manifest.version.empty());
+ const auto* third_result = &parser->results().list[2];
+ EXPECT_EQ(third_result->extension_id, "cccccccc");
+ EXPECT_STREQ("error-invalidAppId", third_result->status.c_str());
+ EXPECT_TRUE(third_result->manifest.version.empty());
+ }
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_serializer.cc b/src/components/update_client/protocol_serializer.cc
new file mode 100644
index 0000000..a03ff79
--- /dev/null
+++ b/src/components/update_client/protocol_serializer.cc
@@ -0,0 +1,254 @@
+// Copyright 2018 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 "components/update_client/protocol_serializer.h"
+
+#include <utility>
+
+#include "base/containers/flat_map.h"
+#include "base/guid.h"
+#include "base/logging.h"
+#include "base/strings/strcat.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/update_query_params.h"
+#include "components/update_client/updater_state.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace update_client {
+
+namespace {
+
+// Returns the amount of physical memory in GB, rounded to the nearest GB.
+int GetPhysicalMemoryGB() {
+ const double kOneGB = 1024 * 1024 * 1024;
+ const int64_t phys_mem = base::SysInfo::AmountOfPhysicalMemory();
+ return static_cast<int>(std::floor(0.5 + phys_mem / kOneGB));
+}
+
+std::string GetOSVersion() {
+#if defined(OS_WIN)
+ const auto ver = base::win::OSInfo::GetInstance()->version_number();
+ return base::StringPrintf("%d.%d.%d.%d", ver.major, ver.minor, ver.build,
+ ver.patch);
+#elif defined(OS_STARBOARD)
+ // TODO: fill in OS version.
+ return "";
+#else
+ return base::SysInfo().OperatingSystemVersion();
+#endif
+}
+
+std::string GetServicePack() {
+#if defined(OS_WIN)
+ return base::win::OSInfo::GetInstance()->service_pack_str();
+#else
+ return {};
+#endif
+}
+
+} // namespace
+
+base::flat_map<std::string, std::string> BuildUpdateCheckExtraRequestHeaders(
+ const std::string& prod_id,
+ const base::Version& browser_version,
+ const std::vector<std::string>& ids,
+ bool is_foreground) {
+ // This number of extension ids results in an HTTP header length of about 1KB.
+ constexpr size_t maxIdsCount = 30;
+ const std::vector<std::string>& app_ids =
+ ids.size() <= maxIdsCount
+ ? ids
+ : std::vector<std::string>(ids.cbegin(), ids.cbegin() + maxIdsCount);
+ return {
+ {"X-Goog-Update-Updater",
+ base::StrCat({prod_id, "-", browser_version.GetString()})},
+ {"X-Goog-Update-Interactivity", is_foreground ? "fg" : "bg"},
+ {"X-Goog-Update-AppId", base::JoinString(app_ids, ",")},
+ };
+}
+
+protocol_request::Request MakeProtocolRequest(
+ const std::string& session_id,
+ const std::string& prod_id,
+ const std::string& browser_version,
+ const std::string& lang,
+ const std::string& channel,
+ const std::string& os_long_name,
+ const std::string& download_preference,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ const std::map<std::string, std::string>* updater_state_attributes,
+ std::vector<protocol_request::App> apps) {
+ protocol_request::Request request;
+ request.protocol_version = kProtocolVersion;
+
+ // Session id and request id.
+ DCHECK(!session_id.empty());
+ DCHECK(base::StartsWith(session_id, "{", base::CompareCase::SENSITIVE));
+ DCHECK(base::EndsWith(session_id, "}", base::CompareCase::SENSITIVE));
+ request.session_id = session_id;
+ request.request_id = base::StrCat({"{", base::GenerateGUID(), "}"});
+
+ request.updatername = prod_id;
+ request.updaterversion = browser_version;
+ request.prodversion = browser_version;
+ request.lang = lang;
+ request.updaterchannel = channel;
+ request.prodchannel = channel;
+ request.operating_system = UpdateQueryParams::GetOS();
+ request.arch = UpdateQueryParams::GetArch();
+ request.nacl_arch = UpdateQueryParams::GetNaclArch();
+ request.dlpref = download_preference;
+ request.additional_attributes = additional_attributes;
+
+#if defined(OS_WIN)
+ if (base::win::OSInfo::GetInstance()->wow64_status() ==
+ base::win::OSInfo::WOW64_ENABLED)
+ request.is_wow64 = true;
+#endif
+
+ if (updater_state_attributes &&
+ updater_state_attributes->count(UpdaterState::kIsEnterpriseManaged)) {
+ request.domain_joined =
+ updater_state_attributes->at(UpdaterState::kIsEnterpriseManaged) == "1";
+ }
+
+ // HW platform information.
+ request.hw.physmemory = GetPhysicalMemoryGB();
+
+ // OS version and platform information.
+ request.os.platform = os_long_name;
+ request.os.version = GetOSVersion();
+ request.os.service_pack = GetServicePack();
+#if defined(OS_STARBOARD)
+ // TODO: fill in arch.
+ request.os.arch = "";
+#else
+ request.os.arch = base::SysInfo().OperatingSystemArchitecture();
+#endif
+
+ if (updater_state_attributes) {
+ request.updater = base::make_optional<protocol_request::Updater>();
+ auto it = updater_state_attributes->find("name");
+ if (it != updater_state_attributes->end())
+ request.updater->name = it->second;
+ it = updater_state_attributes->find("version");
+ if (it != updater_state_attributes->end())
+ request.updater->version = it->second;
+ it = updater_state_attributes->find("ismachine");
+ if (it != updater_state_attributes->end()) {
+ DCHECK(it->second == "0" || it->second == "1");
+ request.updater->is_machine = it->second != "0";
+ }
+ it = updater_state_attributes->find("autoupdatecheckenabled");
+ if (it != updater_state_attributes->end()) {
+ DCHECK(it->second == "0" || it->second == "1");
+ request.updater->autoupdate_check_enabled = it->second != "0";
+ }
+ it = updater_state_attributes->find("laststarted");
+ if (it != updater_state_attributes->end()) {
+ int last_started = 0;
+ if (base::StringToInt(it->second, &last_started))
+ request.updater->last_started = last_started;
+ }
+ it = updater_state_attributes->find("lastchecked");
+ if (it != updater_state_attributes->end()) {
+ int last_checked = 0;
+ if (base::StringToInt(it->second, &last_checked))
+ request.updater->last_checked = last_checked;
+ }
+ it = updater_state_attributes->find("updatepolicy");
+ if (it != updater_state_attributes->end()) {
+ int update_policy = 0;
+ if (base::StringToInt(it->second, &update_policy))
+ request.updater->update_policy = update_policy;
+ }
+ }
+
+ request.apps = std::move(apps);
+ return request;
+}
+
+protocol_request::App MakeProtocolApp(
+ const std::string& app_id,
+ const base::Version& version,
+ base::Optional<std::vector<base::Value>> events) {
+ protocol_request::App app;
+ app.app_id = app_id;
+ app.version = version.GetString();
+ app.events = std::move(events);
+ return app;
+}
+
+protocol_request::App MakeProtocolApp(
+ const std::string& app_id,
+ const base::Version& version,
+ const std::string& brand_code,
+ const std::string& install_source,
+ const std::string& install_location,
+ const std::string& fingerprint,
+ const base::flat_map<std::string, std::string>& installer_attributes,
+ const std::string& cohort,
+ const std::string& cohort_hint,
+ const std::string& cohort_name,
+ const std::vector<int>& disabled_reasons,
+ base::Optional<protocol_request::UpdateCheck> update_check,
+ base::Optional<protocol_request::Ping> ping) {
+ auto app = MakeProtocolApp(app_id, version, base::nullopt);
+ app.brand_code = brand_code;
+ app.install_source = install_source;
+ app.install_location = install_location;
+ app.fingerprint = fingerprint;
+ app.installer_attributes = installer_attributes;
+ app.cohort = cohort;
+ app.cohort_hint = cohort_hint;
+ app.cohort_name = cohort_name;
+ app.enabled = disabled_reasons.empty();
+ app.disabled_reasons = disabled_reasons;
+ app.update_check = std::move(update_check);
+ app.ping = std::move(ping);
+ return app;
+}
+
+protocol_request::UpdateCheck MakeProtocolUpdateCheck(bool is_update_disabled) {
+ protocol_request::UpdateCheck update_check;
+ update_check.is_update_disabled = is_update_disabled;
+ return update_check;
+}
+
+protocol_request::Ping MakeProtocolPing(const std::string& app_id,
+ const PersistedData* metadata) {
+ DCHECK(metadata);
+ protocol_request::Ping ping;
+
+ if (metadata->GetActiveBit(app_id)) {
+ const int date_last_active = metadata->GetDateLastActive(app_id);
+ if (date_last_active != kDateUnknown) {
+ ping.date_last_active = date_last_active;
+ } else {
+ ping.days_since_last_active_ping =
+ metadata->GetDaysSinceLastActive(app_id);
+ }
+ }
+ const int date_last_roll_call = metadata->GetDateLastRollCall(app_id);
+ if (date_last_roll_call != kDateUnknown) {
+ ping.date_last_roll_call = date_last_roll_call;
+ } else {
+ ping.days_since_last_roll_call = metadata->GetDaysSinceLastRollCall(app_id);
+ }
+ ping.ping_freshness = metadata->GetPingFreshness(app_id);
+
+ return ping;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_serializer.h b/src/components/update_client/protocol_serializer.h
new file mode 100644
index 0000000..47c212a
--- /dev/null
+++ b/src/components/update_client/protocol_serializer.h
@@ -0,0 +1,84 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_H_
+
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/protocol_definition.h"
+
+namespace base {
+class Version;
+}
+
+namespace update_client {
+
+class PersistedData;
+
+// Creates the values for the DDOS extra request headers sent with the update
+// check. These headers include "X-Goog-Update-Updater",
+// "X-Goog-Update-AppId", and "X-Goog-Update-Interactivity".
+base::flat_map<std::string, std::string> BuildUpdateCheckExtraRequestHeaders(
+ const std::string& prod_id,
+ const base::Version& browser_version,
+ const std::vector<std::string>& ids_checked,
+ bool is_foreground);
+
+protocol_request::Request MakeProtocolRequest(
+ const std::string& session_id,
+ const std::string& prod_id,
+ const std::string& browser_version,
+ const std::string& lang,
+ const std::string& channel,
+ const std::string& os_long_name,
+ const std::string& download_preference,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ const std::map<std::string, std::string>* updater_state_attributes,
+ std::vector<protocol_request::App> apps);
+
+protocol_request::App MakeProtocolApp(
+ const std::string& app_id,
+ const base::Version& version,
+ base::Optional<std::vector<base::Value>> events);
+
+protocol_request::App MakeProtocolApp(
+ const std::string& app_id,
+ const base::Version& version,
+ const std::string& brand_code,
+ const std::string& install_source,
+ const std::string& install_location,
+ const std::string& fingerprint,
+ const base::flat_map<std::string, std::string>& installer_attributes,
+ const std::string& cohort,
+ const std::string& cohort_hint,
+ const std::string& cohort_name,
+ const std::vector<int>& disabled_reasons,
+ base::Optional<protocol_request::UpdateCheck> update_check,
+ base::Optional<protocol_request::Ping> ping);
+
+protocol_request::UpdateCheck MakeProtocolUpdateCheck(bool is_update_disabled);
+
+protocol_request::Ping MakeProtocolPing(const std::string& app_id,
+ const PersistedData* metadata);
+
+class ProtocolSerializer {
+ public:
+ virtual ~ProtocolSerializer() = default;
+ virtual std::string Serialize(
+ const protocol_request::Request& request) const = 0;
+
+ protected:
+ ProtocolSerializer() = default;
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_H_
diff --git a/src/components/update_client/protocol_serializer_json.cc b/src/components/update_client/protocol_serializer_json.cc
new file mode 100644
index 0000000..b94d2c0
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_json.cc
@@ -0,0 +1,183 @@
+// Copyright 2018 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 "components/update_client/protocol_serializer_json.h"
+
+#include <memory>
+#include <utility>
+#include <vector>
+
+#include "base/json/json_writer.h"
+#include "base/values.h"
+#include "build/build_config.h"
+
+#include "components/update_client/updater_state.h"
+
+namespace update_client {
+
+using Value = base::Value;
+
+std::string ProtocolSerializerJSON::Serialize(
+ const protocol_request::Request& request) const {
+ Value root_node(Value::Type::DICTIONARY);
+ auto* request_node =
+ root_node.SetKey("request", Value(Value::Type::DICTIONARY));
+ request_node->SetKey("protocol", Value(request.protocol_version));
+ request_node->SetKey("dedup", Value("cr"));
+ request_node->SetKey("acceptformat", Value("crx2,crx3"));
+ if (!request.additional_attributes.empty()) {
+ for (const auto& attr : request.additional_attributes)
+ request_node->SetKey(attr.first, Value(attr.second));
+ }
+ request_node->SetKey("sessionid", Value(request.session_id));
+ request_node->SetKey("requestid", Value(request.request_id));
+ request_node->SetKey("@updater", Value(request.updatername));
+ request_node->SetKey("prodversion", Value(request.updaterversion));
+ request_node->SetKey("updaterversion", Value(request.prodversion));
+ request_node->SetKey("lang", Value(request.lang));
+ request_node->SetKey("@os", Value(request.operating_system));
+ request_node->SetKey("arch", Value(request.arch));
+ request_node->SetKey("nacl_arch", Value(request.nacl_arch));
+#if defined(OS_WIN)
+ if (request.is_wow64)
+ request_node->SetKey("wow64", Value(request.is_wow64));
+#endif // OS_WIN
+ if (!request.updaterchannel.empty())
+ request_node->SetKey("updaterchannel", Value(request.updaterchannel));
+ if (!request.prodchannel.empty())
+ request_node->SetKey("prodchannel", Value(request.prodchannel));
+ if (!request.dlpref.empty())
+ request_node->SetKey("dlpref", Value(request.dlpref));
+ if (request.domain_joined) {
+ request_node->SetKey(UpdaterState::kIsEnterpriseManaged,
+ Value(*request.domain_joined));
+ }
+
+ // HW platform information.
+ auto* hw_node = request_node->SetKey("hw", Value(Value::Type::DICTIONARY));
+ hw_node->SetKey("physmemory", Value(static_cast<int>(request.hw.physmemory)));
+
+ // OS version and platform information.
+ auto* os_node = request_node->SetKey("os", Value(Value::Type::DICTIONARY));
+ os_node->SetKey("platform", Value(request.os.platform));
+ os_node->SetKey("arch", Value(request.os.arch));
+ if (!request.os.version.empty())
+ os_node->SetKey("version", Value(request.os.version));
+ if (!request.os.service_pack.empty())
+ os_node->SetKey("sp", Value(request.os.service_pack));
+
+#if defined(GOOGLE_CHROME_BUILD)
+ if (request.updater) {
+ const auto& updater = *request.updater;
+ auto* updater_node =
+ request_node->SetKey("updater", Value(Value::Type::DICTIONARY));
+ updater_node->SetKey("name", Value(updater.name));
+ updater_node->SetKey("ismachine", Value(updater.is_machine));
+ updater_node->SetKey("autoupdatecheckenabled",
+ Value(updater.autoupdate_check_enabled));
+ updater_node->SetKey("updatepolicy", Value(updater.update_policy));
+ if (!updater.version.empty())
+ updater_node->SetKey("version", Value(updater.version));
+ if (updater.last_checked)
+ updater_node->SetKey("lastchecked", Value(*updater.last_checked));
+ if (updater.last_started)
+ updater_node->SetKey("laststarted", Value(*updater.last_started));
+ }
+#endif
+
+ std::vector<Value> app_nodes;
+ for (const auto& app : request.apps) {
+ Value app_node(Value::Type::DICTIONARY);
+ app_node.SetKey("appid", Value(app.app_id));
+ app_node.SetKey("version", Value(app.version));
+ if (!app.brand_code.empty())
+ app_node.SetKey("brand", Value(app.brand_code));
+ if (!app.install_source.empty())
+ app_node.SetKey("installsource", Value(app.install_source));
+ if (!app.install_location.empty())
+ app_node.SetKey("installedby", Value(app.install_location));
+ if (!app.cohort.empty())
+ app_node.SetKey("cohort", Value(app.cohort));
+ if (!app.cohort_name.empty())
+ app_node.SetKey("cohortname", Value(app.cohort_name));
+ if (!app.cohort_hint.empty())
+ app_node.SetKey("cohorthint", Value(app.cohort_hint));
+ if (app.enabled)
+ app_node.SetKey("enabled", Value(*app.enabled));
+
+ if (app.disabled_reasons && !app.disabled_reasons->empty()) {
+ std::vector<Value> disabled_nodes;
+ for (const int disabled_reason : *app.disabled_reasons) {
+ Value disabled_node(Value::Type::DICTIONARY);
+ disabled_node.SetKey("reason", Value(disabled_reason));
+ disabled_nodes.push_back(std::move(disabled_node));
+ }
+ app_node.SetKey("disabled", Value(disabled_nodes));
+ }
+
+ for (const auto& attr : app.installer_attributes)
+ app_node.SetKey(attr.first, Value(attr.second));
+
+ if (app.update_check) {
+ auto* update_check_node =
+ app_node.SetKey("updatecheck", Value(Value::Type::DICTIONARY));
+ if (app.update_check->is_update_disabled)
+ update_check_node->SetKey("updatedisabled", Value(true));
+ }
+
+ if (app.ping) {
+ const auto& ping = *app.ping;
+ auto* ping_node = app_node.SetKey("ping", Value(Value::Type::DICTIONARY));
+ if (!ping.ping_freshness.empty())
+ ping_node->SetKey("ping_freshness", Value(ping.ping_freshness));
+
+ // Output "ad" or "a" only if the this app has been seen 'active'.
+ if (ping.date_last_active) {
+ ping_node->SetKey("ad", Value(*ping.date_last_active));
+ } else if (ping.days_since_last_active_ping) {
+ ping_node->SetKey("a", Value(*ping.days_since_last_active_ping));
+ }
+
+ // Output "rd" if valid or "r" as a last resort roll call metric.
+ if (ping.date_last_roll_call)
+ ping_node->SetKey("rd", Value(*ping.date_last_roll_call));
+ else
+ ping_node->SetKey("r", Value(ping.days_since_last_roll_call));
+ }
+
+ if (!app.fingerprint.empty()) {
+ std::vector<Value> package_nodes;
+ Value package(Value::Type::DICTIONARY);
+ package.SetKey("fp", Value(app.fingerprint));
+ package_nodes.push_back(std::move(package));
+ auto* packages_node =
+ app_node.SetKey("packages", Value(Value::Type::DICTIONARY));
+ packages_node->SetKey("package", Value(package_nodes));
+ }
+
+ if (app.events) {
+ std::vector<Value> event_nodes;
+ for (const auto& event : *app.events) {
+ DCHECK(event.is_dict());
+ DCHECK(!event.DictEmpty());
+ event_nodes.push_back(event.Clone());
+ }
+ app_node.SetKey("event", Value(event_nodes));
+ }
+
+ app_nodes.push_back(std::move(app_node));
+ }
+
+ if (!app_nodes.empty())
+ request_node->SetKey("app", Value(std::move(app_nodes)));
+
+ std::string msg;
+ return base::JSONWriter::WriteWithOptions(
+ root_node, base::JSONWriter::OPTIONS_OMIT_DOUBLE_TYPE_PRESERVATION,
+ &msg)
+ ? msg
+ : std::string();
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_serializer_json.h b/src/components/update_client/protocol_serializer_json.h
new file mode 100644
index 0000000..71f2a99
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_json.h
@@ -0,0 +1,28 @@
+// Copyright 2018 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 COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
+#define COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
+
+#include <string>
+
+#include "components/update_client/protocol_serializer.h"
+
+namespace update_client {
+
+class ProtocolSerializerJSON final : public ProtocolSerializer {
+ public:
+ ProtocolSerializerJSON() = default;
+
+ // Overrides for ProtocolSerializer.
+ std::string Serialize(
+ const protocol_request::Request& request) const override;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(ProtocolSerializerJSON);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_PROTOCOL_SERIALIZER_JSON_H_
diff --git a/src/components/update_client/protocol_serializer_json_unittest.cc b/src/components/update_client/protocol_serializer_json_unittest.cc
new file mode 100644
index 0000000..1f4ce4a
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_json_unittest.cc
@@ -0,0 +1,126 @@
+// Copyright 2018 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 <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/optional.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/protocol_serializer_json.h"
+#include "components/update_client/updater_state.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "third_party/re2/src/re2/re2.h"
+
+using base::Value;
+using std::string;
+
+namespace update_client {
+
+TEST(SerializeRequestJSON, Serialize) {
+ // When no updater state is provided, then check that the elements and
+ // attributes related to the updater state are not serialized.
+
+ std::vector<base::Value> events;
+ events.push_back(Value(Value::Type::DICTIONARY));
+ events.push_back(Value(Value::Type::DICTIONARY));
+ events[0].SetKey("a", Value(1));
+ events[0].SetKey("b", Value("2"));
+ events[1].SetKey("error", Value(0));
+
+ auto pref = std::make_unique<TestingPrefServiceSimple>();
+ PersistedData::RegisterPrefs(pref->registry());
+ auto metadata = std::make_unique<PersistedData>(pref.get(), nullptr);
+ std::vector<std::string> items = {"id1"};
+ metadata->SetDateLastRollCall(items, 1234);
+
+ std::vector<protocol_request::App> apps;
+ apps.push_back(MakeProtocolApp(
+ "id1", base::Version("1.0"), "brand1", "source1", "location1", "fp1",
+ {{"attr1", "1"}, {"attr2", "2"}}, "c1", "ch1", "cn1", {0, 1},
+ MakeProtocolUpdateCheck(true), MakeProtocolPing("id1", metadata.get())));
+ apps.push_back(
+ MakeProtocolApp("id2", base::Version("2.0"), std::move(events)));
+
+ const auto request = std::make_unique<ProtocolSerializerJSON>()->Serialize(
+ MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id",
+ "1.0", "lang", "channel", "OS", "cacheable",
+ {{"extra", "params"}}, nullptr, std::move(apps)));
+ constexpr char regex[] =
+ R"({"request":{"@os":"\w+","@updater":"prod_id",)"
+ R"("acceptformat":"crx2,crx3",)"
+ R"("app":\[{"appid":"id1","attr1":"1","attr2":"2","brand":"brand1",)"
+ R"("cohort":"c1","cohorthint":"ch1","cohortname":"cn1",)"
+ R"("disabled":\[{"reason":0},{"reason":1}],"enabled":false,)"
+ R"("installedby":"location1","installsource":"source1",)"
+ R"("packages":{"package":\[{"fp":"fp1"}]},)"
+ R"("ping":{"ping_freshness":"{[-\w]{36}}","rd":1234},)"
+ R"("updatecheck":{"updatedisabled":true},"version":"1.0"},)"
+ R"({"appid":"id2","event":\[{"a":1,"b":"2"},{"error":0}],)"
+ R"("version":"2.0"}],"arch":"\w+","dedup":"cr","dlpref":"cacheable",)"
+ R"("extra":"params","hw":{"physmemory":\d+},"lang":"lang",)"
+ R"("nacl_arch":"[-\w]+","os":{"arch":"[_,-.\w]+","platform":"OS",)"
+ R"(("sp":"[\s\w]+",)?"version":"[-.\w]+"},"prodchannel":"channel",)"
+ R"("prodversion":"1.0","protocol":"3.1","requestid":"{[-\w]{36}}",)"
+ R"("sessionid":"{[-\w]{36}}","updaterchannel":"channel",)"
+ R"("updaterversion":"1.0"(,"wow64":true)?}})";
+ EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+}
+
+TEST(SerializeRequestJSON, DownloadPreference) {
+ // Verifies that an empty |download_preference| is not serialized.
+ const auto serializer = std::make_unique<ProtocolSerializerJSON>();
+ auto request = serializer->Serialize(
+ MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "", "", "",
+ "", "", "", {}, nullptr, {}));
+ EXPECT_FALSE(RE2::PartialMatch(request, R"("dlpref":)")) << request;
+
+ // Verifies that |download_preference| is serialized.
+ request = serializer->Serialize(
+ MakeProtocolRequest("{15160585-8ADE-4D3C-839B-1281A6035D1F}", "", "", "",
+ "", "", "cacheable", {}, nullptr, {}));
+ EXPECT_TRUE(RE2::PartialMatch(request, R"("dlpref":"cacheable")")) << request;
+}
+
+// When present, updater state attributes are only serialized for Google builds,
+// except the |domainjoined| attribute, which is serialized in all cases.
+TEST(SerializeRequestJSON, UpdaterStateAttributes) {
+ const auto serializer = std::make_unique<ProtocolSerializerJSON>();
+ UpdaterState::Attributes attributes;
+ attributes["ismachine"] = "1";
+ attributes["domainjoined"] = "1";
+ attributes["name"] = "Omaha";
+ attributes["version"] = "1.2.3.4";
+ attributes["laststarted"] = "1";
+ attributes["lastchecked"] = "2";
+ attributes["autoupdatecheckenabled"] = "0";
+ attributes["updatepolicy"] = "-1";
+ const auto request = serializer->Serialize(MakeProtocolRequest(
+ "{15160585-8ADE-4D3C-839B-1281A6035D1F}", "prod_id", "1.0", "lang",
+ "channel", "OS", "cacheable", {{"extra", "params"}}, &attributes, {}));
+ constexpr char regex[] =
+ R"({"request":{"@os":"\w+","@updater":"prod_id",)"
+ R"("acceptformat":"crx2,crx3","arch":"\w+","dedup":"cr",)"
+ R"("dlpref":"cacheable","domainjoined":true,"extra":"params",)"
+ R"("hw":{"physmemory":\d+},"lang":"lang","nacl_arch":"[-\w]+",)"
+ R"("os":{"arch":"[,-.\w]+","platform":"OS",("sp":"[\s\w]+",)?)"
+ R"("version":"[-.\w]+"},"prodchannel":"channel","prodversion":"1.0",)"
+ R"("protocol":"3.1","requestid":"{[-\w]{36}}","sessionid":"{[-\w]{36}}",)"
+#if defined(GOOGLE_CHROME_BUILD)
+ R"("updater":{"autoupdatecheckenabled":false,"ismachine":true,)"
+ R"("lastchecked":2,"laststarted":1,"name":"Omaha","updatepolicy":-1,)"
+ R"("version":"1\.2\.3\.4"},)"
+#endif // GOOGLE_CHROME_BUILD
+ R"("updaterchannel":"channel","updaterversion":"1.0"(,"wow64":true)?}})";
+ EXPECT_TRUE(RE2::FullMatch(request, regex)) << request;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/protocol_serializer_unittest.cc b/src/components/update_client/protocol_serializer_unittest.cc
new file mode 100644
index 0000000..f0c8a14
--- /dev/null
+++ b/src/components/update_client/protocol_serializer_unittest.cc
@@ -0,0 +1,55 @@
+// Copyright 2016 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 <string>
+
+#include "base/strings/string_util.h"
+#include "base/version.h"
+#include "components/update_client/protocol_serializer.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using std::string;
+
+namespace update_client {
+
+TEST(BuildProtocolRequest, BuildUpdateCheckExtraRequestHeaders) {
+ auto headers = BuildUpdateCheckExtraRequestHeaders(
+ "fake_prodid", base::Version("30.0"), {}, true);
+ EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+ EXPECT_EQ("fg", headers["X-Goog-Update-Interactivity"]);
+ EXPECT_TRUE(headers["X-Goog-Update-AppId"].empty());
+
+ headers = BuildUpdateCheckExtraRequestHeaders(
+ "fake_prodid", base::Version("30.0"), {}, false);
+ EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+ EXPECT_EQ("bg", headers["X-Goog-Update-Interactivity"]);
+ EXPECT_TRUE(headers["X-Goog-Update-AppId"].empty());
+
+ headers = BuildUpdateCheckExtraRequestHeaders(
+ "fake_prodid", base::Version("30.0"),
+ {"jebgalgnebhfojomionfpkfelancnnkf"}, true);
+ EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+ EXPECT_EQ("fg", headers["X-Goog-Update-Interactivity"]);
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", headers["X-Goog-Update-AppId"]);
+
+ headers = BuildUpdateCheckExtraRequestHeaders(
+ "fake_prodid", base::Version("30.0"),
+ {"jebgalgnebhfojomionfpkfelancnnkf", "ihfokbkgjpifbbojhneepfflplebdkc"},
+ true);
+ EXPECT_EQ("fake_prodid-30.0", headers["X-Goog-Update-Updater"]);
+ EXPECT_EQ("fg", headers["X-Goog-Update-Interactivity"]);
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf,ihfokbkgjpifbbojhneepfflplebdkc",
+ headers["X-Goog-Update-AppId"]);
+
+ // Test that only 30 extension ids are joined in the headers.
+ headers = BuildUpdateCheckExtraRequestHeaders(
+ "fake_prodid", base::Version("30.0"),
+ std::vector<std::string>(40, "jebgalgnebhfojomionfpkfelancnnkf"), true);
+ EXPECT_EQ(base::JoinString(std::vector<std::string>(
+ 30, "jebgalgnebhfojomionfpkfelancnnkf"),
+ ","),
+ headers["X-Goog-Update-AppId"]);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/request_sender.cc b/src/components/update_client/request_sender.cc
new file mode 100644
index 0000000..a68df98
--- /dev/null
+++ b/src/components/update_client/request_sender.cc
@@ -0,0 +1,204 @@
+// 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 "components/update_client/request_sender.h"
+
+#include <utility>
+
+#include "base/base64.h"
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/numerics/safe_conversions.h"
+#include "base/strings/stringprintf.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/client_update_protocol/ecdsa.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/network.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+namespace {
+
+// This is an ECDSA prime256v1 named-curve key.
+constexpr int kKeyVersion = 9;
+const char kKeyPubBytesBase64[] =
+ "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEsVwVMmIJaWBjktSx9m1JrZWYBvMm"
+ "bsrGGQPhScDtao+DloD871YmEeunAaQvRMZgDh1nCaWkVG6wo75+yDbKDA==";
+
+} // namespace
+
+RequestSender::RequestSender(scoped_refptr<Configurator> config)
+ : config_(config) {}
+
+RequestSender::~RequestSender() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void RequestSender::Send(
+ const std::vector<GURL>& urls,
+ const base::flat_map<std::string, std::string>& request_extra_headers,
+ const std::string& request_body,
+ bool use_signing,
+ RequestSenderCallback request_sender_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ urls_ = urls;
+ request_extra_headers_ = request_extra_headers;
+ request_body_ = request_body;
+ use_signing_ = use_signing;
+ request_sender_callback_ = std::move(request_sender_callback);
+
+ if (urls_.empty()) {
+ return HandleSendError(static_cast<int>(ProtocolError::MISSING_URLS), 0);
+ }
+
+ cur_url_ = urls_.begin();
+
+ if (use_signing_) {
+ public_key_ = GetKey(kKeyPubBytesBase64);
+ if (public_key_.empty())
+ return HandleSendError(
+ static_cast<int>(ProtocolError::MISSING_PUBLIC_KEY), 0);
+ }
+
+ SendInternal();
+}
+
+void RequestSender::SendInternal() {
+ DCHECK(cur_url_ != urls_.end());
+ DCHECK(cur_url_->is_valid());
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ GURL url(*cur_url_);
+
+ if (use_signing_) {
+ DCHECK(!public_key_.empty());
+ signer_ = client_update_protocol::Ecdsa::Create(kKeyVersion, public_key_);
+ std::string request_query_string;
+ signer_->SignRequest(request_body_, &request_query_string);
+
+ url = BuildUpdateUrl(url, request_query_string);
+ }
+
+ network_fetcher_ = config_->GetNetworkFetcherFactory()->Create();
+ if (!network_fetcher_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&RequestSender::SendInternalComplete,
+ base::Unretained(this),
+ static_cast<int>(ProtocolError::URL_FETCHER_FAILED),
+ std::string(), std::string(), 0));
+ }
+ network_fetcher_->PostRequest(
+ url, request_body_, request_extra_headers_,
+ base::BindOnce(&RequestSender::OnResponseStarted, base::Unretained(this)),
+ base::BindRepeating([](int64_t current) {}),
+ base::BindOnce(&RequestSender::OnNetworkFetcherComplete,
+ base::Unretained(this), url));
+}
+
+void RequestSender::SendInternalComplete(int error,
+ const std::string& response_body,
+ const std::string& response_etag,
+ int retry_after_sec) {
+ if (!error) {
+ if (!use_signing_) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(request_sender_callback_), 0,
+ response_body, retry_after_sec));
+ return;
+ }
+
+ DCHECK(use_signing_);
+ DCHECK(signer_);
+ if (signer_->ValidateResponse(response_body, response_etag)) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(request_sender_callback_), 0,
+ response_body, retry_after_sec));
+ return;
+ }
+
+ error = static_cast<int>(ProtocolError::RESPONSE_NOT_TRUSTED);
+ }
+
+ DCHECK(error);
+
+ // A positive |retry_after_sec| is a hint from the server that the client
+ // should not send further request until the cooldown has expired.
+ if (retry_after_sec <= 0 && ++cur_url_ != urls_.end() &&
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&RequestSender::SendInternal,
+ base::Unretained(this)))) {
+ return;
+ }
+
+ HandleSendError(error, retry_after_sec);
+}
+
+void RequestSender::OnResponseStarted(const GURL& final_url,
+ int response_code,
+ int64_t content_length) {
+ response_code_ = response_code;
+}
+
+void RequestSender::OnNetworkFetcherComplete(
+ const GURL& original_url,
+ std::unique_ptr<std::string> response_body,
+ int net_error,
+ const std::string& header_etag,
+ int64_t xheader_retry_after_sec) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ VLOG(1) << "request completed from url: " << original_url.spec();
+
+ int error = -1;
+ if (response_body && response_code_ == 200) {
+ DCHECK_EQ(0, net_error);
+ error = 0;
+ } else if (response_code_ != -1) {
+ error = response_code_;
+ } else {
+ error = net_error;
+ }
+
+ int retry_after_sec = -1;
+ if (original_url.SchemeIsCryptographic() && error > 0)
+ retry_after_sec = base::saturated_cast<int>(xheader_retry_after_sec);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&RequestSender::SendInternalComplete,
+ base::Unretained(this), error,
+ response_body ? *response_body : std::string(),
+ header_etag, retry_after_sec));
+}
+
+void RequestSender::HandleSendError(int error, int retry_after_sec) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(request_sender_callback_), error,
+ std::string(), retry_after_sec));
+}
+
+std::string RequestSender::GetKey(const char* key_bytes_base64) {
+ std::string result;
+ return base::Base64Decode(std::string(key_bytes_base64), &result)
+ ? result
+ : std::string();
+}
+
+GURL RequestSender::BuildUpdateUrl(const GURL& url,
+ const std::string& query_params) {
+ const std::string query_string(
+ url.has_query() ? base::StringPrintf("%s&%s", url.query().c_str(),
+ query_params.c_str())
+ : query_params);
+ GURL::Replacements replacements;
+ replacements.SetQueryStr(query_string);
+
+ return url.ReplaceComponents(replacements);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/request_sender.h b/src/components/update_client/request_sender.h
new file mode 100644
index 0000000..7b9e21b
--- /dev/null
+++ b/src/components/update_client/request_sender.h
@@ -0,0 +1,113 @@
+// 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 COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_
+#define COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "url/gurl.h"
+
+namespace client_update_protocol {
+class Ecdsa;
+}
+
+namespace update_client {
+
+class Configurator;
+class NetworkFetcher;
+
+// Sends a request to one of the urls provided. The class implements a chain
+// of responsibility design pattern, where the urls are tried in the order they
+// are specified, until the request to one of them succeeds or all have failed.
+// CUP signing is optional.
+class RequestSender {
+ public:
+ // If |error| is 0, then the response is provided in the |response| parameter.
+ // |retry_after_sec| contains the value of the X-Retry-After response header,
+ // when the response was received from a cryptographically secure URL. The
+ // range for this value is [-1, 86400]. If |retry_after_sec| is -1 it means
+ // that the header could not be found, or trusted, or had an invalid value.
+ // The upper bound represents a delay of one day.
+ using RequestSenderCallback = base::OnceCallback<
+ void(int error, const std::string& response, int retry_after_sec)>;
+
+ explicit RequestSender(scoped_refptr<Configurator> config);
+ ~RequestSender();
+
+ // |use_signing| enables CUP signing of protocol messages exchanged using
+ // this class. |is_foreground| controls the presence and the value for the
+ // X-GoogleUpdate-Interactvity header serialized in the protocol request.
+ // If this optional parameter is set, the values of "fg" or "bg" are sent
+ // for true or false values of this parameter. Otherwise the header is not
+ // sent at all.
+ void Send(
+ const std::vector<GURL>& urls,
+ const base::flat_map<std::string, std::string>& request_extra_headers,
+ const std::string& request_body,
+ bool use_signing,
+ RequestSenderCallback request_sender_callback);
+
+ private:
+ // Combines the |url| and |query_params| parameters.
+ static GURL BuildUpdateUrl(const GURL& url, const std::string& query_params);
+
+ // Decodes and returns the public key used by CUP.
+ static std::string GetKey(const char* key_bytes_base64);
+
+ void OnResponseStarted(const GURL& final_url,
+ int response_code,
+ int64_t content_length);
+
+ void OnNetworkFetcherComplete(const GURL& original_url,
+ std::unique_ptr<std::string> response_body,
+ int net_error,
+ const std::string& header_etag,
+ int64_t xheader_retry_after_sec);
+
+ // Implements the error handling and url fallback mechanism.
+ void SendInternal();
+
+ // Called when SendInternal completes. |response_body| and |response_etag|
+ // contain the body and the etag associated with the HTTP response.
+ void SendInternalComplete(int error,
+ const std::string& response_body,
+ const std::string& response_etag,
+ int retry_after_sec);
+
+ // Helper function to handle a non-continuable error in Send.
+ void HandleSendError(int error, int retry_after_sec);
+
+ base::ThreadChecker thread_checker_;
+
+ const scoped_refptr<Configurator> config_;
+
+ std::vector<GURL> urls_;
+ base::flat_map<std::string, std::string> request_extra_headers_;
+ std::string request_body_;
+ bool use_signing_ = false; // True if CUP signing is used.
+ RequestSenderCallback request_sender_callback_;
+
+ std::string public_key_;
+ std::vector<GURL>::const_iterator cur_url_;
+ std::unique_ptr<NetworkFetcher> network_fetcher_;
+ std::unique_ptr<client_update_protocol::Ecdsa> signer_;
+
+ int response_code_ = -1;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestSender);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_REQUEST_SENDER_H_
diff --git a/src/components/update_client/request_sender_unittest.cc b/src/components/update_client/request_sender_unittest.cc
new file mode 100644
index 0000000..e1e16d8
--- /dev/null
+++ b/src/components/update_client/request_sender_unittest.cc
@@ -0,0 +1,262 @@
+// 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 "components/update_client/request_sender.h"
+
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/update_client/net/url_loader_post_interceptor.h"
+#include "components/update_client/test_configurator.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+namespace {
+
+const char kUrl1[] = "https://localhost2/path1";
+const char kUrl2[] = "https://localhost2/path2";
+
+// TODO(sorin): refactor as a utility function for unit tests.
+base::FilePath test_file(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("update_client")
+ .AppendASCII(file);
+}
+
+} // namespace
+
+class RequestSenderTest : public testing::Test,
+ public ::testing::WithParamInterface<bool> {
+ public:
+ RequestSenderTest();
+ ~RequestSenderTest() override;
+
+ // Overrides from testing::Test.
+ void SetUp() override;
+ void TearDown() override;
+
+ void RequestSenderComplete(int error,
+ const std::string& response,
+ int retry_after_sec);
+
+ protected:
+ void Quit();
+ void RunThreads();
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+
+ scoped_refptr<TestConfigurator> config_;
+ std::unique_ptr<RequestSender> request_sender_;
+
+ std::unique_ptr<URLLoaderPostInterceptor> post_interceptor_;
+
+ int error_ = 0;
+ std::string response_;
+
+ private:
+ base::OnceClosure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(RequestSenderTest);
+};
+
+INSTANTIATE_TEST_SUITE_P(IsForeground, RequestSenderTest, ::testing::Bool());
+
+RequestSenderTest::RequestSenderTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
+
+RequestSenderTest::~RequestSenderTest() {}
+
+void RequestSenderTest::SetUp() {
+ config_ = base::MakeRefCounted<TestConfigurator>();
+ request_sender_ = std::make_unique<RequestSender>(config_);
+
+ std::vector<GURL> urls;
+ urls.push_back(GURL(kUrl1));
+ urls.push_back(GURL(kUrl2));
+
+ post_interceptor_ = std::make_unique<URLLoaderPostInterceptor>(
+ urls, config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor_);
+}
+
+void RequestSenderTest::TearDown() {
+ request_sender_ = nullptr;
+
+ post_interceptor_.reset();
+
+ // Run the threads until they are idle to allow the clean up
+ // of the network interceptors on the IO thread.
+ scoped_task_environment_.RunUntilIdle();
+ config_ = nullptr;
+}
+
+void RequestSenderTest::RunThreads() {
+ base::RunLoop runloop;
+ quit_closure_ = runloop.QuitClosure();
+ runloop.Run();
+}
+
+void RequestSenderTest::Quit() {
+ if (!quit_closure_.is_null())
+ std::move(quit_closure_).Run();
+}
+
+void RequestSenderTest::RequestSenderComplete(int error,
+ const std::string& response,
+ int retry_after_sec) {
+ error_ = error;
+ response_ = response;
+
+ Quit();
+}
+
+// Tests that when a request to the first url succeeds, the subsequent urls are
+// not tried.
+TEST_P(RequestSenderTest, RequestSendSuccess) {
+ EXPECT_TRUE(
+ post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test"),
+ test_file("updatecheck_reply_1.json")));
+
+ const bool is_foreground = GetParam();
+ request_sender_->Send(
+ {GURL(kUrl1), GURL(kUrl2)},
+ {{"X-Goog-Update-Interactivity", is_foreground ? "fg" : "bg"}}, "test",
+ false,
+ base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_EQ(0, post_interceptor_->GetHitCountForURL(GURL(kUrl2)))
+ << post_interceptor_->GetRequestsAsString();
+
+ // Sanity check the request.
+ EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+
+ // Check the response post conditions.
+ EXPECT_EQ(0, error_);
+ EXPECT_EQ(419ul, response_.size());
+
+ // Check the interactivity header value.
+ const auto extra_request_headers =
+ std::get<1>(post_interceptor_->GetRequests()[0]);
+ EXPECT_TRUE(extra_request_headers.HasHeader("X-Goog-Update-Interactivity"));
+ std::string header;
+ extra_request_headers.GetHeader("X-Goog-Update-Interactivity", &header);
+ EXPECT_STREQ(is_foreground ? "fg" : "bg", header.c_str());
+}
+
+// Tests that the request succeeds using the second url after the first url
+// has failed.
+TEST_F(RequestSenderTest, RequestSendSuccessWithFallback) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("test"), net::HTTP_FORBIDDEN));
+ EXPECT_TRUE(
+ post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test")));
+
+ request_sender_->Send(
+ {GURL(kUrl1), GURL(kUrl2)}, {}, "test", false,
+ base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(2, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(2, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl1)))
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl2)))
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+ EXPECT_STREQ("test", post_interceptor_->GetRequestBody(1).c_str());
+ EXPECT_EQ(0, error_);
+}
+
+// Tests that the request fails when both urls have failed.
+TEST_F(RequestSenderTest, RequestSendFailed) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("test"), net::HTTP_FORBIDDEN));
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("test"), net::HTTP_FORBIDDEN));
+
+ const std::vector<GURL> urls = {GURL(kUrl1), GURL(kUrl2)};
+ request_sender_ = std::make_unique<RequestSender>(config_);
+ request_sender_->Send(
+ urls, {}, "test", false,
+ base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(2, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(2, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl1)))
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetHitCountForURL(GURL(kUrl2)))
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+ EXPECT_STREQ("test", post_interceptor_->GetRequestBody(1).c_str());
+ EXPECT_EQ(403, error_);
+}
+
+// Tests that the request fails when no urls are provided.
+TEST_F(RequestSenderTest, RequestSendFailedNoUrls) {
+ std::vector<GURL> urls;
+ request_sender_ = std::make_unique<RequestSender>(config_);
+ request_sender_->Send(
+ urls, {}, "test", false,
+ base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(-10002, error_);
+}
+
+// Tests that a CUP request fails if the response is not signed.
+TEST_F(RequestSenderTest, RequestSendCupError) {
+ EXPECT_TRUE(
+ post_interceptor_->ExpectRequest(std::make_unique<PartialMatch>("test"),
+ test_file("updatecheck_reply_1.json")));
+
+ const std::vector<GURL> urls = {GURL(kUrl1)};
+ request_sender_ = std::make_unique<RequestSender>(config_);
+ request_sender_->Send(
+ urls, {}, "test", true,
+ base::BindOnce(&RequestSenderTest::RequestSenderComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_STREQ("test", post_interceptor_->GetRequestBody(0).c_str());
+ EXPECT_EQ(-10000, error_);
+ EXPECT_TRUE(response_.empty());
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/task.h b/src/components/update_client/task.h
new file mode 100644
index 0000000..e9b480e
--- /dev/null
+++ b/src/components/update_client/task.h
@@ -0,0 +1,43 @@
+// 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 COMPONENTS_UPDATE_CLIENT_TASK_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_H_
+
+#include <string>
+#include <vector>
+
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace update_client {
+
+// Defines an abstraction for a unit of work done by the update client.
+// Each invocation of the update client API results in a task being created and
+// run. In most cases, a task corresponds to a set of CRXs, which are updated
+// together.
+class Task : public base::RefCounted<Task> {
+ public:
+ Task() = default;
+ virtual void Run() = 0;
+
+ // Does a best effort attempt to make a task release its resources and stop
+ // soon. It is possible that a running task may complete even if this
+ // method is called.
+ virtual void Cancel() = 0;
+
+ // Returns the ids corresponding to the CRXs associated with this update task.
+ virtual std::vector<std::string> GetIds() const = 0;
+
+ protected:
+ friend class base::RefCounted<Task>;
+ virtual ~Task() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Task);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_TASK_H_
diff --git a/src/components/update_client/task_send_uninstall_ping.cc b/src/components/update_client/task_send_uninstall_ping.cc
new file mode 100644
index 0000000..018bb1d
--- /dev/null
+++ b/src/components/update_client/task_send_uninstall_ping.cc
@@ -0,0 +1,64 @@
+// Copyright 2017 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 "components/update_client/task_send_uninstall_ping.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_engine.h"
+
+namespace update_client {
+
+TaskSendUninstallPing::TaskSendUninstallPing(
+ scoped_refptr<UpdateEngine> update_engine,
+ const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback callback)
+ : update_engine_(update_engine),
+ id_(id),
+ version_(version),
+ reason_(reason),
+ callback_(std::move(callback)) {}
+
+TaskSendUninstallPing::~TaskSendUninstallPing() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void TaskSendUninstallPing::Run() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (id_.empty()) {
+ TaskComplete(Error::INVALID_ARGUMENT);
+ return;
+ }
+
+ update_engine_->SendUninstallPing(
+ id_, version_, reason_,
+ base::BindOnce(&TaskSendUninstallPing::TaskComplete, this));
+}
+
+void TaskSendUninstallPing::Cancel() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ TaskComplete(Error::UPDATE_CANCELED);
+}
+
+std::vector<std::string> TaskSendUninstallPing::GetIds() const {
+ return std::vector<std::string>{id_};
+}
+
+void TaskSendUninstallPing::TaskComplete(Error error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback_), scoped_refptr<Task>(this), error));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/task_send_uninstall_ping.h b/src/components/update_client/task_send_uninstall_ping.h
new file mode 100644
index 0000000..9367b3d
--- /dev/null
+++ b/src/components/update_client/task_send_uninstall_ping.h
@@ -0,0 +1,68 @@
+// Copyright 2017 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 COMPONENTS_UPDATE_CLIENT_TASK_SEND_UNINSTALL_PING_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_SEND_UNINSTALL_PING_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "components/update_client/task.h"
+#include "components/update_client/update_client.h"
+
+namespace base {
+class Version;
+}
+
+namespace update_client {
+
+class UpdateEngine;
+enum class Error;
+
+// Defines a specialized task for sending the uninstall ping.
+class TaskSendUninstallPing : public Task {
+ public:
+ using Callback =
+ base::OnceCallback<void(scoped_refptr<Task> task, Error error)>;
+
+ // |update_engine| is injected here to handle the task.
+ // |id| represents the CRX to send the ping for.
+ // |callback| is called to return the execution flow back to creator of
+ // this task when the task is done.
+ TaskSendUninstallPing(scoped_refptr<UpdateEngine> update_engine,
+ const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback callback);
+
+ void Run() override;
+
+ void Cancel() override;
+
+ std::vector<std::string> GetIds() const override;
+
+ private:
+ ~TaskSendUninstallPing() override;
+
+ // Called when the task has completed either because the task has run or
+ // it has been canceled.
+ void TaskComplete(Error error);
+
+ base::ThreadChecker thread_checker_;
+ scoped_refptr<UpdateEngine> update_engine_;
+ const std::string id_;
+ const base::Version version_;
+ int reason_;
+ Callback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskSendUninstallPing);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_TASK_SEND_UNINSTALL_PING_H_
diff --git a/src/components/update_client/task_traits.h b/src/components/update_client/task_traits.h
new file mode 100644
index 0000000..45f4cc9
--- /dev/null
+++ b/src/components/update_client/task_traits.h
@@ -0,0 +1,28 @@
+// Copyright 2017 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 COMPONENTS_UPDATE_CLIENT_TASK_TRAITS_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_TRAITS_H_
+
+#include "base/task/task_traits.h"
+
+namespace update_client {
+
+const base::TaskTraits kTaskTraits = {
+ base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
+
+const base::TaskTraits kTaskTraitsBackgroundDownloader = {
+ base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+
+// This task joins a process, hence .WithBaseSyncPrimitives().
+const base::TaskTraits kTaskTraitsRunCommand = {
+ base::MayBlock(), base::WithBaseSyncPrimitives(),
+ base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_TASK_TRAITS_H_
diff --git a/src/components/update_client/task_update.cc b/src/components/update_client/task_update.cc
new file mode 100644
index 0000000..ab445b5
--- /dev/null
+++ b/src/components/update_client/task_update.cc
@@ -0,0 +1,61 @@
+// 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 "components/update_client/task_update.h"
+
+#include <utility>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_engine.h"
+
+namespace update_client {
+
+TaskUpdate::TaskUpdate(scoped_refptr<UpdateEngine> update_engine,
+ bool is_foreground,
+ const std::vector<std::string>& ids,
+ UpdateClient::CrxDataCallback crx_data_callback,
+ Callback callback)
+ : update_engine_(update_engine),
+ is_foreground_(is_foreground),
+ ids_(ids),
+ crx_data_callback_(std::move(crx_data_callback)),
+ callback_(std::move(callback)) {}
+
+TaskUpdate::~TaskUpdate() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void TaskUpdate::Run() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (ids_.empty()) {
+ TaskComplete(Error::INVALID_ARGUMENT);
+ return;
+ }
+
+ update_engine_->Update(is_foreground_, ids_, std::move(crx_data_callback_),
+ base::BindOnce(&TaskUpdate::TaskComplete, this));
+}
+
+void TaskUpdate::Cancel() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ TaskComplete(Error::UPDATE_CANCELED);
+}
+
+std::vector<std::string> TaskUpdate::GetIds() const {
+ return ids_;
+}
+
+void TaskUpdate::TaskComplete(Error error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback_),
+ scoped_refptr<TaskUpdate>(this), error));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/task_update.h b/src/components/update_client/task_update.h
new file mode 100644
index 0000000..4df796b
--- /dev/null
+++ b/src/components/update_client/task_update.h
@@ -0,0 +1,66 @@
+// 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 COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_
+#define COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_
+
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "components/update_client/task.h"
+#include "components/update_client/update_client.h"
+
+namespace update_client {
+
+class UpdateEngine;
+enum class Error;
+
+// Defines a specialized task for updating a group of CRXs.
+class TaskUpdate : public Task {
+ public:
+ using Callback =
+ base::OnceCallback<void(scoped_refptr<Task> task, Error error)>;
+
+ // |update_engine| is injected here to handle the task.
+ // |is_foreground| is true when the update task is initiated by the user.
+ // |ids| represents the CRXs to be updated by this task.
+ // |crx_data_callback| is called to get update data for the these CRXs.
+ // |callback| is called to return the execution flow back to creator of
+ // this task when the task is done.
+ TaskUpdate(scoped_refptr<UpdateEngine> update_engine,
+ bool is_foreground,
+ const std::vector<std::string>& ids,
+ UpdateClient::CrxDataCallback crx_data_callback,
+ Callback callback);
+
+ void Run() override;
+
+ void Cancel() override;
+
+ std::vector<std::string> GetIds() const override;
+
+ private:
+ ~TaskUpdate() override;
+
+ // Called when the task has completed either because the task has run or
+ // it has been canceled.
+ void TaskComplete(Error error);
+
+ base::ThreadChecker thread_checker_;
+ scoped_refptr<UpdateEngine> update_engine_;
+ const bool is_foreground_;
+ const std::vector<std::string> ids_;
+ UpdateClient::CrxDataCallback crx_data_callback_;
+ Callback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(TaskUpdate);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_TASK_UPDATE_H_
diff --git a/src/components/update_client/test_configurator.cc b/src/components/update_client/test_configurator.cc
new file mode 100644
index 0000000..515be05
--- /dev/null
+++ b/src/components/update_client/test_configurator.cc
@@ -0,0 +1,216 @@
+// 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 "components/update_client/test_configurator.h"
+
+#include <utility>
+
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "components/prefs/pref_service.h"
+#include "components/services/patch/in_process_file_patcher.h"
+#include "components/services/unzip/in_process_unzipper.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/net/network_chromium.h"
+#include "components/update_client/patch/patch_impl.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/unzip/unzip_impl.h"
+#include "components/update_client/unzipper.h"
+#include "services/network/public/cpp/weak_wrapper_shared_url_loader_factory.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+std::vector<GURL> MakeDefaultUrls() {
+ std::vector<GURL> urls;
+ urls.push_back(GURL(POST_INTERCEPT_SCHEME
+ "://" POST_INTERCEPT_HOSTNAME POST_INTERCEPT_PATH));
+ return urls;
+}
+
+} // namespace
+
+TestConfigurator::TestConfigurator()
+ : brand_("TEST"),
+ initial_time_(0),
+ ondemand_time_(0),
+ enabled_cup_signing_(false),
+ enabled_component_updates_(true),
+ unzip_factory_(base::MakeRefCounted<update_client::UnzipChromiumFactory>(
+ base::BindRepeating(&unzip::LaunchInProcessUnzipper))),
+ patch_factory_(base::MakeRefCounted<update_client::PatchChromiumFactory>(
+ base::BindRepeating(&patch::LaunchInProcessFilePatcher))),
+ test_shared_loader_factory_(
+ base::MakeRefCounted<network::WeakWrapperSharedURLLoaderFactory>(
+ &test_url_loader_factory_)),
+ network_fetcher_factory_(
+ base::MakeRefCounted<NetworkFetcherChromiumFactory>(
+ test_shared_loader_factory_)) {}
+
+TestConfigurator::~TestConfigurator() {}
+
+int TestConfigurator::InitialDelay() const {
+ return initial_time_;
+}
+
+int TestConfigurator::NextCheckDelay() const {
+ return 1;
+}
+
+int TestConfigurator::OnDemandDelay() const {
+ return ondemand_time_;
+}
+
+int TestConfigurator::UpdateDelay() const {
+ return 1;
+}
+
+std::vector<GURL> TestConfigurator::UpdateUrl() const {
+ if (!update_check_url_.is_empty())
+ return std::vector<GURL>(1, update_check_url_);
+
+ return MakeDefaultUrls();
+}
+
+std::vector<GURL> TestConfigurator::PingUrl() const {
+ if (!ping_url_.is_empty())
+ return std::vector<GURL>(1, ping_url_);
+
+ return UpdateUrl();
+}
+
+std::string TestConfigurator::GetProdId() const {
+ return "fake_prodid";
+}
+
+base::Version TestConfigurator::GetBrowserVersion() const {
+ // Needs to be larger than the required version in tested component manifests.
+ return base::Version("30.0");
+}
+
+std::string TestConfigurator::GetChannel() const {
+ return "fake_channel_string";
+}
+
+std::string TestConfigurator::GetBrand() const {
+ return brand_;
+}
+
+std::string TestConfigurator::GetLang() const {
+ return "fake_lang";
+}
+
+std::string TestConfigurator::GetOSLongName() const {
+ return "Fake Operating System";
+}
+
+base::flat_map<std::string, std::string> TestConfigurator::ExtraRequestParams()
+ const {
+ return {{"extra", "foo"}};
+}
+
+std::string TestConfigurator::GetDownloadPreference() const {
+ return download_preference_;
+}
+
+scoped_refptr<NetworkFetcherFactory>
+TestConfigurator::GetNetworkFetcherFactory() {
+ return network_fetcher_factory_;
+}
+
+scoped_refptr<UnzipperFactory> TestConfigurator::GetUnzipperFactory() {
+ return unzip_factory_;
+}
+
+scoped_refptr<PatcherFactory> TestConfigurator::GetPatcherFactory() {
+ return patch_factory_;
+}
+
+bool TestConfigurator::EnabledDeltas() const {
+ return true;
+}
+
+bool TestConfigurator::EnabledComponentUpdates() const {
+ return enabled_component_updates_;
+}
+
+bool TestConfigurator::EnabledBackgroundDownloader() const {
+ return false;
+}
+
+bool TestConfigurator::EnabledCupSigning() const {
+ return enabled_cup_signing_;
+}
+
+void TestConfigurator::SetBrand(const std::string& brand) {
+ brand_ = brand;
+}
+
+void TestConfigurator::SetOnDemandTime(int seconds) {
+ ondemand_time_ = seconds;
+}
+
+void TestConfigurator::SetInitialDelay(int seconds) {
+ initial_time_ = seconds;
+}
+
+void TestConfigurator::SetEnabledCupSigning(bool enabled_cup_signing) {
+ enabled_cup_signing_ = enabled_cup_signing;
+}
+
+void TestConfigurator::SetEnabledComponentUpdates(
+ bool enabled_component_updates) {
+ enabled_component_updates_ = enabled_component_updates;
+}
+
+void TestConfigurator::SetDownloadPreference(
+ const std::string& download_preference) {
+ download_preference_ = download_preference;
+}
+
+void TestConfigurator::SetUpdateCheckUrl(const GURL& url) {
+ update_check_url_ = url;
+}
+
+void TestConfigurator::SetPingUrl(const GURL& url) {
+ ping_url_ = url;
+}
+
+void TestConfigurator::SetAppGuid(const std::string& app_guid) {
+ app_guid_ = app_guid;
+}
+
+PrefService* TestConfigurator::GetPrefService() const {
+ return nullptr;
+}
+
+ActivityDataService* TestConfigurator::GetActivityDataService() const {
+ return nullptr;
+}
+
+bool TestConfigurator::IsPerUserInstall() const {
+ return true;
+}
+
+std::vector<uint8_t> TestConfigurator::GetRunActionKeyHash() const {
+ return std::vector<uint8_t>(std::begin(gjpm_hash), std::end(gjpm_hash));
+}
+
+std::string TestConfigurator::GetAppGuid() const {
+ return app_guid_;
+}
+
+std::unique_ptr<ProtocolHandlerFactory>
+TestConfigurator::GetProtocolHandlerFactory() const {
+ return std::make_unique<ProtocolHandlerFactoryJSON>();
+}
+
+RecoveryCRXElevator TestConfigurator::GetRecoveryCRXElevator() const {
+ return {};
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/test_configurator.h b/src/components/update_client/test_configurator.h
new file mode 100644
index 0000000..6935347
--- /dev/null
+++ b/src/components/update_client/test_configurator.h
@@ -0,0 +1,146 @@
+// 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 COMPONENTS_UPDATE_CLIENT_TEST_CONFIGURATOR_H_
+#define COMPONENTS_UPDATE_CLIENT_TEST_CONFIGURATOR_H_
+
+#include <stdint.h>
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/configurator.h"
+#include "services/network/test/test_url_loader_factory.h"
+#include "url/gurl.h"
+
+class PrefService;
+
+namespace network {
+class SharedURLLoaderFactory;
+} // namespace network
+
+namespace update_client {
+
+class ActivityDataService;
+class NetworkFetcherFactory;
+class PatchChromiumFactory;
+class ProtocolHandlerFactory;
+class UnzipChromiumFactory;
+
+#define POST_INTERCEPT_SCHEME "https"
+#define POST_INTERCEPT_HOSTNAME "localhost2"
+#define POST_INTERCEPT_PATH "/update2"
+
+// component 1 has extension id "jebgalgnebhfojomionfpkfelancnnkf", and
+// the RSA public key the following hash:
+const uint8_t jebg_hash[] = {0x94, 0x16, 0x0b, 0x6d, 0x41, 0x75, 0xe9, 0xec,
+ 0x8e, 0xd5, 0xfa, 0x54, 0xb0, 0xd2, 0xdd, 0xa5,
+ 0x6e, 0x05, 0x6b, 0xe8, 0x73, 0x47, 0xf6, 0xc4,
+ 0x11, 0x9f, 0xbc, 0xb3, 0x09, 0xb3, 0x5b, 0x40};
+// component 1 public key (base64 encoded):
+const char jebg_public_key[] =
+ "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQC68bW8i/RzSaeXOcNLuBw0SP9+1bdo5ysLqH"
+ "qfLqZs6XyJWEyL0U6f1axPR6LwViku21kgdc6PI524eb8Cr+a/iXGgZ8SdvZTcfQ/g/ukwlblF"
+ "mtqYfDoVpz03U8rDQ9b6DxeJBF4r48TNlFORggrAiNR26qbf1i178Au12AzWtwIDAQAB";
+// component 2 has extension id "abagagagagagagagagagagagagagagag", and
+// the RSA public key the following hash:
+const uint8_t abag_hash[] = {0x01, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06,
+ 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x06, 0x01};
+// component 3 has extension id "ihfokbkgjpifnbbojhneepfflplebdkc", and
+// the RSA public key the following hash:
+const uint8_t ihfo_hash[] = {0x87, 0x5e, 0xa1, 0xa6, 0x9f, 0x85, 0xd1, 0x1e,
+ 0x97, 0xd4, 0x4f, 0x55, 0xbf, 0xb4, 0x13, 0xa2,
+ 0xe7, 0xc5, 0xc8, 0xf5, 0x60, 0x19, 0x78, 0x1b,
+ 0x6d, 0xe9, 0x4c, 0xeb, 0x96, 0x05, 0x42, 0x17};
+
+// runaction_test_win.crx and its payload id: gjpmebpgbhcamgdgjcmnjfhggjpgcimm
+const uint8_t gjpm_hash[] = {0x69, 0xfc, 0x41, 0xf6, 0x17, 0x20, 0xc6, 0x36,
+ 0x92, 0xcd, 0x95, 0x76, 0x69, 0xf6, 0x28, 0xcc,
+ 0xbe, 0x98, 0x4b, 0x93, 0x17, 0xd6, 0x9c, 0xb3,
+ 0x64, 0x0c, 0x0d, 0x25, 0x61, 0xc5, 0x80, 0x1d};
+
+class TestConfigurator : public Configurator {
+ public:
+ TestConfigurator();
+
+ // Overrrides for Configurator.
+ int InitialDelay() const override;
+ int NextCheckDelay() const override;
+ int OnDemandDelay() const override;
+ int UpdateDelay() const override;
+ std::vector<GURL> UpdateUrl() const override;
+ std::vector<GURL> PingUrl() const override;
+ std::string GetProdId() const override;
+ base::Version GetBrowserVersion() const override;
+ std::string GetChannel() const override;
+ std::string GetBrand() const override;
+ std::string GetLang() const override;
+ std::string GetOSLongName() const override;
+ base::flat_map<std::string, std::string> ExtraRequestParams() const override;
+ std::string GetDownloadPreference() const override;
+ scoped_refptr<NetworkFetcherFactory> GetNetworkFetcherFactory() override;
+ scoped_refptr<UnzipperFactory> GetUnzipperFactory() override;
+ scoped_refptr<PatcherFactory> GetPatcherFactory() override;
+ bool EnabledDeltas() const override;
+ bool EnabledComponentUpdates() const override;
+ bool EnabledBackgroundDownloader() const override;
+ bool EnabledCupSigning() const override;
+ PrefService* GetPrefService() const override;
+ ActivityDataService* GetActivityDataService() const override;
+ bool IsPerUserInstall() const override;
+ std::vector<uint8_t> GetRunActionKeyHash() const override;
+ std::string GetAppGuid() const override;
+ std::unique_ptr<ProtocolHandlerFactory> GetProtocolHandlerFactory()
+ const override;
+ RecoveryCRXElevator GetRecoveryCRXElevator() const override;
+
+ void SetBrand(const std::string& brand);
+ void SetOnDemandTime(int seconds);
+ void SetInitialDelay(int seconds);
+ void SetDownloadPreference(const std::string& download_preference);
+ void SetEnabledCupSigning(bool use_cup_signing);
+ void SetEnabledComponentUpdates(bool enabled_component_updates);
+ void SetUpdateCheckUrl(const GURL& url);
+ void SetPingUrl(const GURL& url);
+ void SetAppGuid(const std::string& app_guid);
+ network::TestURLLoaderFactory* test_url_loader_factory() {
+ return &test_url_loader_factory_;
+ }
+
+ private:
+ friend class base::RefCountedThreadSafe<TestConfigurator>;
+ ~TestConfigurator() override;
+
+ class TestPatchService;
+
+ std::string brand_;
+ int initial_time_;
+ int ondemand_time_;
+ std::string download_preference_;
+ bool enabled_cup_signing_;
+ bool enabled_component_updates_;
+ GURL update_check_url_;
+ GURL ping_url_;
+ std::string app_guid_;
+
+ scoped_refptr<update_client::UnzipChromiumFactory> unzip_factory_;
+ scoped_refptr<update_client::PatchChromiumFactory> patch_factory_;
+
+ scoped_refptr<network::SharedURLLoaderFactory> test_shared_loader_factory_;
+ network::TestURLLoaderFactory test_url_loader_factory_;
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestConfigurator);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_TEST_CONFIGURATOR_H_
diff --git a/src/components/update_client/test_installer.cc b/src/components/update_client/test_installer.cc
new file mode 100644
index 0000000..f4957be
--- /dev/null
+++ b/src/components/update_client/test_installer.cc
@@ -0,0 +1,108 @@
+// Copyright 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 "components/update_client/test_installer.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/values.h"
+
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+TestInstaller::TestInstaller() : error_(0), install_count_(0) {}
+
+TestInstaller::~TestInstaller() {
+ // The unpack path is deleted unconditionally by the component state code,
+ // which is driving this installer. Therefore, the unpack path must not
+ // exist when this object is destroyed.
+ if (!unpack_path_.empty())
+ EXPECT_FALSE(base::DirectoryExists(unpack_path_));
+}
+
+void TestInstaller::OnUpdateError(int error) {
+ error_ = error;
+}
+
+void TestInstaller::Install(const base::FilePath& unpack_path,
+ const std::string& /*public_key*/,
+ Callback callback) {
+ ++install_count_;
+ unpack_path_ = unpack_path;
+
+ InstallComplete(std::move(callback), Result(InstallError::NONE));
+}
+
+void TestInstaller::InstallComplete(Callback callback,
+ const Result& result) const {
+ base::PostTaskWithTraits(FROM_HERE, {base::MayBlock()},
+ base::BindOnce(std::move(callback), result));
+}
+
+bool TestInstaller::GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) {
+ return false;
+}
+
+bool TestInstaller::Uninstall() {
+ return false;
+}
+
+ReadOnlyTestInstaller::ReadOnlyTestInstaller(const base::FilePath& install_dir)
+ : install_directory_(install_dir) {}
+
+ReadOnlyTestInstaller::~ReadOnlyTestInstaller() {}
+
+bool ReadOnlyTestInstaller::GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) {
+ *installed_file = install_directory_.AppendASCII(file);
+ return true;
+}
+
+VersionedTestInstaller::VersionedTestInstaller() {
+ base::CreateNewTempDirectory(FILE_PATH_LITERAL("TEST_"), &install_directory_);
+}
+
+VersionedTestInstaller::~VersionedTestInstaller() {
+ base::DeleteFile(install_directory_, true);
+}
+
+void VersionedTestInstaller::Install(const base::FilePath& unpack_path,
+ const std::string& public_key,
+ Callback callback) {
+ const auto manifest = update_client::ReadManifest(unpack_path);
+ std::string version_string;
+ manifest->GetStringASCII("version", &version_string);
+ const base::Version version(version_string);
+
+ const base::FilePath path =
+ install_directory_.AppendASCII(version.GetString());
+ base::CreateDirectory(path.DirName());
+ if (!base::Move(unpack_path, path)) {
+ InstallComplete(std::move(callback), Result(InstallError::GENERIC_ERROR));
+ return;
+ }
+ current_version_ = version;
+ ++install_count_;
+
+ InstallComplete(std::move(callback), Result(InstallError::NONE));
+}
+
+bool VersionedTestInstaller::GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) {
+ const base::FilePath path =
+ install_directory_.AppendASCII(current_version_.GetString());
+ *installed_file = path.Append(base::FilePath::FromUTF8Unsafe(file));
+ return true;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/test_installer.h b/src/components/update_client/test_installer.h
new file mode 100644
index 0000000..a12baf9
--- /dev/null
+++ b/src/components/update_client/test_installer.h
@@ -0,0 +1,89 @@
+// Copyright 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.
+
+#ifndef COMPONENTS_UPDATE_CLIENT_TEST_INSTALLER_H_
+#define COMPONENTS_UPDATE_CLIENT_TEST_INSTALLER_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+
+#include "base/files/file_path.h"
+#include "components/update_client/update_client.h"
+
+namespace update_client {
+
+// TODO(sorin): consider reducing the number of the installer mocks.
+// A TestInstaller is an installer that does nothing for installation except
+// increment a counter.
+class TestInstaller : public CrxInstaller {
+ public:
+ TestInstaller();
+
+ void OnUpdateError(int error) override;
+
+ void Install(const base::FilePath& unpack_path,
+ const std::string& public_key,
+ Callback callback) override;
+
+ bool GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) override;
+
+ bool Uninstall() override;
+
+ int error() const { return error_; }
+
+ int install_count() const { return install_count_; }
+
+ protected:
+ ~TestInstaller() override;
+
+ void InstallComplete(Callback callback, const Result& result) const;
+
+ int error_;
+ int install_count_;
+
+ private:
+ // Contains the |unpack_path| argument of the Install call.
+ base::FilePath unpack_path_;
+};
+
+// A ReadOnlyTestInstaller is an installer that knows about files in an existing
+// directory. It will not write to the directory.
+class ReadOnlyTestInstaller : public TestInstaller {
+ public:
+ explicit ReadOnlyTestInstaller(const base::FilePath& installed_path);
+
+ bool GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) override;
+
+ private:
+ ~ReadOnlyTestInstaller() override;
+
+ base::FilePath install_directory_;
+};
+
+// A VersionedTestInstaller is an installer that installs files into versioned
+// directories (e.g. somedir/25.23.89.141/<files>).
+class VersionedTestInstaller : public TestInstaller {
+ public:
+ VersionedTestInstaller();
+
+ void Install(const base::FilePath& unpack_path,
+ const std::string& public_key,
+ Callback callback) override;
+
+ bool GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) override;
+
+ private:
+ ~VersionedTestInstaller() override;
+
+ base::FilePath install_directory_;
+ base::Version current_version_;
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_TEST_INSTALLER_H_
diff --git a/src/components/update_client/unzip/unzip_impl.cc b/src/components/update_client/unzip/unzip_impl.cc
new file mode 100644
index 0000000..1fa9058
--- /dev/null
+++ b/src/components/update_client/unzip/unzip_impl.cc
@@ -0,0 +1,39 @@
+// Copyright 2019 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 "components/update_client/unzip/unzip_impl.h"
+
+#include "components/services/unzip/public/cpp/unzip.h"
+
+namespace update_client {
+
+namespace {
+
+class UnzipperImpl : public Unzipper {
+ public:
+ explicit UnzipperImpl(UnzipChromiumFactory::Callback callback)
+ : callback_(std::move(callback)) {}
+
+ void Unzip(const base::FilePath& zip_file,
+ const base::FilePath& destination,
+ UnzipCompleteCallback callback) override {
+ unzip::Unzip(callback_.Run(), zip_file, destination, std::move(callback));
+ }
+
+ private:
+ const UnzipChromiumFactory::Callback callback_;
+};
+
+} // namespace
+
+UnzipChromiumFactory::UnzipChromiumFactory(Callback callback)
+ : callback_(std::move(callback)) {}
+
+std::unique_ptr<Unzipper> UnzipChromiumFactory::Create() const {
+ return std::make_unique<UnzipperImpl>(callback_);
+}
+
+UnzipChromiumFactory::~UnzipChromiumFactory() = default;
+
+} // namespace update_client
diff --git a/src/components/update_client/unzip/unzip_impl.h b/src/components/update_client/unzip/unzip_impl.h
new file mode 100644
index 0000000..bbf9156
--- /dev/null
+++ b/src/components/update_client/unzip/unzip_impl.h
@@ -0,0 +1,38 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_UNZIP_UNZIP_IMPL_H_
+#define COMPONENTS_UPDATE_CLIENT_UNZIP_UNZIP_IMPL_H_
+
+#include <memory>
+
+#include "base/callback.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "components/services/unzip/public/mojom/unzipper.mojom.h"
+#include "components/update_client/unzipper.h"
+#include "mojo/public/cpp/bindings/pending_remote.h"
+
+namespace update_client {
+
+class UnzipChromiumFactory : public UnzipperFactory {
+ public:
+ using Callback =
+ base::RepeatingCallback<mojo::PendingRemote<unzip::mojom::Unzipper>()>;
+ explicit UnzipChromiumFactory(Callback callback);
+
+ std::unique_ptr<Unzipper> Create() const override;
+
+ protected:
+ ~UnzipChromiumFactory() override;
+
+ private:
+ const Callback callback_;
+
+ DISALLOW_COPY_AND_ASSIGN(UnzipChromiumFactory);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UNZIP_UNZIP_IMPL_H_
diff --git a/src/components/update_client/unzipper.h b/src/components/update_client/unzipper.h
new file mode 100644
index 0000000..6aed58c
--- /dev/null
+++ b/src/components/update_client/unzipper.h
@@ -0,0 +1,50 @@
+// Copyright 2019 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 COMPONENTS_UPDATE_CLIENT_UNZIPPER_H_
+#define COMPONENTS_UPDATE_CLIENT_UNZIPPER_H_
+
+#include "base/callback_forward.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+
+namespace base {
+class FilePath;
+} // namespace base
+
+namespace update_client {
+
+class Unzipper {
+ public:
+ using UnzipCompleteCallback = base::OnceCallback<void(bool success)>;
+
+ virtual ~Unzipper() = default;
+
+ virtual void Unzip(const base::FilePath& zip_file,
+ const base::FilePath& destination,
+ UnzipCompleteCallback callback) = 0;
+
+ protected:
+ Unzipper() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(Unzipper);
+};
+
+class UnzipperFactory : public base::RefCountedThreadSafe<UnzipperFactory> {
+ public:
+ virtual std::unique_ptr<Unzipper> Create() const = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<UnzipperFactory>;
+ UnzipperFactory() = default;
+ virtual ~UnzipperFactory() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UnzipperFactory);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UNZIPPER_H_
diff --git a/src/components/update_client/update_checker.cc b/src/components/update_client/update_checker.cc
new file mode 100644
index 0000000..da15867
--- /dev/null
+++ b/src/components/update_client/update_checker.cc
@@ -0,0 +1,340 @@
+// 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 "components/update_client/update_checker.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <functional>
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "build/build_config.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_definition.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/protocol_serializer.h"
+#include "components/update_client/request_sender.h"
+#include "components/update_client/task_traits.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/updater_state.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+#if defined(COBALT_BUILD_TYPE_DEBUG) || defined(COBALT_BUILD_TYPE_DEVEL)
+const std::string kDefaultUpdaterChannel = "dev";
+#elif defined(COBALT_BUILD_TYPE_QA)
+const std::string kDefaultUpdaterChannel = "qa";
+#elif defined(COBALT_BUILD_TYPE_GOLD)
+const std::string kDefaultUpdaterChannel = "prod";
+#endif
+
+// Returns a sanitized version of the brand or an empty string otherwise.
+std::string SanitizeBrand(const std::string& brand) {
+ return IsValidBrand(brand) ? brand : std::string("");
+}
+
+// Returns true if at least one item requires network encryption.
+bool IsEncryptionRequired(const IdToComponentPtrMap& components) {
+ for (const auto& item : components) {
+ const auto& component = item.second;
+ if (component->crx_component() &&
+ component->crx_component()->requires_network_encryption)
+ return true;
+ }
+ return false;
+}
+
+// Filters invalid attributes from |installer_attributes|.
+using InstallerAttributesFlatMap = base::flat_map<std::string, std::string>;
+InstallerAttributesFlatMap SanitizeInstallerAttributes(
+ const InstallerAttributes& installer_attributes) {
+ InstallerAttributesFlatMap sanitized_attrs;
+ for (const auto& attr : installer_attributes) {
+ if (IsValidInstallerAttribute(attr))
+ sanitized_attrs.insert(attr);
+ }
+ return sanitized_attrs;
+}
+
+class UpdateCheckerImpl : public UpdateChecker {
+ public:
+ UpdateCheckerImpl(scoped_refptr<Configurator> config,
+ PersistedData* metadata);
+ ~UpdateCheckerImpl() override;
+
+ // Overrides for UpdateChecker.
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_checked,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override;
+
+#if defined(OS_STARBOARD)
+ PersistedData* GetPersistedData() override { return metadata_; }
+#endif
+
+ private:
+ void ReadUpdaterStateAttributes();
+ void CheckForUpdatesHelper(
+ const std::string& session_id,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates);
+ void OnRequestSenderComplete(int error,
+ const std::string& response,
+ int retry_after_sec);
+ void UpdateCheckSucceeded(const ProtocolParser::Results& results,
+ int retry_after_sec);
+ void UpdateCheckFailed(ErrorCategory error_category,
+ int error,
+ int retry_after_sec);
+
+ base::ThreadChecker thread_checker_;
+
+ const scoped_refptr<Configurator> config_;
+ PersistedData* metadata_ = nullptr;
+ std::vector<std::string> ids_checked_;
+ UpdateCheckCallback update_check_callback_;
+ std::unique_ptr<UpdaterState::Attributes> updater_state_attributes_;
+ std::unique_ptr<RequestSender> request_sender_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateCheckerImpl);
+};
+
+UpdateCheckerImpl::UpdateCheckerImpl(scoped_refptr<Configurator> config,
+ PersistedData* metadata)
+ : config_(config), metadata_(metadata) {}
+
+UpdateCheckerImpl::~UpdateCheckerImpl() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void UpdateCheckerImpl::CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_checked,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ ids_checked_ = ids_checked;
+ update_check_callback_ = std::move(update_check_callback);
+
+ base::PostTaskWithTraitsAndReply(
+ FROM_HERE, kTaskTraits,
+ base::BindOnce(&UpdateCheckerImpl::ReadUpdaterStateAttributes,
+ base::Unretained(this)),
+ base::BindOnce(&UpdateCheckerImpl::CheckForUpdatesHelper,
+ base::Unretained(this), session_id, std::cref(components),
+ additional_attributes, enabled_component_updates));
+}
+
+// This function runs on the blocking pool task runner.
+void UpdateCheckerImpl::ReadUpdaterStateAttributes() {
+#if defined(OS_WIN)
+ // On Windows, the Chrome and the updater install modes are matched by design.
+ updater_state_attributes_ =
+ UpdaterState::GetState(!config_->IsPerUserInstall());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // MacOS ignores this value in the current implementation but this may change.
+ updater_state_attributes_ = UpdaterState::GetState(false);
+#else
+// Other platforms don't have updaters.
+#endif // OS_WIN
+}
+
+void UpdateCheckerImpl::CheckForUpdatesHelper(
+ const std::string& session_id,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto urls(config_->UpdateUrl());
+ if (IsEncryptionRequired(components))
+ RemoveUnsecureUrls(&urls);
+
+ // Components in this update check are either all foreground, or all
+ // background since this member is inherited from the component's update
+ // context. Pick the state of the first component to use in the update check.
+ DCHECK(!components.empty());
+ const bool is_foreground = components.at(ids_checked_[0])->is_foreground();
+ DCHECK(
+ std::all_of(components.cbegin(), components.cend(),
+ [is_foreground](IdToComponentPtrMap::const_reference& elem) {
+ return is_foreground == elem.second->is_foreground();
+ }));
+
+ std::vector<protocol_request::App> apps;
+ for (const auto& app_id : ids_checked_) {
+ DCHECK_EQ(1u, components.count(app_id));
+ const auto& component = components.at(app_id);
+ DCHECK_EQ(component->id(), app_id);
+ const auto& crx_component = component->crx_component();
+ DCHECK(crx_component);
+
+ std::string install_source;
+ if (!crx_component->install_source.empty())
+ install_source = crx_component->install_source;
+ else if (component->is_foreground())
+ install_source = "ondemand";
+
+ const bool is_update_disabled =
+ crx_component->supports_group_policy_enable_component_updates &&
+ !enabled_component_updates;
+
+ base::Version version = crx_component->version;
+#if defined(OS_STARBOARD)
+ std::string unpacked_version =
+ GetPersistedData()->GetLastUnpackedVersion(app_id);
+ // If the version of the last unpacked update package is higher than the
+ // version of the running binary, use the former to indicate the current
+ // update version in the update check request.
+ if (!unpacked_version.empty() &&
+ base::Version(unpacked_version).CompareTo(version) > 0) {
+ version = base::Version(unpacked_version);
+ }
+#endif
+ apps.push_back(MakeProtocolApp(
+ app_id, version, SanitizeBrand(config_->GetBrand()), install_source,
+ crx_component->install_location, crx_component->fingerprint,
+ SanitizeInstallerAttributes(crx_component->installer_attributes),
+ metadata_->GetCohort(app_id), metadata_->GetCohortName(app_id),
+ metadata_->GetCohortHint(app_id), crx_component->disabled_reasons,
+ MakeProtocolUpdateCheck(is_update_disabled),
+ MakeProtocolPing(app_id, metadata_)));
+ }
+ std::string updater_channel = config_->GetChannel();
+#if defined(OS_STARBOARD)
+ // If the updater channel is not set, read from pref store instead.
+ if (updater_channel.empty()) {
+ // All apps of the update use the same channel.
+ updater_channel = GetPersistedData()->GetUpdaterChannel(ids_checked_[0]);
+ if (updater_channel.empty()) {
+ updater_channel = kDefaultUpdaterChannel;
+ }
+ // Set the updater channel from the persistent store or to default channel,
+ // if it's not set already.
+ config_->SetChannel(updater_channel);
+ } else {
+ // Update the record of updater channel in pref store.
+ GetPersistedData()->SetUpdaterChannel(ids_checked_[0], updater_channel);
+ }
+#endif
+
+ const auto request = MakeProtocolRequest(
+ session_id, config_->GetProdId(),
+ config_->GetBrowserVersion().GetString(), config_->GetLang(),
+ updater_channel, config_->GetOSLongName(),
+ config_->GetDownloadPreference(), additional_attributes,
+ updater_state_attributes_.get(), std::move(apps));
+
+ request_sender_ = std::make_unique<RequestSender>(config_);
+ request_sender_->Send(
+ urls,
+ BuildUpdateCheckExtraRequestHeaders(config_->GetProdId(),
+ config_->GetBrowserVersion(),
+ ids_checked_, is_foreground),
+ config_->GetProtocolHandlerFactory()->CreateSerializer()->Serialize(
+ request),
+ config_->EnabledCupSigning(),
+ base::BindOnce(&UpdateCheckerImpl::OnRequestSenderComplete,
+ base::Unretained(this)));
+}
+
+void UpdateCheckerImpl::OnRequestSenderComplete(int error,
+ const std::string& response,
+ int retry_after_sec) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (error) {
+ VLOG(1) << "RequestSender failed " << error;
+ UpdateCheckFailed(ErrorCategory::kUpdateCheck, error, retry_after_sec);
+ return;
+ }
+
+ auto parser = config_->GetProtocolHandlerFactory()->CreateParser();
+ if (!parser->Parse(response)) {
+ VLOG(1) << "Parse failed " << parser->errors();
+ UpdateCheckFailed(ErrorCategory::kUpdateCheck,
+ static_cast<int>(ProtocolError::PARSE_FAILED),
+ retry_after_sec);
+ return;
+ }
+
+ DCHECK_EQ(0, error);
+ UpdateCheckSucceeded(parser->results(), retry_after_sec);
+}
+
+void UpdateCheckerImpl::UpdateCheckSucceeded(
+ const ProtocolParser::Results& results,
+ int retry_after_sec) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const int daynum = results.daystart_elapsed_days;
+ if (daynum != ProtocolParser::kNoDaystart) {
+ metadata_->SetDateLastActive(ids_checked_, daynum);
+ metadata_->SetDateLastRollCall(ids_checked_, daynum);
+ }
+ for (const auto& result : results.list) {
+ auto entry = result.cohort_attrs.find(ProtocolParser::Result::kCohort);
+ if (entry != result.cohort_attrs.end())
+ metadata_->SetCohort(result.extension_id, entry->second);
+ entry = result.cohort_attrs.find(ProtocolParser::Result::kCohortName);
+ if (entry != result.cohort_attrs.end())
+ metadata_->SetCohortName(result.extension_id, entry->second);
+ entry = result.cohort_attrs.find(ProtocolParser::Result::kCohortHint);
+ if (entry != result.cohort_attrs.end())
+ metadata_->SetCohortHint(result.extension_id, entry->second);
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(update_check_callback_),
+ base::make_optional<ProtocolParser::Results>(results),
+ ErrorCategory::kNone, 0, retry_after_sec));
+}
+
+void UpdateCheckerImpl::UpdateCheckFailed(ErrorCategory error_category,
+ int error,
+ int retry_after_sec) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK_NE(0, error);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(update_check_callback_), base::nullopt,
+ error_category, error, retry_after_sec));
+}
+
+} // namespace
+
+std::unique_ptr<UpdateChecker> UpdateChecker::Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* persistent) {
+ return std::make_unique<UpdateCheckerImpl>(config, persistent);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/update_checker.h b/src/components/update_client/update_checker.h
new file mode 100644
index 0000000..30e4fba
--- /dev/null
+++ b/src/components/update_client/update_checker.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 COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_
+
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/flat_map.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "components/update_client/component.h"
+#include "components/update_client/protocol_parser.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+class Configurator;
+class PersistedData;
+
+class UpdateChecker {
+ public:
+ using UpdateCheckCallback = base::OnceCallback<void(
+ const base::Optional<ProtocolParser::Results>& results,
+ ErrorCategory error_category,
+ int error,
+ int retry_after_sec)>;
+
+ using Factory =
+ std::unique_ptr<UpdateChecker> (*)(scoped_refptr<Configurator> config,
+ PersistedData* persistent);
+
+ virtual ~UpdateChecker() = default;
+
+ // Initiates an update check for the components specified by their ids.
+ // |additional_attributes| provides a way to customize the <request> element.
+ // |is_foreground| controls the value of "X-Goog-Update-Interactivity"
+ // header which is sent with the update check.
+ // On completion, the state of |components| is mutated as required by the
+ // server response received.
+ virtual void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) = 0;
+
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* persistent);
+
+#if defined(OS_STARBOARD)
+ virtual PersistedData* GetPersistedData() = 0;
+#endif
+
+ protected:
+ UpdateChecker() = default;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UpdateChecker);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CHECKER_H_
diff --git a/src/components/update_client/update_checker_unittest.cc b/src/components/update_client/update_checker_unittest.cc
new file mode 100644
index 0000000..1554743
--- /dev/null
+++ b/src/components/update_client/update_checker_unittest.cc
@@ -0,0 +1,1218 @@
+// 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 "components/update_client/update_checker.h"
+
+#include <map>
+#include <memory>
+#include <tuple>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_util.h"
+#include "base/json/json_reader.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/strings/stringprintf.h"
+#include "base/task/post_task.h"
+#include "base/test/bind_test_util.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/activity_data_service.h"
+#include "components/update_client/component.h"
+#include "components/update_client/net/url_loader_post_interceptor.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/update_engine.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using std::string;
+
+namespace update_client {
+
+namespace {
+
+base::FilePath test_file(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("update_client")
+ .AppendASCII(file);
+}
+
+const char kUpdateItemId[] = "jebgalgnebhfojomionfpkfelancnnkf";
+
+class ActivityDataServiceTest final : public ActivityDataService {
+ public:
+ bool GetActiveBit(const std::string& id) const override;
+ void ClearActiveBit(const std::string& id) override;
+ int GetDaysSinceLastActive(const std::string& id) const override;
+ int GetDaysSinceLastRollCall(const std::string& id) const override;
+
+ void SetActiveBit(const std::string& id, bool value);
+ void SetDaysSinceLastActive(const std::string& id, int daynum);
+ void SetDaysSinceLastRollCall(const std::string& id, int daynum);
+
+ private:
+ std::map<std::string, bool> actives_;
+ std::map<std::string, int> days_since_last_actives_;
+ std::map<std::string, int> days_since_last_rollcalls_;
+};
+
+bool ActivityDataServiceTest::GetActiveBit(const std::string& id) const {
+ const auto& it = actives_.find(id);
+ return it != actives_.end() ? it->second : false;
+}
+
+void ActivityDataServiceTest::ClearActiveBit(const std::string& id) {
+ SetActiveBit(id, false);
+}
+
+int ActivityDataServiceTest::GetDaysSinceLastActive(
+ const std::string& id) const {
+ const auto& it = days_since_last_actives_.find(id);
+ return it != days_since_last_actives_.end() ? it->second : -2;
+}
+
+int ActivityDataServiceTest::GetDaysSinceLastRollCall(
+ const std::string& id) const {
+ const auto& it = days_since_last_rollcalls_.find(id);
+ return it != days_since_last_rollcalls_.end() ? it->second : -2;
+}
+
+void ActivityDataServiceTest::SetActiveBit(const std::string& id, bool value) {
+ actives_[id] = value;
+}
+
+void ActivityDataServiceTest::SetDaysSinceLastActive(const std::string& id,
+ int daynum) {
+ days_since_last_actives_[id] = daynum;
+}
+
+void ActivityDataServiceTest::SetDaysSinceLastRollCall(const std::string& id,
+ int daynum) {
+ days_since_last_rollcalls_[id] = daynum;
+}
+
+} // namespace
+
+class UpdateCheckerTest : public testing::TestWithParam<bool> {
+ public:
+ UpdateCheckerTest();
+ ~UpdateCheckerTest() override;
+
+ // Overrides from testing::Test.
+ void SetUp() override;
+ void TearDown() override;
+
+ void UpdateCheckComplete(
+ const base::Optional<ProtocolParser::Results>& results,
+ ErrorCategory error_category,
+ int error,
+ int retry_after_sec);
+
+ protected:
+ void Quit();
+ void RunThreads();
+
+ std::unique_ptr<Component> MakeComponent() const;
+
+ scoped_refptr<TestConfigurator> config_;
+ std::unique_ptr<ActivityDataServiceTest> activity_data_service_;
+ std::unique_ptr<TestingPrefServiceSimple> pref_;
+ std::unique_ptr<PersistedData> metadata_;
+
+ std::unique_ptr<UpdateChecker> update_checker_;
+
+ std::unique_ptr<URLLoaderPostInterceptor> post_interceptor_;
+
+ base::Optional<ProtocolParser::Results> results_;
+ ErrorCategory error_category_ = ErrorCategory::kNone;
+ int error_ = 0;
+ int retry_after_sec_ = 0;
+
+ scoped_refptr<UpdateContext> update_context_;
+
+ bool is_foreground_ = false;
+
+ private:
+ scoped_refptr<UpdateContext> MakeMockUpdateContext() const;
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::OnceClosure quit_closure_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateCheckerTest);
+};
+
+// This test is parameterized for |is_foreground|.
+INSTANTIATE_TEST_SUITE_P(Parameterized, UpdateCheckerTest, testing::Bool());
+
+UpdateCheckerTest::UpdateCheckerTest()
+ : scoped_task_environment_(
+ base::test::ScopedTaskEnvironment::MainThreadType::IO) {}
+
+UpdateCheckerTest::~UpdateCheckerTest() {}
+
+void UpdateCheckerTest::SetUp() {
+ is_foreground_ = GetParam();
+
+ config_ = base::MakeRefCounted<TestConfigurator>();
+
+ pref_ = std::make_unique<TestingPrefServiceSimple>();
+ activity_data_service_ = std::make_unique<ActivityDataServiceTest>();
+ PersistedData::RegisterPrefs(pref_->registry());
+ metadata_ = std::make_unique<PersistedData>(pref_.get(),
+ activity_data_service_.get());
+
+ post_interceptor_ = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor_);
+
+ update_checker_ = nullptr;
+
+ error_ = 0;
+ retry_after_sec_ = 0;
+ update_context_ = MakeMockUpdateContext();
+ update_context_->is_foreground = is_foreground_;
+}
+
+void UpdateCheckerTest::TearDown() {
+ update_checker_ = nullptr;
+
+ post_interceptor_.reset();
+
+ config_ = nullptr;
+
+ // The PostInterceptor requires the message loop to run to destruct correctly.
+ // TODO(sorin): This is fragile and should be fixed.
+ scoped_task_environment_.RunUntilIdle();
+}
+
+void UpdateCheckerTest::RunThreads() {
+ base::RunLoop runloop;
+ quit_closure_ = runloop.QuitClosure();
+ runloop.Run();
+}
+
+void UpdateCheckerTest::Quit() {
+ if (!quit_closure_.is_null())
+ std::move(quit_closure_).Run();
+}
+
+void UpdateCheckerTest::UpdateCheckComplete(
+ const base::Optional<ProtocolParser::Results>& results,
+ ErrorCategory error_category,
+ int error,
+ int retry_after_sec) {
+ results_ = results;
+ error_category_ = error_category;
+ error_ = error;
+ retry_after_sec_ = retry_after_sec;
+ Quit();
+}
+
+scoped_refptr<UpdateContext> UpdateCheckerTest::MakeMockUpdateContext() const {
+ return base::MakeRefCounted<UpdateContext>(
+ config_, false, std::vector<std::string>(),
+ UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
+ UpdateEngine::Callback(), nullptr);
+}
+
+std::unique_ptr<Component> UpdateCheckerTest::MakeComponent() const {
+ CrxComponent crx_component;
+ crx_component.name = "test_jebg";
+ crx_component.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx_component.installer = nullptr;
+ crx_component.version = base::Version("0.9");
+ crx_component.fingerprint = "fp1";
+
+ auto component = std::make_unique<Component>(*update_context_, kUpdateItemId);
+ component->state_ = std::make_unique<Component::StateNew>(component.get());
+ component->crx_component_ = crx_component;
+
+ return component;
+}
+
+// This test is parameterized for |is_foreground|.
+TEST_P(UpdateCheckerTest, UpdateCheckSuccess) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ auto& component = components[kUpdateItemId];
+ component->crx_component_->installer_attributes["ap"] = "some_ap";
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}, {"testrequest", "1"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ // Sanity check the request.
+ const auto root =
+ base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+ ASSERT_TRUE(root);
+ const auto* request = root->FindKey("request");
+ ASSERT_TRUE(request);
+ EXPECT_TRUE(request->FindKey("@os"));
+ EXPECT_EQ("fake_prodid", request->FindKey("@updater")->GetString());
+ EXPECT_EQ("crx2,crx3", request->FindKey("acceptformat")->GetString());
+ EXPECT_TRUE(request->FindKey("arch"));
+ EXPECT_EQ("cr", request->FindKey("dedup")->GetString());
+ EXPECT_EQ("params", request->FindKey("extra")->GetString());
+ EXPECT_LT(0, request->FindPath({"hw", "physmemory"})->GetInt());
+ EXPECT_EQ("fake_lang", request->FindKey("lang")->GetString());
+ EXPECT_TRUE(request->FindKey("nacl_arch"));
+ EXPECT_EQ("fake_channel_string",
+ request->FindKey("prodchannel")->GetString());
+ EXPECT_EQ("30.0", request->FindKey("prodversion")->GetString());
+ EXPECT_EQ("3.1", request->FindKey("protocol")->GetString());
+ EXPECT_TRUE(request->FindKey("requestid"));
+ EXPECT_TRUE(request->FindKey("sessionid"));
+ EXPECT_EQ("1", request->FindKey("testrequest")->GetString());
+ EXPECT_EQ("fake_channel_string",
+ request->FindKey("updaterchannel")->GetString());
+ EXPECT_EQ("30.0", request->FindKey("updaterversion")->GetString());
+
+ // No "dlpref" is sent by default.
+ EXPECT_FALSE(request->FindKey("dlpref"));
+
+ EXPECT_TRUE(request->FindPath({"os", "arch"})->is_string());
+ EXPECT_EQ("Fake Operating System",
+ request->FindPath({"os", "platform"})->GetString());
+ EXPECT_TRUE(request->FindPath({"os", "version"})->is_string());
+
+ const auto& app = request->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+ if (is_foreground_)
+ EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+ EXPECT_EQ("some_ap", app.FindKey("ap")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck"));
+ EXPECT_TRUE(app.FindKey("ping"));
+ EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+ EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+ ->GetList()[0]
+ .FindKey("fp")
+ ->GetString());
+
+#if defined(OS_WIN)
+ EXPECT_TRUE(request->FindKey("domainjoined"));
+#if defined(GOOGLE_CHROME_BUILD)
+ const auto* updater = request->FindKey("updater");
+ EXPECT_TRUE(updater);
+ EXPECT_EQ("Omaha", updater->FindKey("name")->GetString());
+ EXPECT_TRUE(updater->FindKey("autoupdatecheckenabled")->is_bool());
+ EXPECT_TRUE(updater->FindKey("ismachine")->is_bool());
+ EXPECT_TRUE(updater->FindKey("updatepolicy")->is_int());
+#endif // GOOGLE_CHROME_BUILD
+#endif // OS_WIN
+
+ // Sanity check the arguments of the callback after parsing.
+ EXPECT_EQ(ErrorCategory::kNone, error_category_);
+ EXPECT_EQ(0, error_);
+ EXPECT_TRUE(results_);
+ EXPECT_EQ(1u, results_->list.size());
+ const auto& result = results_->list.front();
+ EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", result.extension_id.c_str());
+ EXPECT_EQ("1.0", result.manifest.version);
+ EXPECT_EQ("11.0.1.0", result.manifest.browser_min_version);
+ EXPECT_EQ(1u, result.manifest.packages.size());
+ EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf.crx",
+ result.manifest.packages.front().name.c_str());
+ EXPECT_EQ(1u, result.crx_urls.size());
+ EXPECT_EQ(GURL("http://localhost/download/"), result.crx_urls.front());
+ EXPECT_STREQ("this", result.action_run.c_str());
+
+ // Check the DDOS protection header values.
+ const auto extra_request_headers =
+ std::get<1>(post_interceptor_->GetRequests()[0]);
+ EXPECT_TRUE(extra_request_headers.HasHeader("X-Goog-Update-Interactivity"));
+ std::string header;
+ extra_request_headers.GetHeader("X-Goog-Update-Interactivity", &header);
+ EXPECT_STREQ(is_foreground_ ? "fg" : "bg", header.c_str());
+ extra_request_headers.GetHeader("X-Goog-Update-Updater", &header);
+ EXPECT_STREQ("fake_prodid-30.0", header.c_str());
+ extra_request_headers.GetHeader("X-Goog-Update-AppId", &header);
+ EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", header.c_str());
+}
+
+// Tests that an invalid "ap" is not serialized.
+TEST_P(UpdateCheckerTest, UpdateCheckInvalidAp) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ // Make "ap" too long.
+ auto& component = components[kUpdateItemId];
+ component->crx_component_->installer_attributes["ap"] = std::string(257, 'a');
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+
+ RunThreads();
+
+ const auto request = post_interceptor_->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+ if (is_foreground_)
+ EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+ EXPECT_FALSE(app.FindKey("ap"));
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck"));
+ EXPECT_TRUE(app.FindKey("ping"));
+ EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+ EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+ ->GetList()[0]
+ .FindKey("fp")
+ ->GetString());
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckSuccessNoBrand) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+
+ config_->SetBrand("TOOLONG"); // Sets an invalid brand code.
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+
+ RunThreads();
+
+ const auto request = post_interceptor_->GetRequestBody(0);
+
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_FALSE(app.FindKey("brand"));
+ if (is_foreground_)
+ EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck"));
+ EXPECT_TRUE(app.FindKey("ping"));
+ EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+ EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+ ->GetList()[0]
+ .FindKey("fp")
+ ->GetString());
+}
+
+// Simulates a 403 server response error.
+TEST_P(UpdateCheckerTest, UpdateCheckError) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"), net::HTTP_FORBIDDEN));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ EXPECT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+ EXPECT_EQ(403, error_);
+ EXPECT_FALSE(results_);
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckDownloadPreference) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+
+ config_->SetDownloadPreference(string("cacheable"));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ // The request must contain dlpref="cacheable".
+ const auto request = post_interceptor_->GetRequestBody(0);
+ const auto root = base::JSONReader().Read(request);
+ ASSERT_TRUE(root);
+ EXPECT_EQ("cacheable",
+ root->FindKey("request")->FindKey("dlpref")->GetString());
+}
+
+// This test is checking that an update check signed with CUP fails, since there
+// is currently no entity that can respond with a valid signed response.
+// A proper CUP test requires network mocks, which are not available now.
+TEST_P(UpdateCheckerTest, UpdateCheckCupError) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+
+ config_->SetEnabledCupSigning(true);
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ // Sanity check the request.
+ const auto& request = post_interceptor_->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+ if (is_foreground_)
+ EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck"));
+ EXPECT_TRUE(app.FindKey("ping"));
+ EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+ EXPECT_EQ("fp1", app.FindPath({"packages", "package"})
+ ->GetList()[0]
+ .FindKey("fp")
+ ->GetString());
+
+ // Expect an error since the response is not trusted.
+ EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+ EXPECT_EQ(-10000, error_);
+ EXPECT_FALSE(results_);
+}
+
+// Tests that the UpdateCheckers will not make an update check for a
+// component that requires encryption when the update check URL is unsecure.
+TEST_P(UpdateCheckerTest, UpdateCheckRequiresEncryptionError) {
+ config_->SetUpdateCheckUrl(GURL("http:\\foo\bar"));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ auto& component = components[kUpdateItemId];
+ component->crx_component_->requires_network_encryption = true;
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+ EXPECT_EQ(-10002, error_);
+ EXPECT_FALSE(component->next_version_.IsValid());
+}
+
+// Tests that the PersistedData will get correctly update and reserialize
+// the elapsed_days value.
+TEST_P(UpdateCheckerTest, UpdateCheckLastRollCall) {
+ const char* filename = "updatecheck_reply_4.json";
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ // Do two update-checks.
+ activity_data_service_->SetDaysSinceLastRollCall(kUpdateItemId, 5);
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(2, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(2, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ const auto root1 =
+ base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+ ASSERT_TRUE(root1);
+ const auto& app1 = root1->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(5, app1.FindPath({"ping", "r"})->GetInt());
+ const auto root2 =
+ base::JSONReader::Read(post_interceptor_->GetRequestBody(1));
+ ASSERT_TRUE(root2);
+ const auto& app2 = root2->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(3383, app2.FindPath({"ping", "rd"})->GetInt());
+ EXPECT_TRUE(app2.FindPath({"ping", "ping_freshness"})->is_string());
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckLastActive) {
+ const char* filename = "updatecheck_reply_4.json";
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"), test_file(filename)));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ activity_data_service_->SetActiveBit(kUpdateItemId, true);
+ activity_data_service_->SetDaysSinceLastActive(kUpdateItemId, 10);
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ // The active bit should be reset.
+ EXPECT_FALSE(metadata_->GetActiveBit(kUpdateItemId));
+
+ activity_data_service_->SetActiveBit(kUpdateItemId, true);
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ // The active bit should be reset.
+ EXPECT_FALSE(metadata_->GetActiveBit(kUpdateItemId));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components,
+ {{"extra", "params"}}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_FALSE(metadata_->GetActiveBit(kUpdateItemId));
+
+ EXPECT_EQ(3, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(3, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ {
+ const auto root =
+ base::JSONReader::Read(post_interceptor_->GetRequestBody(0));
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(10, app.FindPath({"ping", "a"})->GetInt());
+ EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+ }
+ {
+ const auto root =
+ base::JSONReader::Read(post_interceptor_->GetRequestBody(1));
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(3383, app.FindPath({"ping", "ad"})->GetInt());
+ EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
+ EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
+ }
+ {
+ const auto root =
+ base::JSONReader::Read(post_interceptor_->GetRequestBody(2));
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(3383, app.FindPath({"ping", "rd"})->GetInt());
+ EXPECT_TRUE(app.FindPath({"ping", "ping_freshness"})->is_string());
+ }
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckInstallSource) {
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ auto& component = components[kUpdateItemId];
+ auto crx_component = component->crx_component();
+
+ if (is_foreground_) {
+ {
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ("ondemand", app.FindKey("installsource")->GetString());
+ EXPECT_FALSE(app.FindKey("installedby"));
+ }
+ {
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ crx_component->install_source = "sideload";
+ crx_component->install_location = "policy";
+ component->set_crx_component(*crx_component);
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ("sideload", app.FindKey("installsource")->GetString());
+ EXPECT_EQ("policy", app.FindKey("installedby")->GetString());
+ }
+ return;
+ }
+
+ DCHECK(!is_foreground_);
+ {
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_FALSE(app.FindKey("installsource"));
+ }
+ {
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ crx_component->install_source = "webstore";
+ crx_component->install_location = "external";
+ component->set_crx_component(*crx_component);
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ("webstore", app.FindKey("installsource")->GetString());
+ EXPECT_EQ("external", app.FindKey("installedby")->GetString());
+ }
+}
+
+TEST_P(UpdateCheckerTest, ComponentDisabled) {
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ auto& component = components[kUpdateItemId];
+ auto crx_component = component->crx_component();
+
+ {
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_FALSE(app.FindKey("disabled"));
+ }
+
+ {
+ crx_component->disabled_reasons = {};
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_FALSE(app.FindKey("disabled"));
+ }
+
+ {
+ crx_component->disabled_reasons = {0};
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+ const auto& disabled = app.FindKey("disabled")->GetList();
+ EXPECT_EQ(1u, disabled.size());
+ EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
+ }
+ {
+ crx_component->disabled_reasons = {1};
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+ const auto& disabled = app.FindKey("disabled")->GetList();
+ EXPECT_EQ(1u, disabled.size());
+ EXPECT_EQ(1, disabled[0].FindKey("reason")->GetInt());
+ }
+
+ {
+ crx_component->disabled_reasons = {4, 8, 16};
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+ const auto& disabled = app.FindKey("disabled")->GetList();
+ EXPECT_EQ(3u, disabled.size());
+ EXPECT_EQ(4, disabled[0].FindKey("reason")->GetInt());
+ EXPECT_EQ(8, disabled[1].FindKey("reason")->GetInt());
+ EXPECT_EQ(16, disabled[2].FindKey("reason")->GetInt());
+ }
+
+ {
+ crx_component->disabled_reasons = {0, 4, 8, 16};
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(false, app.FindKey("enabled")->GetBool());
+ const auto& disabled = app.FindKey("disabled")->GetList();
+ EXPECT_EQ(4u, disabled.size());
+ EXPECT_EQ(0, disabled[0].FindKey("reason")->GetInt());
+ EXPECT_EQ(4, disabled[1].FindKey("reason")->GetInt());
+ EXPECT_EQ(8, disabled[2].FindKey("reason")->GetInt());
+ EXPECT_EQ(16, disabled[3].FindKey("reason")->GetInt());
+ }
+}
+
+TEST_P(UpdateCheckerTest, UpdateCheckUpdateDisabled) {
+ config_->SetBrand("");
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ auto& component = components[kUpdateItemId];
+ auto crx_component = component->crx_component();
+
+ // Ignore this test parameter to keep the test simple.
+ update_context_->is_foreground = false;
+ {
+ // Tests the scenario where:
+ // * the component does not support group policies.
+ // * the component updates are disabled.
+ // Expects the group policy to be ignored and the update check to not
+ // include the "updatedisabled" attribute.
+ EXPECT_FALSE(crx_component->supports_group_policy_enable_component_updates);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+ }
+ {
+ // Tests the scenario where:
+ // * the component supports group policies.
+ // * the component updates are disabled.
+ // Expects the update check to include the "updatedisabled" attribute.
+ crx_component->supports_group_policy_enable_component_updates = true;
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, false,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindPath({"updatecheck", "updatedisabled"})->GetBool());
+ }
+ {
+ // Tests the scenario where:
+ // * the component does not support group policies.
+ // * the component updates are enabled.
+ // Expects the update check to not include the "updatedisabled" attribute.
+ crx_component->supports_group_policy_enable_component_updates = false;
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+ }
+ {
+ // Tests the scenario where:
+ // * the component supports group policies.
+ // * the component updates are enabled.
+ // Expects the update check to not include the "updatedisabled" attribute.
+ crx_component->supports_group_policy_enable_component_updates = true;
+ component->set_crx_component(*crx_component);
+ auto post_interceptor = std::make_unique<URLLoaderPostInterceptor>(
+ config_->test_url_loader_factory());
+ EXPECT_TRUE(post_interceptor->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+ const auto& request = post_interceptor->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+ }
+}
+
+TEST_P(UpdateCheckerTest, NoUpdateActionRun) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_noupdate.json")));
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ // Sanity check the arguments of the callback after parsing.
+ EXPECT_EQ(ErrorCategory::kNone, error_category_);
+ EXPECT_EQ(0, error_);
+ EXPECT_TRUE(results_);
+ EXPECT_EQ(1u, results_->list.size());
+ const auto& result = results_->list.front();
+ EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", result.extension_id.c_str());
+ EXPECT_STREQ("noupdate", result.status.c_str());
+ EXPECT_STREQ("this", result.action_run.c_str());
+}
+
+TEST_P(UpdateCheckerTest, UpdatePauseResume) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_noupdate.json")));
+ post_interceptor_->url_job_request_ready_callback(base::BindOnce(
+ [](URLLoaderPostInterceptor* post_interceptor) {
+ post_interceptor->Resume();
+ },
+ post_interceptor_.get()));
+ post_interceptor_->Pause();
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ // Ignore this test parameter to keep the test simple.
+ update_context_->is_foreground = false;
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ const auto& request = post_interceptor_->GetRequestBody(0);
+ const auto root = base::JSONReader::Read(request);
+ ASSERT_TRUE(root);
+ const auto& app = root->FindKey("request")->FindKey("app")->GetList()[0];
+ EXPECT_EQ(kUpdateItemId, app.FindKey("appid")->GetString());
+ EXPECT_EQ("0.9", app.FindKey("version")->GetString());
+ EXPECT_EQ("TEST", app.FindKey("brand")->GetString());
+ EXPECT_EQ(true, app.FindKey("enabled")->GetBool());
+ EXPECT_TRUE(app.FindKey("updatecheck")->DictEmpty());
+ EXPECT_EQ(-2, app.FindPath({"ping", "r"})->GetInt());
+ EXPECT_EQ("fp1", app.FindKey("packages")
+ ->FindKey("package")
+ ->GetList()[0]
+ .FindKey("fp")
+ ->GetString());
+}
+
+// Tests that an update checker object and its underlying SimpleURLLoader can
+// be safely destroyed while it is paused.
+TEST_P(UpdateCheckerTest, UpdateResetUpdateChecker) {
+ base::RunLoop runloop;
+ auto quit_closure = runloop.QuitClosure();
+
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_1.json")));
+ post_interceptor_->url_job_request_ready_callback(base::BindOnce(
+ [](base::OnceClosure quit_closure) { std::move(quit_closure).Run(); },
+ std::move(quit_closure)));
+ post_interceptor_->Pause();
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ runloop.Run();
+}
+
+// The update response contains a protocol version which does not match the
+// expected protocol version.
+TEST_P(UpdateCheckerTest, ParseErrorProtocolVersionMismatch) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_parse_error.json")));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_EQ(ErrorCategory::kUpdateCheck, error_category_);
+ EXPECT_EQ(-10003, error_);
+ EXPECT_FALSE(results_);
+}
+
+// The update response contains a status |error-unknownApplication| for the
+// app. The response is succesfully parsed and a result is extracted to
+// indicate this status.
+TEST_P(UpdateCheckerTest, ParseErrorAppStatusErrorUnknownApplication) {
+ EXPECT_TRUE(post_interceptor_->ExpectRequest(
+ std::make_unique<PartialMatch>("updatecheck"),
+ test_file("updatecheck_reply_unknownapp.json")));
+
+ update_checker_ = UpdateChecker::Create(config_, metadata_.get());
+
+ IdToComponentPtrMap components;
+ components[kUpdateItemId] = MakeComponent();
+
+ update_checker_->CheckForUpdates(
+ update_context_->session_id, {kUpdateItemId}, components, {}, true,
+ base::BindOnce(&UpdateCheckerTest::UpdateCheckComplete,
+ base::Unretained(this)));
+ RunThreads();
+
+ EXPECT_EQ(1, post_interceptor_->GetHitCount())
+ << post_interceptor_->GetRequestsAsString();
+ ASSERT_EQ(1, post_interceptor_->GetCount())
+ << post_interceptor_->GetRequestsAsString();
+
+ EXPECT_EQ(ErrorCategory::kNone, error_category_);
+ EXPECT_EQ(0, error_);
+ EXPECT_TRUE(results_);
+ EXPECT_EQ(1u, results_->list.size());
+ const auto& result = results_->list.front();
+ EXPECT_STREQ("error-unknownApplication", result.status.c_str());
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/update_client.cc b/src/components/update_client/update_client.cc
new file mode 100644
index 0000000..369c6d3
--- /dev/null
+++ b/src/components/update_client/update_client.cc
@@ -0,0 +1,254 @@
+// 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 "components/update_client/update_client.h"
+
+#include <algorithm>
+#include <queue>
+#include <set>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/callback.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/observer_list.h"
+#include "base/stl_util.h"
+#include "base/threading/thread_checker.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/prefs/pref_registry_simple.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/ping_manager.h"
+#include "components/update_client/protocol_parser.h"
+#include "components/update_client/task_send_uninstall_ping.h"
+#include "components/update_client/task_update.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/update_client_internal.h"
+#include "components/update_client/update_engine.h"
+#include "components/update_client/utils.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+CrxUpdateItem::CrxUpdateItem() : state(ComponentState::kNew) {}
+CrxUpdateItem::~CrxUpdateItem() = default;
+CrxUpdateItem::CrxUpdateItem(const CrxUpdateItem& other) = default;
+
+CrxComponent::CrxComponent()
+ : allows_background_download(true),
+ requires_network_encryption(true),
+ crx_format_requirement(
+ crx_file::VerifierFormat::CRX3_WITH_PUBLISHER_PROOF),
+ supports_group_policy_enable_component_updates(false) {}
+CrxComponent::CrxComponent(const CrxComponent& other) = default;
+CrxComponent::~CrxComponent() = default;
+
+// It is important that an instance of the UpdateClient binds an unretained
+// pointer to itself. Otherwise, a life time circular dependency between this
+// instance and its inner members prevents the destruction of this instance.
+// Using unretained references is allowed in this case since the life time of
+// the UpdateClient instance exceeds the life time of its inner members,
+// including any thread objects that might execute callbacks bound to it.
+UpdateClientImpl::UpdateClientImpl(
+ scoped_refptr<Configurator> config,
+ scoped_refptr<PingManager> ping_manager,
+ UpdateChecker::Factory update_checker_factory,
+ CrxDownloader::Factory crx_downloader_factory)
+ : is_stopped_(false),
+ config_(config),
+ ping_manager_(ping_manager),
+ update_engine_(base::MakeRefCounted<UpdateEngine>(
+ config,
+ update_checker_factory,
+ crx_downloader_factory,
+ ping_manager_.get(),
+ base::Bind(&UpdateClientImpl::NotifyObservers,
+ base::Unretained(this)))) {}
+
+UpdateClientImpl::~UpdateClientImpl() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ DCHECK(task_queue_.empty());
+ DCHECK(tasks_.empty());
+
+ config_ = nullptr;
+}
+
+void UpdateClientImpl::Install(const std::string& id,
+ CrxDataCallback crx_data_callback,
+ Callback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (IsUpdating(id)) {
+ std::move(callback).Run(Error::UPDATE_IN_PROGRESS);
+ return;
+ }
+
+ std::vector<std::string> ids = {id};
+
+ // Install tasks are run concurrently and never queued up. They are always
+ // considered foreground tasks.
+ constexpr bool kIsForeground = true;
+ RunTask(base::MakeRefCounted<TaskUpdate>(
+ update_engine_.get(), kIsForeground, ids, std::move(crx_data_callback),
+ base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
+ std::move(callback))));
+}
+
+void UpdateClientImpl::Update(const std::vector<std::string>& ids,
+ CrxDataCallback crx_data_callback,
+ bool is_foreground,
+ Callback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ auto task = base::MakeRefCounted<TaskUpdate>(
+ update_engine_.get(), is_foreground, ids, std::move(crx_data_callback),
+ base::BindOnce(&UpdateClientImpl::OnTaskComplete, this,
+ std::move(callback)));
+
+ // If no other tasks are running at the moment, run this update task.
+ // Otherwise, queue the task up.
+ if (tasks_.empty()) {
+ RunTask(task);
+ } else {
+ task_queue_.push_back(task);
+ }
+}
+
+void UpdateClientImpl::RunTask(scoped_refptr<Task> task) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&Task::Run, base::Unretained(task.get())));
+ tasks_.insert(task);
+}
+
+void UpdateClientImpl::OnTaskComplete(Callback callback,
+ scoped_refptr<Task> task,
+ Error error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(task);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), error));
+
+ // Remove the task from the set of the running tasks. Only tasks handled by
+ // the update engine can be in this data structure.
+ tasks_.erase(task);
+
+ if (is_stopped_)
+ return;
+
+ // Pick up a task from the queue if the queue has pending tasks and no other
+ // task is running.
+ if (tasks_.empty() && !task_queue_.empty()) {
+ auto task = task_queue_.front();
+ task_queue_.pop_front();
+ RunTask(task);
+ }
+}
+
+void UpdateClientImpl::AddObserver(Observer* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observer_list_.AddObserver(observer);
+}
+
+void UpdateClientImpl::RemoveObserver(Observer* observer) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ observer_list_.RemoveObserver(observer);
+}
+
+void UpdateClientImpl::NotifyObservers(Observer::Events event,
+ const std::string& id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ for (auto& observer : observer_list_)
+ observer.OnEvent(event, id);
+}
+
+bool UpdateClientImpl::GetCrxUpdateState(const std::string& id,
+ CrxUpdateItem* update_item) const {
+ return update_engine_->GetUpdateState(id, update_item);
+}
+
+bool UpdateClientImpl::IsUpdating(const std::string& id) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ for (const auto task : tasks_) {
+ const auto ids = task->GetIds();
+ if (base::ContainsValue(ids, id)) {
+ return true;
+ }
+ }
+
+ for (const auto task : task_queue_) {
+ const auto ids = task->GetIds();
+ if (base::ContainsValue(ids, id)) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void UpdateClientImpl::Stop() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ is_stopped_ = true;
+
+ // In the current implementation it is sufficient to cancel the pending
+ // tasks only. The tasks that are run by the update engine will stop
+ // making progress naturally, as the main task runner stops running task
+ // actions. Upon the browser shutdown, the resources employed by the active
+ // tasks will leak, as the operating system kills the thread associated with
+ // the update engine task runner. Further refactoring may be needed in this
+ // area, to cancel the running tasks by canceling the current action update.
+ // This behavior would be expected, correct, and result in no resource leaks
+ // in all cases, in shutdown or not.
+ //
+ // Cancel the pending tasks. These tasks are safe to cancel and delete since
+ // they have not picked up by the update engine, and not shared with any
+ // task runner yet.
+ while (!task_queue_.empty()) {
+ auto task = task_queue_.front();
+ task_queue_.pop_front();
+ task->Cancel();
+ }
+}
+
+void UpdateClientImpl::SendUninstallPing(const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ RunTask(base::MakeRefCounted<TaskSendUninstallPing>(
+ update_engine_.get(), id, version, reason,
+ base::BindOnce(&UpdateClientImpl::OnTaskComplete, base::Unretained(this),
+ std::move(callback))));
+}
+
+scoped_refptr<UpdateClient> UpdateClientFactory(
+ scoped_refptr<Configurator> config) {
+ return base::MakeRefCounted<UpdateClientImpl>(
+ config, base::MakeRefCounted<PingManager>(config), &UpdateChecker::Create,
+ &CrxDownloader::Create);
+}
+
+void RegisterPrefs(PrefRegistrySimple* registry) {
+ PersistedData::RegisterPrefs(registry);
+}
+
+// This function has the exact same implementation as RegisterPrefs. We have
+// this implementation here to make the intention more clear that is local user
+// profile access is needed.
+void RegisterProfilePrefs(PrefRegistrySimple* registry) {
+ PersistedData::RegisterPrefs(registry);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/update_client.gyp b/src/components/update_client/update_client.gyp
new file mode 100644
index 0000000..45dc09d
--- /dev/null
+++ b/src/components/update_client/update_client.gyp
@@ -0,0 +1,113 @@
+# Copyright 2019 The Cobalt Authors. All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the 'License');
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an 'AS IS' BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+{
+ 'targets': [
+ {
+ 'target_name': 'update_client',
+ 'type': 'static_library',
+ 'sources': [
+ 'action_runner.cc',
+ 'action_runner.h',
+ 'activity_data_service.h',
+ 'command_line_config_policy.cc',
+ 'command_line_config_policy.h',
+ 'component.cc',
+ 'component.h',
+ 'component_patcher.cc',
+ 'component_patcher.h',
+ 'component_patcher_operation.cc',
+ 'component_patcher_operation.h',
+ 'component_unpacker.cc',
+ 'component_unpacker.h',
+ 'configurator.h',
+ 'crx_downloader.cc',
+ 'crx_downloader.h',
+ 'crx_update_item.h',
+ 'network.cc',
+ 'network.h',
+ 'patcher.h',
+ 'persisted_data.cc',
+ 'persisted_data.h',
+ 'ping_manager.cc',
+ 'ping_manager.h',
+ 'protocol_definition.cc',
+ 'protocol_definition.h',
+ 'protocol_handler.cc',
+ 'protocol_handler.h',
+ 'protocol_parser.cc',
+ 'protocol_parser.h',
+ 'protocol_parser_json.cc',
+ 'protocol_parser_json.h',
+ 'protocol_serializer.cc',
+ 'protocol_serializer.h',
+ 'protocol_serializer_json.cc',
+ 'protocol_serializer_json.h',
+ 'request_sender.cc',
+ 'request_sender.h',
+ 'task.h',
+ 'task_send_uninstall_ping.cc',
+ 'task_send_uninstall_ping.h',
+ 'task_traits.h',
+ 'task_update.cc',
+ 'task_update.h',
+ 'unzipper.h',
+ 'update_checker.cc',
+ 'update_checker.h',
+ 'update_client.cc',
+ 'update_client.h',
+ 'update_client_errors.h',
+ 'update_client_internal.h',
+ 'update_engine.cc',
+ 'update_engine.h',
+ 'update_query_params.cc',
+ 'update_query_params.h',
+ 'update_query_params_delegate.cc',
+ 'update_query_params_delegate.h',
+ 'updater_state.cc',
+ 'updater_state.h',
+ 'url_fetcher_downloader.cc',
+ 'url_fetcher_downloader.h',
+ 'utils.cc',
+ 'utils.h',
+ ],
+ 'dependencies': [
+ '<(DEPTH)/third_party/libxml/libxml.gyp:libxml',
+ '<(DEPTH)/components/crx_file/crx_file.gyp:crx_file',
+ '<(DEPTH)/components/client_update_protocol/client_update_protocol.gyp:client_update_protocol',
+ '<(DEPTH)/components/prefs/prefs.gyp:prefs',
+ '<(DEPTH)/crypto/crypto.gyp:crypto',
+ '<(DEPTH)/url/url.gyp:url',
+ ],
+ 'defines': [
+ 'LIBXML_READER_ENABLED',
+ 'LIBXML_WRITER_ENABLED',
+ ],
+ },
+ {
+ 'target_name': 'update_client_test',
+ 'type': '<(gtest_target_type)',
+ 'sources': [
+ 'utils_unittest.cc',
+ ],
+ 'dependencies': [
+ ':update_client',
+ '<(DEPTH)/cobalt/base/base.gyp:base',
+ '<(DEPTH)/testing/gmock.gyp:gmock',
+ '<(DEPTH)/testing/gtest.gyp:gtest',
+ ],
+ 'includes': [ '<(DEPTH)/cobalt/test/test.gypi' ],
+ },
+ ]
+}
diff --git a/src/components/update_client/update_client.h b/src/components/update_client/update_client.h
new file mode 100644
index 0000000..4b8061c
--- /dev/null
+++ b/src/components/update_client/update_client.h
@@ -0,0 +1,423 @@
+// 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 COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_
+
+#include <stdint.h>
+
+#include <map>
+#include <string>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/version.h"
+#include "components/update_client/update_client_errors.h"
+
+// The UpdateClient class is a facade with a simple interface. The interface
+// exposes a few APIs to install a CRX or update a group of CRXs.
+//
+// The difference between a CRX install and a CRX update is relatively minor.
+// The terminology going forward will use the word "update" to cover both
+// install and update scenarios, except where details regarding the install
+// case are relevant.
+//
+// Handling an update consists of a series of actions such as sending an update
+// check to the server, followed by parsing the server response, identifying
+// the CRXs that require an update, downloading the differential update if
+// it is available, unpacking and patching the differential update, then
+// falling back to trying a similar set of actions using the full update.
+// At the end of this process, completion pings are sent to the server,
+// as needed, for the CRXs which had updates.
+//
+// As a general idea, this code handles the action steps needed to update
+// a group of components serially, one step at a time. However, concurrent
+// execution of calls to UpdateClient::Update is possible, therefore,
+// queuing of updates could happen in some cases. More below.
+//
+// The UpdateClient class features a subject-observer interface to observe
+// the CRX state changes during an update.
+//
+// The threading model for this code assumes that most of the code in the
+// public interface runs on a SingleThreadTaskRunner.
+// This task runner corresponds to the browser UI thread in many cases. There
+// are parts of the installer interface that run on blocking task runners, which
+// are usually threads in a thread pool.
+//
+// Using the UpdateClient is relatively easy. This assumes that the client
+// of this code has already implemented the observer interface as needed, and
+// can provide an installer, as described below.
+//
+// std::unique_ptr<UpdateClient> update_client(UpdateClientFactory(...));
+// update_client->AddObserver(&observer);
+// std::vector<std::string> ids;
+// ids.push_back(...));
+// update_client->Update(ids, base::BindOnce(...), base::BindOnce(...));
+//
+// UpdateClient::Update takes two callbacks as parameters. First callback
+// allows the client of this code to provide an instance of CrxComponent
+// data structure that specifies additional parameters of the update.
+// CrxComponent has a CrxInstaller data member, which must be provided by the
+// callers of this class. The second callback indicates that this non-blocking
+// call has completed.
+//
+// There could be several ways of triggering updates for a CRX, user-initiated,
+// or timer-based. Since the execution of updates is concurrent, the parameters
+// for the update must be provided right before the update is handled.
+// Otherwise, the version of the CRX set in the CrxComponent may not be correct.
+//
+// The UpdateClient public interface includes two functions: Install and
+// Update. These functions correspond to installing one CRX immediately as a
+// foreground activity (Install), and updating a group of CRXs silently in the
+// background (Update). This distinction is important. Background updates are
+// queued up and their actions run serially, one at a time, for the purpose of
+// conserving local resources such as CPU, network, and I/O.
+// On the other hand, installs are never queued up but run concurrently, as
+// requested by the user.
+//
+// The update client introduces a runtime constraint regarding interleaving
+// updates and installs. If installs or updates for a given CRX are in progress,
+// then installs for the same CRX will fail with a specific error.
+//
+// Implementation details.
+//
+// The implementation details below are not relevant to callers of this
+// code. However, these design notes are relevant to the owners and maintainers
+// of this module.
+//
+// The design for the update client consists of a number of abstractions
+// such as: task, update engine, update context, and action.
+// The execution model for these abstractions is simple. They usually expose
+// a public, non-blocking Run function, and they invoke a callback when
+// the Run function has completed.
+//
+// A task is the unit of work for the UpdateClient. A task is associated
+// with a single call of the Update function. A task represents a group
+// of CRXs that are updated together.
+//
+// The UpdateClient is responsible for the queuing of tasks, if queuing is
+// needed.
+//
+// When the task runs, it calls the update engine to handle the updates for
+// the CRXs associated with the task. The UpdateEngine is the abstraction
+// responsible for breaking down the update in a set of discrete steps, which
+// are implemented as actions, and running the actions.
+//
+// The UpdateEngine maintains a set of UpdateContext instances. Each of
+// these instances maintains the update state for all the CRXs belonging to
+// a given task. The UpdateContext contains a queue of CRX ids.
+// The UpdateEngine will handle updates for the CRXs in the order they appear
+// in the queue, until the queue is empty.
+//
+// The update state for each CRX is maintained in a container of CrxUpdateItem*.
+// As actions run, each action updates the CRX state, represented by one of
+// these CrxUpdateItem* instances.
+//
+// Although the UpdateEngine can and will run update tasks concurrently, the
+// actions of a task are run sequentially.
+//
+// The Action is a polymorphic type. There is some code reuse for convenience,
+// implemented as a mixin. The polymorphic behavior of some of the actions
+// is achieved using a template method.
+//
+// State changes of a CRX could generate events, which are observed using a
+// subject-observer interface.
+//
+// The actions chain up. In some sense, the actions implement a state machine,
+// as the CRX undergoes a series of state transitions in the process of
+// being checked for updates and applying the update.
+
+class PrefRegistrySimple;
+
+namespace base {
+class FilePath;
+}
+
+namespace crx_file {
+enum class VerifierFormat;
+}
+
+namespace update_client {
+
+class Configurator;
+enum class Error;
+struct CrxUpdateItem;
+
+enum class ComponentState {
+ kNew,
+ kChecking,
+ kCanUpdate,
+ kDownloadingDiff,
+ kDownloading,
+ kDownloaded,
+ kUpdatingDiff,
+ kUpdating,
+ kUpdated,
+ kUpToDate,
+ kUpdateError,
+ kUninstalled,
+ kRun,
+ kLastStatus
+};
+
+// Defines an interface for a generic CRX installer.
+class CrxInstaller : public base::RefCountedThreadSafe<CrxInstaller> {
+ public:
+ // Contains the result of the Install operation.
+ struct Result {
+ explicit Result(int error, int extended_error = 0)
+ : error(error), extended_error(extended_error) {}
+ explicit Result(InstallError error, int extended_error = 0)
+ : error(static_cast<int>(error)), extended_error(extended_error) {}
+ int error = 0; // 0 indicates that install has been successful.
+ int extended_error = 0;
+ };
+
+ using Callback = base::OnceCallback<void(const Result& result)>;
+
+ // Called on the main thread when there was a problem unpacking or
+ // verifying the CRX. |error| is a non-zero value which is only meaningful
+ // to the caller.
+ virtual void OnUpdateError(int error) = 0;
+
+ // Called by the update service when a CRX has been unpacked
+ // and it is ready to be installed. |unpack_path| contains the
+ // temporary directory with all the unpacked CRX files. |pubkey| contains the
+ // public key of the CRX in the PEM format, without the header and the footer.
+ // The caller must invoke the |callback| when the install flow has completed.
+ // This method may be called from a thread other than the main thread.
+ virtual void Install(const base::FilePath& unpack_path,
+ const std::string& public_key,
+ Callback callback) = 0;
+
+ // Sets |installed_file| to the full path to the installed |file|. |file| is
+ // the filename of the file in this CRX. Returns false if this is
+ // not possible (the file has been removed or modified, or its current
+ // location is unknown). Otherwise, it returns true.
+ virtual bool GetInstalledFile(const std::string& file,
+ base::FilePath* installed_file) = 0;
+
+ // Called when a CRX has been unregistered and all versions should
+ // be uninstalled from disk. Returns true if uninstallation is supported,
+ // and false otherwise.
+ virtual bool Uninstall() = 0;
+
+ protected:
+ friend class base::RefCountedThreadSafe<CrxInstaller>;
+
+ virtual ~CrxInstaller() {}
+};
+
+// A dictionary of installer-specific, arbitrary name-value pairs, which
+// may be used in the update checks requests.
+using InstallerAttributes = std::map<std::string, std::string>;
+
+struct CrxComponent {
+ CrxComponent();
+ CrxComponent(const CrxComponent& other);
+ ~CrxComponent();
+
+ // Optional SHA256 hash of the CRX's public key. If not supplied, the
+ // unpacker can accept any CRX for this app, provided that the CRX meets the
+ // VerifierFormat requirements specified by the service's configurator.
+ // Callers that know or need a specific developer signature on acceptable CRX
+ // files must provide this.
+ std::vector<uint8_t> pk_hash;
+
+ scoped_refptr<CrxInstaller> installer;
+ std::string app_id;
+
+ // The current version if the CRX is updated. Otherwise, "0" or "0.0" if
+ // the CRX is installed.
+ base::Version version;
+
+ std::string fingerprint; // Optional.
+ std::string name; // Optional.
+ std::vector<std::string> handled_mime_types;
+
+ // Optional.
+ // Valid values for the name part of an attribute match
+ // ^[-_a-zA-Z0-9]{1,256}$ and valid values the value part of an attribute
+ // match ^[-.,;+_=a-zA-Z0-9]{0,256}$ .
+ InstallerAttributes installer_attributes;
+
+ // Specifies that the CRX can be background-downloaded in some cases.
+ // The default for this value is |true|.
+ bool allows_background_download;
+
+ // Specifies that the update checks and pings associated with this component
+ // require confidentiality. The default for this value is |true|. As a side
+ // note, the confidentiality of the downloads is enforced by the server,
+ // which only returns secure download URLs in this case.
+ bool requires_network_encryption;
+
+ // Specifies the strength of package validation required for the item.
+ crx_file::VerifierFormat crx_format_requirement;
+
+ // True if the component allows enabling or disabling updates by group policy.
+ // This member should be set to |false| for data, non-binary components, such
+ // as CRLSet, Supervised User Whitelists, STH Set, Origin Trials, and File
+ // Type Policies.
+ bool supports_group_policy_enable_component_updates;
+
+ // Reasons why this component/extension is disabled.
+ std::vector<int> disabled_reasons;
+
+ // Information about where the component/extension was installed from.
+ // For extension, this information is set from the update service, which
+ // gets the install source from the update URL.
+ std::string install_source;
+
+ // Information about where the component/extension was loaded from.
+ // For extensions, this information is inferred from the extension
+ // registry.
+ std::string install_location;
+};
+
+// Called when a non-blocking call of UpdateClient completes.
+using Callback = base::OnceCallback<void(Error error)>;
+
+// All methods are safe to call only from the browser's main thread. Once an
+// instance of this class is created, the reference to it must be released
+// only after the thread pools of the browser process have been destroyed and
+// the browser process has gone single-threaded.
+class UpdateClient : public base::RefCounted<UpdateClient> {
+ public:
+ using CrxDataCallback =
+ base::OnceCallback<std::vector<base::Optional<CrxComponent>>(
+ const std::vector<std::string>& ids)>;
+
+ // Defines an interface to observe the UpdateClient. It provides
+ // notifications when state changes occur for the service itself or for the
+ // registered CRXs.
+ class Observer {
+ public:
+ enum class Events {
+ // Sent before the update client does an update check.
+ COMPONENT_CHECKING_FOR_UPDATES = 1,
+
+ // Sent when there is a new version of a registered CRX. After
+ // the notification is sent the CRX will be downloaded unless the
+ // update client inserts a
+ COMPONENT_UPDATE_FOUND,
+
+ // Sent when a CRX is in the update queue but it can't be acted on
+ // right away, because the update client spaces out CRX updates due to a
+ // throttling policy.
+ COMPONENT_WAIT,
+
+ // Sent after the new CRX has been downloaded but before the install
+ // or the upgrade is attempted.
+ COMPONENT_UPDATE_READY,
+
+ // Sent when a CRX has been successfully updated.
+ COMPONENT_UPDATED,
+
+ // Sent when a CRX has not been updated because there was no update
+ // available for this component.
+ COMPONENT_NOT_UPDATED,
+
+ // Sent when an error ocurred during an update for any reason, including
+ // the update check itself failed, or the download of the update payload
+ // failed, or applying the update failed.
+ COMPONENT_UPDATE_ERROR,
+
+ // Sent when CRX bytes are being downloaded.
+ COMPONENT_UPDATE_DOWNLOADING,
+ };
+
+ virtual ~Observer() {}
+
+ // Called by the update client when a state change happens.
+ // If an |id| is specified, then the event is fired on behalf of the
+ // specific CRX. The implementors of this interface are
+ // expected to filter the relevant events based on the id of the CRX.
+ virtual void OnEvent(Events event, const std::string& id) = 0;
+ };
+
+ // Adds an observer for this class. An observer should not be added more
+ // than once. The caller retains the ownership of the observer object.
+ virtual void AddObserver(Observer* observer) = 0;
+
+ // Removes an observer. It is safe for an observer to be removed while
+ // the observers are being notified.
+ virtual void RemoveObserver(Observer* observer) = 0;
+
+ // Installs the specified CRX. Calls back on |callback| after the
+ // update has been handled. The |error| parameter of the |callback|
+ // contains an error code in the case of a run-time error, or 0 if the
+ // install has been handled successfully. Overlapping calls of this function
+ // are executed concurrently, as long as the id parameter is different,
+ // meaning that installs of different components are parallelized.
+ // The |Install| function is intended to be used for foreground installs of
+ // one CRX. These cases are usually associated with on-demand install
+ // scenarios, which are triggered by user actions. Installs are never
+ // queued up.
+ virtual void Install(const std::string& id,
+ CrxDataCallback crx_data_callback,
+ Callback callback) = 0;
+
+ // Updates the specified CRXs. Calls back on |crx_data_callback| before the
+ // update is attempted to give the caller the opportunity to provide the
+ // instances of CrxComponent to be used for this update. The |Update| function
+ // is intended to be used for background updates of several CRXs. Overlapping
+ // calls to this function result in a queuing behavior, and the execution
+ // of each call is serialized. In addition, updates are always queued up when
+ // installs are running. The |is_foreground| parameter must be set to true if
+ // the invocation of this function is a result of a user initiated update.
+ virtual void Update(const std::vector<std::string>& ids,
+ CrxDataCallback crx_data_callback,
+ bool is_foreground,
+ Callback callback) = 0;
+
+ // Sends an uninstall ping for the CRX identified by |id| and |version|. The
+ // |reason| parameter is defined by the caller. The current implementation of
+ // this function only sends a best-effort, fire-and-forget ping. It has no
+ // other side effects regarding installs or updates done through an instance
+ // of this class.
+ virtual void SendUninstallPing(const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback callback) = 0;
+
+ // Returns status details about a CRX update. The function returns true in
+ // case of success and false in case of errors, such as |id| was
+ // invalid or not known.
+ virtual bool GetCrxUpdateState(const std::string& id,
+ CrxUpdateItem* update_item) const = 0;
+
+ // Returns true if the |id| is found in any running task.
+ virtual bool IsUpdating(const std::string& id) const = 0;
+
+ // Cancels the queued updates and makes a best effort to stop updates in
+ // progress as soon as possible. Some updates may not be stopped, in which
+ // case, the updates will run to completion. Calling this function has no
+ // effect if updates are not currently executed or queued up.
+ virtual void Stop() = 0;
+
+ protected:
+ friend class base::RefCounted<UpdateClient>;
+
+ virtual ~UpdateClient() {}
+};
+
+// Creates an instance of the update client.
+scoped_refptr<UpdateClient> UpdateClientFactory(
+ scoped_refptr<Configurator> config);
+
+// This must be called prior to the construction of any Configurator that
+// contains a PrefService.
+void RegisterPrefs(PrefRegistrySimple* registry);
+
+// This must be called prior to the construction of any Configurator that
+// needs access to local user profiles.
+// This function is mostly used for ExtensionUpdater, which requires update
+// info from user profiles.
+void RegisterProfilePrefs(PrefRegistrySimple* registry);
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_H_
diff --git a/src/components/update_client/update_client_errors.h b/src/components/update_client/update_client_errors.h
new file mode 100644
index 0000000..3ee0509
--- /dev/null
+++ b/src/components/update_client/update_client_errors.h
@@ -0,0 +1,118 @@
+// Copyright 2016 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 COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_ERRORS_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_ERRORS_H_
+
+namespace update_client {
+
+// Errors generated as a result of calling UpdateClient member functions.
+// These errors are not sent in pings.
+enum class Error {
+ NONE = 0,
+ UPDATE_IN_PROGRESS = 1,
+ UPDATE_CANCELED = 2,
+ RETRY_LATER = 3,
+ SERVICE_ERROR = 4,
+ UPDATE_CHECK_ERROR = 5,
+ CRX_NOT_FOUND = 6,
+ INVALID_ARGUMENT = 7,
+ MAX_VALUE,
+};
+
+// These errors are sent in pings. Add new values only to the bottom of
+// the enums below; the order must be kept stable.
+enum class ErrorCategory {
+ kNone = 0,
+ kDownload,
+ kUnpack,
+ kInstall,
+ kService, // Runtime errors which occur in the service itself.
+ kUpdateCheck,
+};
+
+// These errors are returned with the |kNetworkError| error category. This
+// category could include other errors such as the errors defined by
+// the Chrome net stack.
+enum class CrxDownloaderError {
+ NONE = 0,
+ NO_URL = 10,
+ NO_HASH = 11,
+ BAD_HASH = 12, // The downloaded file fails the hash verification.
+ // The Windows BITS queue contains to many update client jobs. The value is
+ // chosen so that it can be reported as a custom COM error on this platform.
+ BITS_TOO_MANY_JOBS = 0x0200,
+ GENERIC_ERROR = -1
+};
+
+// These errors are returned with the |kUnpack| error category and
+// indicate unpacker or patcher error.
+enum class UnpackerError {
+ kNone = 0,
+ kInvalidParams = 1,
+ kInvalidFile = 2,
+ kUnzipPathError = 3,
+ kUnzipFailed = 4,
+ // kNoManifest = 5, // Deprecated. Never used.
+ kBadManifest = 6,
+ kBadExtension = 7,
+ // kInvalidId = 8, // Deprecated. Combined with kInvalidFile.
+ // kInstallerError = 9, // Deprecated. Don't use.
+ kIoError = 10,
+ kDeltaVerificationFailure = 11,
+ kDeltaBadCommands = 12,
+ kDeltaUnsupportedCommand = 13,
+ kDeltaOperationFailure = 14,
+ kDeltaPatchProcessFailure = 15,
+ kDeltaMissingExistingFile = 16,
+ // kFingerprintWriteFailed = 17, // Deprecated. Don't use.
+};
+
+// These errors are returned with the |kService| error category and
+// are returned by the component installers.
+enum class InstallError {
+ NONE = 0,
+ FINGERPRINT_WRITE_FAILED = 2,
+ BAD_MANIFEST = 3,
+ GENERIC_ERROR = 9, // Matches kInstallerError for compatibility.
+ MOVE_FILES_ERROR = 10,
+ SET_PERMISSIONS_FAILED = 11,
+ INVALID_VERSION = 12,
+ VERSION_NOT_UPGRADED = 13,
+ NO_DIR_COMPONENT_USER = 14,
+ CLEAN_INSTALL_DIR_FAILED = 15,
+ INSTALL_VERIFICATION_FAILED = 16,
+ CUSTOM_ERROR_BASE = 100, // Specific installer errors go above this value.
+};
+
+// These errors are returned with the |kService| error category and
+// indicate critical or configuration errors in the update service.
+enum class ServiceError {
+ NONE = 0,
+ SERVICE_WAIT_FAILED = 1,
+ UPDATE_DISABLED = 2,
+};
+
+// These errors are related to serialization, deserialization, and parsing of
+// protocol requests.
+// The begin value for this enum is chosen not to conflict with network errors
+// defined by net/base/net_error_list.h. The callers don't have to handle this
+// error in any meaningful way, but this value may be reported in UMA stats,
+// therefore avoiding collisions with known network errors is desirable.
+enum class ProtocolError : int {
+ NONE = 0,
+ RESPONSE_NOT_TRUSTED = -10000,
+ MISSING_PUBLIC_KEY = -10001,
+ MISSING_URLS = -10002,
+ PARSE_FAILED = -10003,
+ UPDATE_RESPONSE_NOT_FOUND = -10004,
+ URL_FETCHER_FAILED = -10005,
+ UNKNOWN_APPLICATION = -10006,
+ RESTRICTED_APPLICATION = -10007,
+ INVALID_APPID = -10008,
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_ERRORS_H_
diff --git a/src/components/update_client/update_client_internal.h b/src/components/update_client/update_client_internal.h
new file mode 100644
index 0000000..2b6158c
--- /dev/null
+++ b/src/components/update_client/update_client_internal.h
@@ -0,0 +1,92 @@
+// 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 COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_
+
+#include <memory>
+#include <set>
+#include <string>
+#include <vector>
+
+#include "base/containers/circular_deque.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/observer_list.h"
+#include "base/threading/thread_checker.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client.h"
+
+namespace update_client {
+
+class Configurator;
+class PingManager;
+class Task;
+class UpdateEngine;
+enum class Error;
+
+class UpdateClientImpl : public UpdateClient {
+ public:
+ UpdateClientImpl(scoped_refptr<Configurator> config,
+ scoped_refptr<PingManager> ping_manager,
+ UpdateChecker::Factory update_checker_factory,
+ CrxDownloader::Factory crx_downloader_factory);
+
+ // Overrides for UpdateClient.
+ void AddObserver(Observer* observer) override;
+ void RemoveObserver(Observer* observer) override;
+ void Install(const std::string& id,
+ CrxDataCallback crx_data_callback,
+ Callback callback) override;
+ void Update(const std::vector<std::string>& ids,
+ CrxDataCallback crx_data_callback,
+ bool is_foreground,
+ Callback callback) override;
+ bool GetCrxUpdateState(const std::string& id,
+ CrxUpdateItem* update_item) const override;
+ bool IsUpdating(const std::string& id) const override;
+ void Stop() override;
+ void SendUninstallPing(const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback callback) override;
+
+ private:
+ ~UpdateClientImpl() override;
+
+ void RunTask(scoped_refptr<Task> task);
+ void OnTaskComplete(Callback callback, scoped_refptr<Task> task, Error error);
+
+ void NotifyObservers(Observer::Events event, const std::string& id);
+
+ base::ThreadChecker thread_checker_;
+
+ // True if Stop method has been called.
+ bool is_stopped_ = false;
+
+ scoped_refptr<Configurator> config_;
+
+ // Contains the tasks that are pending. In the current implementation,
+ // only update tasks (background tasks) are queued up. These tasks are
+ // pending while they are in this queue. They have not been picked up yet
+ // by the update engine.
+ base::circular_deque<scoped_refptr<Task>> task_queue_;
+
+ // Contains all tasks in progress. These are the tasks that the update engine
+ // is executing at one moment. Install tasks are run concurrently, update
+ // tasks are always serialized, and update tasks are queued up if install
+ // tasks are running. In addition, concurrent install tasks for the same id
+ // are not allowed.
+ std::set<scoped_refptr<Task>> tasks_;
+ scoped_refptr<PingManager> ping_manager_;
+ scoped_refptr<UpdateEngine> update_engine_;
+ base::ObserverList<Observer>::Unchecked observer_list_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateClientImpl);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_CLIENT_INTERNAL_H_
diff --git a/src/components/update_client/update_client_unittest.cc b/src/components/update_client/update_client_unittest.cc
new file mode 100644
index 0000000..ddee228
--- /dev/null
+++ b/src/components/update_client/update_client_unittest.cc
@@ -0,0 +1,3978 @@
+// 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 <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/containers/flat_map.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/stl_util.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "base/test/scoped_path_override.h"
+#include "base/test/scoped_task_environment.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "base/values.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "components/crx_file/crx_verifier.h"
+#include "components/prefs/testing_pref_service.h"
+#include "components/update_client/component_unpacker.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/network.h"
+#include "components/update_client/patcher.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/ping_manager.h"
+#include "components/update_client/protocol_handler.h"
+#include "components/update_client/test_configurator.h"
+#include "components/update_client/test_installer.h"
+#include "components/update_client/unzipper.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/update_client_internal.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+namespace {
+
+using base::FilePath;
+
+// Makes a copy of the file specified by |from_path| in a temporary directory
+// and returns the path of the copy. Returns true if successful. Cleans up if
+// there was an error creating the copy.
+bool MakeTestFile(const FilePath& from_path, FilePath* to_path) {
+ FilePath temp_dir;
+ bool result =
+ CreateNewTempDirectory(FILE_PATH_LITERAL("update_client"), &temp_dir);
+ if (!result)
+ return false;
+
+ FilePath temp_file;
+ result = CreateTemporaryFileInDir(temp_dir, &temp_file);
+ if (!result)
+ return false;
+
+ result = CopyFile(from_path, temp_file);
+ if (!result) {
+ DeleteFile(temp_file, false);
+ return false;
+ }
+
+ *to_path = temp_file;
+ return true;
+}
+
+using Events = UpdateClient::Observer::Events;
+
+class MockObserver : public UpdateClient::Observer {
+ public:
+ MOCK_METHOD2(OnEvent, void(Events event, const std::string&));
+};
+
+} // namespace
+
+using ::testing::_;
+using ::testing::AtLeast;
+using ::testing::AnyNumber;
+using ::testing::DoAll;
+using ::testing::InSequence;
+using ::testing::Invoke;
+using ::testing::Mock;
+using ::testing::Return;
+
+using std::string;
+
+class MockPingManagerImpl : public PingManager {
+ public:
+ struct PingData {
+ std::string id;
+ base::Version previous_version;
+ base::Version next_version;
+ ErrorCategory error_category = ErrorCategory::kNone;
+ int error_code = 0;
+ int extra_code1 = 0;
+ ErrorCategory diff_error_category = ErrorCategory::kNone;
+ int diff_error_code = 0;
+ bool diff_update_failed = false;
+ };
+
+ explicit MockPingManagerImpl(scoped_refptr<Configurator> config);
+
+ void SendPing(const Component& component, Callback callback) override;
+
+ const std::vector<PingData>& ping_data() const;
+
+ const std::vector<base::Value>& events() const;
+
+ protected:
+ ~MockPingManagerImpl() override;
+
+ private:
+ std::vector<PingData> ping_data_;
+ std::vector<base::Value> events_;
+ DISALLOW_COPY_AND_ASSIGN(MockPingManagerImpl);
+};
+
+MockPingManagerImpl::MockPingManagerImpl(scoped_refptr<Configurator> config)
+ : PingManager(config) {}
+
+MockPingManagerImpl::~MockPingManagerImpl() {}
+
+void MockPingManagerImpl::SendPing(const Component& component,
+ Callback callback) {
+ PingData ping_data;
+ ping_data.id = component.id_;
+ ping_data.previous_version = component.previous_version_;
+ ping_data.next_version = component.next_version_;
+ ping_data.error_category = component.error_category_;
+ ping_data.error_code = component.error_code_;
+ ping_data.extra_code1 = component.extra_code1_;
+ ping_data.diff_error_category = component.diff_error_category_;
+ ping_data.diff_error_code = component.diff_error_code_;
+ ping_data.diff_update_failed = component.diff_update_failed();
+ ping_data_.push_back(ping_data);
+
+ events_ = component.GetEvents();
+
+ std::move(callback).Run(0, "");
+}
+
+const std::vector<MockPingManagerImpl::PingData>&
+MockPingManagerImpl::ping_data() const {
+ return ping_data_;
+}
+
+const std::vector<base::Value>& MockPingManagerImpl::events() const {
+ return events_;
+}
+
+class UpdateClientTest : public testing::Test {
+ public:
+ UpdateClientTest();
+ ~UpdateClientTest() override;
+
+ protected:
+ void RunThreads();
+
+ // Returns the full path to a test file.
+ static base::FilePath TestFilePath(const char* file);
+
+ scoped_refptr<update_client::TestConfigurator> config() { return config_; }
+ update_client::PersistedData* metadata() { return metadata_.get(); }
+
+ base::OnceClosure quit_closure() { return runloop_.QuitClosure(); }
+
+ private:
+ static constexpr int kNumWorkerThreads_ = 2;
+
+ base::test::ScopedTaskEnvironment scoped_task_environment_;
+ base::RunLoop runloop_;
+
+ scoped_refptr<update_client::TestConfigurator> config_ =
+ base::MakeRefCounted<TestConfigurator>();
+ std::unique_ptr<TestingPrefServiceSimple> pref_ =
+ std::make_unique<TestingPrefServiceSimple>();
+ std::unique_ptr<update_client::PersistedData> metadata_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateClientTest);
+};
+
+constexpr int UpdateClientTest::kNumWorkerThreads_;
+
+UpdateClientTest::UpdateClientTest() {
+ PersistedData::RegisterPrefs(pref_->registry());
+ metadata_ = std::make_unique<PersistedData>(pref_.get(), nullptr);
+}
+
+UpdateClientTest::~UpdateClientTest() {}
+
+void UpdateClientTest::RunThreads() {
+ runloop_.Run();
+ scoped_task_environment_.RunUntilIdle();
+}
+
+base::FilePath UpdateClientTest::TestFilePath(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components")
+ .AppendASCII("test")
+ .AppendASCII("data")
+ .AppendASCII("update_client")
+ .AppendASCII(file);
+}
+
+// Tests the scenario where one update check is done for one CRX. The CRX
+// has no update.
+TEST_F(UpdateClientTest, OneCrxNoUpdate) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ std::vector<base::Optional<CrxComponent>> component = {crx};
+ return component;
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check.front());
+ EXPECT_EQ(1u, components.count(id));
+
+ auto& component = components.at(id);
+
+ EXPECT_TRUE(component->is_foreground());
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "noupdate";
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), true,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where two CRXs are checked for updates. On CRX has
+// an update, the other CRX does not.
+TEST_F(UpdateClientTest, TwoCrxUpdateNoUpdate) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx1;
+ crx1.name = "test_jebg";
+ crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx1.version = base::Version("0.9");
+ crx1.installer = base::MakeRefCounted<TestInstaller>();
+ crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ CrxComponent crx2;
+ crx2.name = "test_abag";
+ crx2.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
+ crx2.version = base::Version("2.2");
+ crx2.installer = base::MakeRefCounted<TestInstaller>();
+ crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ return {crx1, crx2};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ <app appid='abagagagagagagagagagagagagagagag'>
+ <updatecheck status='noupdate'/>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(2u, ids_to_check.size());
+
+ ProtocolParser::Results results;
+ {
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf";
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+
+ EXPECT_FALSE(components.at(id)->is_foreground());
+ }
+
+ {
+ const std::string id = "abagagagagagagagagagagagagagagag";
+ EXPECT_EQ(id, ids_to_check[1]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "noupdate";
+ results.list.push_back(result);
+
+ EXPECT_FALSE(components.at(id)->is_foreground());
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1843;
+ download_metrics.total_bytes = 1843;
+ download_metrics.download_time_ms = 1000;
+
+ FilePath path;
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+ Result result;
+ result.error = 0;
+ result.response = path;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(1u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(0, ping_data[0].error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "abagagagagagagagagagagagagagagag"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "abagagagagagagagagagagagagagagag"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+ "abagagagagagagagagagagagagagagag"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where two CRXs are checked for updates. One CRX has
+// an update but the server ignores the second CRX and returns no response for
+// it. The second component gets an |UPDATE_RESPONSE_NOT_FOUND| error and
+// transitions to the error state.
+TEST_F(UpdateClientTest, TwoCrxUpdateFirstServerIgnoresSecond) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx1;
+ crx1.name = "test_jebg";
+ crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx1.version = base::Version("0.9");
+ crx1.installer = base::MakeRefCounted<TestInstaller>();
+ crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ CrxComponent crx2;
+ crx2.name = "test_abag";
+ crx2.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
+ crx2.version = base::Version("2.2");
+ crx2.installer = base::MakeRefCounted<TestInstaller>();
+ crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ return {crx1, crx2};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(2u, ids_to_check.size());
+
+ ProtocolParser::Results results;
+ {
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = "jebgalgnebhfojomionfpkfelancnnkf";
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+
+ EXPECT_FALSE(components.at(id)->is_foreground());
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1843;
+ download_metrics.total_bytes = 1843;
+ download_metrics.download_time_ms = 1000;
+
+ FilePath path;
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+ Result result;
+ result.error = 0;
+ result.response = path;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(1u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(0, ping_data[0].error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "abagagagagagagagagagagagagagagag"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "abagagagagagagagagagagagagagagag"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(5, static_cast<int>(item.error_category));
+ EXPECT_EQ(-10004, item.error_code);
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+ "abagagagagagagagagagagagagagagag"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the update check for two CRXs scenario when the second CRX does not
+// provide a CrxComponent instance. In this case, the update is handled as
+// if only one component were provided as an argument to the |Update| call
+// with the exception that the second component still fires an event such as
+// |COMPONENT_UPDATE_ERROR|.
+TEST_F(UpdateClientTest, TwoCrxUpdateNoCrxComponentData) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return {crx, base::nullopt};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+
+ ProtocolParser::Results results;
+ {
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+
+ EXPECT_FALSE(components.at(id)->is_foreground());
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1843;
+ download_metrics.total_bytes = 1843;
+ download_metrics.download_time_ms = 1000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(1u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(0, ping_data[0].error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+ "ihfokbkgjpifnbbojhneepfflplebdkc"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the update check for two CRXs scenario when no CrxComponent data is
+// provided for either component. In this case, no update check occurs, and
+// |COMPONENT_UPDATE_ERROR| event fires for both components.
+TEST_F(UpdateClientTest, TwoCrxUpdateNoCrxComponentDataAtAll) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ return {base::nullopt, base::nullopt};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ NOTREACHED();
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { NOTREACHED(); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size());
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+ "ihfokbkgjpifnbbojhneepfflplebdkc"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where there is a download timeout for the first
+// CRX. The update for the first CRX fails. The update client waits before
+// attempting the update for the second CRX. This update succeeds.
+TEST_F(UpdateClientTest, TwoCrxUpdateDownloadTimeout) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx1;
+ crx1.name = "test_jebg";
+ crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx1.version = base::Version("0.9");
+ crx1.installer = base::MakeRefCounted<TestInstaller>();
+ crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ CrxComponent crx2;
+ crx2.name = "test_ihfo";
+ crx2.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+ crx2.version = base::Version("0.8");
+ crx2.installer = base::MakeRefCounted<TestInstaller>();
+ crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ return {crx1, crx2};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+ hash_sha256='813c59747e139a608b3b5fc49633affc6db574373f
+ 309f156ea6d27229c0b3f9'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(2u, ids_to_check.size());
+
+ ProtocolParser::Results results;
+ {
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ }
+
+ {
+ const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+ EXPECT_EQ(id, ids_to_check[1]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+ package.hash_sha256 =
+ "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = -118;
+ download_metrics.downloaded_bytes = 0;
+ download_metrics.total_bytes = 0;
+ download_metrics.download_time_ms = 1000;
+
+ // The result must not include a file path in the case of errors.
+ result.error = -118;
+ } else if (url.path() ==
+ "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 53638;
+ download_metrics.total_bytes = 53638;
+ download_metrics.download_time_ms = 2000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(2u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(1, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(-118, ping_data[0].error_code);
+ EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+ EXPECT_EQ(base::Version("0.8"), ping_data[1].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+ EXPECT_EQ(0, ping_data[1].error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(1, static_cast<int>(item.error_category));
+ EXPECT_EQ(-118, item.error_code);
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_WAIT,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+ "ihfokbkgjpifnbbojhneepfflplebdkc"};
+
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the differential update scenario for one CRX.
+TEST_F(UpdateClientTest, OneCrxDiffUpdate) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ static int num_calls = 0;
+
+ // Must use the same stateful installer object.
+ static scoped_refptr<CrxInstaller> installer =
+ base::MakeRefCounted<VersionedTestInstaller>();
+
+ ++num_calls;
+
+ CrxComponent crx;
+ crx.name = "test_ihfo";
+ crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+ crx.installer = installer;
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ if (num_calls == 1) {
+ crx.version = base::Version("0.8");
+ } else if (num_calls == 2) {
+ crx.version = base::Version("1.0");
+ } else {
+ NOTREACHED();
+ }
+
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+
+ static int num_call = 0;
+ ++num_call;
+
+ ProtocolParser::Results results;
+
+ if (num_call == 1) {
+ /*
+ Mock the following response:
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+ hash_sha256='813c59747e139a608b3b5fc49633affc6db57437
+ 3f309f156ea6d27229c0b3f9'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+ package.hash_sha256 =
+ "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ } else if (num_call == 2) {
+ /*
+ Mock the following response:
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ <url codebasediff='http://localhost/download/'/>
+ </urls>
+ <manifest version='2.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx'
+ namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'
+ hash_sha256='1af337fbd19c72db0f870753bcd7711c3ae9dcaa
+ 0ecde26c262bad942b112990'
+ fp='22'
+ hashdiff_sha256='73c6e2d4f783fc4ca5481e89e0b8bfce7aec
+ 8ead3686290c94792658ec06f2f2'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx";
+ package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx";
+ package.hash_sha256 =
+ "1af337fbd19c72db0f870753bcd7711c3ae9dcaa0ecde26c262bad942b112990";
+ package.hashdiff_sha256 =
+ "73c6e2d4f783fc4ca5481e89e0b8bfce7aec8ead3686290c94792658ec06f2f2";
+ package.fingerprint = "22";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.crx_diffurls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "2.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 53638;
+ download_metrics.total_bytes = 53638;
+ download_metrics.download_time_ms = 2000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else if (url.path() ==
+ "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 2105;
+ download_metrics.total_bytes = 2105;
+ download_metrics.download_time_ms = 1000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(2u, ping_data.size());
+ EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(0, ping_data[0].error_code);
+ EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+ EXPECT_EQ(base::Version("1.0"), ping_data[1].previous_version);
+ EXPECT_EQ(base::Version("2.0"), ping_data[1].next_version);
+ EXPECT_FALSE(ping_data[1].diff_update_failed);
+ EXPECT_EQ(0, static_cast<int>(ping_data[1].diff_error_category));
+ EXPECT_EQ(0, ping_data[1].diff_error_code);
+ EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+ EXPECT_EQ(0, ping_data[1].error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
+ {
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ {
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the update scenario for one CRX where the CRX installer returns
+// an error. Tests that the |unpack_path| argument refers to a valid path
+// then |Install| is called, then tests that the |unpack| path is deleted
+// by the |update_client| code before the test ends.
+TEST_F(UpdateClientTest, OneCrxInstallError) {
+ class MockInstaller : public CrxInstaller {
+ public:
+ MOCK_METHOD1(OnUpdateError, void(int error));
+ MOCK_METHOD2(DoInstall,
+ void(const base::FilePath& unpack_path,
+ const Callback& callback));
+ MOCK_METHOD2(GetInstalledFile,
+ bool(const std::string& file, base::FilePath* installed_file));
+ MOCK_METHOD0(Uninstall, bool());
+
+ void Install(const base::FilePath& unpack_path,
+ const std::string& public_key,
+ Callback callback) override {
+ DoInstall(unpack_path, std::move(callback));
+
+ unpack_path_ = unpack_path;
+ EXPECT_TRUE(base::DirectoryExists(unpack_path_));
+ base::PostTaskWithTraits(
+ FROM_HERE, {base::MayBlock()},
+ base::BindOnce(std::move(callback),
+ CrxInstaller::Result(InstallError::GENERIC_ERROR)));
+ }
+
+ protected:
+ ~MockInstaller() override {
+ // The unpack path is deleted unconditionally by the component state code,
+ // which is driving this installer. Therefore, the unpack path must not
+ // exist when this object is destroyed.
+ if (!unpack_path_.empty())
+ EXPECT_FALSE(base::DirectoryExists(unpack_path_));
+ }
+
+ private:
+ // Contains the |unpack_path| argument of the Install call.
+ base::FilePath unpack_path_;
+ };
+
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ scoped_refptr<MockInstaller> installer =
+ base::MakeRefCounted<MockInstaller>();
+
+ EXPECT_CALL(*installer, OnUpdateError(_)).Times(0);
+ EXPECT_CALL(*installer, DoInstall(_, _)).Times(1);
+ EXPECT_CALL(*installer, GetInstalledFile(_, _)).Times(0);
+ EXPECT_CALL(*installer, Uninstall()).Times(0);
+
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = installer;
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1843;
+ download_metrics.total_bytes = 1843;
+ download_metrics.download_time_ms = 1000;
+
+ FilePath path;
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+ Result result;
+ result.error = 0;
+ result.response = path;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(1u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(3, static_cast<int>(ping_data[0].error_category)); // kInstall.
+ EXPECT_EQ(9, ping_data[0].error_code); // kInstallerError.
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the fallback from differential to full update scenario for one CRX.
+TEST_F(UpdateClientTest, OneCrxDiffUpdateFailsFullUpdateSucceeds) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ static int num_calls = 0;
+
+ // Must use the same stateful installer object.
+ static scoped_refptr<CrxInstaller> installer =
+ base::MakeRefCounted<VersionedTestInstaller>();
+
+ ++num_calls;
+
+ CrxComponent crx;
+ crx.name = "test_ihfo";
+ crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+ crx.installer = installer;
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ if (num_calls == 1) {
+ crx.version = base::Version("0.8");
+ } else if (num_calls == 2) {
+ crx.version = base::Version("1.0");
+ } else {
+ NOTREACHED();
+ }
+
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+
+ static int num_call = 0;
+ ++num_call;
+
+ ProtocolParser::Results results;
+
+ if (num_call == 1) {
+ /*
+ Mock the following response:
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+ hash_sha256='813c59747e139a608b3b5fc49633affc6db57437
+ 3f309f156ea6d27229c0b3f9'
+ fp='1'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+ package.hash_sha256 =
+ "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+ package.fingerprint = "1";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ } else if (num_call == 2) {
+ /*
+ Mock the following response:
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ <url codebasediff='http://localhost/download/'/>
+ </urls>
+ <manifest version='2.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='ihfokbkgjpifnbbojhneepfflplebdkc_2.crx'
+ namediff='ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx'
+ hash_sha256='1af337fbd19c72db0f870753bcd7711c3ae9dcaa
+ 0ecde26c262bad942b112990'
+ fp='22'
+ hashdiff_sha256='73c6e2d4f783fc4ca5481e89e0b8bfce7aec
+ 8ead3686290c94792658ec06f2f2'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_2.crx";
+ package.namediff = "ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx";
+ package.hash_sha256 =
+ "1af337fbd19c72db0f870753bcd7711c3ae9dcaa0ecde26c262bad942b112990";
+ package.hashdiff_sha256 =
+ "73c6e2d4f783fc4ca5481e89e0b8bfce7aec8ead3686290c94792658ec06f2f2";
+ package.fingerprint = "22";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.crx_diffurls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "2.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 53638;
+ download_metrics.total_bytes = 53638;
+ download_metrics.download_time_ms = 2000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else if (url.path() ==
+ "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1to2.crx") {
+ // A download error is injected on this execution path.
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = -1;
+ download_metrics.downloaded_bytes = 0;
+ download_metrics.total_bytes = 2105;
+ download_metrics.download_time_ms = 1000;
+
+ // The response must not include a file path in the case of errors.
+ result.error = -1;
+ } else if (url.path() ==
+ "/download/ihfokbkgjpifnbbojhneepfflplebdkc_2.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 53855;
+ download_metrics.total_bytes = 53855;
+ download_metrics.download_time_ms = 1000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_2.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(2u, ping_data.size());
+ EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.8"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(0, ping_data[0].error_code);
+ EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+ EXPECT_EQ(base::Version("1.0"), ping_data[1].previous_version);
+ EXPECT_EQ(base::Version("2.0"), ping_data[1].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+ EXPECT_EQ(0, ping_data[1].error_code);
+ EXPECT_TRUE(ping_data[1].diff_update_failed);
+ EXPECT_EQ(1, static_cast<int>(ping_data[1].diff_error_category));
+ EXPECT_EQ(-1, ping_data[1].diff_error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"ihfokbkgjpifnbbojhneepfflplebdkc"};
+
+ {
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ {
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the queuing of update checks. In this scenario, two update checks are
+// done for one CRX. The second update check call is queued up and will run
+// after the first check has completed. The CRX has no updates.
+TEST_F(UpdateClientTest, OneCrxNoUpdateQueuedCall) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ static int num_call = 0;
+ ++num_call;
+
+ EXPECT_EQ(Error::NONE, error);
+
+ if (num_call == 2)
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check.front());
+ EXPECT_EQ(1u, components.count(id));
+
+ auto& component = components.at(id);
+
+ EXPECT_FALSE(component->is_foreground());
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "noupdate";
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the install of one CRX.
+TEST_F(UpdateClientTest, OneCrxInstall) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.0");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ // Verify that calling Install sets ondemand.
+ EXPECT_TRUE(components.at(id)->is_foreground());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/jebgalgnebhfojomionfpkfelancnnkf.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1843;
+ download_metrics.total_bytes = 1843;
+ download_metrics.download_time_ms = 1000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(1u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.0"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(0, ping_data[0].error_code);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+
+ update_client->AddObserver(&observer);
+
+ update_client->Install(
+ std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+ base::BindOnce(&DataCallbackMock::Callback),
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the install of one CRX when no component data is provided. This
+// results in an install error.
+TEST_F(UpdateClientTest, OneCrxInstallNoCrxComponentData) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ return {base::nullopt};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ NOTREACHED();
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { NOTREACHED(); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ EXPECT_EQ(0u, MockPingManagerImpl::ping_data().size());
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ // Tests that the state of the component when the CrxComponent data
+ // is not provided. In this case, the optional |item.component| instance
+ // is not present.
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_STREQ("jebgalgnebhfojomionfpkfelancnnkf", item.id.c_str());
+ EXPECT_FALSE(item.component);
+ EXPECT_EQ(ErrorCategory::kService, item.error_category);
+ EXPECT_EQ(static_cast<int>(Error::CRX_NOT_FOUND), item.error_code);
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+
+ update_client->AddObserver(&observer);
+
+ update_client->Install(
+ std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+ base::BindOnce(&DataCallbackMock::Callback),
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests that overlapping installs of the same CRX result in an error.
+TEST_F(UpdateClientTest, ConcurrentInstallSameCRX) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.0");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ static int num_call = 0;
+ ++num_call;
+
+ EXPECT_LE(num_call, 2);
+
+ if (num_call == 1) {
+ EXPECT_EQ(Error::UPDATE_IN_PROGRESS, error);
+ return;
+ }
+ if (num_call == 2) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check.front());
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "noupdate";
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ // Verify that calling Install sets |is_foreground| for the component.
+ EXPECT_TRUE(components.at(id)->is_foreground());
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+
+ update_client->AddObserver(&observer);
+
+ update_client->Install(
+ std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+ base::BindOnce(&DataCallbackMock::Callback),
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ update_client->Install(
+ std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+ base::BindOnce(&DataCallbackMock::Callback),
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests that UpdateClient::Update returns Error::INVALID_ARGUMENT when
+// the |ids| parameter is empty.
+TEST_F(UpdateClientTest, EmptyIdList) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ return {};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ DCHECK_EQ(Error::INVALID_ARGUMENT, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ NOTREACHED();
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ const std::vector<std::string> empty_id_list;
+ update_client->Update(
+ empty_id_list, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+ RunThreads();
+}
+
+TEST_F(UpdateClientTest, SendUninstallPing) {
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return nullptr;
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ NOTREACHED();
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return nullptr;
+ }
+
+ private:
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+ ~MockCrxDownloader() override {}
+
+ void DoStartDownload(const GURL& url) override {}
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(1u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("1.2.3.4"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("0"), ping_data[0].next_version);
+ EXPECT_EQ(10, ping_data[0].extra_code1);
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ update_client->SendUninstallPing(
+ "jebgalgnebhfojomionfpkfelancnnkf", base::Version("1.2.3.4"), 10,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+}
+
+TEST_F(UpdateClientTest, RetryAfter) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ static int num_call = 0;
+ ++num_call;
+
+ EXPECT_LE(num_call, 4);
+
+ if (num_call == 1) {
+ EXPECT_EQ(Error::NONE, error);
+ } else if (num_call == 2) {
+ // This request is throttled since the update engine received a
+ // positive |retry_after_sec| value in the update check response.
+ EXPECT_EQ(Error::RETRY_LATER, error);
+ } else if (num_call == 3) {
+ // This request is a foreground Install, which is never throttled.
+ // The update engine received a |retry_after_sec| value of 0, which
+ // resets the throttling.
+ EXPECT_EQ(Error::NONE, error);
+ } else if (num_call == 4) {
+ // This request succeeds since there is no throttling in effect.
+ EXPECT_EQ(Error::NONE, error);
+ }
+
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+
+ static int num_call = 0;
+ ++num_call;
+
+ EXPECT_LE(num_call, 3);
+
+ int retry_after_sec(0);
+ if (num_call == 1) {
+ // Throttle the next call.
+ retry_after_sec = 60 * 60; // 1 hour.
+ }
+
+ EXPECT_EQ(1u, ids_to_check.size());
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check.front());
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "noupdate";
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, retry_after_sec));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_NOT_UPDATED,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+ {
+ // The engine handles this Update call but responds with a valid
+ // |retry_after_sec|, which causes subsequent calls to fail.
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ {
+ // This call will result in a completion callback invoked with
+ // Error::ERROR_UPDATE_RETRY_LATER.
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ {
+ // The Install call is handled, and the throttling is reset due to
+ // the value of |retry_after_sec| in the completion callback.
+ base::RunLoop runloop;
+ update_client->Install(std::string("jebgalgnebhfojomionfpkfelancnnkf"),
+ base::BindOnce(&DataCallbackMock::Callback),
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ {
+ // This call succeeds.
+ base::RunLoop runloop;
+ update_client->Update(ids, base::BindOnce(&DataCallbackMock::Callback),
+ false,
+ base::BindOnce(&CompletionCallbackMock::Callback,
+ runloop.QuitClosure()));
+ runloop.Run();
+ }
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the update check for two CRXs scenario. The first component supports
+// the group policy to enable updates, and has its updates disabled. The second
+// component has an update. The server does not honor the "updatedisabled"
+// attribute and returns updates for both components. However, the update for
+// the first component is not applied and the client responds with a
+// (SERVICE_ERROR, UPDATE_DISABLED)
+TEST_F(UpdateClientTest, TwoCrxUpdateOneUpdateDisabled) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx1;
+ crx1.name = "test_jebg";
+ crx1.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx1.version = base::Version("0.9");
+ crx1.installer = base::MakeRefCounted<TestInstaller>();
+ crx1.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ crx1.supports_group_policy_enable_component_updates = true;
+
+ CrxComponent crx2;
+ crx2.name = "test_ihfo";
+ crx2.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+ crx2.version = base::Version("0.8");
+ crx2.installer = base::MakeRefCounted<TestInstaller>();
+ crx2.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+
+ return {crx1, crx2};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='jebgalgnebhfojomionfpkfelancnnkf'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='jebgalgnebhfojomionfpkfelancnnkf.crx'
+ hash_sha256='6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd
+ 7c9b12cb7cc067667bde87'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ <app appid='ihfokbkgjpifnbbojhneepfflplebdkc'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='ihfokbkgjpifnbbojhneepfflplebdkc_1.crx'
+ hash_sha256='813c59747e139a608b3b5fc49633affc6db574373f
+ 309f156ea6d27229c0b3f9'/>
+ </packages>
+ </manifest>
+ </updatecheck>
+ </app>
+ </response>
+ */
+
+ // UpdateClient reads the state of |enabled_component_updates| from the
+ // configurator instance, persists its value in the corresponding
+ // update context, and propagates it down to each of the update actions,
+ // and further down to the UpdateChecker instance.
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_FALSE(enabled_component_updates);
+ EXPECT_EQ(2u, ids_to_check.size());
+
+ ProtocolParser::Results results;
+ {
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "jebgalgnebhfojomionfpkfelancnnkf.crx";
+ package.hash_sha256 =
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ }
+
+ {
+ const std::string id = "ihfokbkgjpifnbbojhneepfflplebdkc";
+ EXPECT_EQ(id, ids_to_check[1]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "ihfokbkgjpifnbbojhneepfflplebdkc_1.crx";
+ package.hash_sha256 =
+ "813c59747e139a608b3b5fc49633affc6db574373f309f156ea6d27229c0b3f9";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ results.list.push_back(result);
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/ihfokbkgjpifnbbojhneepfflplebdkc_1.crx") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 53638;
+ download_metrics.total_bytes = 53638;
+ download_metrics.download_time_ms = 2000;
+
+ EXPECT_TRUE(MakeTestFile(
+ TestFilePath("ihfokbkgjpifnbbojhneepfflplebdkc_1.crx"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadProgress,
+ base::Unretained(this)));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ const auto ping_data = MockPingManagerImpl::ping_data();
+ EXPECT_EQ(2u, ping_data.size());
+ EXPECT_EQ("jebgalgnebhfojomionfpkfelancnnkf", ping_data[0].id);
+ EXPECT_EQ(base::Version("0.9"), ping_data[0].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[0].next_version);
+ EXPECT_EQ(4, static_cast<int>(ping_data[0].error_category));
+ EXPECT_EQ(2, ping_data[0].error_code);
+ EXPECT_EQ("ihfokbkgjpifnbbojhneepfflplebdkc", ping_data[1].id);
+ EXPECT_EQ(base::Version("0.8"), ping_data[1].previous_version);
+ EXPECT_EQ(base::Version("1.0"), ping_data[1].next_version);
+ EXPECT_EQ(0, static_cast<int>(ping_data[1].error_category));
+ EXPECT_EQ(0, ping_data[1].error_code);
+ }
+ };
+
+ // Disables updates for the components declaring support for the group policy.
+ config()->SetEnabledComponentUpdates(false);
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_FOUND,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_DOWNLOADING,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(AtLeast(1));
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_READY,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATED,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf",
+ "ihfokbkgjpifnbbojhneepfflplebdkc"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where the update check fails.
+TEST_F(UpdateClientTest, OneCrxUpdateCheckFails) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return {crx};
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::UPDATE_CHECK_ERROR, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+ const std::string id = "jebgalgnebhfojomionfpkfelancnnkf";
+ EXPECT_EQ(id, ids_to_check.front());
+ EXPECT_EQ(1u, components.count(id));
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(update_check_callback), base::nullopt,
+ ErrorCategory::kUpdateCheck, -1, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(5, static_cast<int>(item.error_category));
+ EXPECT_EQ(-1, item.error_code);
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {"jebgalgnebhfojomionfpkfelancnnkf"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), false,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+// Tests the scenario where the server responds with different values for
+// application status.
+TEST_F(UpdateClientTest, OneCrxErrorUnknownApp) {
+ class DataCallbackMock {
+ public:
+ static std::vector<base::Optional<CrxComponent>> Callback(
+ const std::vector<std::string>& ids) {
+ std::vector<base::Optional<CrxComponent>> component;
+ {
+ CrxComponent crx;
+ crx.name = "test_jebg";
+ crx.pk_hash.assign(jebg_hash, jebg_hash + base::size(jebg_hash));
+ crx.version = base::Version("0.9");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ component.push_back(crx);
+ }
+ {
+ CrxComponent crx;
+ crx.name = "test_abag";
+ crx.pk_hash.assign(abag_hash, abag_hash + base::size(abag_hash));
+ crx.version = base::Version("0.1");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ component.push_back(crx);
+ }
+ {
+ CrxComponent crx;
+ crx.name = "test_ihfo";
+ crx.pk_hash.assign(ihfo_hash, ihfo_hash + base::size(ihfo_hash));
+ crx.version = base::Version("0.2");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ component.push_back(crx);
+ }
+ {
+ CrxComponent crx;
+ crx.name = "test_gjpm";
+ crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
+ crx.version = base::Version("0.3");
+ crx.installer = base::MakeRefCounted<TestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ component.push_back(crx);
+ }
+ return component;
+ }
+ };
+
+ class CompletionCallbackMock {
+ public:
+ static void Callback(base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ }
+ };
+
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(4u, ids_to_check.size());
+
+ const std::string update_response =
+ ")]}'"
+ R"({"response": {)"
+ R"( "protocol": "3.1",)"
+ R"( "app": [)"
+ R"({"appid": "jebgalgnebhfojomionfpkfelancnnkf",)"
+ R"( "status": "error-unknownApplication"},)"
+ R"({"appid": "abagagagagagagagagagagagagagagag",)"
+ R"( "status": "restricted"},)"
+ R"({"appid": "ihfokbkgjpifnbbojhneepfflplebdkc",)"
+ R"( "status": "error-invalidAppId"},)"
+ R"({"appid": "gjpmebpgbhcamgdgjcmnjfhggjpgcimm",)"
+ R"( "status": "error-foobarApp"})"
+ R"(]}})";
+
+ const auto parser = ProtocolHandlerFactoryJSON().CreateParser();
+ EXPECT_TRUE(parser->Parse(update_response));
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(update_check_callback), parser->results(),
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override { EXPECT_TRUE(ping_data().empty()); }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ MockObserver observer;
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "jebgalgnebhfojomionfpkfelancnnkf"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(5, static_cast<int>(item.error_category));
+ EXPECT_EQ(-10006, item.error_code); // UNKNOWN_APPPLICATION.
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "abagagagagagagagagagagagagagagag"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "abagagagagagagagagagagagagagagag"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(5, static_cast<int>(item.error_category));
+ EXPECT_EQ(-10007, item.error_code); // RESTRICTED_APPLICATION.
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "ihfokbkgjpifnbbojhneepfflplebdkc"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(5, static_cast<int>(item.error_category));
+ EXPECT_EQ(-10008, item.error_code); // INVALID_APPID.
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+ }
+ {
+ InSequence seq;
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_CHECKING_FOR_UPDATES,
+ "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"))
+ .Times(1);
+ EXPECT_CALL(observer, OnEvent(Events::COMPONENT_UPDATE_ERROR,
+ "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"))
+ .Times(1)
+ .WillOnce(Invoke([&update_client](Events event, const std::string& id) {
+ CrxUpdateItem item;
+ EXPECT_TRUE(update_client->GetCrxUpdateState(id, &item));
+ EXPECT_EQ(ComponentState::kUpdateError, item.state);
+ EXPECT_EQ(5, static_cast<int>(item.error_category));
+ EXPECT_EQ(-10004, item.error_code); // UPDATE_RESPONSE_NOT_FOUND.
+ EXPECT_EQ(0, item.extra_code1);
+ }));
+ }
+
+ update_client->AddObserver(&observer);
+
+ const std::vector<std::string> ids = {
+ "jebgalgnebhfojomionfpkfelancnnkf", "abagagagagagagagagagagagagagagag",
+ "ihfokbkgjpifnbbojhneepfflplebdkc", "gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
+ update_client->Update(
+ ids, base::BindOnce(&DataCallbackMock::Callback), true,
+ base::BindOnce(&CompletionCallbackMock::Callback, quit_closure()));
+
+ RunThreads();
+
+ update_client->RemoveObserver(&observer);
+}
+
+#if defined(OS_WIN) // ActionRun is only implemented on Windows.
+
+// Tests that a run action in invoked in the CRX install scenario.
+TEST_F(UpdateClientTest, ActionRun_Install) {
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='gjpmebpgbhcamgdgjcmnjfhggjpgcimm'>
+ <updatecheck status='ok'>
+ <urls>
+ <url codebase='http://localhost/download/'/>
+ </urls>
+ <manifest version='1.0' prodversionmin='11.0.1.0'>
+ <packages>
+ <package name='runaction_test_win.crx3'
+ hash_sha256='89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea'/>
+ </packages>
+ </manifest>
+ <actions>"
+ <action run='ChromeRecovery.crx3'/>"
+ </actions>"
+ </updatecheck>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_TRUE(enabled_component_updates);
+ EXPECT_EQ(1u, ids_to_check.size());
+
+ const std::string id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result::Manifest::Package package;
+ package.name = "runaction_test_win.crx3";
+ package.hash_sha256 =
+ "89290a0d2ff21ca5b45e109c6cc859ab5fe294e19c102d54acd321429c372cea";
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "ok";
+ result.crx_urls.push_back(GURL("http://localhost/download/"));
+ result.manifest.version = "1.0";
+ result.manifest.browser_min_version = "11.0.1.0";
+ result.manifest.packages.push_back(package);
+ result.action_run = "ChromeRecovery.crx3";
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override {
+ DownloadMetrics download_metrics;
+ FilePath path;
+ Result result;
+ if (url.path() == "/download/runaction_test_win.crx3") {
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kNone;
+ download_metrics.error = 0;
+ download_metrics.downloaded_bytes = 1843;
+ download_metrics.total_bytes = 1843;
+ download_metrics.download_time_ms = 1000;
+
+ EXPECT_TRUE(
+ MakeTestFile(TestFilePath("runaction_test_win.crx3"), &path));
+
+ result.error = 0;
+ result.response = path;
+ } else {
+ NOTREACHED();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&MockCrxDownloader::OnDownloadComplete,
+ base::Unretained(this), true, result,
+ download_metrics));
+ }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ EXPECT_EQ(3u, events().size());
+
+ /*
+ "<event eventtype="14" eventresult="1" downloader="unknown" "
+ "url="http://localhost/download/runaction_test_win.crx3"
+ "downloaded=1843 "
+ "total=1843 download_time_ms="1000" previousversion="0.0" "
+ "nextversion="1.0"/>"
+ */
+ const auto& event0 = events()[0];
+ EXPECT_EQ(14, event0.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(1, event0.FindKey("eventresult")->GetInt());
+ EXPECT_EQ("unknown", event0.FindKey("downloader")->GetString());
+ EXPECT_EQ("http://localhost/download/runaction_test_win.crx3",
+ event0.FindKey("url")->GetString());
+ EXPECT_EQ(1843, event0.FindKey("downloaded")->GetDouble());
+ EXPECT_EQ(1843, event0.FindKey("total")->GetDouble());
+ EXPECT_EQ(1000, event0.FindKey("download_time_ms")->GetDouble());
+ EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
+ EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
+
+ // "<event eventtype="42" eventresult="1" errorcode="1877345072"/>"
+ const auto& event1 = events()[1];
+ EXPECT_EQ(42, event1.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(1, event1.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(1877345072, event1.FindKey("errorcode")->GetInt());
+
+ // "<event eventtype=\"3\" eventresult=\"1\" previousversion=\"0.0\" "
+ // "nextversion=\"1.0\"/>",
+ const auto& event2 = events()[2];
+ EXPECT_EQ(3, event2.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(1, event1.FindKey("eventresult")->GetInt());
+ EXPECT_EQ("0.0", event0.FindKey("previousversion")->GetString());
+ EXPECT_EQ("1.0", event0.FindKey("nextversion")->GetString());
+ }
+ };
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ // The action is a program which returns 1877345072 as a hardcoded value.
+ update_client->Install(
+ std::string("gjpmebpgbhcamgdgjcmnjfhggjpgcimm"),
+ base::BindOnce([](const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_niea";
+ crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
+ crx.version = base::Version("0.0");
+ crx.installer = base::MakeRefCounted<VersionedTestInstaller>();
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return std::vector<base::Optional<CrxComponent>>{crx};
+ }),
+ base::BindOnce(
+ [](base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ },
+ quit_closure()));
+
+ RunThreads();
+}
+
+// Tests that a run action is invoked in an update scenario when there was
+// no update.
+TEST_F(UpdateClientTest, ActionRun_NoUpdate) {
+ class MockUpdateChecker : public UpdateChecker {
+ public:
+ static std::unique_ptr<UpdateChecker> Create(
+ scoped_refptr<Configurator> config,
+ PersistedData* metadata) {
+ return std::make_unique<MockUpdateChecker>();
+ }
+
+ void CheckForUpdates(
+ const std::string& session_id,
+ const std::vector<std::string>& ids_to_check,
+ const IdToComponentPtrMap& components,
+ const base::flat_map<std::string, std::string>& additional_attributes,
+ bool enabled_component_updates,
+ UpdateCheckCallback update_check_callback) override {
+ /*
+ Mock the following response:
+
+ <?xml version='1.0' encoding='UTF-8'?>
+ <response protocol='3.1'>
+ <app appid='gjpmebpgbhcamgdgjcmnjfhggjpgcimm'>
+ <updatecheck status='noupdate'>
+ <actions>"
+ <action run=ChromeRecovery.crx3'/>"
+ </actions>"
+ </updatecheck>
+ </app>
+ </response>
+ */
+ EXPECT_FALSE(session_id.empty());
+ EXPECT_EQ(1u, ids_to_check.size());
+ const std::string id = "gjpmebpgbhcamgdgjcmnjfhggjpgcimm";
+ EXPECT_EQ(id, ids_to_check[0]);
+ EXPECT_EQ(1u, components.count(id));
+
+ ProtocolParser::Result result;
+ result.extension_id = id;
+ result.status = "noupdate";
+ result.action_run = "ChromeRecovery.crx3";
+
+ ProtocolParser::Results results;
+ results.list.push_back(result);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_check_callback), results,
+ ErrorCategory::kNone, 0, 0));
+ }
+ };
+
+ class MockCrxDownloader : public CrxDownloader {
+ public:
+ static std::unique_ptr<CrxDownloader> Create(
+ bool is_background_download,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory) {
+ return std::make_unique<MockCrxDownloader>();
+ }
+
+ MockCrxDownloader() : CrxDownloader(nullptr) {}
+
+ private:
+ void DoStartDownload(const GURL& url) override { EXPECT_TRUE(false); }
+ };
+
+ class MockPingManager : public MockPingManagerImpl {
+ public:
+ explicit MockPingManager(scoped_refptr<Configurator> config)
+ : MockPingManagerImpl(config) {}
+
+ protected:
+ ~MockPingManager() override {
+ EXPECT_EQ(1u, events().size());
+
+ // "<event eventtype="42" eventresult="1" errorcode="1877345072"/>"
+ const auto& event = events()[0];
+ EXPECT_EQ(42, event.FindKey("eventtype")->GetInt());
+ EXPECT_EQ(1, event.FindKey("eventresult")->GetInt());
+ EXPECT_EQ(1877345072, event.FindKey("errorcode")->GetInt());
+ }
+ };
+
+ // Unpack the CRX to mock an existing install to be updated. The payload to
+ // run is going to be picked up from this directory.
+ base::FilePath unpack_path;
+ {
+ base::RunLoop runloop;
+ base::OnceClosure quit_closure = runloop.QuitClosure();
+
+ auto config = base::MakeRefCounted<TestConfigurator>();
+ auto component_unpacker = base::MakeRefCounted<ComponentUnpacker>(
+ std::vector<uint8_t>(std::begin(gjpm_hash), std::end(gjpm_hash)),
+ TestFilePath("runaction_test_win.crx3"), nullptr,
+ config->GetUnzipperFactory()->Create(),
+ config->GetPatcherFactory()->Create(),
+ crx_file::VerifierFormat::CRX2_OR_CRX3);
+
+ component_unpacker->Unpack(base::BindOnce(
+ [](base::FilePath* unpack_path, base::OnceClosure quit_closure,
+ const ComponentUnpacker::Result& result) {
+ EXPECT_EQ(UnpackerError::kNone, result.error);
+ EXPECT_EQ(0, result.extended_error);
+ *unpack_path = result.unpack_path;
+ std::move(quit_closure).Run();
+ },
+ &unpack_path, runloop.QuitClosure()));
+
+ runloop.Run();
+ }
+
+ EXPECT_FALSE(unpack_path.empty());
+ EXPECT_TRUE(base::DirectoryExists(unpack_path));
+ int64_t file_size = 0;
+ EXPECT_TRUE(base::GetFileSize(unpack_path.AppendASCII("ChromeRecovery.crx3"),
+ &file_size));
+ EXPECT_EQ(44582, file_size);
+
+ base::ScopedTempDir unpack_path_owner;
+ EXPECT_TRUE(unpack_path_owner.Set(unpack_path));
+
+ scoped_refptr<UpdateClient> update_client =
+ base::MakeRefCounted<UpdateClientImpl>(
+ config(), base::MakeRefCounted<MockPingManager>(config()),
+ &MockUpdateChecker::Create, &MockCrxDownloader::Create);
+
+ // The action is a program which returns 1877345072 as a hardcoded value.
+ const std::vector<std::string> ids = {"gjpmebpgbhcamgdgjcmnjfhggjpgcimm"};
+ update_client->Update(
+ ids,
+ base::BindOnce(
+ [](const base::FilePath& unpack_path,
+ const std::vector<std::string>& ids) {
+ CrxComponent crx;
+ crx.name = "test_niea";
+ crx.pk_hash.assign(gjpm_hash, gjpm_hash + base::size(gjpm_hash));
+ crx.version = base::Version("1.0");
+ crx.installer =
+ base::MakeRefCounted<ReadOnlyTestInstaller>(unpack_path);
+ crx.crx_format_requirement = crx_file::VerifierFormat::CRX2_OR_CRX3;
+ return std::vector<base::Optional<CrxComponent>>{crx};
+ },
+ unpack_path),
+ false,
+ base::BindOnce(
+ [](base::OnceClosure quit_closure, Error error) {
+ EXPECT_EQ(Error::NONE, error);
+ std::move(quit_closure).Run();
+ },
+ quit_closure()));
+
+ RunThreads();
+}
+
+#endif // OS_WIN
+
+} // namespace update_client
diff --git a/src/components/update_client/update_engine.cc b/src/components/update_client/update_engine.cc
new file mode 100644
index 0000000..a6d6920
--- /dev/null
+++ b/src/components/update_client/update_engine.cc
@@ -0,0 +1,438 @@
+// 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 "components/update_client/update_engine.h"
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/guid.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/optional.h"
+#include "base/stl_util.h"
+#include "base/strings/strcat.h"
+#include "base/threading/thread_task_runner_handle.h"
+#include "components/prefs/pref_service.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/persisted_data.h"
+#include "components/update_client/protocol_parser.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client_errors.h"
+#include "components/update_client/utils.h"
+
+namespace update_client {
+
+UpdateContext::UpdateContext(
+ scoped_refptr<Configurator> config,
+ bool is_foreground,
+ const std::vector<std::string>& ids,
+ UpdateClient::CrxDataCallback crx_data_callback,
+ const UpdateEngine::NotifyObserversCallback& notify_observers_callback,
+ UpdateEngine::Callback callback,
+ CrxDownloader::Factory crx_downloader_factory)
+ : config(config),
+ is_foreground(is_foreground),
+ enabled_component_updates(config->EnabledComponentUpdates()),
+ ids(ids),
+ crx_data_callback(std::move(crx_data_callback)),
+ notify_observers_callback(notify_observers_callback),
+ callback(std::move(callback)),
+ crx_downloader_factory(crx_downloader_factory),
+ session_id(base::StrCat({"{", base::GenerateGUID(), "}"})) {
+ for (const auto& id : ids) {
+ components.insert(
+ std::make_pair(id, std::make_unique<Component>(*this, id)));
+ }
+}
+
+UpdateContext::~UpdateContext() {}
+
+UpdateEngine::UpdateEngine(
+ scoped_refptr<Configurator> config,
+ UpdateChecker::Factory update_checker_factory,
+ CrxDownloader::Factory crx_downloader_factory,
+ scoped_refptr<PingManager> ping_manager,
+ const NotifyObserversCallback& notify_observers_callback)
+ : config_(config),
+ update_checker_factory_(update_checker_factory),
+ crx_downloader_factory_(crx_downloader_factory),
+ ping_manager_(ping_manager),
+ metadata_(
+ std::make_unique<PersistedData>(config->GetPrefService(),
+ config->GetActivityDataService())),
+ notify_observers_callback_(notify_observers_callback) {}
+
+UpdateEngine::~UpdateEngine() {
+ DCHECK(thread_checker_.CalledOnValidThread());
+}
+
+void UpdateEngine::Update(bool is_foreground,
+ const std::vector<std::string>& ids,
+ UpdateClient::CrxDataCallback crx_data_callback,
+ Callback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (ids.empty()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(std::move(callback), Error::INVALID_ARGUMENT));
+ return;
+ }
+
+ if (IsThrottled(is_foreground)) {
+ // TODO(xiaochu): remove this log after https://crbug.com/851151 is fixed.
+ VLOG(1) << "Background update is throttled for following components:";
+ for (const auto& id : ids) {
+ VLOG(1) << "id:" << id;
+ }
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(callback), Error::RETRY_LATER));
+ return;
+ }
+
+ const auto update_context = base::MakeRefCounted<UpdateContext>(
+ config_, is_foreground, ids, std::move(crx_data_callback),
+ notify_observers_callback_, std::move(callback), crx_downloader_factory_);
+ DCHECK(!update_context->session_id.empty());
+
+ const auto result = update_contexts_.insert(
+ std::make_pair(update_context->session_id, update_context));
+ DCHECK(result.second);
+
+ // Calls out to get the corresponding CrxComponent data for the CRXs in this
+ // update context.
+ const auto crx_components =
+ std::move(update_context->crx_data_callback).Run(update_context->ids);
+ DCHECK_EQ(update_context->ids.size(), crx_components.size());
+
+ for (size_t i = 0; i != update_context->ids.size(); ++i) {
+ const auto& id = update_context->ids[i];
+
+ DCHECK(update_context->components[id]->state() == ComponentState::kNew);
+
+ const auto crx_component = crx_components[i];
+ if (crx_component) {
+ // This component can be checked for updates.
+ DCHECK_EQ(id, GetCrxComponentID(*crx_component));
+ auto& component = update_context->components[id];
+ component->set_crx_component(*crx_component);
+ component->set_previous_version(component->crx_component()->version);
+ component->set_previous_fp(component->crx_component()->fingerprint);
+ update_context->components_to_check_for_updates.push_back(id);
+ } else {
+ // |CrxDataCallback| did not return a CrxComponent instance for this
+ // component, which most likely, has been uninstalled. This component
+ // is going to be transitioned to an error state when the its |Handle|
+ // method is called later on by the engine.
+ update_context->component_queue.push(id);
+ }
+ }
+
+ if (update_context->components_to_check_for_updates.empty()) {
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+ base::Unretained(this), update_context));
+ return;
+ }
+
+ for (const auto& id : update_context->components_to_check_for_updates)
+ update_context->components[id]->Handle(
+ base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesStart,
+ base::Unretained(this), update_context, id));
+}
+
+void UpdateEngine::ComponentCheckingForUpdatesStart(
+ scoped_refptr<UpdateContext> update_context,
+ const std::string& id) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ DCHECK_EQ(1u, update_context->components.count(id));
+ DCHECK(update_context->components.at(id));
+
+ // Handle |kChecking| state.
+ auto& component = *update_context->components.at(id);
+ component.Handle(
+ base::BindOnce(&UpdateEngine::ComponentCheckingForUpdatesComplete,
+ base::Unretained(this), update_context));
+
+ ++update_context->num_components_ready_to_check;
+ if (update_context->num_components_ready_to_check <
+ update_context->components_to_check_for_updates.size()) {
+ return;
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&UpdateEngine::DoUpdateCheck,
+ base::Unretained(this), update_context));
+}
+
+void UpdateEngine::DoUpdateCheck(scoped_refptr<UpdateContext> update_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ update_context->update_checker =
+ update_checker_factory_(config_, metadata_.get());
+
+ update_context->update_checker->CheckForUpdates(
+ update_context->session_id,
+ update_context->components_to_check_for_updates,
+ update_context->components, config_->ExtraRequestParams(),
+ update_context->enabled_component_updates,
+ base::BindOnce(&UpdateEngine::UpdateCheckResultsAvailable,
+ base::Unretained(this), update_context));
+}
+
+void UpdateEngine::UpdateCheckResultsAvailable(
+ scoped_refptr<UpdateContext> update_context,
+ const base::Optional<ProtocolParser::Results>& results,
+ ErrorCategory error_category,
+ int error,
+ int retry_after_sec) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ update_context->retry_after_sec = retry_after_sec;
+
+ // Only positive values for throttle_sec are effective. 0 means that no
+ // throttling occurs and it resets |throttle_updates_until_|.
+ // Negative values are not trusted and are ignored.
+ constexpr int kMaxRetryAfterSec = 24 * 60 * 60; // 24 hours.
+ const int throttle_sec =
+ std::min(update_context->retry_after_sec, kMaxRetryAfterSec);
+ if (throttle_sec >= 0) {
+ throttle_updates_until_ =
+ throttle_sec ? base::TimeTicks::Now() +
+ base::TimeDelta::FromSeconds(throttle_sec)
+ : base::TimeTicks();
+ }
+
+ update_context->update_check_error = error;
+
+ if (error) {
+ DCHECK(!results);
+ for (const auto& id : update_context->components_to_check_for_updates) {
+ DCHECK_EQ(1u, update_context->components.count(id));
+ auto& component = update_context->components.at(id);
+ component->SetUpdateCheckResult(base::nullopt,
+ ErrorCategory::kUpdateCheck, error);
+ }
+ return;
+ }
+
+ DCHECK(results);
+ DCHECK_EQ(0, error);
+
+ std::map<std::string, ProtocolParser::Result> id_to_result;
+ for (const auto& result : results->list)
+ id_to_result[result.extension_id] = result;
+
+ for (const auto& id : update_context->components_to_check_for_updates) {
+ DCHECK_EQ(1u, update_context->components.count(id));
+ auto& component = update_context->components.at(id);
+ const auto& it = id_to_result.find(id);
+ if (it != id_to_result.end()) {
+ const auto result = it->second;
+ const auto error = [](const std::string& status) {
+ // First, handle app status literals which can be folded down as an
+ // updatecheck status
+ if (status == "error-unknownApplication")
+ return std::make_pair(ErrorCategory::kUpdateCheck,
+ ProtocolError::UNKNOWN_APPLICATION);
+ if (status == "restricted")
+ return std::make_pair(ErrorCategory::kUpdateCheck,
+ ProtocolError::RESTRICTED_APPLICATION);
+ if (status == "error-invalidAppId")
+ return std::make_pair(ErrorCategory::kUpdateCheck,
+ ProtocolError::INVALID_APPID);
+ // If the parser has return a valid result and the status is not one of
+ // the literals above, then this must be a success an not a parse error.
+ return std::make_pair(ErrorCategory::kNone, ProtocolError::NONE);
+ }(result.status);
+ component->SetUpdateCheckResult(result, error.first,
+ static_cast<int>(error.second));
+ } else {
+ component->SetUpdateCheckResult(
+ base::nullopt, ErrorCategory::kUpdateCheck,
+ static_cast<int>(ProtocolError::UPDATE_RESPONSE_NOT_FOUND));
+ }
+ }
+}
+
+void UpdateEngine::ComponentCheckingForUpdatesComplete(
+ scoped_refptr<UpdateContext> update_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ ++update_context->num_components_checked;
+ if (update_context->num_components_checked <
+ update_context->components_to_check_for_updates.size()) {
+ return;
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&UpdateEngine::UpdateCheckComplete,
+ base::Unretained(this), update_context));
+}
+
+void UpdateEngine::UpdateCheckComplete(
+ scoped_refptr<UpdateContext> update_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ for (const auto& id : update_context->components_to_check_for_updates)
+ update_context->component_queue.push(id);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+ base::Unretained(this), update_context));
+}
+
+void UpdateEngine::HandleComponent(
+ scoped_refptr<UpdateContext> update_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ auto& queue = update_context->component_queue;
+
+ if (queue.empty()) {
+ const Error error = update_context->update_check_error
+ ? Error::UPDATE_CHECK_ERROR
+ : Error::NONE;
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE,
+ base::BindOnce(&UpdateEngine::UpdateComplete, base::Unretained(this),
+ update_context, error));
+ return;
+ }
+
+ const auto& id = queue.front();
+ DCHECK_EQ(1u, update_context->components.count(id));
+ const auto& component = update_context->components.at(id);
+ DCHECK(component);
+
+ auto& next_update_delay = update_context->next_update_delay;
+ if (!next_update_delay.is_zero() && component->IsUpdateAvailable()) {
+ base::ThreadTaskRunnerHandle::Get()->PostDelayedTask(
+ FROM_HERE,
+ base::BindOnce(&UpdateEngine::HandleComponent, base::Unretained(this),
+ update_context),
+ next_update_delay);
+ next_update_delay = base::TimeDelta();
+
+ notify_observers_callback_.Run(
+ UpdateClient::Observer::Events::COMPONENT_WAIT, id);
+ return;
+ }
+
+ component->Handle(base::BindOnce(&UpdateEngine::HandleComponentComplete,
+ base::Unretained(this), update_context));
+}
+
+void UpdateEngine::HandleComponentComplete(
+ scoped_refptr<UpdateContext> update_context) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ auto& queue = update_context->component_queue;
+ DCHECK(!queue.empty());
+
+ const auto& id = queue.front();
+ DCHECK_EQ(1u, update_context->components.count(id));
+ const auto& component = update_context->components.at(id);
+ DCHECK(component);
+
+ if (component->IsHandled()) {
+ update_context->next_update_delay = component->GetUpdateDuration();
+
+ if (!component->events().empty()) {
+ ping_manager_->SendPing(*component,
+ base::BindOnce([](int, const std::string&) {}));
+ }
+
+ queue.pop();
+ }
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+ base::Unretained(this), update_context));
+}
+
+void UpdateEngine::UpdateComplete(scoped_refptr<UpdateContext> update_context,
+ Error error) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ DCHECK(update_context);
+
+ const auto num_erased = update_contexts_.erase(update_context->session_id);
+ DCHECK_EQ(1u, num_erased);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(std::move(update_context->callback), error));
+}
+
+bool UpdateEngine::GetUpdateState(const std::string& id,
+ CrxUpdateItem* update_item) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+ for (const auto& context : update_contexts_) {
+ const auto& components = context.second->components;
+ const auto it = components.find(id);
+ if (it != components.end()) {
+ *update_item = it->second->GetCrxUpdateItem();
+ return true;
+ }
+ }
+ return false;
+}
+
+bool UpdateEngine::IsThrottled(bool is_foreground) const {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ if (is_foreground || throttle_updates_until_.is_null())
+ return false;
+
+ const auto now(base::TimeTicks::Now());
+
+ // Throttle the calls in the interval (t - 1 day, t) to limit the effect of
+ // unset clocks or clock drift.
+ return throttle_updates_until_ - base::TimeDelta::FromDays(1) < now &&
+ now < throttle_updates_until_;
+}
+
+void UpdateEngine::SendUninstallPing(const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback callback) {
+ DCHECK(thread_checker_.CalledOnValidThread());
+
+ const auto update_context = base::MakeRefCounted<UpdateContext>(
+ config_, false, std::vector<std::string>{id},
+ UpdateClient::CrxDataCallback(), UpdateEngine::NotifyObserversCallback(),
+ std::move(callback), nullptr);
+ DCHECK(!update_context->session_id.empty());
+
+ const auto result = update_contexts_.insert(
+ std::make_pair(update_context->session_id, update_context));
+ DCHECK(result.second);
+
+ DCHECK(update_context);
+ DCHECK_EQ(1u, update_context->ids.size());
+ DCHECK_EQ(1u, update_context->components.count(id));
+ const auto& component = update_context->components.at(id);
+
+ component->Uninstall(version, reason);
+
+ update_context->component_queue.push(id);
+
+ base::ThreadTaskRunnerHandle::Get()->PostTask(
+ FROM_HERE, base::BindOnce(&UpdateEngine::HandleComponent,
+ base::Unretained(this), update_context));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/update_engine.h b/src/components/update_client/update_engine.h
new file mode 100644
index 0000000..9601873
--- /dev/null
+++ b/src/components/update_client/update_engine.h
@@ -0,0 +1,201 @@
+// 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 COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
+
+#include <list>
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+#include "base/callback.h"
+#include "base/containers/queue.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/optional.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/update_client/component.h"
+#include "components/update_client/crx_downloader.h"
+#include "components/update_client/crx_update_item.h"
+#include "components/update_client/ping_manager.h"
+#include "components/update_client/update_checker.h"
+#include "components/update_client/update_client.h"
+
+namespace base {
+class TimeTicks;
+} // namespace base
+
+namespace update_client {
+
+class Configurator;
+struct UpdateContext;
+
+// Handles updates for a group of components. Updates for different groups
+// are run concurrently but within the same group of components, updates are
+// applied one at a time.
+class UpdateEngine : public base::RefCounted<UpdateEngine> {
+ public:
+ using Callback = base::OnceCallback<void(Error error)>;
+ using NotifyObserversCallback =
+ base::Callback<void(UpdateClient::Observer::Events event,
+ const std::string& id)>;
+ using CrxDataCallback = UpdateClient::CrxDataCallback;
+
+ UpdateEngine(scoped_refptr<Configurator> config,
+ UpdateChecker::Factory update_checker_factory,
+ CrxDownloader::Factory crx_downloader_factory,
+ scoped_refptr<PingManager> ping_manager,
+ const NotifyObserversCallback& notify_observers_callback);
+
+ // Returns true and the state of the component identified by |id|, if the
+ // component is found in any update context. Returns false if the component
+ // is not found.
+ bool GetUpdateState(const std::string& id, CrxUpdateItem* update_state);
+
+ void Update(bool is_foreground,
+ const std::vector<std::string>& ids,
+ UpdateClient::CrxDataCallback crx_data_callback,
+ Callback update_callback);
+
+ void SendUninstallPing(const std::string& id,
+ const base::Version& version,
+ int reason,
+ Callback update_callback);
+
+ private:
+ friend class base::RefCounted<UpdateEngine>;
+ ~UpdateEngine();
+
+ using UpdateContexts = std::map<std::string, scoped_refptr<UpdateContext>>;
+
+ void UpdateComplete(scoped_refptr<UpdateContext> update_context, Error error);
+
+ void ComponentCheckingForUpdatesStart(
+ scoped_refptr<UpdateContext> update_context,
+ const std::string& id);
+ void ComponentCheckingForUpdatesComplete(
+ scoped_refptr<UpdateContext> update_context);
+ void UpdateCheckComplete(scoped_refptr<UpdateContext> update_context);
+
+ void DoUpdateCheck(scoped_refptr<UpdateContext> update_context);
+ void UpdateCheckResultsAvailable(
+ scoped_refptr<UpdateContext> update_context,
+ const base::Optional<ProtocolParser::Results>& results,
+ ErrorCategory error_category,
+ int error,
+ int retry_after_sec);
+
+ void HandleComponent(scoped_refptr<UpdateContext> update_context);
+ void HandleComponentComplete(scoped_refptr<UpdateContext> update_context);
+
+ // Returns true if the update engine rejects this update call because it
+ // occurs too soon.
+ bool IsThrottled(bool is_foreground) const;
+
+ base::ThreadChecker thread_checker_;
+ scoped_refptr<Configurator> config_;
+ UpdateChecker::Factory update_checker_factory_;
+ CrxDownloader::Factory crx_downloader_factory_;
+ scoped_refptr<PingManager> ping_manager_;
+ std::unique_ptr<PersistedData> metadata_;
+
+ // Called when CRX state changes occur.
+ const NotifyObserversCallback notify_observers_callback_;
+
+ // Contains the contexts associated with each update in progress.
+ UpdateContexts update_contexts_;
+
+ // Implements a rate limiting mechanism for background update checks. Has the
+ // effect of rejecting the update call if the update call occurs before
+ // a certain time, which is negotiated with the server as part of the
+ // update protocol. See the comments for X-Retry-After header.
+ base::TimeTicks throttle_updates_until_;
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateEngine);
+};
+
+// Describes a group of components which are installed or updated together.
+struct UpdateContext : public base::RefCounted<UpdateContext> {
+ UpdateContext(
+ scoped_refptr<Configurator> config,
+ bool is_foreground,
+ const std::vector<std::string>& ids,
+ UpdateClient::CrxDataCallback crx_data_callback,
+ const UpdateEngine::NotifyObserversCallback& notify_observers_callback,
+ UpdateEngine::Callback callback,
+ CrxDownloader::Factory crx_downloader_factory);
+
+ scoped_refptr<Configurator> config;
+
+ // True if the component is updated as a result of user interaction.
+ bool is_foreground = false;
+
+ // True if the component updates are enabled in this context.
+ const bool enabled_component_updates;
+
+ // Contains the ids of all CRXs in this context in the order specified
+ // by the caller of |UpdateClient::Update| or |UpdateClient:Install|.
+ const std::vector<std::string> ids;
+
+ // Contains the map of ids to components for all the CRX in this context.
+ IdToComponentPtrMap components;
+
+ // Called before an update check, when update metadata is needed.
+ UpdateEngine::CrxDataCallback crx_data_callback;
+
+ // Called when there is a state change for any update in this context.
+ const UpdateEngine::NotifyObserversCallback notify_observers_callback;
+
+ // Called when the all updates associated with this context have completed.
+ UpdateEngine::Callback callback;
+
+ // Creates instances of CrxDownloader;
+ CrxDownloader::Factory crx_downloader_factory;
+
+ std::unique_ptr<UpdateChecker> update_checker;
+
+ // The time in seconds to wait until doing further update checks.
+ int retry_after_sec = 0;
+
+ // Contains the ids of the components to check for updates. It is possible
+ // for a component to be uninstalled after it has been added in this context
+ // but before an update check is made. When this happens, the component won't
+ // have a CrxComponent instance, therefore, it can't be included in an
+ // update check.
+ std::vector<std::string> components_to_check_for_updates;
+
+ // The error reported by the update checker.
+ int update_check_error = 0;
+
+ size_t num_components_ready_to_check = 0;
+ size_t num_components_checked = 0;
+
+ // Contains the ids of the components that the state machine must handle.
+ base::queue<std::string> component_queue;
+
+ // The time to wait before handling the update for a component.
+ // The wait time is proportional with the cost incurred by updating
+ // the component. The more time it takes to download and apply the
+ // update for the current component, the longer the wait until the engine
+ // is handling the next component in the queue.
+ base::TimeDelta next_update_delay;
+
+ // The unique session id of this context. The session id is serialized in
+ // every protocol request. It is also used as a key in various data stuctures
+ // to uniquely identify an update context.
+ const std::string session_id;
+
+ private:
+ friend class base::RefCounted<UpdateContext>;
+ ~UpdateContext();
+
+ DISALLOW_COPY_AND_ASSIGN(UpdateContext);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_ENGINE_H_
diff --git a/src/components/update_client/update_query_params.cc b/src/components/update_client/update_query_params.cc
new file mode 100644
index 0000000..f921da1
--- /dev/null
+++ b/src/components/update_client/update_query_params.cc
@@ -0,0 +1,160 @@
+// 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 "components/update_client/update_query_params.h"
+
+#include "base/logging.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "build/build_config.h"
+#include "components/update_client/update_query_params_delegate.h"
+
+#if defined(OS_WIN)
+#include "base/win/windows_version.h"
+#endif
+
+namespace update_client {
+
+namespace {
+
+const char kUnknown[] = "unknown";
+
+// The request extra information is the OS and architecture, this helps
+// the server select the right package to be delivered.
+const char kOs[] =
+#if defined(OS_MACOSX)
+ "mac";
+#elif defined(OS_WIN)
+ "win";
+#elif defined(OS_ANDROID)
+ "android";
+#elif defined(OS_CHROMEOS)
+ "cros";
+#elif defined(OS_LINUX)
+ "linux";
+#elif defined(OS_FUCHSIA)
+ "fuchsia";
+#elif defined(OS_OPENBSD)
+ "openbsd";
+#elif defined(OS_STARBOARD)
+ "starboard";
+#else
+#error "unknown os"
+#endif
+
+const char kArch[] =
+#if defined(__amd64__) || defined(_WIN64)
+ "x64";
+#elif defined(__i386__) || defined(_WIN32)
+ "x86";
+#elif defined(__arm__)
+ "arm";
+#elif defined(__aarch64__)
+ "arm64";
+#elif defined(__mips__) && (__mips == 64)
+ "mips64el";
+#elif defined(__mips__)
+ "mipsel";
+#elif defined(__powerpc64__)
+ "ppc64";
+#else
+#error "unknown arch"
+#endif
+
+const char kChrome[] = "chrome";
+
+#if defined(GOOGLE_CHROME_BUILD)
+const char kChromeCrx[] = "chromecrx";
+#else
+const char kChromiumCrx[] = "chromiumcrx";
+#endif // defined(GOOGLE_CHROME_BUILD)
+
+UpdateQueryParamsDelegate* g_delegate = nullptr;
+
+} // namespace
+
+// static
+std::string UpdateQueryParams::Get(ProdId prod) {
+ return base::StringPrintf(
+ "os=%s&arch=%s&os_arch=%s&nacl_arch=%s&prod=%s%s&acceptformat=crx2,crx3",
+ kOs, kArch,
+#if !defined(OS_STARBOARD)
+ base::SysInfo().OperatingSystemArchitecture().c_str(),
+#else
+ "",
+#endif
+ GetNaclArch(), GetProdIdString(prod),
+ g_delegate ? g_delegate->GetExtraParams().c_str() : "");
+}
+
+// static
+const char* UpdateQueryParams::GetProdIdString(UpdateQueryParams::ProdId prod) {
+ switch (prod) {
+ case UpdateQueryParams::CHROME:
+ return kChrome;
+ break;
+ case UpdateQueryParams::CRX:
+#if defined(GOOGLE_CHROME_BUILD)
+ return kChromeCrx;
+#else
+ return kChromiumCrx;
+#endif
+ break;
+ }
+ return kUnknown;
+}
+
+// static
+const char* UpdateQueryParams::GetOS() {
+ return kOs;
+}
+
+// static
+const char* UpdateQueryParams::GetArch() {
+ return kArch;
+}
+
+// static
+const char* UpdateQueryParams::GetNaclArch() {
+#if defined(ARCH_CPU_X86_FAMILY)
+#if defined(ARCH_CPU_X86_64)
+ return "x86-64";
+#elif defined(OS_WIN)
+ bool x86_64 = (base::win::OSInfo::GetInstance()->wow64_status() ==
+ base::win::OSInfo::WOW64_ENABLED);
+ return x86_64 ? "x86-64" : "x86-32";
+#else
+ return "x86-32";
+#endif
+#elif defined(ARCH_CPU_ARMEL)
+ return "arm";
+#elif defined(ARCH_CPU_ARM64)
+ return "arm64";
+#elif defined(ARCH_CPU_MIPSEL)
+ return "mips32";
+#elif defined(ARCH_CPU_MIPS64EL)
+ return "mips64";
+#elif defined(ARCH_CPU_PPC64)
+ return "ppc64";
+#else
+// NOTE: when adding new values here, please remember to update the
+// comment in the .h file about possible return values from this function.
+#error "You need to add support for your architecture here"
+#endif
+}
+
+// static
+std::string UpdateQueryParams::GetProdVersion() {
+ // TODO: fill in prod versoin number
+ // return version_info::GetVersionNumber();
+ return "0.0.1";
+}
+
+// static
+void UpdateQueryParams::SetDelegate(UpdateQueryParamsDelegate* delegate) {
+ DCHECK(!g_delegate || !delegate || (delegate == g_delegate));
+ g_delegate = delegate;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/update_query_params.h b/src/components/update_client/update_query_params.h
new file mode 100644
index 0000000..e315e14
--- /dev/null
+++ b/src/components/update_client/update_query_params.h
@@ -0,0 +1,63 @@
+// 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 COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+class UpdateQueryParamsDelegate;
+
+// Generates a string of URL query parameters to be used when getting
+// component and extension updates. These parameters generally remain
+// fixed for a particular build. Embedders can use the delegate to
+// define different implementations. This should be used only in the
+// browser process.
+class UpdateQueryParams {
+ public:
+ enum ProdId {
+ CHROME = 0,
+ CRX,
+ };
+
+ // Generates a string of URL query parameters for Omaha. Includes the
+ // following fields: "os", "arch", "nacl_arch", "prod", "prodchannel",
+ // "prodversion", and "lang"
+ static std::string Get(ProdId prod);
+
+ // Returns the value we use for the "prod=" parameter. Possible return values
+ // include "chrome", "chromecrx", "chromiumcrx", and "unknown".
+ static const char* GetProdIdString(ProdId prod);
+
+ // Returns the value we use for the "os=" parameter. Possible return values
+ // include: "mac", "win", "android", "cros", "linux", and "openbsd".
+ static const char* GetOS();
+
+ // Returns the value we use for the "arch=" parameter. Possible return values
+ // include: "x86", "x64", and "arm".
+ static const char* GetArch();
+
+ // Returns the value we use for the "nacl_arch" parameter. Note that this may
+ // be different from the "arch" parameter above (e.g. one may be 32-bit and
+ // the other 64-bit). Possible return values include: "x86-32", "x86-64",
+ // "arm", "mips32", and "ppc64".
+ static const char* GetNaclArch();
+
+ // Returns the current version of Chrome/Chromium.
+ static std::string GetProdVersion();
+
+ // Use this delegate.
+ static void SetDelegate(UpdateQueryParamsDelegate* delegate);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(UpdateQueryParams);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_H_
diff --git a/src/components/update_client/update_query_params_delegate.cc b/src/components/update_client/update_query_params_delegate.cc
new file mode 100644
index 0000000..458ae00
--- /dev/null
+++ b/src/components/update_client/update_query_params_delegate.cc
@@ -0,0 +1,13 @@
+// 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 "components/update_client/update_query_params_delegate.h"
+
+namespace update_client {
+
+UpdateQueryParamsDelegate::UpdateQueryParamsDelegate() {}
+
+UpdateQueryParamsDelegate::~UpdateQueryParamsDelegate() {}
+
+} // namespace update_client
diff --git a/src/components/update_client/update_query_params_delegate.h b/src/components/update_client/update_query_params_delegate.h
new file mode 100644
index 0000000..6230e2e
--- /dev/null
+++ b/src/components/update_client/update_query_params_delegate.h
@@ -0,0 +1,32 @@
+// 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 COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_DELEGATE_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_DELEGATE_H_
+
+#include <string>
+
+#include "base/macros.h"
+
+namespace update_client {
+
+// Embedders can specify an UpdateQueryParamsDelegate to provide additional
+// custom parameters. If not specified (Set is never called), no additional
+// parameters are added.
+class UpdateQueryParamsDelegate {
+ public:
+ UpdateQueryParamsDelegate();
+ virtual ~UpdateQueryParamsDelegate();
+
+ // Returns additional parameters, if any. If there are any parameters, the
+ // string should begin with a & character.
+ virtual std::string GetExtraParams() = 0;
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UpdateQueryParamsDelegate);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATE_QUERY_PARAMS_DELEGATE_H_
diff --git a/src/components/update_client/update_query_params_unittest.cc b/src/components/update_client/update_query_params_unittest.cc
new file mode 100644
index 0000000..0e5701a
--- /dev/null
+++ b/src/components/update_client/update_query_params_unittest.cc
@@ -0,0 +1,68 @@
+// 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 "components/update_client/update_query_params.h"
+#include "base/strings/stringprintf.h"
+#include "base/sys_info.h"
+#include "components/update_client/update_query_params_delegate.h"
+#include "components/version_info/version_info.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using base::StringPrintf;
+
+namespace update_client {
+
+namespace {
+
+bool Contains(const std::string& source, const std::string& target) {
+ return source.find(target) != std::string::npos;
+}
+
+class TestUpdateQueryParamsDelegate : public UpdateQueryParamsDelegate {
+ std::string GetExtraParams() override { return "&cat=dog"; }
+};
+
+} // namespace
+
+void TestParams(UpdateQueryParams::ProdId prod_id, bool extra_params) {
+ std::string params = UpdateQueryParams::Get(prod_id);
+
+ // This doesn't so much test what the values are (since that would be an
+ // almost exact duplication of code with update_query_params.cc, and wouldn't
+ // really test anything) as it is a verification that all the params are
+ // present in the generated string.
+ EXPECT_TRUE(
+ Contains(params, StringPrintf("os=%s", UpdateQueryParams::GetOS())));
+ EXPECT_TRUE(
+ Contains(params, StringPrintf("arch=%s", UpdateQueryParams::GetArch())));
+ EXPECT_TRUE(Contains(
+ params,
+ StringPrintf("os_arch=%s",
+ base::SysInfo().OperatingSystemArchitecture().c_str())));
+ EXPECT_TRUE(Contains(
+ params,
+ StringPrintf("prod=%s", UpdateQueryParams::GetProdIdString(prod_id))));
+ if (extra_params)
+ EXPECT_TRUE(Contains(params, "cat=dog"));
+}
+
+void TestProdVersion() {
+ EXPECT_EQ(version_info::GetVersionNumber(),
+ UpdateQueryParams::GetProdVersion());
+}
+
+TEST(UpdateQueryParamsTest, GetParams) {
+ TestProdVersion();
+
+ TestParams(UpdateQueryParams::CRX, false);
+ TestParams(UpdateQueryParams::CHROME, false);
+
+ TestUpdateQueryParamsDelegate delegate;
+ UpdateQueryParams::SetDelegate(&delegate);
+
+ TestParams(UpdateQueryParams::CRX, true);
+ TestParams(UpdateQueryParams::CHROME, true);
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/updater_state.cc b/src/components/update_client/updater_state.cc
new file mode 100644
index 0000000..89df8cd
--- /dev/null
+++ b/src/components/update_client/updater_state.cc
@@ -0,0 +1,103 @@
+
+// Copyright (c) 2016 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 "components/update_client/updater_state.h"
+
+#include <utility>
+
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+#include "base/enterprise_util.h"
+#endif // OS_WIN or Mac
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "build/build_config.h"
+
+namespace update_client {
+
+// The value of this constant does not reflect its name (i.e. "domainjoined"
+// vs something like "isenterprisemanaged") because it is used with omaha.
+// After discussion with omaha team it was decided to leave the value as is to
+// keep continuity with previous chrome versions.
+const char UpdaterState::kIsEnterpriseManaged[] = "domainjoined";
+
+UpdaterState::UpdaterState(bool is_machine) : is_machine_(is_machine) {}
+
+UpdaterState::~UpdaterState() {}
+
+std::unique_ptr<UpdaterState::Attributes> UpdaterState::GetState(
+ bool is_machine) {
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+ UpdaterState updater_state(is_machine);
+ updater_state.ReadState();
+ return std::make_unique<Attributes>(updater_state.BuildAttributes());
+#else
+ return nullptr;
+#endif // OS_WIN or Mac
+}
+
+#if defined(OS_WIN) || (defined(OS_MACOSX) && !defined(OS_IOS))
+void UpdaterState::ReadState() {
+ is_enterprise_managed_ = base::IsMachineExternallyManaged();
+
+#if defined(GOOGLE_CHROME_BUILD)
+ updater_name_ = GetUpdaterName();
+ updater_version_ = GetUpdaterVersion(is_machine_);
+ last_autoupdate_started_ = GetUpdaterLastStartedAU(is_machine_);
+ last_checked_ = GetUpdaterLastChecked(is_machine_);
+ is_autoupdate_check_enabled_ = IsAutoupdateCheckEnabled();
+ update_policy_ = GetUpdatePolicy();
+#endif // GOOGLE_CHROME_BUILD
+}
+#endif // OS_WIN or Mac
+
+UpdaterState::Attributes UpdaterState::BuildAttributes() const {
+ Attributes attributes;
+
+#if defined(OS_WIN)
+ // Only Windows implements this attribute in a meaningful way.
+ attributes["ismachine"] = is_machine_ ? "1" : "0";
+#endif // OS_WIN
+ attributes[kIsEnterpriseManaged] = is_enterprise_managed_ ? "1" : "0";
+
+ attributes["name"] = updater_name_;
+
+ if (updater_version_.IsValid())
+ attributes["version"] = updater_version_.GetString();
+
+ const base::Time now = base::Time::NowFromSystemTime();
+ if (!last_autoupdate_started_.is_null())
+ attributes["laststarted"] =
+ NormalizeTimeDelta(now - last_autoupdate_started_);
+ if (!last_checked_.is_null())
+ attributes["lastchecked"] = NormalizeTimeDelta(now - last_checked_);
+
+ attributes["autoupdatecheckenabled"] =
+ is_autoupdate_check_enabled_ ? "1" : "0";
+
+ DCHECK((update_policy_ >= 0 && update_policy_ <= 3) || update_policy_ == -1);
+ attributes["updatepolicy"] = base::NumberToString(update_policy_);
+
+ return attributes;
+}
+
+std::string UpdaterState::NormalizeTimeDelta(const base::TimeDelta& delta) {
+ const base::TimeDelta two_weeks = base::TimeDelta::FromDays(14);
+ const base::TimeDelta two_months = base::TimeDelta::FromDays(60);
+
+ std::string val; // Contains the value to return in hours.
+ if (delta <= two_weeks) {
+ val = "0";
+ } else if (two_weeks < delta && delta <= two_months) {
+ val = "408"; // 2 weeks in hours.
+ } else {
+ val = "1344"; // 2*28 days in hours.
+ }
+
+ DCHECK(!val.empty());
+ return val;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/updater_state.h b/src/components/update_client/updater_state.h
new file mode 100644
index 0000000..ec11d4a
--- /dev/null
+++ b/src/components/update_client/updater_state.h
@@ -0,0 +1,69 @@
+// Copyright (c) 2016 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 COMPONENTS_UPDATE_CLIENT_UPDATER_STATE_H_
+#define COMPONENTS_UPDATE_CLIENT_UPDATER_STATE_H_
+
+#include <map>
+#include <memory>
+#include <string>
+
+#include "base/gtest_prod_util.h"
+#include "base/time/time.h"
+#include "base/version.h"
+
+namespace update_client {
+
+class UpdaterState {
+ public:
+ using Attributes = std::map<std::string, std::string>;
+
+ static const char kIsEnterpriseManaged[];
+
+ // Returns a map of items representing the state of an updater.
+ // If |is_machine| is true, this indicates that the updater state corresponds
+ // to the machine instance of the updater. Returns nullptr on
+ // the platforms and builds where this feature is not supported.
+ static std::unique_ptr<Attributes> GetState(bool is_machine);
+
+ ~UpdaterState();
+
+ private:
+ FRIEND_TEST_ALL_PREFIXES(UpdaterStateTest, Serialize);
+
+ explicit UpdaterState(bool is_machine);
+
+ // This function is best-effort. It updates the class members with
+ // the relevant values that could be retrieved.
+ void ReadState();
+
+ // Builds the map of state attributes by serializing this object state.
+ Attributes BuildAttributes() const;
+
+ static std::string GetUpdaterName();
+ static base::Version GetUpdaterVersion(bool is_machine);
+ static bool IsAutoupdateCheckEnabled();
+ static base::Time GetUpdaterLastStartedAU(bool is_machine);
+ static base::Time GetUpdaterLastChecked(bool is_machine);
+
+ static int GetUpdatePolicy();
+
+ static std::string NormalizeTimeDelta(const base::TimeDelta& delta);
+
+ // True if the Omaha updater is installed per-machine.
+ // The MacOS implementation ignores the value of this member but this may
+ // change in the future.
+ bool is_machine_;
+ std::string updater_name_;
+ base::Version updater_version_;
+ base::Time last_autoupdate_started_;
+ base::Time last_checked_;
+ bool is_enterprise_managed_ = false;
+ bool is_autoupdate_check_enabled_ = false;
+ int update_policy_ = 0;
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UPDATER_STATE_H_
diff --git a/src/components/update_client/updater_state_mac.mm b/src/components/update_client/updater_state_mac.mm
new file mode 100644
index 0000000..28c8c8b
--- /dev/null
+++ b/src/components/update_client/updater_state_mac.mm
@@ -0,0 +1,117 @@
+// Copyright 2017 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.
+
+#import <Foundation/Foundation.h>
+
+#include "base/enterprise_util.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/mac/foundation_util.h"
+#include "base/mac/scoped_nsautorelease_pool.h"
+#include "base/mac/scoped_nsobject.h"
+#include "base/strings/sys_string_conversions.h"
+#include "base/version.h"
+#include "components/update_client/updater_state.h"
+
+namespace update_client {
+
+namespace {
+
+const base::FilePath::CharType kKeystonePlist[] = FILE_PATH_LITERAL(
+ "Google/GoogleSoftwareUpdate/GoogleSoftwareUpdate.bundle/"
+ "Contents/Info.plist");
+
+// Gets a value from the updater settings. Returns a retained object.
+// T should be a toll-free Foundation framework type. See Apple's
+// documentation for toll-free bridging.
+template <class T>
+base::scoped_nsobject<T> GetUpdaterSettingsValue(NSString* value_name) {
+ CFStringRef app_id = CFSTR("com.google.Keystone.Agent");
+ base::ScopedCFTypeRef<CFPropertyListRef> plist(
+ CFPreferencesCopyAppValue(base::mac::NSToCFCast(value_name), app_id));
+ return base::scoped_nsobject<T>(
+ base::mac::ObjCCastStrict<T>(static_cast<id>(plist.get())),
+ base::scoped_policy::RETAIN);
+}
+
+base::Time GetUpdaterSettingsTime(NSString* value_name) {
+ base::scoped_nsobject<NSDate> date =
+ GetUpdaterSettingsValue<NSDate>(value_name);
+ base::Time result =
+ base::Time::FromCFAbsoluteTime([date timeIntervalSinceReferenceDate]);
+
+ return result;
+}
+
+base::Version GetVersionFromPlist(const base::FilePath& info_plist) {
+ base::mac::ScopedNSAutoreleasePool scoped_pool;
+ NSData* data =
+ [NSData dataWithContentsOfFile:base::mac::FilePathToNSString(info_plist)];
+ if ([data length] == 0) {
+ return base::Version();
+ }
+ NSDictionary* all_keys = base::mac::ObjCCastStrict<NSDictionary>(
+ [NSPropertyListSerialization propertyListWithData:data
+ options:NSPropertyListImmutable
+ format:nil
+ error:nil]);
+ if (all_keys == nil) {
+ return base::Version();
+ }
+ CFStringRef version = base::mac::GetValueFromDictionary<CFStringRef>(
+ base::mac::NSToCFCast(all_keys), kCFBundleVersionKey);
+ if (version == NULL) {
+ return base::Version();
+ }
+ return base::Version(base::SysCFStringRefToUTF8(version));
+}
+
+} // namespace
+
+std::string UpdaterState::GetUpdaterName() {
+ return std::string("Keystone");
+}
+
+base::Version UpdaterState::GetUpdaterVersion(bool /*is_machine*/) {
+ // System Keystone trumps user one, so check this one first
+ base::FilePath local_library;
+ bool success =
+ base::mac::GetLocalDirectory(NSLibraryDirectory, &local_library);
+ DCHECK(success);
+ base::FilePath system_bundle_plist = local_library.Append(kKeystonePlist);
+ base::Version system_keystone = GetVersionFromPlist(system_bundle_plist);
+ if (system_keystone.IsValid()) {
+ return system_keystone;
+ }
+
+ base::FilePath user_bundle_plist =
+ base::mac::GetUserLibraryPath().Append(kKeystonePlist);
+ return GetVersionFromPlist(user_bundle_plist);
+}
+
+base::Time UpdaterState::GetUpdaterLastStartedAU(bool /*is_machine*/) {
+ return GetUpdaterSettingsTime(@"lastCheckStartDate");
+}
+
+base::Time UpdaterState::GetUpdaterLastChecked(bool /*is_machine*/) {
+ return GetUpdaterSettingsTime(@"lastServerCheckDate");
+}
+
+bool UpdaterState::IsAutoupdateCheckEnabled() {
+ // Auto-update check period override (in seconds).
+ // Applies only to older versions of Keystone.
+ base::scoped_nsobject<NSNumber> timeInterval =
+ GetUpdaterSettingsValue<NSNumber>(@"checkInterval");
+ if (!timeInterval.get())
+ return true;
+ int value = [timeInterval intValue];
+
+ return 0 < value && value < (24 * 60 * 60);
+}
+
+int UpdaterState::GetUpdatePolicy() {
+ return -1; // Keystone does not support update policies.
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/updater_state_unittest.cc b/src/components/update_client/updater_state_unittest.cc
new file mode 100644
index 0000000..81c9964
--- /dev/null
+++ b/src/components/update_client/updater_state_unittest.cc
@@ -0,0 +1,120 @@
+// Copyright 2016 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 "components/update_client/updater_state.h"
+#include "base/macros.h"
+#include "base/time/time.h"
+#include "base/version.h"
+#include "build/build_config.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace update_client {
+
+class UpdaterStateTest : public testing::Test {
+ public:
+ UpdaterStateTest() {}
+ ~UpdaterStateTest() override {}
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(UpdaterStateTest);
+};
+
+TEST_F(UpdaterStateTest, Serialize) {
+ UpdaterState updater_state(false);
+
+ updater_state.updater_name_ = "the updater";
+ updater_state.updater_version_ = base::Version("1.0");
+ updater_state.last_autoupdate_started_ = base::Time::NowFromSystemTime();
+ updater_state.last_checked_ = base::Time::NowFromSystemTime();
+ updater_state.is_enterprise_managed_ = false;
+ updater_state.is_autoupdate_check_enabled_ = true;
+ updater_state.update_policy_ = 1;
+
+ auto attributes = updater_state.BuildAttributes();
+
+ // Sanity check all members.
+ EXPECT_STREQ("the updater", attributes.at("name").c_str());
+ EXPECT_STREQ("1.0", attributes.at("version").c_str());
+ EXPECT_STREQ("0", attributes.at("laststarted").c_str());
+ EXPECT_STREQ("0", attributes.at("lastchecked").c_str());
+ EXPECT_STREQ("0", attributes.at("domainjoined").c_str());
+ EXPECT_STREQ("1", attributes.at("autoupdatecheckenabled").c_str());
+ EXPECT_STREQ("1", attributes.at("updatepolicy").c_str());
+
+#if defined(GOOGLE_CHROME_BUILD)
+#if defined(OS_WIN)
+ // The value of "ismachine".
+ EXPECT_STREQ("0", UpdaterState::GetState(false)->at("ismachine").c_str());
+ EXPECT_STREQ("1", UpdaterState::GetState(true)->at("ismachine").c_str());
+
+ // The name of the Windows updater for Chrome.
+ EXPECT_STREQ("Omaha", UpdaterState::GetState(false)->at("name").c_str());
+#elif defined(OS_MACOSX) && !defined(OS_IOS)
+ // MacOS does not serialize "ismachine".
+ EXPECT_EQ(0UL, UpdaterState::GetState(false)->count("ismachine"));
+ EXPECT_EQ(0UL, UpdaterState::GetState(true)->count("ismachine"));
+ EXPECT_STREQ("Keystone", UpdaterState::GetState(false)->at("name").c_str());
+#endif // OS_WIN
+#endif // GOOGLE_CHROME_BUILD
+
+ // Tests some of the remaining values.
+ updater_state = UpdaterState(false);
+
+ // Don't serialize an invalid version if it could not be read.
+ updater_state.updater_version_ = base::Version();
+ attributes = updater_state.BuildAttributes();
+ EXPECT_EQ(0u, attributes.count("version"));
+
+ updater_state.updater_version_ = base::Version("0.0.0.0");
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("0.0.0.0", attributes.at("version").c_str());
+
+ updater_state.last_autoupdate_started_ =
+ base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(15);
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("408", attributes.at("laststarted").c_str());
+
+ updater_state.last_autoupdate_started_ =
+ base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(90);
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("1344", attributes.at("laststarted").c_str());
+
+ // Don't serialize the time if it could not be read.
+ updater_state.last_autoupdate_started_ = base::Time();
+ attributes = updater_state.BuildAttributes();
+ EXPECT_EQ(0u, attributes.count("laststarted"));
+
+ updater_state.last_checked_ =
+ base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(15);
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("408", attributes.at("lastchecked").c_str());
+
+ updater_state.last_checked_ =
+ base::Time::NowFromSystemTime() - base::TimeDelta::FromDays(90);
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("1344", attributes.at("lastchecked").c_str());
+
+ // Don't serialize the time if it could not be read (the value is invalid).
+ updater_state.last_checked_ = base::Time();
+ attributes = updater_state.BuildAttributes();
+ EXPECT_EQ(0u, attributes.count("lastchecked"));
+
+ updater_state.is_enterprise_managed_ = true;
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("1", attributes.at("domainjoined").c_str());
+
+ updater_state.is_autoupdate_check_enabled_ = false;
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("0", attributes.at("autoupdatecheckenabled").c_str());
+
+ updater_state.update_policy_ = 0;
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("0", attributes.at("updatepolicy").c_str());
+
+ updater_state.update_policy_ = -1;
+ attributes = updater_state.BuildAttributes();
+ EXPECT_STREQ("-1", attributes.at("updatepolicy").c_str());
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/updater_state_win.cc b/src/components/update_client/updater_state_win.cc
new file mode 100644
index 0000000..89c2c18
--- /dev/null
+++ b/src/components/update_client/updater_state_win.cc
@@ -0,0 +1,136 @@
+// Copyright 2016 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 "components/update_client/updater_state.h"
+
+#include <windows.h>
+
+#include <string>
+#include <utility>
+
+#include "base/enterprise_util.h"
+#include "base/strings/string16.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/win/registry.h"
+#include "base/win/win_util.h"
+
+// TODO(sorin): implement this in terms of
+// chrome/installer/util/google_update_settings (crbug.com/615187).
+
+namespace update_client {
+
+namespace {
+
+// Google Update group policy settings.
+const wchar_t kGoogleUpdatePoliciesKey[] =
+ L"SOFTWARE\\Policies\\Google\\Update";
+const wchar_t kCheckPeriodOverrideMinutes[] = L"AutoUpdateCheckPeriodMinutes";
+const wchar_t kUpdatePolicyValue[] = L"UpdateDefault";
+const wchar_t kChromeUpdatePolicyOverride[] =
+ L"Update{8A69D345-D564-463C-AFF1-A69D9E530F96}";
+
+// Don't allow update periods longer than six weeks (Chrome release cadence).
+const int kCheckPeriodOverrideMinutesMax = 60 * 24 * 7 * 6;
+
+// Google Update registry settings.
+const wchar_t kRegPathGoogleUpdate[] = L"Software\\Google\\Update";
+const wchar_t kRegPathClientsGoogleUpdate[] =
+ L"Software\\Google\\Update\\Clients\\"
+ L"{430FD4D0-B729-4F61-AA34-91526481799D}";
+const wchar_t kRegValueGoogleUpdatePv[] = L"pv";
+const wchar_t kRegValueLastStartedAU[] = L"LastStartedAU";
+const wchar_t kRegValueLastChecked[] = L"LastChecked";
+
+base::Time GetUpdaterTimeValue(bool is_machine, const wchar_t* value_name) {
+ const HKEY root_key = is_machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+ base::win::RegKey update_key;
+
+ if (update_key.Open(root_key, kRegPathGoogleUpdate,
+ KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS) {
+ DWORD value(0);
+ if (update_key.ReadValueDW(value_name, &value) == ERROR_SUCCESS) {
+ return base::Time::FromTimeT(value);
+ }
+ }
+
+ return base::Time();
+}
+
+} // namespace
+
+std::string UpdaterState::GetUpdaterName() {
+ return std::string("Omaha");
+}
+
+base::Version UpdaterState::GetUpdaterVersion(bool is_machine) {
+ const HKEY root_key = is_machine ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
+ base::string16 version;
+ base::win::RegKey key;
+
+ if (key.Open(root_key, kRegPathClientsGoogleUpdate,
+ KEY_QUERY_VALUE | KEY_WOW64_32KEY) == ERROR_SUCCESS &&
+ key.ReadValue(kRegValueGoogleUpdatePv, &version) == ERROR_SUCCESS) {
+ return base::Version(base::UTF16ToUTF8(version));
+ }
+
+ return base::Version();
+}
+
+base::Time UpdaterState::GetUpdaterLastStartedAU(bool is_machine) {
+ return GetUpdaterTimeValue(is_machine, kRegValueLastStartedAU);
+}
+
+base::Time UpdaterState::GetUpdaterLastChecked(bool is_machine) {
+ return GetUpdaterTimeValue(is_machine, kRegValueLastChecked);
+}
+
+bool UpdaterState::IsAutoupdateCheckEnabled() {
+ // Check the auto-update check period override. If it is 0 or exceeds the
+ // maximum timeout, then for all intents and purposes auto updates are
+ // disabled.
+ base::win::RegKey policy_key;
+ DWORD value = 0;
+ if (policy_key.Open(HKEY_LOCAL_MACHINE, kGoogleUpdatePoliciesKey,
+ KEY_QUERY_VALUE) == ERROR_SUCCESS &&
+ policy_key.ReadValueDW(kCheckPeriodOverrideMinutes, &value) ==
+ ERROR_SUCCESS &&
+ (value == 0 || value > kCheckPeriodOverrideMinutesMax)) {
+ return false;
+ }
+
+ return true;
+}
+
+// Returns -1 if the policy is not found or the value was invalid. Otherwise,
+// returns a value in the [0, 3] range, representing the value of the
+// Chrome update group policy.
+int UpdaterState::GetUpdatePolicy() {
+ const int kMaxUpdatePolicyValue = 3;
+
+ base::win::RegKey policy_key;
+
+ if (policy_key.Open(HKEY_LOCAL_MACHINE, kGoogleUpdatePoliciesKey,
+ KEY_QUERY_VALUE) != ERROR_SUCCESS) {
+ return -1;
+ }
+
+ DWORD value = 0;
+ // First try to read the Chrome-specific override.
+ if (policy_key.ReadValueDW(kChromeUpdatePolicyOverride, &value) ==
+ ERROR_SUCCESS &&
+ value <= kMaxUpdatePolicyValue) {
+ return value;
+ }
+
+ // Try to read default override.
+ if (policy_key.ReadValueDW(kUpdatePolicyValue, &value) == ERROR_SUCCESS &&
+ value <= kMaxUpdatePolicyValue) {
+ return value;
+ }
+
+ return -1;
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/url_fetcher_downloader.cc b/src/components/update_client/url_fetcher_downloader.cc
new file mode 100644
index 0000000..61530d7
--- /dev/null
+++ b/src/components/update_client/url_fetcher_downloader.cc
@@ -0,0 +1,237 @@
+// 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 "components/update_client/url_fetcher_downloader.h"
+
+#include <stdint.h>
+#include <stack>
+#include <utility>
+#include <vector>
+
+#include "base/bind.h"
+#include "base/files/file_enumerator.h"
+#include "base/files/file_util.h"
+#include "base/location.h"
+#include "base/logging.h"
+#include "base/sequenced_task_runner.h"
+#include "base/task/post_task.h"
+#include "base/task/task_traits.h"
+#include "components/update_client/network.h"
+#include "components/update_client/utils.h"
+#include "starboard/configuration_constants.h"
+#include "url/gurl.h"
+
+namespace {
+
+#if defined(OS_STARBOARD)
+void CleanupDirectory(base::FilePath& dir) {
+ std::stack<std::string> directories;
+ base::FileEnumerator file_enumerator(
+ dir, true,
+ base::FileEnumerator::FILES | base::FileEnumerator::DIRECTORIES);
+ for (auto path = file_enumerator.Next(); !path.value().empty();
+ path = file_enumerator.Next()) {
+ base::FileEnumerator::FileInfo info(file_enumerator.GetInfo());
+
+ if (info.IsDirectory()) {
+ directories.push(path.value());
+ } else {
+ SbFileDelete(path.value().c_str());
+ }
+ }
+ while (!directories.empty()) {
+ SbFileDelete(directories.top().c_str());
+ directories.pop();
+ }
+}
+#endif
+
+const base::TaskTraits kTaskTraits = {
+ base::MayBlock(), base::TaskPriority::BEST_EFFORT,
+ base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN};
+
+} // namespace
+
+namespace update_client {
+
+UrlFetcherDownloader::UrlFetcherDownloader(
+ std::unique_ptr<CrxDownloader> successor,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory)
+ : CrxDownloader(std::move(successor)),
+ network_fetcher_factory_(network_fetcher_factory) {}
+
+UrlFetcherDownloader::~UrlFetcherDownloader() {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+}
+
+void UrlFetcherDownloader::DoStartDownload(const GURL& url) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ base::PostTaskWithTraitsAndReply(
+ FROM_HERE, kTaskTraits,
+ base::BindOnce(&UrlFetcherDownloader::CreateDownloadDir,
+ base::Unretained(this)),
+ base::BindOnce(&UrlFetcherDownloader::StartURLFetch,
+ base::Unretained(this), url));
+}
+
+void UrlFetcherDownloader::CreateDownloadDir() {
+#if !defined(OS_STARBOARD)
+ base::CreateNewTempDirectory(FILE_PATH_LITERAL("chrome_url_fetcher_"),
+ &download_dir_);
+#else
+ const CobaltExtensionInstallationManagerApi* installation_api =
+ static_cast<const CobaltExtensionInstallationManagerApi*>(
+ SbSystemGetExtension(kCobaltExtensionInstallationManagerName));
+ if (!installation_api) {
+ SB_LOG(ERROR) << "Failed to get installation manager";
+ return;
+ }
+ // Get new installation index.
+ installation_index_ = installation_api->SelectNewInstallationIndex();
+ SB_DLOG(INFO) << "installation_index = " << installation_index_;
+ if (installation_index_ == IM_EXT_ERROR) {
+ SB_LOG(ERROR) << "Failed to get installation index";
+ return;
+ }
+
+ // Get the path to new installation.
+ std::vector<char> installation_path(kSbFileMaxPath);
+ if (installation_api->GetInstallationPath(
+ installation_index_, installation_path.data(),
+ installation_path.size()) == IM_EXT_ERROR) {
+ SB_LOG(ERROR) << "Failed to get installation path";
+ return;
+ }
+
+ SB_DLOG(INFO) << "installation_path = " << installation_path.data();
+ download_dir_ = base::FilePath(
+ std::string(installation_path.begin(), installation_path.end()));
+
+ // Cleanup the download dir.
+ CleanupDirectory(download_dir_);
+#endif
+}
+
+void UrlFetcherDownloader::StartURLFetch(const GURL& url) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ if (download_dir_.empty()) {
+ Result result;
+ result.error = -1;
+
+ DownloadMetrics download_metrics;
+ download_metrics.url = url;
+ download_metrics.downloader = DownloadMetrics::kUrlFetcher;
+ download_metrics.error = -1;
+ download_metrics.downloaded_bytes = -1;
+ download_metrics.total_bytes = -1;
+ download_metrics.download_time_ms = 0;
+
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
+ base::Unretained(this), false, result,
+ download_metrics));
+ return;
+ }
+
+ const auto file_path = download_dir_.AppendASCII(url.ExtractFileName());
+ network_fetcher_ = network_fetcher_factory_->Create();
+ network_fetcher_->DownloadToFile(
+ url, file_path,
+ base::BindOnce(&UrlFetcherDownloader::OnResponseStarted,
+ base::Unretained(this)),
+ base::BindRepeating(&UrlFetcherDownloader::OnDownloadProgress,
+ base::Unretained(this)),
+ base::BindOnce(&UrlFetcherDownloader::OnNetworkFetcherComplete,
+ base::Unretained(this)));
+
+ download_start_time_ = base::TimeTicks::Now();
+}
+
+void UrlFetcherDownloader::OnNetworkFetcherComplete(base::FilePath file_path,
+ int net_error,
+ int64_t content_size) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ const base::TimeTicks download_end_time(base::TimeTicks::Now());
+ const base::TimeDelta download_time =
+ download_end_time >= download_start_time_
+ ? download_end_time - download_start_time_
+ : base::TimeDelta();
+
+ // Consider a 5xx response from the server as an indication to terminate
+ // the request and avoid overloading the server in this case.
+ // is not accepting requests for the moment.
+ int error = -1;
+ if (!file_path.empty() && response_code_ == 200) {
+ DCHECK_EQ(0, net_error);
+ error = 0;
+ } else if (response_code_ != -1) {
+ error = response_code_;
+ } else {
+ error = net_error;
+ }
+
+ const bool is_handled = error == 0 || IsHttpServerError(error);
+
+ Result result;
+ result.error = error;
+ if (!error) {
+ result.response = file_path;
+#if defined(OS_STARBOARD)
+ result.installation_index = installation_index_;
+#endif
+ }
+
+ DownloadMetrics download_metrics;
+ download_metrics.url = url();
+ download_metrics.downloader = DownloadMetrics::kUrlFetcher;
+ download_metrics.error = error;
+ // Tests expected -1, in case of failures and no content is available.
+ download_metrics.downloaded_bytes = error ? -1 : content_size;
+ download_metrics.total_bytes = total_bytes_;
+ download_metrics.download_time_ms = download_time.InMilliseconds();
+
+ VLOG(1) << "Downloaded " << content_size << " bytes in "
+ << download_time.InMilliseconds() << "ms from " << final_url_.spec()
+ << " to " << result.response.value();
+
+#if !defined(OS_STARBOARD)
+ // Delete the download directory in the error cases.
+ if (error && !download_dir_.empty())
+ base::PostTaskWithTraits(
+ FROM_HERE, kTaskTraits,
+ base::BindOnce(IgnoreResult(&base::DeleteFile), download_dir_, true));
+#else
+ if (error && !download_dir_.empty()) {
+ // Cleanup the download dir.
+ CleanupDirectory(download_dir_);
+ }
+#endif
+
+ main_task_runner()->PostTask(
+ FROM_HERE, base::BindOnce(&UrlFetcherDownloader::OnDownloadComplete,
+ base::Unretained(this), is_handled, result,
+ download_metrics));
+}
+
+// This callback is used to indicate that a download has been started.
+void UrlFetcherDownloader::OnResponseStarted(const GURL& final_url,
+ int response_code,
+ int64_t content_length) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+
+ VLOG(1) << "url fetcher response started for: " << final_url.spec();
+
+ final_url_ = final_url;
+ response_code_ = response_code;
+ total_bytes_ = content_length;
+}
+
+void UrlFetcherDownloader::OnDownloadProgress(int64_t current) {
+ DCHECK_CALLED_ON_VALID_THREAD(thread_checker_);
+ CrxDownloader::OnDownloadProgress();
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/url_fetcher_downloader.h b/src/components/update_client/url_fetcher_downloader.h
new file mode 100644
index 0000000..10b5503
--- /dev/null
+++ b/src/components/update_client/url_fetcher_downloader.h
@@ -0,0 +1,73 @@
+// 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 COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_
+#define COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_
+
+#include <stdint.h>
+
+#include <memory>
+
+#include "base/files/file_path.h"
+#include "base/macros.h"
+#include "base/memory/ref_counted.h"
+#include "base/threading/thread_checker.h"
+#include "base/time/time.h"
+#include "components/update_client/crx_downloader.h"
+
+#if defined(OS_STARBOARD)
+#include "cobalt/extension/installation_manager.h"
+#endif
+
+namespace update_client {
+
+class NetworkFetcher;
+class NetworkFetcherFactory;
+
+// Implements a CRX downloader using a NetworkFetcher object.
+class UrlFetcherDownloader : public CrxDownloader {
+ public:
+ UrlFetcherDownloader(
+ std::unique_ptr<CrxDownloader> successor,
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory);
+ ~UrlFetcherDownloader() override;
+
+ private:
+ // Overrides for CrxDownloader.
+ void DoStartDownload(const GURL& url) override;
+
+ void CreateDownloadDir();
+ void StartURLFetch(const GURL& url);
+ void OnNetworkFetcherComplete(base::FilePath file_path,
+ int net_error,
+ int64_t content_size);
+ void OnResponseStarted(const GURL& final_url,
+ int response_code,
+ int64_t content_length);
+ void OnDownloadProgress(int64_t content_length);
+
+ THREAD_CHECKER(thread_checker_);
+
+ scoped_refptr<NetworkFetcherFactory> network_fetcher_factory_;
+ std::unique_ptr<NetworkFetcher> network_fetcher_;
+
+ // Contains a temporary download directory for the downloaded file.
+ base::FilePath download_dir_;
+
+ base::TimeTicks download_start_time_;
+
+ GURL final_url_;
+ int response_code_ = -1;
+ int64_t total_bytes_ = -1;
+
+#if defined(OS_STARBOARD)
+ int installation_index_ = IM_EXT_INVALID_INDEX;
+#endif
+
+ DISALLOW_COPY_AND_ASSIGN(UrlFetcherDownloader);
+};
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_URL_FETCHER_DOWNLOADER_H_
diff --git a/src/components/update_client/utils.cc b/src/components/update_client/utils.cc
new file mode 100644
index 0000000..bcedb8a
--- /dev/null
+++ b/src/components/update_client/utils.cc
@@ -0,0 +1,212 @@
+// 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 "components/update_client/utils.h"
+
+#include <stddef.h>
+
+#include <algorithm>
+#include <cmath>
+#include <cstring>
+
+#include "base/callback.h"
+#include "base/files/file_path.h"
+#include "base/files/file_util.h"
+#include "base/files/memory_mapped_file.h"
+#include "base/json/json_file_value_serializer.h"
+#include "base/stl_util.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_piece.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "components/crx_file/id_util.h"
+#include "components/update_client/component.h"
+#include "components/update_client/configurator.h"
+#include "components/update_client/network.h"
+#include "components/update_client/update_client.h"
+#include "components/update_client/update_client_errors.h"
+#include "crypto/secure_hash.h"
+#include "crypto/sha2.h"
+#include "url/gurl.h"
+
+namespace update_client {
+
+bool HasDiffUpdate(const Component& component) {
+ return !component.crx_diffurls().empty();
+}
+
+bool IsHttpServerError(int status_code) {
+ return 500 <= status_code && status_code < 600;
+}
+
+bool DeleteFileAndEmptyParentDirectory(const base::FilePath& filepath) {
+ if (!base::DeleteFile(filepath, false))
+ return false;
+
+ const base::FilePath dirname(filepath.DirName());
+ if (!base::IsDirectoryEmpty(dirname))
+ return true;
+
+ return base::DeleteFile(dirname, false);
+}
+
+std::string GetCrxComponentID(const CrxComponent& component) {
+ return component.app_id.empty() ? GetCrxIdFromPublicKeyHash(component.pk_hash)
+ : component.app_id;
+}
+
+std::string GetCrxIdFromPublicKeyHash(const std::vector<uint8_t>& pk_hash) {
+ const std::string result =
+ crx_file::id_util::GenerateIdFromHash(&pk_hash[0], pk_hash.size());
+ DCHECK(crx_file::id_util::IdIsValid(result));
+ return result;
+}
+
+#if defined(OS_STARBOARD)
+bool VerifyFileHash256(const base::FilePath& filepath,
+ const std::string& expected_hash_str) {
+ std::vector<uint8_t> expected_hash;
+ if (!base::HexStringToBytes(expected_hash_str, &expected_hash) ||
+ expected_hash.size() != crypto::kSHA256Length) {
+ return false;
+ }
+
+ base::File source_file(filepath,
+ base::File::FLAG_OPEN | base::File::FLAG_READ);
+ if (!source_file.IsValid()) {
+ DPLOG(ERROR) << "VerifyFileHash256(): Unable to open source file: "
+ << filepath.value();
+ return false;
+ }
+
+ const size_t kBufferSize = 32768;
+ std::vector<char> buffer(kBufferSize);
+ uint8_t actual_hash[crypto::kSHA256Length] = {0};
+ std::unique_ptr<crypto::SecureHash> hasher(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+
+ while (true) {
+ int bytes_read = source_file.ReadAtCurrentPos(&buffer[0], buffer.size());
+ if (bytes_read < 0) {
+ DPLOG(ERROR) << "VerifyFileHash256(): error reading from source file: "
+ << filepath.value();
+
+ return false;
+ }
+
+ if (bytes_read == 0) {
+ break;
+ }
+
+ hasher->Update(&buffer[0], bytes_read);
+ }
+
+ hasher->Finish(actual_hash, sizeof(actual_hash));
+
+ return memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash)) == 0;
+}
+#else
+bool VerifyFileHash256(const base::FilePath& filepath,
+ const std::string& expected_hash_str) {
+ std::vector<uint8_t> expected_hash;
+ if (!base::HexStringToBytes(expected_hash_str, &expected_hash) ||
+ expected_hash.size() != crypto::kSHA256Length) {
+ return false;
+ }
+ base::MemoryMappedFile mmfile;
+ if (!mmfile.Initialize(filepath))
+ return false;
+
+ uint8_t actual_hash[crypto::kSHA256Length] = {0};
+ std::unique_ptr<crypto::SecureHash> hasher(
+ crypto::SecureHash::Create(crypto::SecureHash::SHA256));
+ hasher->Update(mmfile.data(), mmfile.length());
+ hasher->Finish(actual_hash, sizeof(actual_hash));
+
+ return memcmp(actual_hash, &expected_hash[0], sizeof(actual_hash)) == 0;
+}
+#endif
+
+bool IsValidBrand(const std::string& brand) {
+ const size_t kMaxBrandSize = 4;
+ if (!brand.empty() && brand.size() != kMaxBrandSize)
+ return false;
+
+ return std::find_if_not(brand.begin(), brand.end(), [](char ch) {
+ return base::IsAsciiAlpha(ch);
+ }) == brand.end();
+}
+
+// Helper function.
+// Returns true if |part| matches the expression
+// ^[<special_chars>a-zA-Z0-9]{min_length,max_length}$
+bool IsValidInstallerAttributePart(const std::string& part,
+ const std::string& special_chars,
+ size_t min_length,
+ size_t max_length) {
+ if (part.size() < min_length || part.size() > max_length)
+ return false;
+
+ return std::find_if_not(part.begin(), part.end(), [&special_chars](char ch) {
+ if (base::IsAsciiAlpha(ch) || base::IsAsciiDigit(ch))
+ return true;
+
+ for (auto c : special_chars) {
+ if (c == ch)
+ return true;
+ }
+
+ return false;
+ }) == part.end();
+}
+
+// Returns true if the |name| parameter matches ^[-_a-zA-Z0-9]{1,256}$ .
+bool IsValidInstallerAttributeName(const std::string& name) {
+ return IsValidInstallerAttributePart(name, "-_", 1, 256);
+}
+
+// Returns true if the |value| parameter matches ^[-.,;+_=a-zA-Z0-9]{0,256}$ .
+bool IsValidInstallerAttributeValue(const std::string& value) {
+ return IsValidInstallerAttributePart(value, "-.,;+_=", 0, 256);
+}
+
+bool IsValidInstallerAttribute(const InstallerAttribute& attr) {
+ return IsValidInstallerAttributeName(attr.first) &&
+ IsValidInstallerAttributeValue(attr.second);
+}
+
+void RemoveUnsecureUrls(std::vector<GURL>* urls) {
+ DCHECK(urls);
+ base::EraseIf(*urls,
+ [](const GURL& url) { return !url.SchemeIsCryptographic(); });
+}
+
+CrxInstaller::Result InstallFunctionWrapper(
+ base::OnceCallback<bool()> callback) {
+ return CrxInstaller::Result(std::move(callback).Run()
+ ? InstallError::NONE
+ : InstallError::GENERIC_ERROR);
+}
+
+// TODO(cpu): add a specific attribute check to a component json that the
+// extension unpacker will reject, so that a component cannot be installed
+// as an extension.
+std::unique_ptr<base::DictionaryValue> ReadManifest(
+ const base::FilePath& unpack_path) {
+ base::FilePath manifest =
+ unpack_path.Append(FILE_PATH_LITERAL("manifest.json"));
+ if (!base::PathExists(manifest))
+ return std::unique_ptr<base::DictionaryValue>();
+ JSONFileValueDeserializer deserializer(manifest);
+ std::string error;
+ std::unique_ptr<base::Value> root = deserializer.Deserialize(nullptr, &error);
+ if (!root)
+ return std::unique_ptr<base::DictionaryValue>();
+ if (!root->is_dict())
+ return std::unique_ptr<base::DictionaryValue>();
+ return std::unique_ptr<base::DictionaryValue>(
+ static_cast<base::DictionaryValue*>(root.release()));
+}
+
+} // namespace update_client
diff --git a/src/components/update_client/utils.h b/src/components/update_client/utils.h
new file mode 100644
index 0000000..8beebe8
--- /dev/null
+++ b/src/components/update_client/utils.h
@@ -0,0 +1,91 @@
+// 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 COMPONENTS_UPDATE_CLIENT_UTILS_H_
+#define COMPONENTS_UPDATE_CLIENT_UTILS_H_
+
+#include <memory>
+#include <string>
+#include <utility>
+#include <vector>
+
+#include "base/callback_forward.h"
+#include "base/memory/ref_counted.h"
+#include "components/update_client/update_client.h"
+
+class GURL;
+
+namespace base {
+class DictionaryValue;
+class FilePath;
+} // namespace base
+
+namespace update_client {
+
+class Component;
+struct CrxComponent;
+
+// Defines a name-value pair that represents an installer attribute.
+// Installer attributes are component-specific metadata, which may be serialized
+// in an update check request.
+using InstallerAttribute = std::pair<std::string, std::string>;
+
+// Returns true if the |component| contains a valid differential update url.
+bool HasDiffUpdate(const Component& component);
+
+// Returns true if the |status_code| represents a server error 5xx.
+bool IsHttpServerError(int status_code);
+
+// Deletes the file and its directory, if the directory is empty. If the
+// parent directory is not empty, the function ignores deleting the directory.
+// Returns true if the file and the empty directory are deleted.
+bool DeleteFileAndEmptyParentDirectory(const base::FilePath& filepath);
+
+// Returns the component id of the |component|. The component id is in a
+// format similar with the format of an extension id.
+std::string GetCrxComponentID(const CrxComponent& component);
+
+// Returns a CRX id from a public key hash.
+std::string GetCrxIdFromPublicKeyHash(const std::vector<uint8_t>& pk_hash);
+
+// Returns true if the actual SHA-256 hash of the |filepath| matches the
+// |expected_hash|.
+bool VerifyFileHash256(const base::FilePath& filepath,
+ const std::string& expected_hash);
+
+// Returns true if the |brand| parameter matches ^[a-zA-Z]{4}?$ .
+bool IsValidBrand(const std::string& brand);
+
+// Returns true if the name part of the |attr| parameter matches
+// ^[-_a-zA-Z0-9]{1,256}$ and the value part of the |attr| parameter
+// matches ^[-.,;+_=a-zA-Z0-9]{0,256}$ .
+bool IsValidInstallerAttribute(const InstallerAttribute& attr);
+
+// Removes the unsecure urls in the |urls| parameter.
+void RemoveUnsecureUrls(std::vector<GURL>* urls);
+
+// Adapter function for the old definitions of CrxInstaller::Install until the
+// component installer code is migrated to use a Result instead of bool.
+CrxInstaller::Result InstallFunctionWrapper(
+ base::OnceCallback<bool()> callback);
+
+// Deserializes the CRX manifest. The top level must be a dictionary.
+std::unique_ptr<base::DictionaryValue> ReadManifest(
+ const base::FilePath& unpack_path);
+
+// Converts a custom, specific installer error (and optionally extended error)
+// to an installer result.
+template <typename T>
+CrxInstaller::Result ToInstallerResult(const T& error, int extended_error = 0) {
+ static_assert(std::is_enum<T>::value,
+ "Use an enum class to define custom installer errors");
+ return CrxInstaller::Result(
+ static_cast<int>(update_client::InstallError::CUSTOM_ERROR_BASE) +
+ static_cast<int>(error),
+ extended_error);
+}
+
+} // namespace update_client
+
+#endif // COMPONENTS_UPDATE_CLIENT_UTILS_H_
diff --git a/src/components/update_client/utils_unittest.cc b/src/components/update_client/utils_unittest.cc
new file mode 100644
index 0000000..5045e2a
--- /dev/null
+++ b/src/components/update_client/utils_unittest.cc
@@ -0,0 +1,205 @@
+// Copyright 2016 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 "components/update_client/utils.h"
+
+#include <iterator>
+
+#include "base/files/file_path.h"
+#include "base/path_service.h"
+#include "components/update_client/updater_state.h"
+#include "nb/cpp14oncpp11.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+using std::string;
+
+namespace {
+
+base::FilePath MakeTestFilePath(const char* file) {
+ base::FilePath path;
+ base::PathService::Get(base::DIR_SOURCE_ROOT, &path);
+ return path.AppendASCII("components/test/data/update_client")
+ .AppendASCII(file);
+}
+
+} // namespace
+
+namespace update_client {
+
+TEST(UpdateClientUtils, VerifyFileHash256) {
+ EXPECT_TRUE(VerifyFileHash256(
+ MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+ std::string(
+ "6fc4b93fd11134de1300c2c0bb88c12b644a4ec0fd7c9b12cb7cc067667bde87")));
+
+ EXPECT_FALSE(VerifyFileHash256(
+ MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+ std::string("")));
+
+ EXPECT_FALSE(VerifyFileHash256(
+ MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+ std::string("abcd")));
+
+ EXPECT_FALSE(VerifyFileHash256(
+ MakeTestFilePath("jebgalgnebhfojomionfpkfelancnnkf.crx"),
+ std::string(
+ "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa")));
+}
+
+// Tests that the brand matches ^[a-zA-Z]{4}?$
+TEST(UpdateClientUtils, IsValidBrand) {
+ // The valid brand code must be empty or exactly 4 chars long.
+ EXPECT_TRUE(IsValidBrand(std::string("")));
+ EXPECT_TRUE(IsValidBrand(std::string("TEST")));
+ EXPECT_TRUE(IsValidBrand(std::string("test")));
+ EXPECT_TRUE(IsValidBrand(std::string("TEst")));
+
+ EXPECT_FALSE(IsValidBrand(std::string("T"))); // Too short.
+ EXPECT_FALSE(IsValidBrand(std::string("TE"))); //
+ EXPECT_FALSE(IsValidBrand(std::string("TES"))); //
+ EXPECT_FALSE(IsValidBrand(std::string("TESTS"))); // Too long.
+ EXPECT_FALSE(IsValidBrand(std::string("TES1"))); // Has digit.
+ EXPECT_FALSE(IsValidBrand(std::string(" TES"))); // Begins with white space.
+ EXPECT_FALSE(IsValidBrand(std::string("TES "))); // Ends with white space.
+ EXPECT_FALSE(IsValidBrand(std::string("T ES"))); // Contains white space.
+ EXPECT_FALSE(IsValidBrand(std::string("<TE"))); // Has <.
+ EXPECT_FALSE(IsValidBrand(std::string("TE>"))); // Has >.
+ EXPECT_FALSE(IsValidBrand(std::string("\""))); // Has "
+ EXPECT_FALSE(IsValidBrand(std::string("\\"))); // Has backslash.
+ EXPECT_FALSE(IsValidBrand(std::string("\xaa"))); // Has non-ASCII char.
+}
+
+TEST(UpdateClientUtils, GetCrxComponentId) {
+ static const uint8_t kHash[16] = {
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+ };
+ CrxComponent component;
+ component.pk_hash.assign(kHash, kHash + sizeof(kHash));
+
+ EXPECT_EQ(std::string("abcdefghijklmnopabcdefghijklmnop"),
+ GetCrxComponentID(component));
+}
+
+TEST(UpdateClientUtils, GetCrxIdFromPublicKeyHash) {
+ static const uint8_t kHash[16] = {
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+ 0x01, 0x23, 0x45, 0x67, 0x89, 0xab, 0xcd, 0xef,
+ };
+
+ EXPECT_EQ(std::string("abcdefghijklmnopabcdefghijklmnop"),
+ GetCrxIdFromPublicKeyHash({std::cbegin(kHash), std::cend(kHash)}));
+}
+
+// Tests that the name of an InstallerAttribute matches ^[-_=a-zA-Z0-9]{1,256}$
+TEST(UpdateClientUtils, IsValidInstallerAttributeName) {
+ // Test the length boundaries.
+ EXPECT_FALSE(IsValidInstallerAttribute(
+ make_pair(std::string(0, 'a'), std::string("value"))));
+ EXPECT_TRUE(IsValidInstallerAttribute(
+ make_pair(std::string(1, 'a'), std::string("value"))));
+ EXPECT_TRUE(IsValidInstallerAttribute(
+ make_pair(std::string(256, 'a'), std::string("value"))));
+ EXPECT_FALSE(IsValidInstallerAttribute(
+ make_pair(std::string(257, 'a'), std::string("value"))));
+
+ const char* const valid_names[] = {"A", "Z", "a", "a-b", "A_B",
+ "z", "0", "9", "-_"};
+ for (const char* name : valid_names)
+ EXPECT_TRUE(IsValidInstallerAttribute(
+ make_pair(std::string(name), std::string("value"))));
+
+ const char* const invalid_names[] = {
+ "", "a=1", " name", "name ", "na me", "<name", "name>",
+ "\"", "\\", "\xaa", ".", ",", ";", "+"};
+ for (const char* name : invalid_names)
+ EXPECT_FALSE(IsValidInstallerAttribute(
+ make_pair(std::string(name), std::string("value"))));
+}
+
+// Tests that the value of an InstallerAttribute matches
+// ^[-.,;+_=a-zA-Z0-9]{0,256}$
+TEST(UpdateClientUtils, IsValidInstallerAttributeValue) {
+ // Test the length boundaries.
+ EXPECT_TRUE(IsValidInstallerAttribute(
+ make_pair(std::string("name"), std::string(0, 'a'))));
+ EXPECT_TRUE(IsValidInstallerAttribute(
+ make_pair(std::string("name"), std::string(256, 'a'))));
+ EXPECT_FALSE(IsValidInstallerAttribute(
+ make_pair(std::string("name"), std::string(257, 'a'))));
+
+ const char* const valid_values[] = {"", "a=1", "A", "Z", "a",
+ "z", "0", "9", "-.,;+_="};
+ for (const char* value : valid_values)
+ EXPECT_TRUE(IsValidInstallerAttribute(
+ make_pair(std::string("name"), std::string(value))));
+
+ const char* const invalid_values[] = {" ap", "ap ", "a p", "<ap",
+ "ap>", "\"", "\\", "\xaa"};
+ for (const char* value : invalid_values)
+ EXPECT_FALSE(IsValidInstallerAttribute(
+ make_pair(std::string("name"), std::string(value))));
+}
+
+TEST(UpdateClientUtils, RemoveUnsecureUrls) {
+ const GURL test1[] = {GURL("http://foo"), GURL("https://foo")};
+ std::vector<GURL> urls(std::begin(test1), std::end(test1));
+ RemoveUnsecureUrls(&urls);
+ EXPECT_EQ(1u, urls.size());
+ EXPECT_EQ(urls[0], GURL("https://foo"));
+
+ const GURL test2[] = {GURL("https://foo"), GURL("http://foo")};
+ urls.assign(std::begin(test2), std::end(test2));
+ RemoveUnsecureUrls(&urls);
+ EXPECT_EQ(1u, urls.size());
+ EXPECT_EQ(urls[0], GURL("https://foo"));
+
+ const GURL test3[] = {GURL("https://foo"), GURL("https://bar")};
+ urls.assign(std::begin(test3), std::end(test3));
+ RemoveUnsecureUrls(&urls);
+ EXPECT_EQ(2u, urls.size());
+ EXPECT_EQ(urls[0], GURL("https://foo"));
+ EXPECT_EQ(urls[1], GURL("https://bar"));
+
+ const GURL test4[] = {GURL("http://foo")};
+ urls.assign(std::begin(test4), std::end(test4));
+ RemoveUnsecureUrls(&urls);
+ EXPECT_EQ(0u, urls.size());
+
+ const GURL test5[] = {GURL("http://foo"), GURL("http://bar")};
+ urls.assign(std::begin(test5), std::end(test5));
+ RemoveUnsecureUrls(&urls);
+ EXPECT_EQ(0u, urls.size());
+}
+
+TEST(UpdateClientUtils, ToInstallerResult) {
+ enum EnumA {
+ ENTRY0 = 10,
+ ENTRY1 = 20,
+ };
+
+ enum class EnumB {
+ ENTRY0 = 0,
+ ENTRY1,
+ };
+
+ const auto result1 = ToInstallerResult(EnumA::ENTRY0);
+ EXPECT_EQ(110, result1.error);
+ EXPECT_EQ(0, result1.extended_error);
+
+ const auto result2 = ToInstallerResult(ENTRY1, 10000);
+ EXPECT_EQ(120, result2.error);
+ EXPECT_EQ(10000, result2.extended_error);
+
+ const auto result3 = ToInstallerResult(EnumB::ENTRY0);
+ EXPECT_EQ(100, result3.error);
+ EXPECT_EQ(0, result3.extended_error);
+
+ const auto result4 = ToInstallerResult(EnumB::ENTRY1, 20000);
+ EXPECT_EQ(101, result4.error);
+ EXPECT_EQ(20000, result4.extended_error);
+}
+
+} // namespace update_client