// Copyright 2017 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/win/conflicts/installed_applications.h"
#include <algorithm>
#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/win/registry.h"
#include "base/win/win_util.h"
#include "base/win/windows_version.h"
#include "chrome/browser/win/conflicts/msi_util.h"
namespace {
// Returns true if |candidate| is registered as a system component.
bool IsSystemComponent(const base::win::RegKey& candidate) {
DWORD system_component = 0;
return candidate.ReadValueDW(L"SystemComponent", &system_component) ==
ERROR_SUCCESS &&
system_component == 1;
}
// Fetches a string |value| out of |key|. Return false if a non-empty value
// could not be retrieved.
bool GetValue(const base::win::RegKey& key,
const wchar_t* value,
std::wstring* result) {
return key.ReadValue(value, result) == ERROR_SUCCESS && !result->empty();
}
// Try to get the |install_path| from |candidate| using the InstallLocation
// value. Return true on success.
bool GetInstallPathUsingInstallLocation(const base::win::RegKey& candidate,
base::FilePath* install_path) {
std::wstring install_location;
if (GetValue(candidate, L"InstallLocation", &install_location)) {
*install_path = base::FilePath(std::move(install_location));
return true;
}
return false;
}
// Returns true if the |component_path| points to a registry key. Registry key
// paths are characterized by a number instead of a drive letter.
// See the documentation for ::MsiGetComponentPath():
// https://msdn.microsoft.com/library/windows/desktop/aa370112.aspx
bool IsRegistryComponentPath(const std::wstring& component_path) {
std::wstring drive_letter =
component_path.substr(0, component_path.find(':'));
for (const wchar_t* registry_drive_letter :
{L"00", L"01", L"02", L"03", L"20", L"21", L"22", L"23"}) {
if (drive_letter == registry_drive_letter)
return true;
}
return false;
}
// Returns all the files installed by the product identified by |product_guid|.
// Returns true on success.
bool GetInstalledFilesUsingMsiGuid(
const std::wstring& product_guid,
const MsiUtil& msi_util,
const std::wstring& user_sid,
std::vector<base::FilePath>* installed_files) {
// An invalid product guid may have been passed to this function. In this
// case, GetMsiComponentPaths() will return false so it is not necessary to
// specifically filter those out.
std::vector<std::wstring> component_paths;
if (!msi_util.GetMsiComponentPaths(product_guid, user_sid, &component_paths))
return false;
for (auto& component_path : component_paths) {
// Exclude registry component paths.
if (IsRegistryComponentPath(component_path))
continue;
installed_files->push_back(base::FilePath(std::move(component_path)));
}
return true;
}
// Helper function to sort |container| using CompareLessIgnoreCase.
void SortByFilePaths(
std::vector<std::pair<base::FilePath, size_t>>* container) {
std::sort(container->begin(), container->end(),
[](const auto& lhs, const auto& rhs) {
return base::FilePath::CompareLessIgnoreCase(lhs.first.value(),
rhs.first.value());
});
}
} // namespace
InstalledApplications::InstalledApplications()
: InstalledApplications(std::make_unique<MsiUtil>()) {}
InstalledApplications::~InstalledApplications() = default;
bool InstalledApplications::GetInstalledApplications(
const base::FilePath& file,
std::vector<ApplicationInfo>* applications) const {
// First, check if an exact file match exists in the installed files list.
if (GetApplicationsFromInstalledFiles(file, applications))
return true;
// Then try to find a parent directory in the install directories list.
return GetApplicationsFromInstallDirectories(file, applications);
}
InstalledApplications::InstalledApplications(
std::unique_ptr<MsiUtil> msi_util) {
base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
base::BlockingType::MAY_BLOCK);
// Iterate over all the variants of the uninstall registry key.
static constexpr wchar_t kUninstallKeyPath[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
// Retrieve the current user's Security Identifier. If it fails, |user_sid|
// will stay empty.
std::wstring user_sid;
base::win::GetUserSidString(&user_sid);
for (const auto& combination : GenRegistryKeyCombinations()) {
for (base::win::RegistryKeyIterator i(combination.first, kUninstallKeyPath,
combination.second);
i.Valid(); ++i) {
CheckRegistryKeyForInstalledApplication(
combination.first, kUninstallKeyPath, combination.second, i.Name(),
*msi_util, user_sid);
}
}
// The vectors are sorted so that binary searching can be used. No additional
// entries will be added anyways.
SortByFilePaths(&installed_files_);
SortByFilePaths(&install_directories_);
}
std::vector<std::pair<HKEY, REGSAM>>
InstalledApplications::GenRegistryKeyCombinations() const {
std::vector<std::pair<HKEY, REGSAM>> registry_key_combinations;
if (base::win::OSInfo::GetArchitecture() ==
base::win::OSInfo::X86_ARCHITECTURE) {
// On 32-bit Windows, there is only one view of the registry.
registry_key_combinations.emplace_back(HKEY_CURRENT_USER, 0);
registry_key_combinations.emplace_back(HKEY_LOCAL_MACHINE, 0);
} else {
// On 64-bit Windows, there also exists a 32-bit view (Wow6432Node). Except
// that the "HKCU\SOFTWARE\" subtree is shared between the 32-bits and
// 64 bits views. Accessing both would create duplicate entries.
// See https://msdn.microsoft.com/library/windows/desktop/aa384253.aspx
registry_key_combinations.emplace_back(HKEY_CURRENT_USER, 0);
registry_key_combinations.emplace_back(HKEY_LOCAL_MACHINE, KEY_WOW64_32KEY);
registry_key_combinations.emplace_back(HKEY_LOCAL_MACHINE, KEY_WOW64_64KEY);
}
return registry_key_combinations;
}
void InstalledApplications::CheckRegistryKeyForInstalledApplication(
HKEY hkey,
const std::wstring& key_path,
REGSAM wow64access,
const std::wstring& key_name,
const MsiUtil& msi_util,
const std::wstring& user_sid) {
std::wstring candidate_key_path = base::StrCat({key_path, L"\\", key_name});
base::win::RegKey candidate(hkey, candidate_key_path.c_str(),
KEY_QUERY_VALUE | wow64access);
if (!candidate.Valid())
return;
// System components are not displayed in the Add or remove applications list.
if (IsSystemComponent(candidate))
return;
// If there is no UninstallString, the Uninstall button is grayed out.
std::wstring uninstall_string;
if (!GetValue(candidate, L"UninstallString", &uninstall_string))
return;
// Ignore Microsoft applications.
std::wstring publisher;
if (GetValue(candidate, L"Publisher", &publisher) &&
base::StartsWith(publisher, L"Microsoft", base::CompareCase::SENSITIVE)) {
return;
}
// Because this class is used to display a warning to the user, not having
// a display name renders the warning somewhat useless. Ignore those
// candidates.
std::wstring display_name;
if (!GetValue(candidate, L"DisplayName", &display_name))
return;
base::FilePath install_path;
if (GetInstallPathUsingInstallLocation(candidate, &install_path)) {
applications_.push_back({std::move(display_name), hkey,
std::move(candidate_key_path), wow64access});
const size_t application_index = applications_.size() - 1;
install_directories_.emplace_back(std::move(install_path),
application_index);
return;
}
std::vector<base::FilePath> installed_files;
if (GetInstalledFilesUsingMsiGuid(key_name, msi_util, user_sid,
&installed_files)) {
applications_.push_back({std::move(display_name), hkey,
std::move(candidate_key_path), wow64access});
const size_t application_index = applications_.size() - 1;
for (auto& installed_file : installed_files) {
installed_files_.emplace_back(std::move(installed_file),
application_index);
}
}
}
bool InstalledApplications::GetApplicationsFromInstalledFiles(
const base::FilePath& file,
std::vector<ApplicationInfo>* applications) const {
// This functor is used to find all exact items by their key in a collection
// of key/value pairs.
struct FilePathLess {
bool operator()(const std::pair<base::FilePath, size_t> element,
const base::FilePath& file) {
return base::FilePath::CompareLessIgnoreCase(element.first.value(),
file.value());
}
bool operator()(const base::FilePath& file,
const std::pair<base::FilePath, size_t> element) {
return base::FilePath::CompareLessIgnoreCase(file.value(),
element.first.value());
}
};
auto equal_range = std::equal_range(
installed_files_.begin(), installed_files_.end(), file, FilePathLess());
auto nb_matches = std::distance(equal_range.first, equal_range.second);
if (nb_matches == 0)
return false;
applications->reserve(applications->size() + nb_matches);
for (auto iter = equal_range.first; iter != equal_range.second; ++iter)
applications->push_back(applications_[iter->second]);
return true;
}
bool InstalledApplications::GetApplicationsFromInstallDirectories(
const base::FilePath& file,
std::vector<ApplicationInfo>* applications) const {
// This functor is used to find all matching items by their key in a
// collection of key/value pairs. This also takes advantage of the fact that
// only the first element of the pair is a directory.
struct FilePathParentLess {
bool operator()(const std::pair<base::FilePath, size_t> directory,
const base::FilePath& file) {
if (directory.first.IsParent(file))
return false;
return base::FilePath::CompareLessIgnoreCase(directory.first.value(),
file.value());
}
bool operator()(const base::FilePath& file,
const std::pair<base::FilePath, size_t> directory) {
if (directory.first.IsParent(file))
return false;
return base::FilePath::CompareLessIgnoreCase(file.value(),
directory.first.value());
}
};
auto equal_range =
std::equal_range(install_directories_.begin(), install_directories_.end(),
file, FilePathParentLess());
// Skip cases where there are multiple matches because there is no way to know
// which application is the real owner of the |file| with the information this
// class possess.
if (std::distance(equal_range.first, equal_range.second) != 1)
return false;
applications->push_back(applications_[equal_range.first->second]);
return true;
}
bool operator<(const InstalledApplications::ApplicationInfo& lhs,
const InstalledApplications::ApplicationInfo& 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);
}