chromium/chrome/installer/util/helper_unittest.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/installer/util/helper.h"

#include <windows.h>

#include <optional>
#include <string_view>

#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/path_service.h"
#include "base/test/scoped_path_override.h"
#include "base/test/test_reg_util_win.h"
#include "base/values.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/google_update_constants.h"
#include "chrome/installer/util/initial_preferences.h"
#include "chrome/installer/util/util_constants.h"
#include "testing/gtest/include/gtest/gtest.h"

namespace installer {

namespace {

// A helper that overrides an environment variable for the lifetime of an
// instance.
class ScopedEnvironmentOverride {
 public:
  ScopedEnvironmentOverride(std::wstring_view name, const wchar_t* new_value)
      : name_(name) {
    std::array<wchar_t, MAX_PATH> value;
    value[0] = L'\0';
    DWORD len =
        ::GetEnvironmentVariableW(name_.c_str(), value.data(), value.size());
    if (len > 0 && len < value.size()) {
      old_value_.emplace(value.data(), len);
    }
    ::SetEnvironmentVariableW(name_.c_str(), new_value);
  }
  ~ScopedEnvironmentOverride() {
    ::SetEnvironmentVariableW(name_.c_str(),
                              old_value_ ? old_value_->c_str() : nullptr);
  }

 private:
  const std::wstring name_;
  std::optional<std::wstring> old_value_;
};

}  // namespace

class GetInstalledDirectoryTest : public testing::TestWithParam<bool> {
 protected:
  void SetUp() override {
    ASSERT_TRUE(random_.CreateUniqueTempDir());
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
  }

  static bool is_system_level() { return GetParam(); }
  base::FilePath random_path() const { return random_.GetPath(); }

  static base::win::RegKey GetClientsRegKey() {
    return base::win::RegKey(
        is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
        install_static::GetClientsKeyPath().c_str(),
        KEY_SET_VALUE | KEY_WOW64_32KEY);
  }

  static base::win::RegKey GetClientStateRegKey() {
    return base::win::RegKey(
        is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
        install_static::GetClientStateKeyPath().c_str(),
        KEY_SET_VALUE | KEY_WOW64_32KEY);
  }

 private:
  base::ScopedTempDir random_;
  registry_util::RegistryOverrideManager registry_override_manager_;
};

TEST_P(GetInstalledDirectoryTest, NoRegistryValue) {
  EXPECT_EQ(GetInstalledDirectory(is_system_level()), base::FilePath());
}

TEST_P(GetInstalledDirectoryTest, RegistryValueSet) {
  const base::FilePath install_path =
      random_path()
          .Append(install_static::GetChromeInstallSubDirectory())
          .Append(kInstallBinaryDir);
  ASSERT_TRUE(base::CreateDirectory(install_path));
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                install_path.AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);

  EXPECT_EQ(GetInstalledDirectory(is_system_level()), install_path);
}

TEST_P(GetInstalledDirectoryTest, NoDirectory) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                random_path()
                    .Append(install_static::GetChromeInstallSubDirectory())
                    .Append(kInstallBinaryDir)
                    .AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);
  EXPECT_EQ(GetInstalledDirectory(is_system_level()), base::FilePath());
}

TEST_P(GetInstalledDirectoryTest, ReferencesParent) {
  const base::FilePath install_path =
      random_path()
          .Append(install_static::GetChromeInstallSubDirectory())
          .Append(kInstallBinaryDir);
  ASSERT_TRUE(base::CreateDirectory(install_path));
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                install_path
                    .AppendASCII("1.0.0.0\\Installer\\..\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);

  EXPECT_EQ(GetInstalledDirectory(is_system_level()), base::FilePath());
}

TEST_P(GetInstalledDirectoryTest, NotAbsolute) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                base::FilePath(install_static::GetChromeInstallSubDirectory())
                    .Append(kInstallBinaryDir)
                    .AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);

  EXPECT_EQ(GetInstalledDirectory(is_system_level()), base::FilePath());
}

