 Chromium Code Reviews
 Chromium Code Reviews Issue 2854983002:
  Add the ThirdPartyModules.Uninstallable histogram.  (Closed)
    
  
    Issue 2854983002:
  Add the ThirdPartyModules.Uninstallable histogram.  (Closed) 
  | OLD | NEW | 
|---|---|
| (Empty) | |
| 1 // Copyright 2017 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "chrome/browser/conflicts/installed_programs_win.h" | |
| 6 | |
| 7 #include <algorithm> | |
| 8 #include <utility> | |
| 9 | |
| 10 #include "base/callback.h" | |
| 11 #include "base/memory/ptr_util.h" | |
| 12 #include "base/strings/string_util.h" | |
| 13 #include "base/strings/stringprintf.h" | |
| 14 #include "base/task_scheduler/post_task.h" | |
| 15 #include "base/win/registry.h" | |
| 16 #include "chrome/browser/conflicts/msi_util_win.h" | |
| 17 | |
| 18 namespace { | |
| 19 | |
| 20 // Fetches a string |value| out of |key|. Return false if a non-empty value | |
| 21 // could not be retrived. | |
| 
chrisha
2017/05/02 21:28:24
retrieved*
 
Patrick Monette
2017/05/29 22:04:54
Done.
 | |
| 22 bool GetRegistryKeyValue(const base::win::RegKey& key, | |
| 23 const wchar_t* value, | |
| 24 base::string16* result) { | |
| 25 return key.ReadValue(value, result) == ERROR_SUCCESS && !result->empty(); | |
| 26 } | |
| 27 | |
| 28 // Returns true if |candidate| is registered as a system component. | |
| 29 bool IsSystemComponent(const base::win::RegKey& candidate) { | |
| 30 DWORD system_component = 0; | |
| 31 return candidate.ReadValueDW(L"SystemComponent", &system_component) == | |
| 32 ERROR_SUCCESS && | |
| 33 system_component == 1; | |
| 34 } | |
| 35 | |
| 36 // Fetches |value| out of |key|. Return false if a non-empty value could not | |
| 37 // be retrived. | |
| 
chrisha
2017/05/02 21:28:25
retrieved*
 
Patrick Monette
2017/05/29 22:04:54
Done.
 | |
| 38 bool GetValue(const base::win::RegKey& key, | |
| 39 const wchar_t* value, | |
| 40 base::string16* result) { | |
| 41 return key.ReadValue(value, result) == ERROR_SUCCESS && !result->empty(); | |
| 42 } | |
| 43 | |
| 44 // Try to get the |install_path| from |candidate| using the InstallLocation | |
| 45 // value. Return true on success. | |
| 46 bool GetInstallPathUsingInstallLocation(const base::win::RegKey& candidate, | |
| 47 base::FilePath* install_path) { | |
| 48 base::string16 install_location; | |
| 49 if (GetValue(candidate, L"InstallLocation", &install_location)) { | |
| 50 *install_path = base::FilePath(std::move(install_location)); | |
| 51 return true; | |
| 52 } | |
| 53 return false; | |
| 54 } | |
| 55 | |
| 56 // Returns true if the |component_path| points to a registry key. Registry key | |
| 57 // paths are characterized by a number instead of a drive letter. | |
| 58 // See the documentation for ::MsiGetComponentPath(): | |
| 59 // https://msdn.microsoft.com/en-us/library/windows/desktop/aa370112(v=vs.85).as px | |
| 60 bool IsRegistryComponentPath(const base::string16& component_path) { | |
| 61 base::string16 drive_letter = | |
| 62 component_path.substr(0, component_path.find(':')); | |
| 63 | |
| 64 for (const auto& registry_drive_letter : | |
| 65 {L"00", L"01", L"02", L"03", L"20", L"21", L"22", L"23"}) { | |
| 66 if (drive_letter == registry_drive_letter) | |
| 67 return true; | |
| 68 } | |
| 69 | |
| 70 return false; | |
| 71 } | |
| 72 | |
| 73 // Returns all the dlls installed by the product identified by |product_guid|. | |
| 74 // Returns true on success. | |
| 75 bool GetInstalledDllsUsingMsiGuid(const base::string16& product_guid, | |
| 76 std::vector<base::FilePath>* dlls) { | |
| 77 // 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.
 | |
| 78 // case, GetMsiComponentPaths() will return false so it is not necessary to | |
| 79 // specifically filter those out. | |
| 80 std::vector<base::string16> component_paths; | |
| 81 if (!GetMsiComponentPaths(product_guid, &component_paths)) | |
| 82 return false; | |
| 83 | |
| 84 for (const auto& component_path : component_paths) { | |
| 85 // Exclude registry component paths. | |
| 86 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.
 | |
| 87 base::FilePath file_path(std::move(component_path)); | |
| 88 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
 | |
| 89 dlls->push_back(std::move(file_path)); | |
| 90 } | |
| 91 } | |
| 92 | |
| 93 return true; | |
| 94 } | |
| 95 | |
| 96 // Checks if the registry key that the first 3 parameters refers to references | |
| 97 // an installed program in the Apps & Features settings page. If so, populates | |
| 98 // |internal_data| with the program name and its related file paths. | |
| 99 void CheckRegistryKeyForInstalledProgram( | |
| 100 HKEY hkey, | |
| 101 const base::string16& key_path, | |
| 102 const base::string16& key_name, | |
| 103 InstalledPrograms::InternalData* internal_data) { | |
| 104 base::win::RegKey candidate( | |
| 105 hkey, | |
| 106 base::StringPrintf(L"%ls\\%ls", key_path.c_str(), key_name.c_str()) | |
| 107 .c_str(), | |
| 108 KEY_READ); | |
| 109 | |
| 110 if (!candidate.Valid()) | |
| 111 return; | |
| 112 | |
| 113 // System components are not displayed in the Add or remove programs list. | |
| 114 if (IsSystemComponent(candidate)) | |
| 115 return; | |
| 116 | |
| 117 // If there is no UninstallString, the Uninstall button is grayed out. | |
| 118 base::string16 uninstall_string; | |
| 119 if (!GetValue(candidate, L"UninstallString", &uninstall_string)) | |
| 120 return; | |
| 121 | |
| 122 // Ignore Microsoft programs. | |
| 123 base::string16 publisher; | |
| 124 if (GetValue(candidate, L"Publisher", &publisher) && | |
| 125 publisher == L"Microsoft Corporation") { | |
| 126 return; | |
| 127 } | |
| 128 | |
| 129 // Candidates with no display names are ignored. | |
| 130 // 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.
 | |
| 131 // a display name renders the warning somewhat useless. | |
| 132 base::string16 display_name; | |
| 133 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
 | |
| 134 return; | |
| 135 | |
| 136 base::FilePath install_path; | |
| 137 if (GetInstallPathUsingInstallLocation(candidate, &install_path)) { | |
| 138 internal_data->program_names.push_back(std::move(display_name)); | |
| 139 internal_data->install_locations.push_back(std::make_pair( | |
| 140 std::move(install_path), internal_data->program_names.size() - 1)); | |
| 141 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
 | |
| 142 } | |
| 143 | |
| 144 std::vector<base::FilePath> dlls; | |
| 145 if (GetInstalledDllsUsingMsiGuid(key_name, &dlls)) { | |
| 146 internal_data->program_names.push_back(std::move(display_name)); | |
| 147 for (size_t i = 0; i < dlls.size(); i++) { | |
| 148 internal_data->dll_map.push_back(std::make_pair( | |
| 149 std::move(dlls[i]), internal_data->program_names.size() - 1)); | |
| 150 } | |
| 151 } | |
| 152 } | |
| 153 | |
| 154 // Returns the index of the matching parent directory of |file| in |container|. | |
| 155 // Returns false if no matches were found. | |
| 156 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!
 | |
