chromium/chrome/browser/win/conflicts/msi_util.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/msi_util.h"

#include <windows.h>

#include <string_view>

// By default msi.h includes wincrypt.h which clashes with OpenSSL
// (both define X509_NAME) so to be able to include
// third_party/openssl (indirectly) in the same translation unit we
// tell msi.h to not include wincrypt.h.
#define _MSI_NO_CRYPTO
#include <msi.h>
#include <msiquery.h>

#include <utility>

#include "base/strings/strcat.h"
#include "base/strings/string_util.h"
#include "base/threading/scoped_blocking_call.h"
#include "base/uuid.h"
#include "base/win/registry.h"
#include "chrome/installer/util/install_util.h"

namespace {

// Most strings returned by the MSI API are smaller than this value, so only
// 1 call to the API is needed in the common case.
constexpr DWORD kBufferInitialSize = 256;

// Retrieves the file path to the product's installer.
bool GetMsiPath(const std::wstring& product_guid, std::wstring* result) {
  DWORD buffer_size = kBufferInitialSize;
  UINT ret =
      ::MsiGetProductInfo(product_guid.c_str(), INSTALLPROPERTY_LOCALPACKAGE,
                          base::WriteInto(result, buffer_size), &buffer_size);
  if (ret == ERROR_MORE_DATA) {
    // Must account for the null terminator.
    buffer_size++;

    ret =
        ::MsiGetProductInfo(product_guid.c_str(), INSTALLPROPERTY_LOCALPACKAGE,
                            base::WriteInto(result, buffer_size), &buffer_size);
  }

  if (ret == ERROR_SUCCESS) {
    result->resize(buffer_size);
    return true;
  }
  return false;
}

// Returns the string value at position |index| in the given |record_handle|.
// Note that columns are 1-indexed.
bool GetRecordString(MSIHANDLE record_handle,
                     size_t index,
                     std::wstring* result) {
  DWORD buffer_size = kBufferInitialSize;
  UINT ret = ::MsiRecordGetString(
      record_handle, index, base::WriteInto(result, buffer_size), &buffer_size);
  if (ret == ERROR_MORE_DATA) {
    // Must account for the null terminator.
    buffer_size++;

    ret = ::MsiRecordGetString(record_handle, index,
                               base::WriteInto(result, buffer_size),
                               &buffer_size);
  }

  if (ret == ERROR_SUCCESS) {
    result->resize(buffer_size);
    return true;
  }
  return false;
}

// Inspects the installer file and extracts the component guids. Each .msi file
// is actually an SQL database.
bool GetMsiComponentGuids(const std::wstring& msi_database_path,
                          std::vector<std::wstring>* component_guids) {
  PMSIHANDLE msi_database_handle;
  if (::MsiOpenDatabase(msi_database_path.c_str(), MSIDBOPEN_READONLY,
                        &msi_database_handle) != ERROR_SUCCESS) {
    return false;
  }

  PMSIHANDLE components_view_handle;
  if (::MsiDatabaseOpenView(msi_database_handle,
                            L"SELECT ComponentId FROM Component",
                            &components_view_handle) != ERROR_SUCCESS) {
    return false;
  }

  if (::MsiViewExecute(components_view_handle, 0) != ERROR_SUCCESS)
    return false;

  PMSIHANDLE record_handle;
  while (::MsiViewFetch(components_view_handle, &record_handle) ==
         ERROR_SUCCESS) {
    // The record only have the ComponentId column, and its index is 1.
    std::wstring component_guid;
    if (GetRecordString(record_handle, 1, &component_guid))
      component_guids->push_back(std::move(component_guid));
  }

  return true;
}

// Retrieves the |path| to the given component.
// This function basically mimics the functionality of ::MsiGetComponentPath(),
// but without directly calling it. This is because that function can trigger
// the configuration of a product in some rare cases.
// See https://crbug.com/860537.
bool GetMsiComponentPath(std::wstring_view product_guid,
                         std::wstring_view component_guid,
                         const std::wstring& user_sid,
                         std::wstring* path) {
  // Internally, the Microsoft Installer uses a special formatting of the guids
  // to store the information in the registry.
  product_guid = product_guid.substr(1, 36);
  if (!base::Uuid::ParseCaseInsensitive(base::AsStringPiece16(product_guid))
           .is_valid()) {
    return false;
  }
  std::wstring product_squid = InstallUtil::GuidToSquid(product_guid);

  component_guid = component_guid.substr(1, 36);
  if (!base::Uuid::ParseCaseInsensitive(base::AsStringPiece16(component_guid))
           .is_valid()) {
    return false;
  }
  std::wstring component_squid = InstallUtil::GuidToSquid(component_guid);

  std::vector<std::wstring> sids = {
      L"S-1-5-18",
  };
  if (!user_sid.empty())
    sids.push_back(user_sid);

  for (const auto& sid : sids) {
    std::wstring value;
    base::win::RegKey registry_key(
        HKEY_LOCAL_MACHINE,
        base::StrCat({L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Installer"
                      L"\\UserData\\",
                      sid, L"\\Components\\", component_squid})
            .c_str(),
        KEY_QUERY_VALUE | KEY_WOW64_64KEY);
    if (registry_key.Valid() &&
        registry_key.ReadValue(product_squid.c_str(), &value) ==
            ERROR_SUCCESS &&
        !value.empty()) {
      *path = std::move(value);
      return true;
    }
  }

  return false;
}

}  // namespace

// The most efficient way to get the list of components associated to an
// installed product is to inspect the installer file. A copy of the installer
// exists somewhere on the file system because Windows needs it to uninstall the
// product.
//
// So this function retrieves the path to the installer, extracts the component
// GUIDS from it, and uses those to find the path of each component.
bool MsiUtil::GetMsiComponentPaths(
    const std::wstring& product_guid,
    const std::wstring& user_sid,
    std::vector<std::wstring>* component_paths) const {
  base::ScopedBlockingCall scoped_blocking_call(FROM_HERE,
                                                base::BlockingType::WILL_BLOCK);

  std::wstring msi_path;
  if (!GetMsiPath(product_guid, &msi_path))
    return false;

  std::vector<std::wstring> component_guids;
  if (!GetMsiComponentGuids(msi_path, &component_guids))
    return false;

  for (const auto& component_guid : component_guids) {
    std::wstring component_path;
    if (!GetMsiComponentPath(product_guid, component_guid, user_sid,
                             &component_path)) {
      continue;
    }

    component_paths->push_back(std::move(component_path));
  }

  return true;
}