blob: a904a170de6bce0626b6ef2b9b662141f207d7d9 [file] [log] [blame]
// 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