blob: 1036a32fb97edc379390ace502a6832f488c94c5 [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_programs_win.h"
#include <algorithm>
#include "base/callback.h"
#include "base/memory/ptr_util.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/histogram_macros.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/task_scheduler/post_task.h"
#include "base/win/registry.h"
#include "chrome/browser/conflicts/msi_util_win.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,
base::string16* 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) {
base::string16 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 base::string16& component_path) {
base::string16 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 base::string16& product_guid,
const MsiUtil& msi_util,
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<base::string16> component_paths;
if (!msi_util.GetMsiComponentPaths(product_guid, &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;
}
// Checks if the registry key references an installed program in the Apps &
// Features settings page.
void CheckRegistryKeyForInstalledProgram(
HKEY hkey,
const base::string16& key_path,
REGSAM wow64access,
const base::string16& key_name,
const MsiUtil& msi_util,
InstalledPrograms::ProgramsData* programs_data) {
base::string16 candidate_key_path =
base::StringPrintf(L"%ls\\%ls", key_path.c_str(), key_name.c_str());
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 programs list.
if (IsSystemComponent(candidate))
return;
// If there is no UninstallString, the Uninstall button is grayed out.
base::string16 uninstall_string;
if (!GetValue(candidate, L"UninstallString", &uninstall_string))
return;
// Ignore Microsoft programs.
base::string16 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.
base::string16 display_name;
if (!GetValue(candidate, L"DisplayName", &display_name))
return;
base::FilePath install_path;
if (GetInstallPathUsingInstallLocation(candidate, &install_path)) {
programs_data->programs.emplace_back(std::move(display_name), hkey,
std::move(candidate_key_path),
wow64access);
const size_t program_index = programs_data->programs.size() - 1;
programs_data->install_directories.emplace_back(std::move(install_path),
program_index);
return;
}
std::vector<base::FilePath> installed_files;
if (GetInstalledFilesUsingMsiGuid(key_name, msi_util, &installed_files)) {
programs_data->programs.emplace_back(std::move(display_name), hkey,
std::move(candidate_key_path),
wow64access);
const size_t program_index = programs_data->programs.size() - 1;
for (auto& installed_file : installed_files) {
programs_data->installed_files.emplace_back(std::move(installed_file),
program_index);
}
}
}
// 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());
});
}
// Populates and returns a ProgramsData instance.
std::unique_ptr<InstalledPrograms::ProgramsData> GetProgramsData(
std::unique_ptr<MsiUtil> msi_util) {
SCOPED_UMA_HISTOGRAM_TIMER("ThirdPartyModules.InstalledPrograms.GetDataTime");
auto programs_data = base::MakeUnique<InstalledPrograms::ProgramsData>();
// Iterate over all the variants of the uninstall registry key.
const wchar_t kUninstallKeyPath[] =
L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall";
// The "HKCU\SOFTWARE\" registry subtree is shared between both 32-bits and
// 64-bits views. Accessing both would create duplicate entries.
// https://msdn.microsoft.com/library/windows/desktop/aa384253.aspx
static const std::pair<HKEY, REGSAM> kCombinations[] = {
{HKEY_CURRENT_USER, 0},
{HKEY_LOCAL_MACHINE, KEY_WOW64_32KEY},
{HKEY_LOCAL_MACHINE, KEY_WOW64_64KEY},
};
for (const auto& combination : kCombinations) {
for (base::win::RegistryKeyIterator i(combination.first, kUninstallKeyPath,
combination.second);
i.Valid(); ++i) {
CheckRegistryKeyForInstalledProgram(combination.first, kUninstallKeyPath,
combination.second, i.Name(),
*msi_util, programs_data.get());
}
}
// The vectors are sorted so that binary searching can be used. No additional
// entries will be added anyways.
SortByFilePaths(&programs_data->installed_files);
SortByFilePaths(&programs_data->install_directories);
return programs_data;
}
} // namespace
InstalledPrograms::ProgramInfo::ProgramInfo(base::string16 name,
HKEY registry_root,
base::string16 registry_key_path,
REGSAM registry_wow64_access)
: name(std::move(name)),
registry_root(registry_root),
registry_key_path(std::move(registry_key_path)),
registry_wow64_access(registry_wow64_access) {}
InstalledPrograms::ProgramInfo::~ProgramInfo() = default;
InstalledPrograms::ProgramsData::ProgramsData() = default;
InstalledPrograms::ProgramsData::~ProgramsData() = default;
InstalledPrograms::InstalledPrograms()
: initialized_(false), weak_ptr_factory_(this) {}
InstalledPrograms::~InstalledPrograms() = default;
void InstalledPrograms::Initialize(base::OnceClosure on_initialized_callback) {
Initialize(std::move(on_initialized_callback), base::MakeUnique<MsiUtil>());
}
bool InstalledPrograms::GetInstalledPrograms(
const base::FilePath& file,
std::vector<ProgramInfo>* programs) const {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(initialized_);
// First, check if an exact file match exists in the installed files list.
if (GetProgramsFromInstalledFiles(file, programs))
return true;
// Then try to find a parent directory in the install directories list.
return GetProgramsFromInstallDirectories(file, programs);
}
void InstalledPrograms::Initialize(base::OnceClosure on_initialized_callback,
std::unique_ptr<MsiUtil> msi_util) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!initialized_);
base::PostTaskWithTraitsAndReplyWithResult(
FROM_HERE,
{base::MayBlock(), base::TaskPriority::BACKGROUND,
base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN},
base::BindOnce(&GetProgramsData, std::move(msi_util)),
base::BindOnce(&InstalledPrograms::OnInitializationDone,
weak_ptr_factory_.GetWeakPtr(),
std::move(on_initialized_callback)));
}
bool InstalledPrograms::GetProgramsFromInstalledFiles(
const base::FilePath& file,
std::vector<ProgramInfo>* programs) 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(programs_data_->installed_files.begin(),
programs_data_->installed_files.end(),
file, FilePathLess());
auto nb_matches = std::distance(equal_range.first, equal_range.second);
if (nb_matches == 0)
return false;
programs->reserve(programs->size() + nb_matches);
for (auto iter = equal_range.first; iter != equal_range.second; ++iter)
programs->push_back(programs_data_->programs[iter->second]);
return true;
}
bool InstalledPrograms::GetProgramsFromInstallDirectories(
const base::FilePath& file,
std::vector<ProgramInfo>* programs) 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(
programs_data_->install_directories.begin(),
programs_data_->install_directories.end(), file, FilePathParentLess());
// Skip cases where there are multiple matches because there is no way to know
// which program is the real owner of the |file| with the data owned by us.
if (std::distance(equal_range.first, equal_range.second) != 1)
return false;
programs->push_back(programs_data_->programs[equal_range.first->second]);
return true;
}
void InstalledPrograms::OnInitializationDone(
base::OnceClosure on_initialized_callback,
std::unique_ptr<ProgramsData> programs_data) {
DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_);
DCHECK(!initialized_);
programs_data_ = std::move(programs_data);
initialized_ = true;
if (on_initialized_callback)
std::move(on_initialized_callback).Run();
}