Chromium Code Reviews| Index: chrome/browser/conflicts/installed_programs_win.cc |
| diff --git a/chrome/browser/conflicts/installed_programs_win.cc b/chrome/browser/conflicts/installed_programs_win.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..958cc04f12514c61ad22b700edc4a498c682169f |
| --- /dev/null |
| +++ b/chrome/browser/conflicts/installed_programs_win.cc |
| @@ -0,0 +1,271 @@ |
| +// 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 <utility> |
| + |
| +#include "base/callback.h" |
| +#include "base/memory/ptr_util.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 { |
| + |
| +// Fetches a string |value| out of |key|. Return false if a non-empty value |
| +// could not be retrived. |
|
chrisha
2017/05/02 21:28:24
retrieved*
Patrick Monette
2017/05/29 22:04:54
Done.
|
| +bool GetRegistryKeyValue(const base::win::RegKey& key, |
| + const wchar_t* value, |
| + base::string16* result) { |
| + return key.ReadValue(value, result) == ERROR_SUCCESS && !result->empty(); |
| +} |
| + |
| +// 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 |value| out of |key|. Return false if a non-empty value could not |
| +// be retrived. |
|
chrisha
2017/05/02 21:28:25
retrieved*
Patrick Monette
2017/05/29 22:04:54
Done.
|
| +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/en-us/library/windows/desktop/aa370112(v=vs.85).aspx |
| +bool IsRegistryComponentPath(const base::string16& component_path) { |
| + base::string16 drive_letter = |
| + component_path.substr(0, component_path.find(':')); |
| + |
| + for (const auto& 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 dlls installed by the product identified by |product_guid|. |
| +// Returns true on success. |
| +bool GetInstalledDllsUsingMsiGuid(const base::string16& product_guid, |
| + std::vector<base::FilePath>* dlls) { |
| + // An invalid product guid may have been passed to this function. In those |
|
chrisha
2017/05/02 21:28:24
In this*
Patrick Monette
2017/05/29 22:04:55
Done.
|
| + // case, GetMsiComponentPaths() will return false so it is not necessary to |
| + // specifically filter those out. |
| + std::vector<base::string16> component_paths; |
| + if (!GetMsiComponentPaths(product_guid, &component_paths)) |
| + return false; |
| + |
| + for (const auto& component_path : component_paths) { |
| + // Exclude registry component paths. |
| + if (!IsRegistryComponentPath(component_path)) { |
|
chrisha
2017/05/02 21:28:25
I have a very minor preference:
if (...)
contin
Patrick Monette
2017/05/29 22:04:55
Done.
|
| + base::FilePath file_path(std::move(component_path)); |
| + if (base::EqualsCaseInsensitiveASCII(file_path.Extension(), L".dll")) |
|
chrisha
2017/05/02 21:28:24
This might be an artificial constraint. Loadable m
Patrick Monette
2017/05/29 22:04:54
This was mainly done so that searching for the ass
|
| + dlls->push_back(std::move(file_path)); |
| + } |
| + } |
| + |
| + return true; |
| +} |
| + |
| +// Checks if the registry key that the first 3 parameters refers to references |
| +// an installed program in the Apps & Features settings page. If so, populates |
| +// |internal_data| with the program name and its related file paths. |
| +void CheckRegistryKeyForInstalledProgram( |
| + HKEY hkey, |
| + const base::string16& key_path, |
| + const base::string16& key_name, |
| + InstalledPrograms::InternalData* internal_data) { |
| + base::win::RegKey candidate( |
| + hkey, |
| + base::StringPrintf(L"%ls\\%ls", key_path.c_str(), key_name.c_str()) |
| + .c_str(), |
| + KEY_READ); |
| + |
| + 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) && |
| + publisher == L"Microsoft Corporation") { |
| + return; |
| + } |
| + |
| + // Candidates with no display names are ignored. |
| + // Because this class is used to display warning to the user, not having the |
|
chrisha
2017/05/02 21:28:25
s/the//
Patrick Monette
2017/05/29 22:04:54
Done.
|
| + // a display name renders the warning somewhat useless. |
| + base::string16 display_name; |
| + if (!GetValue(candidate, L"DisplayName", &display_name)) |
|
chrisha
2017/05/02 21:28:25
In this case could we directly check the image fil
Patrick Monette
2017/05/29 22:04:54
It would be possible. But remember that having no
|
| + return; |
| + |
| + base::FilePath install_path; |
| + if (GetInstallPathUsingInstallLocation(candidate, &install_path)) { |
| + internal_data->program_names.push_back(std::move(display_name)); |
| + internal_data->install_locations.push_back(std::make_pair( |
| + std::move(install_path), internal_data->program_names.size() - 1)); |
| + return; |
|
chrisha
2017/05/02 21:28:25
So if we find an InstallPath then we don't want to
Patrick Monette
2017/05/29 22:04:54
If the InstallPath is good, it's faster to check v
|
| + } |
| + |
| + std::vector<base::FilePath> dlls; |
| + if (GetInstalledDllsUsingMsiGuid(key_name, &dlls)) { |
| + internal_data->program_names.push_back(std::move(display_name)); |
| + for (size_t i = 0; i < dlls.size(); i++) { |
| + internal_data->dll_map.push_back(std::make_pair( |
| + std::move(dlls[i]), internal_data->program_names.size() - 1)); |
| + } |
| + } |
| +} |
| + |
| +// Returns the index of the matching parent directory of |file| in |container|. |
| +// Returns false if no matches were found. |
| +bool BinarySearch( |
|
chrisha
2017/05/02 21:28:25
Yay, you can write a binary search! You're hired :
Patrick Monette
2017/05/29 22:04:55
Yay!
|
| + const std::vector<std::pair<base::FilePath, size_t>>& container, |
| + const base::FilePath& file, |
| + size_t* result) { |
| + int min = 0; |
| + int max = container.size() - 1; |
| + while (min <= max) { |
| + int mid_index = (max + min) / 2; |
| + const auto& mid = container[mid_index]; |
| + if (mid.first.IsParent(file)) { |
| + *result = mid.second; |
| + return true; |
| + } |
| + |
| + if (base::FilePath::CompareLessIgnoreCase(file.value(), mid.first.value())) |
| + max = mid_index - 1; |
| + else |
| + min = mid_index + 1; |
| + } |
| + |
| + return false; |
| +} |
| + |
| +} // namespace |
| + |
| +InstalledPrograms::InstalledPrograms() |
| + : initialized_(false), weak_ptr_factory_(this) {} |
| + |
| +InstalledPrograms::~InstalledPrograms() = default; |
| + |
| +bool InstalledPrograms::GetInstalledProgramName(const base::FilePath& file, |
| + base::string16* program_name) { |
| + DCHECK(initialized_); |
| + |
| + // First check if the exact file path can be found. |
| + auto iter = dll_map_.find(file); |
| + if (iter != dll_map_.end()) { |
| + *program_name = program_names_[iter->second]; |
| + return true; |
| + } |
| + |
| + // Then check if one of the install locations matches the file. |
| + if (install_locations_.empty()) |
| + return false; |
| + |
| + size_t index; |
| + if (!BinarySearch(install_locations_, file, &index)) |
| + return false; |
| + |
| + *program_name = program_names_[index]; |
| + return true; |
| +} |
| + |
| +// static |
| +std::unique_ptr<InstalledPrograms::InternalData> |
| +InstalledPrograms::GetInternalData() { |
| + auto internal_data = base::MakeUnique<InternalData>(); |
| + |
| + const wchar_t* kUninstallKeyPaths[] = { |
| + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", |
| + L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall", |
| + }; |
| + |
| + for (const auto& uninstall_key_path : kUninstallKeyPaths) { |
| + for (const auto& hkey : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) { |
| + for (base::win::RegistryKeyIterator i(hkey, uninstall_key_path); |
| + i.Valid(); ++i) { |
| + CheckRegistryKeyForInstalledProgram(hkey, uninstall_key_path, i.Name(), |
| + internal_data.get()); |
| + } |
| + } |
| + } |
| + |
| + return internal_data; |
| +} |
| + |
| +void InstalledPrograms::OnInternalDataReceived( |
| + const base::Closure& on_initialized_callback, |
| + std::unique_ptr<InternalData> internal_data) { |
| + program_names_ = std::move(internal_data->program_names); |
| + base::flat_map<base::FilePath, size_t, FilePathLess> flat_map( |
| + internal_data->dll_map, base::KEEP_FIRST_OF_DUPES); |
| + dll_map_ = std::move(flat_map); |
| + install_locations_ = std::move(internal_data->install_locations); |
|
chrisha
2017/05/02 21:28:25
Just std::move the entire InternalData object?
Patrick Monette
2017/05/29 22:04:54
Done.
|
| + |
| + // Sort |install_locations_| so that it is possible to use binary search. |
| + std::sort(install_locations_.begin(), install_locations_.end(), |
| + [](const auto& lhs, const auto& rhs) { |
| + return base::FilePath::CompareLessIgnoreCase(lhs.first.value(), |
| + rhs.first.value()); |
| + }); |
| + |
| + initialized_ = true; |
| + if (on_initialized_callback) |
| + on_initialized_callback.Run(); |
| +} |
| + |
| +void InstalledPrograms::Initialize( |
| + const base::Closure& on_initialized_callback) { |
| + DCHECK(!initialized_); |
| + base::PostTaskWithTraitsAndReplyWithResult( |
| + FROM_HERE, |
| + base::TaskTraits() |
| + .MayBlock() |
| + .WithPriority(base::TaskPriority::BACKGROUND) |
| + .WithShutdownBehavior( |
| + base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), |
| + base::Bind(&InstalledPrograms::GetInternalData), |
| + base::Bind(&InstalledPrograms::OnInternalDataReceived, |
| + weak_ptr_factory_.GetWeakPtr(), on_initialized_callback)); |
| +} |
| +bool InstalledPrograms::FilePathLess::operator()( |
| + const base::FilePath& lhs, |
| + const base::FilePath& rhs) const { |
| + return base::FilePath::CompareLessIgnoreCase(lhs.value(), rhs.value()); |
| +} |