chromium/chrome/installer/util/installation_state.cc

// Copyright 2012 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/installer/util/installation_state.h"

#include <memory>
#include <string>
#include <string_view>

#include "base/check.h"
#include "base/strings/cstring_view.h"
#include "base/strings/string_util.h"
#include "base/strings/utf_string_conversions.h"
#include "base/version.h"
#include "base/win/registry.h"
#include "build/build_config.h"
#include "chrome/install_static/install_util.h"
#include "chrome/installer/util/app_commands.h"
#include "chrome/installer/util/google_update_constants.h"
#include "chrome/installer/util/helper.h"
#include "chrome/installer/util/install_util.h"
#include "chrome/installer/util/util_constants.h"

namespace installer {

namespace {

// Initializes |commands| from the "Commands" subkey of |version_key|. Returns
// false if there is no "Commands" subkey or on error.
bool InitializeCommands(const base::win::RegKey& clients_key,
                        AppCommands* commands) {
  static const DWORD kAccess =
      KEY_ENUMERATE_SUB_KEYS | KEY_QUERY_VALUE | KEY_WOW64_32KEY;
  base::win::RegKey commands_key;

  if (commands_key.Open(clients_key.Handle(), google_update::kRegCommandsKey,
                        kAccess) == ERROR_SUCCESS) {
    return commands->Initialize(commands_key, KEY_WOW64_32KEY);
  }
  return false;
}

}  // namespace

ProductState::ProductState()
    : uninstall_command_(base::CommandLine::NO_PROGRAM),
      eula_accepted_(0),
      usagestats_(0),
      msi_(false),
      has_eula_accepted_(false),
      has_oem_install_(false),
      has_usagestats_(false) {}

ProductState::~ProductState() {}

bool ProductState::Initialize(bool system_install) {
  static const DWORD kAccess = KEY_QUERY_VALUE | KEY_WOW64_32KEY;
  const std::wstring clients_key(install_static::GetClientsKeyPath());
  const std::wstring state_key(install_static::GetClientStateKeyPath());
  const HKEY root_key = system_install ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER;
  base::win::RegKey key;

  // Clear the runway.
  Clear();

  // Read from the Clients key.
  if (key.Open(root_key, clients_key.c_str(), kAccess) == ERROR_SUCCESS) {
    std::wstring version_str;
    if (key.ReadValue(google_update::kRegVersionField, &version_str) ==
        ERROR_SUCCESS) {
      version_ =
          std::make_unique<base::Version>(base::WideToASCII(version_str));
      if (!version_->IsValid())
        version_.reset();
    }

    // Attempt to read the other values even if the "pv" version value was
    // absent. Note that ProductState instances containing these values will
    // only be accessible via InstallationState::GetNonVersionedProductState.
    if (key.ReadValue(google_update::kRegOldVersionField, &version_str) ==
        ERROR_SUCCESS) {
      old_version_ =
          std::make_unique<base::Version>(base::WideToASCII(version_str));
      if (!old_version_->IsValid())
        old_version_.reset();
    }

    if (key.ReadValue(google_update::kRegChannelField, &channel_) !=
        ERROR_SUCCESS) {
      channel_.clear();
    }

    if (!InitializeCommands(key, &commands_))
      commands_.Clear();
  }

  // Read from the ClientState key.
  if (key.Open(root_key, state_key.c_str(), kAccess) == ERROR_SUCCESS) {
    std::wstring setup_path;
    std::wstring uninstall_arguments;

    // Read in the brand code, it may be absent
    key.ReadValue(google_update::kRegBrandField, &brand_);

    key.ReadValue(kUninstallStringField, &setup_path);
    key.ReadValue(kUninstallArgumentsField, &uninstall_arguments);
    InstallUtil::ComposeCommandLine(setup_path, uninstall_arguments,
                                    &uninstall_command_);

    // "usagestats" may be absent, 0 (false), or 1 (true).  On the chance that
    // different values are permitted in the future, we'll simply hold whatever
    // we find.
    has_usagestats_ = (key.ReadValueDW(google_update::kRegUsageStatsField,
                                       &usagestats_) == ERROR_SUCCESS);
    // "oeminstall" may be present with any value or absent.
    has_oem_install_ = (key.ReadValue(google_update::kRegOemInstallField,
                                      &oem_install_) == ERROR_SUCCESS);
    // "eulaaccepted" may be absent, 0 or 1.
    has_eula_accepted_ = (key.ReadValueDW(google_update::kRegEulaAceptedField,
                                          &eula_accepted_) == ERROR_SUCCESS);
    // "msi" may be absent, 0 or 1
    DWORD dw_value = 0;
    msi_ = (key.ReadValueDW(google_update::kRegMSIField, &dw_value) ==
            ERROR_SUCCESS) &&
           (dw_value != 0);

    constexpr base::wcstring_view kEnterpriseProductPrefix(
        L"EnterpriseProduct");
    for (base::win::RegistryValueIterator iter(root_key, state_key.c_str(),
                                               KEY_WOW64_32KEY);
         iter.Valid(); ++iter) {
      std::wstring_view value_name(iter.Name());
      if (base::StartsWith(value_name, kEnterpriseProductPrefix,
                           base::CompareCase::INSENSITIVE_ASCII)) {
        product_guid_ = value_name.substr(kEnterpriseProductPrefix.size());
        break;
      }
    }
  }

  // Read from the ClientStateMedium key.  Values here override those in
  // ClientState.
  if (system_install &&
      key.Open(root_key, install_static::GetClientStateMediumKeyPath().c_str(),
               kAccess) == ERROR_SUCCESS) {
    DWORD dword_value = 0;

    if (key.ReadValueDW(google_update::kRegUsageStatsField, &dword_value) ==
        ERROR_SUCCESS) {
      has_usagestats_ = true;
      usagestats_ = dword_value;
    }

    if (key.ReadValueDW(google_update::kRegEulaAceptedField, &dword_value) ==
        ERROR_SUCCESS) {
      has_eula_accepted_ = true;
      eula_accepted_ = dword_value;
    }
  }

  if (version_.get() && uninstall_command_.GetProgram().empty()) {
    // The product has a "pv" in its Clients key, but is missing the
    // "UninstallString" value expected in its ClientState key. Manufacture the
    // expected uninstall command on the basis of what appears to be present on
    // the machine.

    // FindInstallPath returns the path to a version directory if one exists in
    // any of the standard install locations.
    if (const auto version_dir = FindInstallPath(system_install, *version_);
        !version_dir.empty()) {
      uninstall_command_ = base::CommandLine(
          version_dir.Append(kInstallerDir).Append(kSetupExe));
      uninstall_command_.AppendSwitch(switches::kUninstall);
      InstallUtil::AppendModeAndChannelSwitches(&uninstall_command_);

      // Note that `AppendModeAndChannelSwitches` will use the current process's
      // channel. When called from within a browser process, this will be
      // correct. When called from within the installer, it will not be. To
      // ensure that the browser's notion of the channel is always used, strip
      // off a value added above and explicitly add the value from the Clients
      // key.
      uninstall_command_.RemoveSwitch(switches::kChannel);
      if (!channel_.empty()) {
        uninstall_command_.AppendSwitchNative(switches::kChannel, channel_);
      }

      if (system_install) {
        uninstall_command_.AppendSwitch(switches::kSystemLevel);
      }
      uninstall_command_.AppendSwitch(switches::kVerboseLogging);
    }
  }

  if (version_.get() && system_install && (!msi_ || product_guid_.empty())) {
    // A system-level install may be missing the "msi" and/or
    // "EnterpriseProduct" values in ClientState. To repair from this, search
    // for an ARP entry under a product guid with a display name matching this
    // product.
    if (auto product_guid =
            FindProductGuid(InstallUtil::GetDisplayName(), product_guid_);
        !product_guid.empty()) {
      msi_ = true;
      product_guid_ = std::move(product_guid);
      // Add `--msi` to the uninstall args if it is missing.
      if (!uninstall_command_.HasSwitch(switches::kMsi)) {
        uninstall_command_.AppendSwitch(switches::kMsi);
      }
    }
  }

  return version_.get() != nullptr;
}

base::FilePath ProductState::GetSetupPath() const {
  return uninstall_command_.GetProgram();
}

const base::Version& ProductState::version() const {
  DCHECK(version_);
  return *version_;
}

ProductState& ProductState::CopyFrom(const ProductState& other) {
  version_.reset(other.version_.get() ? new base::Version(*other.version_)
                                      : nullptr);
  old_version_.reset(other.old_version_.get()
                         ? new base::Version(*other.old_version_)
                         : nullptr);
  channel_ = other.channel_;
  brand_ = other.brand_;
  uninstall_command_ = other.uninstall_command_;
  product_guid_ = other.product_guid_;
  oem_install_ = other.oem_install_;
  commands_.CopyFrom(other.commands_);
  eula_accepted_ = other.eula_accepted_;
  usagestats_ = other.usagestats_;
  msi_ = other.msi_;
  has_eula_accepted_ = other.has_eula_accepted_;
  has_oem_install_ = other.has_oem_install_;
  has_usagestats_ = other.has_usagestats_;

  return *this;
}

void ProductState::Clear() {
  version_.reset();
  old_version_.reset();
  channel_.clear();
  brand_.clear();
  oem_install_.clear();
  uninstall_command_ = base::CommandLine(base::CommandLine::NO_PROGRAM);
  product_guid_.clear();
  commands_.Clear();
  eula_accepted_ = 0;
  usagestats_ = 0;
  msi_ = false;
  has_eula_accepted_ = false;
  has_oem_install_ = false;
  has_usagestats_ = false;
}

bool ProductState::GetEulaAccepted(DWORD* eula_accepted) const {
  DCHECK(eula_accepted);
  if (!has_eula_accepted_)
    return false;
  *eula_accepted = eula_accepted_;
  return true;
}

bool ProductState::GetOemInstall(std::wstring* oem_install) const {
  DCHECK(oem_install);
  if (!has_oem_install_)
    return false;
  *oem_install = oem_install_;
  return true;
}

bool ProductState::GetUsageStats(DWORD* usagestats) const {
  DCHECK(usagestats);
  if (!has_usagestats_)
    return false;
  *usagestats = usagestats_;
  return true;
}

// static
std::wstring ProductState::FindProductGuid(std::wstring_view display_name,
                                           std::wstring_view hint) {
  constexpr base::wcstring_view kUninstallRootKey(
      L"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\");
  constexpr size_t kGuidLength = 36;  // Does not include braces.
  constexpr REGSAM kViews[] = {
      0,  // The default view for this bitness.
#if defined(ARCH_CPU_64_BITS)
      KEY_WOW64_32KEY,  // 32-bit view.
#else
      KEY_WOW64_64KEY,  // 64-bit view.
#endif
  };
  // Returns true if the DisplayName value for the uninstall entry for `name` in
  // `view` of HKLM equals `display_name`.
  auto display_name_is = [&kUninstallRootKey, storage = std::wstring()](
                             REGSAM view, std::wstring_view name,
                             std::wstring_view display_name) mutable {
    return base::win::RegKey(HKEY_LOCAL_MACHINE,
                             base::StrCat({kUninstallRootKey, name}).c_str(),
                             KEY_QUERY_VALUE | view)
                   .ReadValue(kUninstallDisplayNameField, &storage) ==
               ERROR_SUCCESS &&
           storage == display_name;
  };

  // If the caller provided a hint, look first for an entry for it.
  if (!hint.empty()) {
    const std::wstring name = base::StrCat({L"{", hint, L"}"});
    for (const REGSAM view : kViews) {
      if (display_name_is(view, name, display_name)) {
        return std::wstring(hint);
      }
    }
  }

  // Otherwise, search through all subkeys named with GUIDs looking for a hit.
  for (const REGSAM view : kViews) {
    for (base::win::RegistryKeyIterator iter(HKEY_LOCAL_MACHINE,
                                             kUninstallRootKey.c_str(), view);
         iter.Valid(); ++iter) {
      const std::wstring_view key_name(iter.Name());
      // Skip this key if it doesn't plausibly look like a product guid.
      if (key_name.size() != kGuidLength + 2 || key_name.front() != L'{' ||
          key_name.back() != L'}') {
        continue;
      }
      if (display_name_is(view, key_name, display_name)) {
        return std::wstring(key_name.substr(1, kGuidLength));
      }
    }
  }

  return {};
}

InstallationState::InstallationState() {}

void InstallationState::Initialize() {
  user_chrome_.Initialize(false);
  system_chrome_.Initialize(true);
}

const ProductState* InstallationState::GetProductState(
    bool system_install) const {
  const ProductState* product_state =
      GetNonVersionedProductState(system_install);
  return product_state->version_.get() ? product_state : nullptr;
}

const ProductState* InstallationState::GetNonVersionedProductState(
    bool system_install) const {
  return system_install ? &system_chrome_ : &user_chrome_;
}

}  // namespace installer