TEST_P(GetInstalledDirectoryTest, AtRoot) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(
      client_state_key.WriteValue(
          kUninstallStringField,
          base::FilePath(FILE_PATH_LITERAL("C:\\1.0.0.0\\Installer\\setup.exe"))
              .value()
              .c_str()),
      ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);

  EXPECT_EQ(GetInstalledDirectory(is_system_level()), base::FilePath());
}

TEST_P(GetInstalledDirectoryTest, RegistryValueSetWrongScope) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                random_path()
                    .Append(install_static::GetChromeInstallSubDirectory())
                    .Append(kInstallBinaryDir)
                    .AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);
  EXPECT_EQ(GetInstalledDirectory(!is_system_level()), base::FilePath());
}

TEST_P(GetInstalledDirectoryTest, RegistryValueSetNoProductVersion) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                random_path()
                    .Append(install_static::GetChromeInstallSubDirectory())
                    .Append(kInstallBinaryDir)
                    .AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);
  EXPECT_EQ(GetInstalledDirectory(is_system_level()), base::FilePath());
}

INSTANTIATE_TEST_SUITE_P(UserLevelTest,
                         GetInstalledDirectoryTest,
                         testing::Values(false));
INSTANTIATE_TEST_SUITE_P(SystemLevelTest,
                         GetInstalledDirectoryTest,
                         testing::Values(true));

class GetDefaultChromeInstallPathTest : public testing::TestWithParam<bool> {
 protected:
  void SetUp() override {
    ASSERT_TRUE(program_files_.CreateUniqueTempDir());
    ASSERT_TRUE(program_files_x86_.CreateUniqueTempDir());
    ASSERT_TRUE(local_app_data_.CreateUniqueTempDir());
    program_files_override_.emplace(base::DIR_PROGRAM_FILES,
                                    program_files_path());
    program_files_x86_override_.emplace(base::DIR_PROGRAM_FILESX86,
                                        program_files_x86_path());
    local_data_app_override_.emplace(base::DIR_LOCAL_APP_DATA,
                                     local_app_data_path());
  }

  static bool is_system_level() { return GetParam(); }
  base::FilePath program_files_path() const { return program_files_.GetPath(); }
  base::FilePath program_files_x86_path() const {
    return program_files_x86_.GetPath();
  }
  base::FilePath local_app_data_path() const {
    return local_app_data_.GetPath();
  }

  base::FilePath GetExpectedPath(bool system_level) const {
    auto path = system_level ? program_files_path() : local_app_data_path();
    return path.Append(install_static::GetChromeInstallSubDirectory())
        .Append(kInstallBinaryDir);
  }

 private:
  base::ScopedTempDir program_files_;
  base::ScopedTempDir program_files_x86_;
  base::ScopedTempDir local_app_data_;
  std::optional<base::ScopedPathOverride> program_files_override_;
  std::optional<base::ScopedPathOverride> program_files_x86_override_;
  std::optional<base::ScopedPathOverride> local_data_app_override_;
};

// Tests that the PathService is used to get the default install path.
TEST_P(GetDefaultChromeInstallPathTest, PathService) {
  EXPECT_EQ(GetDefaultChromeInstallPath(is_system_level()),
            GetExpectedPath(is_system_level()));
}

// Tests that the environment variable fallback is used if the PathService
// returns a path that doesn't exist.
TEST_P(GetDefaultChromeInstallPathTest, EnvironmentFallback) {
  // Override the relevant paths with directories that do not exist so that the
  // env var fallback is reached.
  base::ScopedPathOverride bad_program_files_override(
      base::DIR_PROGRAM_FILES, program_files_path().AppendASCII("doesnotexist"),
      /*is_absolute=*/true, /*create=*/false);
  base::ScopedPathOverride bad_program_files_x86_override(
      base::DIR_PROGRAM_FILESX86,
      program_files_x86_path().AppendASCII("doesnotexist"),
      /*is_absolute=*/true, /*create=*/false);
  base::ScopedPathOverride bad_local_ap_data_override(
      base::DIR_LOCAL_APP_DATA,
      local_app_data_path().AppendASCII("doesnotexist"),
      /*is_absolute=*/true, /*create=*/false);

  ScopedEnvironmentOverride program_files_env(
      L"PROGRAMFILES", program_files_path().value().c_str());
  ScopedEnvironmentOverride program_files_x86_env(
      L"PROGRAMFILES(X86)", program_files_x86_path().value().c_str());
  ScopedEnvironmentOverride local_app_data_env(
      L"LOCALAPPDATA", local_app_data_path().value().c_str());

  EXPECT_EQ(GetDefaultChromeInstallPath(is_system_level()),
            GetExpectedPath(is_system_level()));
}

