// Copyright 2016 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/component_updater/updater_state.h"
#include <map>
#include <memory>
#include <optional>
#include <string>
#include <string_view>
#include <utility>
#include "base/check.h"
#include "base/enterprise_util.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/json/json_reader.h"
#include "base/json/values_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "build/branding_buildflags.h"
#include "build/build_config.h"
#include "chrome/updater/updater_scope.h"
#include "chrome/updater/util/util.h"
#include "components/update_client/persisted_data.h"
#include "components/update_client/update_client_errors.h"
#if BUILDFLAG(IS_WIN)
#include "chrome/updater/util/win_util.h"
#endif
namespace component_updater {
namespace {
// These literals must not be changed since they affect the forward and
// backward compatibility with //chrome/updater.
constexpr char kUpdaterPrefsActiveVersion[] = "active_version";
constexpr char kUpdaterPrefsLastChecked[] = "last_checked";
constexpr char kUpdaterPrefsLastStarted[] = "last_started";
} // namespace
UpdaterState::State::State() = default;
UpdaterState::State::State(const UpdaterState::State&) = default;
UpdaterState::State& UpdaterState::State::operator=(
const UpdaterState::State&) = default;
UpdaterState::State::~State() = default;
std::unique_ptr<UpdaterState::StateReader> UpdaterState::StateReader::Create(
bool is_machine) {
#if BUILDFLAG(GOOGLE_CHROME_BRANDING)
#if BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
if (std::unique_ptr<StateReader> state_reader_chromium_updater =
[is_machine]() -> std::unique_ptr<StateReader> {
// Create a `StateReaderChromiumUpdater` instance only if a prefs.json
// file for the updater can be found and parsed successfully.
const updater::UpdaterScope updater_scope =
is_machine ? updater::UpdaterScope::kSystem
: updater::UpdaterScope::kUser;
const std::optional<base::FilePath> global_prefs_dir =
#if BUILDFLAG(IS_WIN)
// Google Chrome ships with an x86 updater.
updater::GetInstallDirectoryX86(updater_scope);
#else
updater::GetInstallDirectory(updater_scope);
#endif // IS_WIN
if (!global_prefs_dir)
return nullptr;
std::string contents;
constexpr char kUpdaterPrefsFilename[] = "prefs.json";
constexpr int kMaxPrefsFileSize = 0x20000; // 128KiB.
if (!base::ReadFileToStringWithMaxSize(
global_prefs_dir->AppendASCII(kUpdaterPrefsFilename), &contents,
kMaxPrefsFileSize)) {
return nullptr;
}
std::optional<base::Value::Dict> parsed_json =
base::JSONReader::ReadDict(contents);
return parsed_json ? std::make_unique<StateReaderChromiumUpdater>(
std::move(*parsed_json))
: nullptr;
}()) {
return state_reader_chromium_updater;
}
#endif // BUILDFLAG(IS_MAC) || BUILDFLAG(IS_WIN)
#if BUILDFLAG(IS_MAC)
return std::make_unique<UpdaterState::StateReaderKeystone>();
#elif BUILDFLAG(IS_WIN)
return std::make_unique<UpdaterState::StateReaderOmaha>();
#else
return nullptr;
#endif // IS_MAC
#else
return nullptr;
#endif // GOOGLE_CHROME_BRANDING
}
UpdaterState::StateReaderChromiumUpdater::StateReaderChromiumUpdater(
base::Value::Dict parsed_json)
: parsed_json_(std::move(parsed_json)) {}
base::Time UpdaterState::StateReaderChromiumUpdater::FindTimeKey(
std::string_view key) const {
return base::ValueToTime(parsed_json_.Find(key)).value_or(base::Time());
}
std::string UpdaterState::StateReaderChromiumUpdater::GetUpdaterName() const {
return "ChromiumUpdater";
}
base::Version UpdaterState::StateReaderChromiumUpdater::GetUpdaterVersion(
bool /*is_machine*/) const {
const std::string* val = parsed_json_.FindString(kUpdaterPrefsActiveVersion);
return val ? base::Version(*val) : base::Version();
}
bool UpdaterState::StateReaderChromiumUpdater::IsAutoupdateCheckEnabled()
const {
return UpdaterState::IsAutoupdateCheckEnabled();
}
base::Time UpdaterState::StateReaderChromiumUpdater::GetUpdaterLastStartedAU(
bool /*is_machine*/) const {
return FindTimeKey(kUpdaterPrefsLastStarted);
}
base::Time UpdaterState::StateReaderChromiumUpdater::GetUpdaterLastChecked(
bool /*is_machine*/) const {
return FindTimeKey(kUpdaterPrefsLastChecked);
}
int UpdaterState::StateReaderChromiumUpdater::GetUpdatePolicy() const {
return UpdaterState::GetUpdatePolicy();
}
update_client::CategorizedError
UpdaterState::StateReaderChromiumUpdater::GetLastUpdateCheckError() const {
return {
.category_ = static_cast<update_client::ErrorCategory>(
parsed_json_
.FindInt(update_client::kLastUpdateCheckErrorCategoryPreference)
.value_or(0)),
.code_ =
parsed_json_.FindInt(update_client::kLastUpdateCheckErrorPreference)
.value_or(0),
.extra_ =
parsed_json_
.FindInt(update_client::kLastUpdateCheckErrorExtraCode1Preference)
.value_or(0)};
}
UpdaterState::State UpdaterState::StateReader::Read(bool is_machine) const {
State state;
state.updater_name = GetUpdaterName();
state.updater_version = GetUpdaterVersion(is_machine);
state.last_autoupdate_started = GetUpdaterLastStartedAU(is_machine);
state.last_checked = GetUpdaterLastChecked(is_machine);
state.is_autoupdate_check_enabled = IsAutoupdateCheckEnabled();
state.update_policy = [this] {
const int update_policy = GetUpdatePolicy();
CHECK((update_policy >= 0 && update_policy <= 3) || update_policy == -1);
return update_policy;
}();
state.last_update_check_error = GetLastUpdateCheckError();
return state;
}
UpdaterState::UpdaterState(bool is_machine)
: is_machine_(is_machine), state_(ReadState(is_machine)) {}
UpdaterState::~UpdaterState() = default;
UpdaterState::Attributes UpdaterState::GetState(bool is_machine) {
#if BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
return UpdaterState(is_machine).Serialize();
#else
return {};
#endif // BUILDFLAG(IS_WIN) || BUILDFLAG(IS_MAC)
}
std::optional<UpdaterState::State> UpdaterState::ReadState(bool is_machine) {
std::unique_ptr<UpdaterState::StateReader> state_reader =
UpdaterState::StateReader::Create(is_machine);
if (!state_reader) {
return std::nullopt;
}
return state_reader->Read(is_machine);
}
UpdaterState::Attributes UpdaterState::Serialize() const {
Attributes attributes;
attributes["ismachine"] = is_machine_ ? "1" : "0";
if (state_) {
attributes["name"] = state_->updater_name;
if (state_->updater_version.IsValid()) {
attributes["version"] = state_->updater_version.GetString();
}
const base::Time now = base::Time::NowFromSystemTime();
if (!state_->last_autoupdate_started.is_null()) {
attributes["laststarted"] =
NormalizeTimeDelta(now - state_->last_autoupdate_started);
}
if (!state_->last_checked.is_null()) {
attributes["lastchecked"] =
NormalizeTimeDelta(now - state_->last_checked);
}
attributes["autoupdatecheckenabled"] =
state_->is_autoupdate_check_enabled ? "1" : "0";
attributes["updatepolicy"] = base::NumberToString(state_->update_policy);
attributes["lastupdatecheckerror"] = state_->last_update_check_error.code_;
attributes["lastupdatecheckerrorcategory"] =
static_cast<int>(state_->last_update_check_error.category_);
attributes["lastupdatecheckerrorextracode1"] =
state_->last_update_check_error.extra_;
}
return attributes;
}
std::string UpdaterState::NormalizeTimeDelta(const base::TimeDelta& delta) {
const base::TimeDelta two_weeks = base::Days(14);
const base::TimeDelta two_months = base::Days(56);
std::string val; // Contains the value to return in hours.
if (delta <= two_weeks) {
val = "0";
} else if (two_weeks < delta && delta <= two_months) {
val = "336"; // 2 weeks in hours.
} else {
val = "1344"; // 2*28 days in hours.
}
CHECK(!val.empty());
return val;
}
} // namespace component_updater