| // 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 |