INSTANTIATE_TEST_SUITE_P(UserLevelTest,
                         GetDefaultChromeInstallPathTest,
                         testing::Values(false));
INSTANTIATE_TEST_SUITE_P(SystemLevelTest,
                         GetDefaultChromeInstallPathTest,
                         testing::Values(true));

struct Params {
  Params(bool system_level, std::optional<int> target_dir_key)
      : system_level(system_level), target_dir_key(target_dir_key) {}
  bool system_level;
  std::optional<int> target_dir_key;
};

// Tests GetChromeInstallPath with a params object that contains a boolean
// |system_level| which is |true| if the test must use system-level values or
// |false| it the test must use user-level values, and a |target_dir| path in
// which the installation should be made.
class GetChromeInstallPathWithPrefsTest
    : public testing::TestWithParam<Params> {
 protected:
  void SetUp() override {
    ASSERT_TRUE(program_files_.CreateUniqueTempDir());
    ASSERT_TRUE(program_files_x86_.CreateUniqueTempDir());
    ASSERT_TRUE(random_.CreateUniqueTempDir());
    ASSERT_TRUE(local_app_data_.CreateUniqueTempDir());
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
    ASSERT_NO_FATAL_FAILURE(
        registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
    program_files_override_.emplace(base::DIR_PROGRAM_FILES,
                                    program_files_path());
    program_files_x86_override_.emplace(base::DIR_PROGRAM_FILESX86,
                                        program_files_x86_path());
    local_data_app_override_.emplace(base::DIR_LOCAL_APP_DATA,
                                     local_app_data_path());
  }

  base::FilePath random_path() const { return random_.GetPath(); }
  base::FilePath program_files_path() const { return program_files_.GetPath(); }
  base::FilePath program_files_x86_path() const {
    return program_files_x86_.GetPath();
  }
  base::FilePath local_app_data_path() const {
    return local_app_data_.GetPath();
  }
  static bool is_system_level() { return GetParam().system_level; }

  base::FilePath GetExpectedPath(bool system_level) {
    auto path = system_level ? program_files_path() : local_app_data_path();
    return path.Append(install_static::GetChromeInstallSubDirectory())
        .Append(kInstallBinaryDir);
  }

  static base::win::RegKey GetClientsRegKey() {
    return base::win::RegKey(
        is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
        install_static::GetClientsKeyPath().c_str(),
        KEY_SET_VALUE | KEY_WOW64_32KEY);
  }

  static base::win::RegKey GetClientStateRegKey() {
    return base::win::RegKey(
        is_system_level() ? HKEY_LOCAL_MACHINE : HKEY_CURRENT_USER,
        install_static::GetClientStateKeyPath().c_str(),
        KEY_SET_VALUE | KEY_WOW64_32KEY);
  }

  base::FilePath GetExpectedPathForSetup(bool system_level,
                                         base::FilePath target_dir) {
    if (system_level && !target_dir.empty() &&
        (target_dir == program_files_path() ||
         target_dir == program_files_x86_path())) {
      return target_dir.Append(install_static::GetChromeInstallSubDirectory())
          .Append(kInstallBinaryDir);
    }
    return GetExpectedPath(system_level);
  }

  static base::FilePath target_dir() {
    base::FilePath result;
    if (GetParam().target_dir_key.has_value())
      base::PathService::Get(GetParam().target_dir_key.value(), &result);
    return result;
  }

  static base::Value::Dict prefs_json() {
    base::FilePath result;
    if (GetParam().target_dir_key.has_value())
      base::PathService::Get(GetParam().target_dir_key.value(), &result);
    base::Value::Dict distribution;
    distribution.SetByDottedPath("distribution.program_files_dir",
                                 result.AsUTF8Unsafe());
    return distribution;
  }

 private:
  base::ScopedTempDir program_files_;
  base::ScopedTempDir program_files_x86_;
  base::ScopedTempDir random_;
  base::ScopedTempDir local_app_data_;
  registry_util::RegistryOverrideManager registry_override_manager_;
  std::optional<base::ScopedPathOverride> program_files_override_;
  std::optional<base::ScopedPathOverride> program_files_x86_override_;
  std::optional<base::ScopedPathOverride> local_data_app_override_;
};

TEST_P(GetChromeInstallPathWithPrefsTest, NoRegistryValue) {
  EXPECT_EQ(GetChromeInstallPathWithPrefs(is_system_level(),
                                          InitialPreferences(prefs_json())),
            GetExpectedPathForSetup(is_system_level(), target_dir()));
}

TEST_P(GetChromeInstallPathWithPrefsTest, RegistryValueSet) {
  const base::FilePath install_path =
      random_path()
          .Append(install_static::GetChromeInstallSubDirectory())
          .Append(kInstallBinaryDir);
  ASSERT_TRUE(base::CreateDirectory(install_path));
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                install_path.AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);
  EXPECT_EQ(GetChromeInstallPathWithPrefs(is_system_level(),
                                          InitialPreferences(prefs_json())),
            install_path);
}