| 157 const std::vector<std::pair<base::FilePath, size_t>>& container, | |
| 158 const base::FilePath& file, | |
| 159 size_t* result) { | |
| 160 int min = 0; | |
| 161 int max = container.size() - 1; | |
| 162 while (min <= max) { | |
| 163 int mid_index = (max + min) / 2; | |
| 164 const auto& mid = container[mid_index]; | |
| 165 if (mid.first.IsParent(file)) { | |
| 166 *result = mid.second; | |
| 167 return true; | |
| 168 } | |
| 169 | |
| 170 if (base::FilePath::CompareLessIgnoreCase(file.value(), mid.first.value())) | |
| 171 max = mid_index - 1; | |
| 172 else | |
| 173 min = mid_index + 1; | |
| 174 } | |
| 175 | |
| 176 return false; | |
| 177 } | |
| 178 | |
| 179 } // namespace | |
| 180 | |
| 181 InstalledPrograms::InstalledPrograms() | |
| 182 : initialized_(false), weak_ptr_factory_(this) {} | |
| 183 | |
| 184 InstalledPrograms::~InstalledPrograms() = default; | |
| 185 | |
| 186 bool InstalledPrograms::GetInstalledProgramName(const base::FilePath& file, | |
| 187 base::string16* program_name) { | |
| 188 DCHECK(initialized_); | |
| 189 | |
| 190 // First check if the exact file path can be found. | |
| 191 auto iter = dll_map_.find(file); | |
| 192 if (iter != dll_map_.end()) { | |
| 193 *program_name = program_names_[iter->second]; | |
| 194 return true; | |
| 195 } | |
| 196 | |
| 197 // Then check if one of the install locations matches the file. | |
| 198 if (install_locations_.empty()) | |
| 199 return false; | |
| 200 | |
| 201 size_t index; | |
| 202 if (!BinarySearch(install_locations_, file, &index)) | |
| 203 return false; | |
| 204 | |
| 205 *program_name = program_names_[index]; | |
| 206 return true; | |
| 207 } | |
| 208 | |
| 209 // static | |
| 210 std::unique_ptr<InstalledPrograms::InternalData> | |
| 211 InstalledPrograms::GetInternalData() { | |
| 212 auto internal_data = base::MakeUnique<InternalData>(); | |
| 213 | |
| 214 const wchar_t* kUninstallKeyPaths[] = { | |
| 215 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall", | |
| 216 L"SOFTWARE\\WOW6432Node\\Microsoft\\Windows\\CurrentVersion\\Uninstall", | |
| 217 }; | |
| 218 | |
| 219 for (const auto& uninstall_key_path : kUninstallKeyPaths) { | |
| 220 for (const auto& hkey : {HKEY_LOCAL_MACHINE, HKEY_CURRENT_USER}) { | |
| 221 for (base::win::RegistryKeyIterator i(hkey, uninstall_key_path); | |
| 222 i.Valid(); ++i) { | |
| 223 CheckRegistryKeyForInstalledProgram(hkey, uninstall_key_path, i.Name(), | |
| 224 internal_data.get()); | |
| 225 } | |
| 226 } | |
| 227 } | |
| 228 | |
| 229 return internal_data; | |
| 230 } | |
| 231 | |
| 232 void InstalledPrograms::OnInternalDataReceived( | |
| 233 const base::Closure& on_initialized_callback, | |
| 234 std::unique_ptr<InternalData> internal_data) { | |
| 235 program_names_ = std::move(internal_data->program_names); | |
| 236 base::flat_map<base::FilePath, size_t, FilePathLess> flat_map( | |
| 237 internal_data->dll_map, base::KEEP_FIRST_OF_DUPES); | |
| 238 dll_map_ = std::move(flat_map); | |
| 239 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.
 | |
| 240 | |
| 241 // Sort |install_locations_| so that it is possible to use binary search. | |
| 242 std::sort(install_locations_.begin(), install_locations_.end(), | |
| 243 [](const auto& lhs, const auto& rhs) { | |
| 244 return base::FilePath::CompareLessIgnoreCase(lhs.first.value(), | |
| 245 rhs.first.value()); | |
| 246 }); | |
| 247 | |
| 248 initialized_ = true; | |
| 249 if (on_initialized_callback) | |
| 250 on_initialized_callback.Run(); | |
| 251 } | |
| 252 | |
| 253 void InstalledPrograms::Initialize( | |
| 254 const base::Closure& on_initialized_callback) { | |
| 255 DCHECK(!initialized_); | |
| 256 base::PostTaskWithTraitsAndReplyWithResult( | |
| 257 FROM_HERE, | |
| 258 base::TaskTraits() | |
| 259 .MayBlock() | |
| 260 .WithPriority(base::TaskPriority::BACKGROUND) | |
| 261 .WithShutdownBehavior( | |
| 262 base::TaskShutdownBehavior::CONTINUE_ON_SHUTDOWN), | |
| 263 base::Bind(&InstalledPrograms::GetInternalData), | |
| 264 base::Bind(&InstalledPrograms::OnInternalDataReceived, | |
| 265 weak_ptr_factory_.GetWeakPtr(), on_initialized_callback)); | |
| 266 } | |
| 267 bool InstalledPrograms::FilePathLess::operator()( | |
| 268 const base::FilePath& lhs, | |
| 269 const base::FilePath& rhs) const { | |
| 270 return base::FilePath::CompareLessIgnoreCase(lhs.value(), rhs.value()); | |
| 271 } | |
| OLD | NEW |