blob: 1459d561204f1f419ee5be819142afe0c6e69dbc [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 <memory>
#include <string>
#include <vector>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/threading/thread_checker.h"
#include "base/threading/thread_task_runner_handle.h"
#include "components/update_client/configurator.h"
#include "components/update_client/crx_update_item.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/request_sender.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 {
// 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("");
}
// Filters invalid attributes from |installer_attributes|.
update_client::InstallerAttributes SanitizeInstallerAttributes(
const update_client::InstallerAttributes& installer_attributes) {
update_client::InstallerAttributes sanitized_attrs;
for (const auto& attr : installer_attributes) {
if (IsValidInstallerAttribute(attr))
sanitized_attrs.insert(attr);
}
return sanitized_attrs;
}
// Returns true if at least one item requires network encryption.
bool IsEncryptionRequired(const IdToCrxUpdateItemMap& items) {
for (const auto& item : items) {
if (item.second->component.requires_network_encryption)
return true;
}
return false;
}
// Builds an update check request for |components|. |additional_attributes| is
// serialized as part of the <request> element of the request to customize it
// with data that is not platform or component specific. For each |item|, a
// corresponding <app> element is created and inserted as a child node of
// the <request>.
//
// An app element looks like this:
// <app appid="hnimpnehoodheedghdeeijklkeaacbdc"
// version="0.1.2.3" installsource="ondemand">
// <updatecheck/>
// <packages>
// <package fp="abcd"/>
// </packages>
// </app>
std::string BuildUpdateCheckRequest(
const Configurator& config,
const IdToCrxUpdateItemMap& items,
PersistedData* metadata,
const std::string& additional_attributes,
bool enabled_component_updates,
const std::unique_ptr<UpdaterState::Attributes>& updater_state_attributes) {
const std::string brand(SanitizeBrand(config.GetBrand()));
std::string app_elements;
for (const auto& item_pair : items) {
const CrxUpdateItem* item = item_pair.second.get();
const update_client::InstallerAttributes installer_attributes(
SanitizeInstallerAttributes(item->component.installer_attributes));
std::string app("<app ");
base::StringAppendF(&app, "appid=\"%s\" version=\"%s\"", item->id.c_str(),
item->component.version.GetString().c_str());
if (!brand.empty())
base::StringAppendF(&app, " brand=\"%s\"", brand.c_str());
if (item->on_demand)
base::StringAppendF(&app, " installsource=\"ondemand\"");
for (const auto& attr : installer_attributes) {
base::StringAppendF(&app, " %s=\"%s\"", attr.first.c_str(),
attr.second.c_str());
}
const std::string cohort = metadata->GetCohort(item->id);
const std::string cohort_name = metadata->GetCohortName(item->id);
const std::string cohort_hint = metadata->GetCohortHint(item->id);
if (!cohort.empty())
base::StringAppendF(&app, " cohort=\"%s\"", cohort.c_str());
if (!cohort_name.empty())
base::StringAppendF(&app, " cohortname=\"%s\"", cohort_name.c_str());
if (!cohort_hint.empty())
base::StringAppendF(&app, " cohorthint=\"%s\"", cohort_hint.c_str());
base::StringAppendF(&app, ">");
base::StringAppendF(&app, "<updatecheck");
if (item->component.supports_group_policy_enable_component_updates &&
!enabled_component_updates) {
base::StringAppendF(&app, " updatedisabled=\"true\"");
}
base::StringAppendF(&app, "/>");
base::StringAppendF(&app, "<ping rd=\"%d\" ping_freshness=\"%s\"/>",
metadata->GetDateLastRollCall(item->id),
metadata->GetPingFreshness(item->id).c_str());
if (!item->component.fingerprint.empty()) {
base::StringAppendF(&app,
"<packages>"
"<package fp=\"%s\"/>"
"</packages>",
item->component.fingerprint.c_str());
}
base::StringAppendF(&app, "</app>");
app_elements.append(app);
VLOG(1) << "Appending to update request: " << app;
}
// Include the updater state in the update check request.
return BuildProtocolRequest(
config.GetProdId(), config.GetBrowserVersion().GetString(),
config.GetChannel(), config.GetLang(), config.GetOSLongName(),
config.GetDownloadPreference(), app_elements, additional_attributes,
updater_state_attributes);
}
class UpdateCheckerImpl : public UpdateChecker {
public:
UpdateCheckerImpl(const scoped_refptr<Configurator>& config,
PersistedData* metadata);
~UpdateCheckerImpl() override;
// Overrides for UpdateChecker.
bool CheckForUpdates(
const IdToCrxUpdateItemMap& items_to_check,
const std::string& additional_attributes,
bool enabled_component_updates,
const UpdateCheckCallback& update_check_callback) override;
private:
void ReadUpdaterStateAttributes();
void CheckForUpdatesHelper(const IdToCrxUpdateItemMap& items_to_check,
const std::string& additional_attributes,
bool enabled_component_updates);
void OnRequestSenderComplete(
std::unique_ptr<std::vector<std::string>> ids_checked,
int error,
const std::string& response,
int retry_after_sec);
base::ThreadChecker thread_checker_;
const scoped_refptr<Configurator> config_;
PersistedData* metadata_;
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(const scoped_refptr<Configurator>& config,
PersistedData* metadata)
: config_(config), metadata_(metadata) {}
UpdateCheckerImpl::~UpdateCheckerImpl() {
DCHECK(thread_checker_.CalledOnValidThread());
}
bool UpdateCheckerImpl::CheckForUpdates(
const IdToCrxUpdateItemMap& items_to_check,
const std::string& additional_attributes,
bool enabled_component_updates,
const UpdateCheckCallback& update_check_callback) {
DCHECK(thread_checker_.CalledOnValidThread());
update_check_callback_ = update_check_callback;
return config_->GetSequencedTaskRunner()->PostTaskAndReply(
FROM_HERE, base::Bind(&UpdateCheckerImpl::ReadUpdaterStateAttributes,
base::Unretained(this)),
base::Bind(&UpdateCheckerImpl::CheckForUpdatesHelper,
base::Unretained(this), base::ConstRef(items_to_check),
additional_attributes, enabled_component_updates));
}
// This function runs on the blocking pool task runner.
void UpdateCheckerImpl::ReadUpdaterStateAttributes() {
const bool is_machine_install = !config_->IsPerUserInstall();
updater_state_attributes_ = UpdaterState::GetState(is_machine_install);
}
void UpdateCheckerImpl::CheckForUpdatesHelper(
const IdToCrxUpdateItemMap& items_to_check,
const std::string& additional_attributes,
bool enabled_component_updates) {
DCHECK(thread_checker_.CalledOnValidThread());
auto urls(config_->UpdateUrl());
if (IsEncryptionRequired(items_to_check))
RemoveUnsecureUrls(&urls);
std::unique_ptr<std::vector<std::string>> ids_checked(
new std::vector<std::string>());
for (const auto& item : items_to_check)
ids_checked->push_back(item.second->id);
request_sender_.reset(new RequestSender(config_));
request_sender_->Send(
config_->EnabledCupSigning(),
BuildUpdateCheckRequest(*config_, items_to_check, metadata_,
additional_attributes, enabled_component_updates,
updater_state_attributes_),
urls, base::Bind(&UpdateCheckerImpl::OnRequestSenderComplete,
base::Unretained(this), base::Passed(&ids_checked)));
}
void UpdateCheckerImpl::OnRequestSenderComplete(
std::unique_ptr<std::vector<std::string>> ids_checked,
int error,
const std::string& response,
int retry_after_sec) {
DCHECK(thread_checker_.CalledOnValidThread());
if (!error) {
UpdateResponse update_response;
if (update_response.Parse(response)) {
int daynum = update_response.results().daystart_elapsed_days;
if (daynum != UpdateResponse::kNoDaystart)
metadata_->SetDateLastRollCall(*ids_checked, daynum);
for (const auto& result : update_response.results().list) {
auto entry = result.cohort_attrs.find(UpdateResponse::Result::kCohort);
if (entry != result.cohort_attrs.end())
metadata_->SetCohort(result.extension_id, entry->second);
entry = result.cohort_attrs.find(UpdateResponse::Result::kCohortName);
if (entry != result.cohort_attrs.end())
metadata_->SetCohortName(result.extension_id, entry->second);
entry = result.cohort_attrs.find(UpdateResponse::Result::kCohortHint);
if (entry != result.cohort_attrs.end())
metadata_->SetCohortHint(result.extension_id, entry->second);
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(update_check_callback_, error,
update_response.results(), retry_after_sec));
return;
}
error = -1;
VLOG(1) << "Parse failed " << update_response.errors();
}
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::Bind(update_check_callback_, error,
UpdateResponse::Results(), retry_after_sec));
}
} // namespace
std::unique_ptr<UpdateChecker> UpdateChecker::Create(
const scoped_refptr<Configurator>& config,
PersistedData* persistent) {
return std::unique_ptr<UpdateChecker>(
new UpdateCheckerImpl(config, persistent));
}
} // namespace update_client