chromium/chrome/browser/component_updater/updater_state.cc

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