blob: d3be1869f65941fd6755c0ba091424acbdb70c8a [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/update_client/update_checker.h"
#include <stddef.h>
#include <algorithm>
#include <functional>
#include <memory>
#include <optional>
#include <string>
#include <utility>
#include <vector>
#include "base/check_op.h"
#include "base/containers/flat_map.h"
#include "base/functional/bind.h"
#include "base/functional/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/sequence_checker.h"
#include "base/task/sequenced_task_runner.h"
#include "base/task/thread_pool.h"
#include "build/build_config.h"
#include "components/update_client/activity_data_service.h"
#include "components/update_client/cancellation.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/update_engine.h"
#include "components/update_client/utils.h"
#include "url/gurl.h"
namespace update_client {
namespace {
class UpdateCheckerImpl : public UpdateChecker {
public:
explicit UpdateCheckerImpl(scoped_refptr<Configurator> config);
UpdateCheckerImpl(const UpdateCheckerImpl&) = delete;
UpdateCheckerImpl& operator=(const UpdateCheckerImpl&) = delete;
~UpdateCheckerImpl() override;
// Overrides for UpdateChecker.
void CheckForUpdates(
scoped_refptr<UpdateContext> context,
const base::flat_map<std::string, std::string>& additional_attributes,
UpdateCheckCallback update_check_callback) override;
private:
static UpdaterStateAttributes ReadUpdaterStateAttributes(
UpdaterStateProvider update_state_provider,
bool is_machine);
void CheckForUpdatesHelper(
scoped_refptr<UpdateContext> context,
const std::vector<GURL>& urls,
const base::flat_map<std::string, std::string>& additional_attributes,
const std::multimap<std::string, std::string>& cache_contents,
const UpdaterStateAttributes& updater_state_attributes,
const std::set<std::string>& active_ids);
void OnRequestSenderComplete(scoped_refptr<UpdateContext> context,
std::optional<base::OnceClosure> fallback,
int error,
const std::string& response,
int retry_after_sec);
void UpdateCheckSucceeded(scoped_refptr<UpdateContext> context,
const ProtocolParser::Results& results,
int retry_after_sec);
void UpdateCheckFailed(ErrorCategory error_category,
int error,
int retry_after_sec);
SEQUENCE_CHECKER(sequence_checker_);
const scoped_refptr<Configurator> config_;
UpdateCheckCallback update_check_callback_;
scoped_refptr<Cancellation> cancellation_ =
base::MakeRefCounted<Cancellation>();
base::WeakPtrFactory<UpdateCheckerImpl> weak_factory_{this};
};
UpdateCheckerImpl::UpdateCheckerImpl(scoped_refptr<Configurator> config)
: config_(config) {}
UpdateCheckerImpl::~UpdateCheckerImpl() {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cancellation_->Cancel();
}
void UpdateCheckerImpl::CheckForUpdates(
scoped_refptr<UpdateContext> context,
const base::flat_map<std::string, std::string>& additional_attributes,
UpdateCheckCallback update_check_callback) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
update_check_callback_ = std::move(update_check_callback);
auto check_for_updates_invoker = base::BindOnce(
&UpdateCheckerImpl::CheckForUpdatesHelper, weak_factory_.GetWeakPtr(),
context, config_->UpdateUrl(), additional_attributes);
context->config->GetCrxCache()->ListHashesByAppId(base::BindOnce(
[](base::OnceCallback<void(const std::multimap<std::string, std::string>&,
const UpdaterStateAttributes&,
const std::set<std::string>&)>
check_for_updates_invoker,
scoped_refptr<Configurator> config, std::vector<std::string> ids,
const std::multimap<std::string, std::string>& cache_contents) {
base::ThreadPool::PostTaskAndReplyWithResult(
FROM_HERE, kTaskTraits,
base::BindOnce(&UpdateCheckerImpl::ReadUpdaterStateAttributes,
config->GetUpdaterStateProvider(),
!config->IsPerUserInstall()),
base::BindOnce(
[](base::OnceCallback<void(const UpdaterStateAttributes&,
const std::set<std::string>&)>
check_for_updates_invoker,
scoped_refptr<Configurator> config,
std::vector<std::string> ids,
const UpdaterStateAttributes& updater_state_attributes) {
config->GetPersistedData()->GetActiveBits(
ids, base::BindOnce(std::move(check_for_updates_invoker),
updater_state_attributes));
},
base::BindOnce(std::move(check_for_updates_invoker),
cache_contents),
config, ids));
},
std::move(check_for_updates_invoker), config_,
context->components_to_check_for_updates));
}
// This function runs on the blocking pool task runner.
UpdaterStateAttributes UpdateCheckerImpl::ReadUpdaterStateAttributes(
UpdaterStateProvider update_state_provider,
bool is_machine) {
#if BUILDFLAG(IS_WIN)
// On Windows, the Chrome and the updater install modes are matched by design.
return update_state_provider.Run(is_machine);
#elif BUILDFLAG(IS_MAC)
return update_state_provider.Run(false);
#else
return {};
#endif // BUILDFLAG(IS_WIN)
}
void UpdateCheckerImpl::CheckForUpdatesHelper(
scoped_refptr<UpdateContext> context,
const std::vector<GURL>& urls,
const base::flat_map<std::string, std::string>& additional_attributes,
const std::multimap<std::string, std::string>& cache_contents,
const UpdaterStateAttributes& updater_state_attributes,
const std::set<std::string>& active_ids) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
if (urls.empty()) {
UpdateCheckFailed(ErrorCategory::kUpdateCheck,
static_cast<int>(ProtocolError::MISSING_URLS), 0);
return;
}
GURL url = urls.front();
// 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.
CHECK(!context->components.empty());
const bool is_foreground =
context->components.cbegin()->second->is_foreground();
CHECK(std::ranges::all_of(
context->components,
[is_foreground](IdToComponentPtrMap::const_reference& elem) {
return is_foreground == elem.second->is_foreground();
}));
PersistedData* metadata = config_->GetPersistedData();
std::vector<std::string> sent_ids;
std::vector<protocol_request::App> apps;
for (const auto& app_id : context->components_to_check_for_updates) {
CHECK_EQ(1u, context->components.count(app_id));
const auto& component = context->components.at(app_id);
CHECK_EQ(component->id(), app_id);
const auto& crx_component = component->crx_component();
CHECK(crx_component);
if (crx_component->requires_network_encryption &&
!url.SchemeIsCryptographic()) {
continue;
}
sent_ids.push_back(app_id);
std::string install_source;
if (!crx_component->install_source.empty()) {
install_source = crx_component->install_source;
} else if (component->is_foreground()) {
install_source = "ondemand";
}
std::vector<std::string> cached_hashes;
auto range = cache_contents.equal_range(app_id);
for (auto i = range.first; i != range.second; i++) {
cached_hashes.push_back(i->second);
}
apps.push_back(MakeProtocolApp(
app_id, crx_component->version, crx_component->ap, crx_component->brand,
active_ids.find(app_id) != active_ids.end()
? metadata->GetInstallId(app_id)
: "",
crx_component->lang.empty() ? config_->GetLang() : crx_component->lang,
metadata->GetInstallDate(app_id), install_source,
crx_component->install_location, crx_component->installer_attributes,
metadata->GetCohort(app_id), metadata->GetCohortHint(app_id),
metadata->GetCohortName(app_id), crx_component->channel,
crx_component->disabled_reasons, cached_hashes,
MakeProtocolUpdateCheck(
!crx_component->updates_enabled ||
(!crx_component->allow_updates_on_metered_connection &&
config_->IsConnectionMetered()),
crx_component->target_version_prefix,
crx_component->rollback_allowed,
crx_component->same_version_update_allowed),
[](const std::string& install_data_index)
-> std::vector<protocol_request::Data> {
if (install_data_index.empty()) {
return {};
} else {
return {{"install", install_data_index, ""}};
}
}(crx_component->install_data_index),
MakeProtocolPing(app_id, config_->GetPersistedData(),
active_ids.find(app_id) != active_ids.end()),
std::nullopt));
}
if (sent_ids.empty()) {
// No apps could be checked over this URL.
UpdateCheckFailed(ErrorCategory::kUpdateCheck,
static_cast<int>(ProtocolError::MISSING_URLS), 0);
return;
}
const auto request = MakeProtocolRequest(
!config_->IsPerUserInstall(), context->session_id, config_->GetProdId(),
config_->GetBrowserVersion().GetString(), config_->GetChannel(),
config_->GetOSLongName(), config_->GetDownloadPreference(),
config_->IsMachineExternallyManaged(), additional_attributes,
updater_state_attributes, std::move(apps));
cancellation_->OnCancel(
base::MakeRefCounted<RequestSender>(config_->GetNetworkFetcherFactory())
->Send({url},
BuildUpdateCheckExtraRequestHeaders(
config_->GetProdId(), config_->GetBrowserVersion(),
sent_ids, is_foreground),
config_->GetProtocolHandlerFactory()
->CreateSerializer()
->Serialize(request),
config_->EnabledCupSigning(),
base::BindOnce(
&UpdateCheckerImpl::OnRequestSenderComplete,
weak_factory_.GetWeakPtr(), context,
urls.size() > 1
? std::optional<base::OnceClosure>(base::BindOnce(
&UpdateCheckerImpl::CheckForUpdatesHelper,
weak_factory_.GetWeakPtr(), context,
std::vector<GURL>(urls.begin() + 1, urls.end()),
additional_attributes, cache_contents,
updater_state_attributes, active_ids))
: std::nullopt)));
}
void UpdateCheckerImpl::OnRequestSenderComplete(
scoped_refptr<UpdateContext> context,
std::optional<base::OnceClosure> fallback,
int error,
const std::string& response,
int retry_after_sec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
cancellation_->Clear();
if (error) {
VLOG(1) << "RequestSender failed " << error;
if (fallback && retry_after_sec <= 0) {
std::move(*fallback).Run();
} else {
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;
}
CHECK_EQ(0, error);
UpdateCheckSucceeded(context, parser->results(), retry_after_sec);
}
void UpdateCheckerImpl::UpdateCheckSucceeded(
scoped_refptr<UpdateContext> context,
const ProtocolParser::Results& results,
int retry_after_sec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
PersistedData* metadata = config_->GetPersistedData();
const int daynum = results.daystart_elapsed_days;
for (const auto& result : results.apps) {
if (result.cohort) {
metadata->SetCohort(result.app_id, *result.cohort);
}
if (result.cohort_name) {
metadata->SetCohortName(result.app_id, *result.cohort_name);
}
if (result.cohort_hint) {
metadata->SetCohortHint(result.app_id, *result.cohort_hint);
}
}
base::OnceClosure reply =
base::BindOnce(std::move(update_check_callback_),
std::make_optional<ProtocolParser::Results>(results),
ErrorCategory::kNone, 0, retry_after_sec);
if (daynum != ProtocolParser::kNoDaystart) {
metadata->SetDateLastData(context->components_to_check_for_updates, daynum,
std::move(reply));
return;
}
metadata->SetLastUpdateCheckError({});
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(FROM_HERE,
std::move(reply));
}
void UpdateCheckerImpl::UpdateCheckFailed(ErrorCategory error_category,
int error,
int retry_after_sec) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
CHECK_NE(0, error);
config_->GetPersistedData()->SetLastUpdateCheckError(
{.category = error_category, .code = error});
base::SequencedTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(update_check_callback_), std::nullopt,
error_category, error, retry_after_sec));
}
} // namespace
std::unique_ptr<UpdateChecker> UpdateChecker::Create(
scoped_refptr<Configurator> config) {
return std::make_unique<UpdateCheckerImpl>(config);
}
} // namespace update_client