| // 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" |
| #if defined(OS_STARBOARD) |
| #include "cobalt/extension/installation_manager.h" |
| #include "cobalt/updater/utils.h" |
| #include "starboard/file.h" |
| #include "starboard/loader_app/app_key_files.h" |
| #include "starboard/loader_app/drain_file.h" |
| #endif |
| #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; |
| } |
| |
| #if defined(OS_STARBOARD) |
| |
| bool CheckBadFileExists(const char* installation_path, const char* app_key) { |
| std::string bad_app_key_file_path = |
| starboard::loader_app::GetBadAppKeyFilePath(installation_path, app_key); |
| SB_DCHECK(!bad_app_key_file_path.empty()); |
| SB_LOG(INFO) << "bad_app_key_file_path: " << bad_app_key_file_path; |
| SB_LOG(INFO) << "bad_app_key_file_path SbFileExists: " |
| << SbFileExists(bad_app_key_file_path.c_str()); |
| return !bad_app_key_file_path.empty() && |
| SbFileExists(bad_app_key_file_path.c_str()); |
| } |
| #endif |
| |
| // 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 current_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(current_version) > 0) { |
| current_version = base::Version(unpacked_version); |
| } |
| |
| // Check if there is an available update already for quick roll-forward |
| auto installation_api = |
| static_cast<const CobaltExtensionInstallationManagerApi*>( |
| SbSystemGetExtension(kCobaltExtensionInstallationManagerName)); |
| if (!installation_api) { |
| SB_LOG(ERROR) << "Failed to get installation manager extension."; |
| return; |
| } |
| |
| char app_key[IM_EXT_MAX_APP_KEY_LENGTH]; |
| if (installation_api->GetAppKey(app_key, IM_EXT_MAX_APP_KEY_LENGTH) == |
| IM_EXT_ERROR) { |
| SB_LOG(ERROR) << "Failed to get app key."; |
| return; |
| } |
| |
| int max_slots = installation_api->GetMaxNumberInstallations(); |
| if (max_slots == IM_EXT_ERROR) { |
| SB_LOG(ERROR) << "Failed to get max number of slots."; |
| return; |
| } |
| |
| // We'll find the newest version of the installation that satisfies the |
| // requirements as the final candidate slot. |
| base::Version slot_candidate_version("1.0.1"); |
| int slot_candidate = -1; |
| |
| // Iterate over all writeable slots - index >= 1. |
| for (int i = 1; i < max_slots; i++) { |
| SB_LOG(INFO) << "UpdateCheckerImpl::CheckForUpdatesHelper iterating slot=" |
| << i; |
| // Get the path to new installation. |
| std::vector<char> installation_path(kSbFileMaxPath); |
| if (installation_api->GetInstallationPath(i, installation_path.data(), |
| installation_path.size()) == |
| IM_EXT_ERROR) { |
| SB_LOG(ERROR) |
| << "UpdateCheckerImpl::CheckForUpdatesHelper: Failed to get " |
| "installation path for slot=" |
| << i; |
| continue; |
| } |
| |
| SB_DLOG(INFO) |
| << "UpdateCheckerImpl::CheckForUpdatesHelper installation_path = " |
| << installation_path.data(); |
| |
| base::FilePath installation_dir = base::FilePath( |
| std::string(installation_path.begin(), installation_path.end())); |
| |
| base::Version installed_version = |
| cobalt::updater::ReadEvergreenVersion(installation_dir); |
| |
| if (!installed_version.IsValid()) { |
| continue; |
| } else if (slot_candidate_version < installed_version && |
| current_version < installed_version && |
| !DrainFileDraining(installation_dir.value().c_str(), "") && |
| !CheckBadFileExists(installation_dir.value().c_str(), |
| app_key) && |
| starboard::loader_app::AnyGoodAppKeyFile( |
| installation_dir.value().c_str())) { |
| // Found a slot with newer version than the current version that's not |
| // draining, and no bad file of current app exists, and a good file |
| // exists. The final candidate is the newest version of the valid |
| // candidates. |
| SB_LOG(INFO) |
| << "UpdateCheckerImpl::CheckForUpdatesHelper slot candidate: " << i; |
| slot_candidate_version = installed_version; |
| slot_candidate = i; |
| } |
| } |
| |
| if (slot_candidate != -1) { |
| if (installation_api->RequestRollForwardToInstallation(slot_candidate) != |
| IM_EXT_ERROR) { |
| SB_LOG(INFO) << "UpdateCheckerImpl::CheckForUpdatesHelper: quick " |
| "update succeeded."; |
| return; |
| } |
| SB_LOG(WARNING) |
| << "UpdateCheckerImpl::CheckForUpdatesHelper: quick update failed."; |
| } |
| // If the quick roll forward update slot candidate doesn't exist, continue |
| // with update check. |
| #endif |
| apps.push_back(MakeProtocolApp( |
| app_id, current_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 |