TEST_P(GetChromeInstallPathWithPrefsTest, RegistryValueSetWrongScope) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                random_path()
                    .Append(install_static::GetChromeInstallSubDirectory())
                    .Append(kInstallBinaryDir)
                    .AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);

  base::win::RegKey client_key(GetClientsRegKey());
  ASSERT_EQ(client_key.WriteValue(google_update::kRegVersionField, L"1.0.0.0"),
            ERROR_SUCCESS);
  EXPECT_EQ(GetChromeInstallPathWithPrefs(!is_system_level(),
                                          InitialPreferences(prefs_json())),
            GetExpectedPathForSetup(!is_system_level(), target_dir()));
}

TEST_P(GetChromeInstallPathWithPrefsTest, RegistryValueSetNoProductVersion) {
  base::win::RegKey client_state_key(GetClientStateRegKey());
  ASSERT_EQ(client_state_key.WriteValue(
                kUninstallStringField,
                random_path()
                    .Append(install_static::GetChromeInstallSubDirectory())
                    .Append(kInstallBinaryDir)
                    .AppendASCII("1.0.0.0\\Installer\\setup.exe")
                    .value()
                    .c_str()),
            ERROR_SUCCESS);
  EXPECT_EQ(GetChromeInstallPathWithPrefs(is_system_level(),
                                          InitialPreferences(prefs_json())),
            GetExpectedPathForSetup(is_system_level(), target_dir()));
}

INSTANTIATE_TEST_SUITE_P(
    UserLevelX86SetupTest,
    GetChromeInstallPathWithPrefsTest,
    testing::Values<Params>(Params(false, base::DIR_PROGRAM_FILESX86)));

INSTANTIATE_TEST_SUITE_P(
    UserLevelX64SetupTest,
    GetChromeInstallPathWithPrefsTest,
    testing::Values<Params>(Params(false, base::DIR_PROGRAM_FILES)));

INSTANTIATE_TEST_SUITE_P(UserLevelUnsupportedPathSetupTest,
                         GetChromeInstallPathWithPrefsTest,
                         testing::Values<Params>(Params(false,
                                                        base::DIR_HOME)));

INSTANTIATE_TEST_SUITE_P(UserLevelEmptyPathSetupTest,
                         GetChromeInstallPathWithPrefsTest,
                         testing::Values<Params>(Params(false, std::nullopt)));

INSTANTIATE_TEST_SUITE_P(
    MachineLevelX86SetupTest,
    GetChromeInstallPathWithPrefsTest,
    testing::Values<Params>(Params(true, base::DIR_PROGRAM_FILESX86)));

