chromium/chrome/browser/win/conflicts/installed_applications.cc

// 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);
}