blob: b3f90aaeb80fedeeeb24d7798fdd76325089f8b6 [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/conflicts/installed_applications_win.h"
#include <algorithm>
#include <map>
#include "base/macros.h"
#include "base/strings/stringprintf.h"
#include "base/test/test_reg_util_win.h"
#include "base/win/registry.h"
#include "chrome/browser/conflicts/msi_util_win.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace {
static const wchar_t kRegistryKeyPathFormat[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\%ls";
struct CommonInfo {
base::string16 product_id;
bool is_system_component;
bool is_microsoft_published;
base::string16 display_name;
base::string16 uninstall_string;
};
struct InstallLocationApplicationInfo {
CommonInfo common_info;
base::string16 install_location;
};
struct MsiApplicationInfo {
CommonInfo common_info;
std::vector<base::string16> components;
};
class MockMsiUtil : public MsiUtil {
public:
MockMsiUtil(const std::map<base::string16, std::vector<base::string16>>&
component_paths_map)
: component_paths_map_(component_paths_map) {}
bool GetMsiComponentPaths(
const base::string16& product_guid,
const base::string16& user_sid,
std::vector<base::string16>* component_paths) const override {
auto iter = component_paths_map_.find(product_guid);
if (iter == component_paths_map_.end())
return false;
*component_paths = iter->second;
return true;
}
private:
const std::map<base::string16, std::vector<base::string16>>&
component_paths_map_;
};
class TestInstalledApplications : public InstalledApplications {
public:
explicit TestInstalledApplications(std::unique_ptr<MsiUtil> msi_util)
: InstalledApplications(std::move(msi_util)) {}
};
class InstalledApplicationsTest : public testing::Test {
public:
InstalledApplicationsTest() = default;
~InstalledApplicationsTest() override = default;
// Overrides HKLM and HKCU to prevent real keys from messing with the tests.
void OverrideRegistry() {
ASSERT_NO_FATAL_FAILURE(
registry_override_manager_.OverrideRegistry(HKEY_LOCAL_MACHINE));
ASSERT_NO_FATAL_FAILURE(
registry_override_manager_.OverrideRegistry(HKEY_CURRENT_USER));
}
void AddCommonInfo(const CommonInfo& common_info,
base::win::RegKey* registry_key) {
registry_key->WriteValue(L"SystemComponent",
common_info.is_system_component ? 1 : 0);
registry_key->WriteValue(L"UninstallString",
common_info.uninstall_string.c_str());
if (common_info.is_microsoft_published)
registry_key->WriteValue(L"Publisher", L"Microsoft Corporation");
registry_key->WriteValue(L"DisplayName", common_info.display_name.c_str());
}
void AddFakeApplication(const MsiApplicationInfo& application_info) {
const base::string16 registry_key_path =
base::StringPrintf(kRegistryKeyPathFormat,
application_info.common_info.product_id.c_str());
base::win::RegKey registry_key(HKEY_CURRENT_USER, registry_key_path.c_str(),
KEY_WRITE);
AddCommonInfo(application_info.common_info, &registry_key);
component_paths_map_.insert(
{application_info.common_info.product_id, application_info.components});
}
void AddFakeApplication(
const InstallLocationApplicationInfo& application_info) {
const base::string16 registry_key_path =
base::StringPrintf(kRegistryKeyPathFormat,
application_info.common_info.product_id.c_str());
base::win::RegKey registry_key(HKEY_CURRENT_USER, registry_key_path.c_str(),
KEY_WRITE);
AddCommonInfo(application_info.common_info, &registry_key);
registry_key.WriteValue(L"InstallLocation",
application_info.install_location.c_str());
}
TestInstalledApplications& installed_applications() {
return *installed_applications_;
}
void InitializeInstalledApplications() {
installed_applications_ = std::make_unique<TestInstalledApplications>(
std::make_unique<MockMsiUtil>(component_paths_map_));
}
private:
registry_util::RegistryOverrideManager registry_override_manager_;
std::unique_ptr<TestInstalledApplications> installed_applications_;
std::map<base::string16, std::vector<base::string16>> component_paths_map_;
DISALLOW_COPY_AND_ASSIGN(InstalledApplicationsTest);
};
} // namespace
// Checks that registry entries with invalid information are skipped.
TEST_F(InstalledApplicationsTest, InvalidEntries) {
const wchar_t kValidDisplayName[] = L"ADisplayName";
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kInstallLocation[] = L"c:\\application files\\application\\";
InstallLocationApplicationInfo kTestCases[] = {
{
{
L"Is SystemComponent", true, false, kValidDisplayName,
kValidUninstallString,
},
kInstallLocation,
},
{
{
L"Is Microsoft published", false, true, kValidDisplayName,
kValidUninstallString,
},
kInstallLocation,
},
{
{
L"Missing DisplayName", false, false, L"", kValidUninstallString,
},
kInstallLocation,
},
{
{
L"Missing UninstallString", false, false, kValidDisplayName, L"",
},
kInstallLocation,
},
};
ASSERT_NO_FATAL_FAILURE(OverrideRegistry());
for (const auto& test_case : kTestCases)
AddFakeApplication(test_case);
InitializeInstalledApplications();
// None of the invalid entries were picked up.
const base::FilePath valid_child_file =
base::FilePath(kInstallLocation).Append(L"file.dll");
std::vector<InstalledApplications::ApplicationInfo> applications;
EXPECT_FALSE(installed_applications().GetInstalledApplications(
valid_child_file, &applications));
}
// Tests InstalledApplications on a valid entry with an InstallLocation.
TEST_F(InstalledApplicationsTest, InstallLocation) {
const wchar_t kValidDisplayName[] = L"ADisplayName";
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kInstallLocation[] = L"c:\\application files\\application\\";
InstallLocationApplicationInfo kTestCase = {
{
L"Completely valid", false, false, kValidDisplayName,
kValidUninstallString,
},
kInstallLocation,
};
ASSERT_NO_FATAL_FAILURE(OverrideRegistry());
AddFakeApplication(kTestCase);
InitializeInstalledApplications();
// Child file path.
const base::FilePath valid_child_file =
base::FilePath(kInstallLocation).Append(L"file.dll");
std::vector<InstalledApplications::ApplicationInfo> applications;
EXPECT_TRUE(installed_applications().GetInstalledApplications(
valid_child_file, &applications));
ASSERT_EQ(1u, applications.size());
EXPECT_EQ(kTestCase.common_info.display_name, applications[0].name);
EXPECT_EQ(HKEY_CURRENT_USER, applications[0].registry_root);
EXPECT_FALSE(applications[0].registry_key_path.empty());
EXPECT_EQ(0u, applications[0].registry_wow64_access);
// Non-child file path.
const base::FilePath invalid_child_file(
L"c:\\application files\\another application\\test.dll");
EXPECT_FALSE(installed_applications().GetInstalledApplications(
invalid_child_file, &applications));
}
// Tests InstalledApplications on a valid MSI entry.
TEST_F(InstalledApplicationsTest, Msi) {
const wchar_t kValidDisplayName[] = L"ADisplayName";
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
MsiApplicationInfo kTestCase = {
{
L"Completely valid", false, false, kValidDisplayName,
kValidUninstallString,
},
{
L"c:\\application files\\application\\file1.dll",
L"c:\\application files\\application\\file2.dll",
L"c:\\application files\\application\\sub\\file3.dll",
L"c:\\windows\\system32\\file4.dll",
},
};
ASSERT_NO_FATAL_FAILURE(OverrideRegistry());
AddFakeApplication(kTestCase);
InitializeInstalledApplications();
// Checks that all the files match the application.
for (const auto& component : kTestCase.components) {
std::vector<InstalledApplications::ApplicationInfo> applications;
EXPECT_TRUE(installed_applications().GetInstalledApplications(
base::FilePath(component), &applications));
ASSERT_EQ(1u, applications.size());
EXPECT_EQ(kTestCase.common_info.display_name, applications[0].name);
EXPECT_EQ(HKEY_CURRENT_USER, applications[0].registry_root);
EXPECT_FALSE(applications[0].registry_key_path.empty());
EXPECT_EQ(0u, applications[0].registry_wow64_access);
}
// Any other file shouldn't work.
const base::FilePath invalid_child_file(
L"c:\\application files\\another application\\test.dll");
std::vector<InstalledApplications::ApplicationInfo> applications;
EXPECT_FALSE(installed_applications().GetInstalledApplications(
invalid_child_file, &applications));
}
// Checks that if a file matches an InstallLocation and an MSI component, only
// the MSI application will be considered.
TEST_F(InstalledApplicationsTest, PrioritizeMsi) {
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kInstallLocationDisplayName[] = L"InstallLocation DisplayName";
const wchar_t kMsiDisplayName[] = L"Msi DisplayName";
const wchar_t kInstallLocation[] = L"c:\\application files\\application\\";
const wchar_t kMsiComponent[] =
L"c:\\application files\\application\\file.dll";
InstallLocationApplicationInfo kInstallLocationFakeApplication = {
{
L"GUID1", false, false, kInstallLocationDisplayName,
kValidUninstallString,
},
kInstallLocation,
};
MsiApplicationInfo kMsiFakeApplication = {
{
L"GUID2", false, false, kMsiDisplayName, kValidUninstallString,
},
{
kMsiComponent,
},
};
ASSERT_NO_FATAL_FAILURE(OverrideRegistry());
AddFakeApplication(kInstallLocationFakeApplication);
AddFakeApplication(kMsiFakeApplication);
InitializeInstalledApplications();
std::vector<InstalledApplications::ApplicationInfo> applications;
EXPECT_TRUE(installed_applications().GetInstalledApplications(
base::FilePath(kMsiComponent), &applications));
ASSERT_EQ(1u, applications.size());
EXPECT_NE(kInstallLocationDisplayName, applications[0].name);
EXPECT_EQ(kMsiDisplayName, applications[0].name);
}
// Tests that if 2 entries with conflicting InstallLocation exist, both are
// ignored.
TEST_F(InstalledApplicationsTest, ConflictingInstallLocations) {
const wchar_t kValidUninstallString[] = L"c:\\an\\UninstallString.exe";
const wchar_t kDisplayName1[] = L"DisplayName1";
const wchar_t kDisplayName2[] = L"DisplayName2";
const wchar_t kInstallLocationParent[] = L"c:\\application files\\company\\";
const wchar_t kInstallLocationChild[] =
L"c:\\application files\\company\\application";
const wchar_t kFile[] =
L"c:\\application files\\company\\application\\file.dll";
InstallLocationApplicationInfo kFakeApplication1 = {
{
L"GUID1", false, false, kDisplayName1, kValidUninstallString,
},
kInstallLocationParent,
};
InstallLocationApplicationInfo kFakeApplication2 = {
{
L"GUID2", false, false, kDisplayName2, kValidUninstallString,
},
kInstallLocationChild,
};
ASSERT_NO_FATAL_FAILURE(OverrideRegistry());
AddFakeApplication(kFakeApplication1);
AddFakeApplication(kFakeApplication2);
InitializeInstalledApplications();
std::vector<InstalledApplications::ApplicationInfo> applications;
EXPECT_FALSE(installed_applications().GetInstalledApplications(
base::FilePath(kFile), &applications));
}
// This test ensures that each uninstall registry key is only read once, and
// thus no applications are picked up twice.
// This is possible if the same registry key is requested for both the 32-bit
// and 64-bit view but either that key is shared between the views, or the host
// OS is 32-bit, and there is no 64-bit view.
TEST_F(InstalledApplicationsTest, NoDuplicates) {
InitializeInstalledApplications();
auto applications = installed_applications().applications_;
std::sort(std::begin(applications), std::end(applications));
EXPECT_EQ(std::end(applications),
std::adjacent_find(std::begin(applications), std::end(applications),
[](const auto& lhs, const auto& rhs) {
return std::tie(lhs.name, lhs.registry_root,
lhs.registry_key_path,
lhs.registry_wow64_access) ==
std::tie(rhs.name, rhs.registry_root,
rhs.registry_key_path,
rhs.registry_wow64_access);
}));
}