chromium/chrome/credential_provider/setup/setup_utils.cc

// Copyright 2020 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/credential_provider/setup/setup_utils.h"

#include <Windows.h>

#include <string>

#include "base/files/file_util.h"
#include "base/json/json_string_value_serializer.h"
#include "base/strings/utf_string_conversions.h"
#include "base/win/registry.h"
#include "chrome/common/chrome_version.h"
#include "chrome/credential_provider/common/gcp_strings.h"
#include "chrome/credential_provider/gaiacp/gaia_resources.h"
#include "chrome/credential_provider/gaiacp/gcp_utils.h"
#include "chrome/credential_provider/gaiacp/logging.h"
#include "chrome/credential_provider/gaiacp/reg_utils.h"
#include "chrome/credential_provider/setup/gcpw_files.h"

namespace credential_provider {

namespace switches {

const char kParentHandle[] = "parent-handle";
const char kInstallPath[] = "install-path";
const char kUninstall[] = "uninstall";

const char kEnableStats[] = "enable-stats";
const char kDisableStats[] = "disable-stats";

const char kInstallerData[] = "installerdata";

const char kStandaloneInstall[] = "standalone";
}  // namespace switches

namespace {

// Path to the msi json value inside the dictionary which is parsed from
// installer data argument in the command line.
const char kMsiJsonPath[] = "distribution.msi";

// The registry name which is saved to indicate installation source.
const wchar_t kMsiInstall[] = L"msi";

// Parses the json data and returns it as a dictionary. If the json data isn't
// valid, returns std::nullopt.
std::optional<base::Value::Dict> ParseDistributionPreferences(
    const std::string& json_data) {
  JSONStringValueDeserializer json(json_data);
  std::string error;
  std::unique_ptr<base::Value> root(json.Deserialize(nullptr, &error));
  if (!root.get()) {
    LOGFN(WARNING) << "Failed to parse initial prefs file: " << error;
    return std::nullopt;
  }
  if (!root->is_dict()) {
    LOGFN(WARNING) << "Failed to parse installer data file";
    return std::nullopt;
  }
  return std::move(*root).TakeDict();
}

}  // namespace

StandaloneInstallerConfigurator::StandaloneInstallerConfigurator()
    : is_msi_installation_(false) {}

StandaloneInstallerConfigurator::~StandaloneInstallerConfigurator() {}

// static
StandaloneInstallerConfigurator**
StandaloneInstallerConfigurator::GetInstanceStorage() {
  static StandaloneInstallerConfigurator* instance =
      new StandaloneInstallerConfigurator();

  return &instance;
}

// static
StandaloneInstallerConfigurator* StandaloneInstallerConfigurator::Get() {
  return *GetInstanceStorage();
}

// Sets the installer source for GCPW. When installed through MSI,
// contains installer data file name as argument.
void StandaloneInstallerConfigurator::ConfigureInstallationType(
    const base::CommandLine& cmdline) {
  // There are following scenarios for installations:
  // First time install from MSI
  // First time install from EXE
  // MSIs before this kMsiInstall registry gets auto-updated
  // MSIs with kMsiInstall registry gets auto-updated
  // EXEs with kMsiInstall registry gets auto-updated

  // |kStandaloneInstall| indicates fresh installation.
  if (cmdline.HasSwitch(switches::kStandaloneInstall)) {
    base::Value* is_msi = nullptr;
    if (cmdline.HasSwitch(switches::kInstallerData)) {
      base::FilePath prefs_path(
          cmdline.GetSwitchValuePath(switches::kInstallerData));

      if (InitializeFromInstallerData(prefs_path))
        is_msi = installer_data_dictionary_.FindByDottedPath(kMsiJsonPath);
    }

    is_msi_installation_ = false;
    if (is_msi && is_msi->is_bool() && is_msi->GetBool()) {
      is_msi_installation_ = true;
    }
  } else {
    // Honor the registry if it is found, otherwise fall back to MSI
    // installation.
    is_msi_installation_ =
        GetUpdaterClientsAppPathFlagOrDefault(kMsiInstall, 1);
  }

  HRESULT hr =
      SetUpdaterClientsAppPathFlag(kMsiInstall, is_msi_installation_ ? 1 : 0);
  if (FAILED(hr))
    LOGFN(ERROR) << "SetGlobalFlag failed" << putHR(hr);
}

std::wstring StandaloneInstallerConfigurator::GetCurrentDate() {
  static const wchar_t kDateFormat[] = L"yyyyMMdd";
  wchar_t date_str[std::size(kDateFormat)] = {0};
  int len = GetDateFormatW(LOCALE_INVARIANT, 0, nullptr, kDateFormat, date_str,
                           std::size(date_str));
  if (len) {
    --len;  // Subtract terminating \0.
  } else {
    LOGFN(ERROR) << "GetDateFormat failed";
    return L"";
  }

  return std::wstring(date_str, len);
}

bool StandaloneInstallerConfigurator::IsStandaloneInstallation() const {
  return !is_msi_installation_;
}

HRESULT StandaloneInstallerConfigurator::AddUninstallKey(
    const base::FilePath& install_path) {
  LOGFN(VERBOSE);

  if (is_msi_installation_)
    return S_OK;

  std::wstring uninstall_reg = kRegUninstall;
  uninstall_reg.append(L"\\");
  uninstall_reg.append(kRegUninstallProduct);

  base::win::RegKey key;
  LONG status =
      key.Create(HKEY_LOCAL_MACHINE, uninstall_reg.c_str(), KEY_SET_VALUE);
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to create " << uninstall_reg << " hr=" << putHR(hr);
    return hr;
  }

