blob: 9ce87c9e6351309057d62fc4fa3b382aaa9c66ba [file] [log] [blame]
// Copyright 2014 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "components/component_updater/component_updater_service.h"
#include <map>
#include <memory>
#include <string>
#include <utility>
#include <vector>
#include "base/check.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/functional/callback_helpers.h"
#include "base/logging.h"
#include "base/metrics/histogram_macros.h"
#include "base/ranges/algorithm.h"
#include "base/strings/utf_string_conversions.h"
#include "base/task/single_thread_task_runner.h"
#include "base/threading/thread_checker.h"
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/component_updater/component_installer.h"
#include "components/component_updater/component_updater_service_internal.h"
#include "components/component_updater/component_updater_utils.h"
#include "components/component_updater/pref_names.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/update_client.h"
#include "components/update_client/update_client_errors.h"
#include "components/update_client/utils.h"
#include "third_party/abseil-cpp/absl/types/optional.h"
#include "url/gurl.h"
using CrxInstaller = update_client::CrxInstaller;
using UpdateClient = update_client::UpdateClient;
namespace {
enum UpdateType {
UPDATE_TYPE_MANUAL = 0,
UPDATE_TYPE_AUTOMATIC,
UPDATE_TYPE_COUNT,
};
} // namespace
namespace component_updater {
ComponentInfo::ComponentInfo(const std::string& id,
const std::string& fingerprint,
const std::u16string& name,
const base::Version& version,
const std::string& cohort_id)
: id(id),
fingerprint(fingerprint),
name(name),
version(version),
cohort_id(cohort_id) {}
ComponentInfo::ComponentInfo(const ComponentInfo& other) = default;
ComponentInfo& ComponentInfo::operator=(const ComponentInfo& other) = default;
ComponentInfo::ComponentInfo(ComponentInfo&& other) = default;
ComponentInfo& ComponentInfo::operator=(ComponentInfo&& other) = default;
ComponentInfo::~ComponentInfo() = default;
ComponentRegistration::ComponentRegistration(
const std::string& app_id,
const std::string& name,
std::vector<uint8_t> public_key_hash,
const base::Version& version,
const std::string& fingerprint,
std::map<std::string, std::string> installer_attributes,
scoped_refptr<update_client::ActionHandler> action_handler,
scoped_refptr<update_client::CrxInstaller> installer,
bool requires_network_encryption,
bool supports_group_policy_enable_component_updates)
: app_id(app_id),
name(name),
public_key_hash(public_key_hash),
version(version),
fingerprint(fingerprint),
installer_attributes(installer_attributes),
action_handler(action_handler),
installer(installer),
requires_network_encryption(requires_network_encryption),
supports_group_policy_enable_component_updates(
supports_group_policy_enable_component_updates) {}
ComponentRegistration::ComponentRegistration(
const ComponentRegistration& other) = default;
ComponentRegistration& ComponentRegistration::operator=(
const ComponentRegistration& other) = default;
ComponentRegistration::ComponentRegistration(ComponentRegistration&& other) =
default;
ComponentRegistration& ComponentRegistration::operator=(
ComponentRegistration&& other) = default;
ComponentRegistration::~ComponentRegistration() = default;
CrxUpdateService::CrxUpdateService(scoped_refptr<Configurator> config,
std::unique_ptr<UpdateScheduler> scheduler,
scoped_refptr<UpdateClient> update_client,
const std::string& brand)
: config_(config),
scheduler_(std::move(scheduler)),
update_client_(update_client),
brand_(brand) {
AddObserver(this);
}
CrxUpdateService::~CrxUpdateService() {
DCHECK(thread_checker_.CalledOnValidThread());
for (auto& item : ready_callbacks_) {
std::move(item.second).Run();
}
RemoveObserver(this);
Stop();
}
void CrxUpdateService::AddObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
update_client_->AddObserver(observer);
}
void CrxUpdateService::RemoveObserver(Observer* observer) {
DCHECK(thread_checker_.CalledOnValidThread());
update_client_->RemoveObserver(observer);
}
base::Version CrxUpdateService::GetRegisteredVersion(
const std::string& app_id) {
DCHECK(thread_checker_.CalledOnValidThread());
base::Version registered_version =
std::make_unique<update_client::PersistedData>(
config_->GetPrefService(), config_->GetActivityDataService())
->GetProductVersion(app_id);
return (registered_version.IsValid()) ? registered_version
: base::Version(kNullVersion);
}
void CrxUpdateService::Start() {
DCHECK(thread_checker_.CalledOnValidThread());
VLOG(1) << "CrxUpdateService starting up. "
<< "First update attempt will take place in "
<< config_->InitialDelay() << " seconds. "
<< "Next update attempt will take place in "
<< config_->NextCheckDelay() << " seconds. ";
scheduler_->Schedule(
config_->InitialDelay(), config_->NextCheckDelay(),
base::BindRepeating(
base::IgnoreResult(&CrxUpdateService::CheckForUpdates),
base::Unretained(this)),
base::DoNothing());
}
// Stops the update loop. In flight operations will be completed.
void CrxUpdateService::Stop() {
DCHECK(thread_checker_.CalledOnValidThread());
VLOG(1) << "CrxUpdateService stopping";
scheduler_->Stop();
update_client_->Stop();
}
// Adds a component to be checked for upgrades. If the component exists it
// it will be replaced.
bool CrxUpdateService::RegisterComponent(
const ComponentRegistration& component) {
DCHECK(thread_checker_.CalledOnValidThread());
if (component.app_id.empty() || !component.version.IsValid() ||
!component.installer) {
return false;
}
// Update the registration data if the component has been registered before.
auto it = components_.find(component.app_id);
if (it != components_.end()) {
it->second = component;
return true;
}
components_.insert(std::make_pair(component.app_id, component));
components_order_.push_back(component.app_id);
// Create an initial state for this component. The state is mutated in
// response to events from the UpdateClient instance.
CrxUpdateItem item;
item.id = component.app_id;
item.component = ToCrxComponent(component);
const auto inserted =
component_states_.insert(std::make_pair(component.app_id, item));
DCHECK(inserted.second);
// Start the timer if this is the first component registered. The first timer
// event occurs after an interval defined by the component update
// configurator. The subsequent timer events are repeated with a period
// defined by the same configurator.
if (components_.size() == 1)
Start();
return true;
}
bool CrxUpdateService::UnregisterComponent(const std::string& id) {
DCHECK(thread_checker_.CalledOnValidThread());
auto it = components_.find(id);
if (it == components_.end())
return false;
DCHECK_EQ(id, it->first);
// Delay the uninstall of the component if the component is being updated.
if (update_client_->IsUpdating(id)) {
components_pending_unregistration_.push_back(id);
return true;
}
return DoUnregisterComponent(id);
}
bool CrxUpdateService::DoUnregisterComponent(const std::string& id) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(ready_callbacks_.find(id) == ready_callbacks_.end());
const bool result = components_.find(id)->second.installer->Uninstall();
const auto pos = base::ranges::find(components_order_, id);
if (pos != components_order_.end())
components_order_.erase(pos);
components_.erase(id);
component_states_.erase(id);
return result;
}
std::vector<std::string> CrxUpdateService::GetComponentIDs() const {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<std::string> ids;
for (const auto& it : components_)
ids.push_back(it.first);
return ids;
}
std::vector<ComponentInfo> CrxUpdateService::GetComponents() const {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<ComponentInfo> result;
auto data = std::make_unique<update_client::PersistedData>(
config_->GetPrefService(), config_->GetActivityDataService());
for (const auto& it : components_) {
result.push_back(ComponentInfo(
it.first, it.second.fingerprint, base::UTF8ToUTF16(it.second.name),
it.second.version, data->GetCohort(it.second.app_id)));
}
return result;
}
OnDemandUpdater& CrxUpdateService::GetOnDemandUpdater() {
DCHECK(thread_checker_.CalledOnValidThread());
return *this;
}
update_client::CrxComponent CrxUpdateService::ToCrxComponent(
const ComponentRegistration& component) const {
update_client::CrxComponent crx;
crx.pk_hash = component.public_key_hash;
crx.app_id = component.app_id;
crx.installer = component.installer;
crx.action_handler = component.action_handler;
crx.version = component.version;
crx.fingerprint = component.fingerprint;
crx.name = component.name;
crx.installer_attributes = component.installer_attributes;
crx.requires_network_encryption = component.requires_network_encryption;
crx.brand = brand_;
crx.crx_format_requirement =
crx_file::VerifierFormat::CRX3_WITH_PUBLISHER_PROOF;
crx.updates_enabled =
!component.supports_group_policy_enable_component_updates ||
config_->GetPrefService()->GetBoolean(prefs::kComponentUpdatesEnabled);
return crx;
}
absl::optional<ComponentRegistration> CrxUpdateService::GetComponent(
const std::string& id) const {
DCHECK(thread_checker_.CalledOnValidThread());
return component_updater::GetComponent(components_, id);
}
const CrxUpdateItem* CrxUpdateService::GetComponentState(
const std::string& id) const {
DCHECK(thread_checker_.CalledOnValidThread());
const auto it(component_states_.find(id));
return it != component_states_.end() ? &it->second : nullptr;
}
void CrxUpdateService::MaybeThrottle(const std::string& id,
base::OnceClosure callback) {
DCHECK(thread_checker_.CalledOnValidThread());
const auto it = components_.find(id);
if (it != components_.end()) {
DCHECK_EQ(it->first, id);
if (OnDemandUpdateWithCooldown(id)) {
ready_callbacks_.insert(std::make_pair(id, std::move(callback)));
return;
}
}
// Unblock the request if the request can't be throttled.
std::move(callback).Run();
}
void CrxUpdateService::OnDemandUpdate(const std::string& id,
Priority priority,
Callback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!GetComponent(id)) {
if (!callback.is_null()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback),
update_client::Error::INVALID_ARGUMENT));
}
return;
}
OnDemandUpdateInternal(id, priority, std::move(callback));
}
bool CrxUpdateService::OnDemandUpdateWithCooldown(const std::string& id) {
DCHECK(thread_checker_.CalledOnValidThread());
DCHECK(GetComponent(id));
// Check if the request is too soon.
const auto* component_state(GetComponentState(id));
if (component_state && !component_state->last_check.is_null()) {
base::TimeDelta delta =
base::TimeTicks::Now() - component_state->last_check;
if (delta < config_->OnDemandDelay())
return false;
}
OnDemandUpdateInternal(id, Priority::FOREGROUND, Callback());
return true;
}
void CrxUpdateService::OnDemandUpdateInternal(const std::string& id,
Priority priority,
Callback callback) {
DCHECK(thread_checker_.CalledOnValidThread());
UMA_HISTOGRAM_ENUMERATION("ComponentUpdater.Calls", UPDATE_TYPE_MANUAL,
UPDATE_TYPE_COUNT);
auto crx_data_callback = base::BindOnce(&CrxUpdateService::GetCrxComponents,
base::Unretained(this));
auto update_complete_callback = base::BindOnce(
&CrxUpdateService::OnUpdateComplete, base::Unretained(this),
std::move(callback), base::TimeTicks::Now());
if (priority == Priority::FOREGROUND)
update_client_->Install(id, std::move(crx_data_callback), {},
std::move(update_complete_callback));
else if (priority == Priority::BACKGROUND)
update_client_->Update({id}, std::move(crx_data_callback), {}, false,
std::move(update_complete_callback));
else
NOTREACHED();
}
bool CrxUpdateService::CheckForUpdates(
UpdateScheduler::OnFinishedCallback on_finished) {
DCHECK(thread_checker_.CalledOnValidThread());
UMA_HISTOGRAM_ENUMERATION("ComponentUpdater.Calls", UPDATE_TYPE_AUTOMATIC,
UPDATE_TYPE_COUNT);
if (components_order_.empty()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, std::move(on_finished));
return true;
}
update_client_->Update(
components_order_,
base::BindOnce(&CrxUpdateService::GetCrxComponents,
base::Unretained(this)),
{}, false,
base::BindOnce(&CrxUpdateService::OnUpdateComplete,
base::Unretained(this),
base::BindOnce(
[](UpdateScheduler::OnFinishedCallback on_finished,
update_client::Error /*error*/) {
std::move(on_finished).Run();
},
std::move(on_finished)),
base::TimeTicks::Now()));
return true;
}
bool CrxUpdateService::GetComponentDetails(const std::string& id,
CrxUpdateItem* item) const {
DCHECK(thread_checker_.CalledOnValidThread());
// First, if this component is currently being updated, return its state from
// the update client.
if (update_client_->GetCrxUpdateState(id, item))
return true;
// Otherwise, return the last seen state of the component, if such a
// state exists.
const auto component_states_it = component_states_.find(id);
if (component_states_it != component_states_.end()) {
*item = component_states_it->second;
return true;
}
return false;
}
std::vector<absl::optional<CrxComponent>> CrxUpdateService::GetCrxComponents(
const std::vector<std::string>& ids) {
DCHECK(thread_checker_.CalledOnValidThread());
std::vector<absl::optional<CrxComponent>> crxs;
for (absl::optional<ComponentRegistration> item :
component_updater::GetCrxComponents(components_, ids)) {
crxs.push_back(item ? absl::optional<CrxComponent>{ToCrxComponent(*item)}
: absl::nullopt);
}
return crxs;
}
void CrxUpdateService::OnUpdateComplete(Callback callback,
const base::TimeTicks& start_time,
update_client::Error error) {
DCHECK(thread_checker_.CalledOnValidThread());
VLOG(1) << "Update completed with error " << static_cast<int>(error);
UMA_HISTOGRAM_BOOLEAN("ComponentUpdater.UpdateCompleteResult",
error != update_client::Error::NONE);
UMA_HISTOGRAM_ENUMERATION("ComponentUpdater.UpdateCompleteError", error,
update_client::Error::MAX_VALUE);
UMA_HISTOGRAM_LONG_TIMES_100("ComponentUpdater.UpdateCompleteTime",
base::TimeTicks::Now() - start_time);
for (const auto& id : components_pending_unregistration_) {
if (!update_client_->IsUpdating(id)) {
const auto component = GetComponent(id);
if (component)
DoUnregisterComponent(id);
}
}
if (!callback.is_null()) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(callback), error));
}
}
void CrxUpdateService::OnEvent(Events event, const std::string& id) {
DCHECK(thread_checker_.CalledOnValidThread());
// Unblock all throttles for the component.
if (event == Observer::Events::COMPONENT_UPDATED ||
event == Observer::Events::COMPONENT_ALREADY_UP_TO_DATE ||
event == Observer::Events::COMPONENT_UPDATE_ERROR) {
auto callbacks = ready_callbacks_.equal_range(id);
for (auto it = callbacks.first; it != callbacks.second; ++it) {
std::move(it->second).Run();
}
ready_callbacks_.erase(id);
}
CrxUpdateItem update_item;
if (!update_client_->GetCrxUpdateState(id, &update_item))
return;
// Update the state of the item.
const auto state_it = component_states_.find(id);
if (state_it != component_states_.end())
state_it->second = update_item;
// Update the component registration with the new version.
if (event == Observer::Events::COMPONENT_UPDATED) {
const auto component_it = components_.find(id);
if (component_it != components_.end()) {
component_it->second.version = update_item.next_version;
component_it->second.fingerprint = update_item.next_fp;
}
}
}
///////////////////////////////////////////////////////////////////////////////
// The component update factory. Using the component updater as a singleton
// is the job of the browser process.
// TODO(sorin): consider making this a singleton.
std::unique_ptr<ComponentUpdateService> ComponentUpdateServiceFactory(
scoped_refptr<Configurator> config,
std::unique_ptr<UpdateScheduler> scheduler,
const std::string& brand) {
DCHECK(config);
DCHECK(scheduler);
auto update_client = update_client::UpdateClientFactory(config);
return std::make_unique<CrxUpdateService>(config, std::move(scheduler),
std::move(update_client), brand);
}
// Register prefs required by the component update service.
void RegisterComponentUpdateServicePrefs(PrefRegistrySimple* registry) {
// The component updates are enabled by default, if the preference is not set.
registry->RegisterBooleanPref(prefs::kComponentUpdatesEnabled, true);
}
} // namespace component_updater