INSTANTIATE_TEST_SUITE_P(
    MachineLevelX64SetupTest,
    GetChromeInstallPathWithPrefsTest,
    testing::Values<Params>(Params(true, base::DIR_PROGRAM_FILES)));

INSTANTIATE_TEST_SUITE_P(MachineLevelUnsupportedPathSetupTest,
                         GetChromeInstallPathWithPrefsTest,
                         testing::Values<Params>(Params(true, base::DIR_HOME)));

INSTANTIATE_TEST_SUITE_P(MachineLevelEmptyPathSetupTest,
                         GetChromeInstallPathWithPrefsTest,
                         testing::Values<Params>(Params(true, std::nullopt)));

class FindInstallPathTest
    : public ::testing::TestWithParam<std::tuple<bool, int>> {
 protected:
  void SetUp() override {
    ASSERT_TRUE(program_files_.CreateUniqueTempDir());
    ASSERT_TRUE(program_files_x86_.CreateUniqueTempDir());
    ASSERT_TRUE(program_files_6432_.CreateUniqueTempDir());
    ASSERT_TRUE(local_app_data_.CreateUniqueTempDir());
    program_files_override_.emplace(base::DIR_PROGRAM_FILES,
                                    program_files_path());
    program_files_x86_override_.emplace(base::DIR_PROGRAM_FILESX86,
                                        program_files_x86_path());
    program_files_6432_override_.emplace(base::DIR_PROGRAM_FILES6432,
                                         program_files_6432_path());
    local_data_app_override_.emplace(base::DIR_LOCAL_APP_DATA,
                                     local_app_data_path());
  }

  static bool is_system_level() { return std::get<0>(GetParam()); }
  static int target_path_key() { return std::get<1>(GetParam()); }
  base::FilePath program_files_path() const { return program_files_.GetPath(); }
  base::FilePath program_files_x86_path() const {
    return program_files_x86_.GetPath();
  }
  base::FilePath program_files_6432_path() const {
    return program_files_6432_.GetPath();
  }
  base::FilePath local_app_data_path() const {
    return local_app_data_.GetPath();
  }

 private:
  base::ScopedTempDir program_files_;
  base::ScopedTempDir program_files_x86_;
  base::ScopedTempDir program_files_6432_;
  base::ScopedTempDir local_app_data_;
  std::optional<base::ScopedPathOverride> program_files_override_;
  std::optional<base::ScopedPathOverride> program_files_x86_override_;
  std::optional<base::ScopedPathOverride> program_files_6432_override_;
  std::optional<base::ScopedPathOverride> local_data_app_override_;
};

// Tests that FindInstallPath returns an empty string when no install directory
// is present.
TEST_P(FindInstallPathTest, NoDirectory) {
  EXPECT_EQ(FindInstallPath(is_system_level(), base::Version("1.0.0.0")),
            base::FilePath());
}

// Tests that FindInstallPath returns the path to the installed version
// directory.
TEST_P(FindInstallPathTest, Installed) {
  const auto path = base::PathService::CheckedGet(target_path_key())
                        .Append(install_static::GetChromeInstallSubDirectory())
                        .Append(kInstallBinaryDir)
                        .Append(L"1.0.0.0");
  ASSERT_TRUE(base::CreateDirectoryAndGetError(path, nullptr));
  EXPECT_EQ(FindInstallPath(is_system_level(), base::Version("1.0.0.0")), path);
}

INSTANTIATE_TEST_SUITE_P(
    UserLevelTest,
    FindInstallPathTest,
    testing::Values(std::make_tuple(false, base::DIR_LOCAL_APP_DATA)));
INSTANTIATE_TEST_SUITE_P(
    SystemLevelTest,
    FindInstallPathTest,
    testing::Values(std::make_tuple(true, base::DIR_PROGRAM_FILES),
#if defined(ARCH_CPU_64_BITS)
                    std::make_tuple(true, base::DIR_PROGRAM_FILESX86)
#else
                    std::make_tuple(true, base::DIR_PROGRAM_FILES6432)
#endif

                        ));

}  // namespace installer