  base::CommandLine uninstall_string(
      install_path.Append(kCredentialProviderSetupExe));
  uninstall_string.AppendSwitch(switches::kUninstall);

  status = key.WriteValue(kRegUninstallString,
                          uninstall_string.GetCommandLineString().c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegUninstallString
                 << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegUninstallDisplayName,
                          GetStringResource(IDS_PROJNAME_BASE).c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegUninstallDisplayName
                 << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegInstallLocation, install_path.value().c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegInstallLocation
                 << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(
      kRegDisplayIcon,
      (install_path.Append(kCredentialProviderSetupExe).value() + L",0")
          .c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegDisplayIcon
                 << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegNoModify, 1);
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegNoModify << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegNoRepair, 1);
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegNoRepair << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegPublisherName, kRegPublisher);
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegPublisherName
                 << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegInstallDate, GetCurrentDate().c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegInstallDate
                 << " hr=" << putHR(hr);
    return hr;
  }

  base::Version version(CHROME_VERSION_STRING);

  status = key.WriteValue(kRegVersion,
                          base::ASCIIToWide(version.GetString()).c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegVersion << " hr=" << putHR(hr);
    return hr;
  }

  status = key.WriteValue(kRegDisplayVersion,
                          base::ASCIIToWide(version.GetString()).c_str());
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to write " << kRegDisplayVersion
                 << " hr=" << putHR(hr);
    return hr;
  }

  const std::vector<uint32_t>& version_components = version.components();
  if (version_components.size() == 4) {
    status = key.WriteValue(kRegVersionMajor,
                            static_cast<DWORD>(version_components[2]));
    if (status != ERROR_SUCCESS) {
      HRESULT hr = HRESULT_FROM_WIN32(status);
      LOGFN(ERROR) << "Unable to write " << kRegVersionMajor
                   << " hr=" << putHR(hr);
      return hr;
    }

    status = key.WriteValue(kRegVersionMinor,
                            static_cast<DWORD>(version_components[3]));
    if (status != ERROR_SUCCESS) {
      HRESULT hr = HRESULT_FROM_WIN32(status);
      LOGFN(ERROR) << "Unable to write " << kRegVersionMinor
                   << " hr=" << putHR(hr);
      return hr;
    }
  }

  return S_OK;
}

HRESULT StandaloneInstallerConfigurator::RemoveUninstallKey() {
  LOGFN(VERBOSE);
  base::win::RegKey key;

  LONG status = key.Create(HKEY_LOCAL_MACHINE, kRegUninstall, KEY_SET_VALUE);
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to create " << kRegUninstall << " hr=" << putHR(hr);
    return hr;
  }

  status = key.DeleteKey(kRegUninstallProduct);
  if (status != ERROR_SUCCESS) {
    HRESULT hr = HRESULT_FROM_WIN32(status);
    LOGFN(ERROR) << "Unable to delete " << kRegUninstallProduct
                 << " hr=" << putHR(hr);
    return hr;
  }
  return S_OK;
}

bool StandaloneInstallerConfigurator::InitializeFromInstallerData(
    base::FilePath prefs_path) {
  std::string json_data;
  if (base::PathExists(prefs_path) &&
      !base::ReadFileToString(prefs_path, &json_data)) {
    LOGFN(ERROR) << "Failed to read preferences from " << prefs_path.value();
    return false;
  }

  if (json_data.empty()) {
    LOGFN(WARNING) << "Installer data is empty!";
    return false;
  }

  std::optional<base::Value::Dict> prefs =
      ParseDistributionPreferences(json_data);
  if (!prefs) {
    LOGFN(WARNING) << "Installer data isn't formatted correctly";
    return false;
  }

  installer_data_dictionary_ = std::move(prefs).value();

  return true;
}

}  // namespace credential_provider