forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			787 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
			
		
		
	
	
			787 lines
		
	
	
	
		
			27 KiB
		
	
	
	
		
			C++
		
	
	
	
	
	
| /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 | |
| /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 | |
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| #include "AboutThirdParty.h"
 | |
| 
 | |
| #include "AboutThirdPartyUtils.h"
 | |
| #include "mozilla/ClearOnShutdown.h"
 | |
| #include "mozilla/dom/Promise.h"
 | |
| #include "mozilla/NativeNt.h"
 | |
| #include "mozilla/StaticPtr.h"
 | |
| #include "MsiDatabase.h"
 | |
| #include "nsComponentManagerUtils.h"
 | |
| #include "nsIWindowsRegKey.h"
 | |
| #include "nsThreadUtils.h"
 | |
| 
 | |
| #include <objbase.h>
 | |
| 
 | |
| using namespace mozilla;
 | |
| 
 | |
| namespace {
 | |
| 
 | |
| // A callback function passed to EnumSubkeys uses this type
 | |
| // to control the enumeration loop.
 | |
| enum class CallbackResult { Continue, Stop };
 | |
| 
 | |
| template <typename CallbackT>
 | |
| void EnumSubkeys(nsIWindowsRegKey* aRegBase, const CallbackT& aCallback) {
 | |
|   uint32_t count = 0;
 | |
|   if (NS_FAILED(aRegBase->GetChildCount(&count))) {
 | |
|     return;
 | |
|   }
 | |
| 
 | |
|   for (uint32_t i = 0; i < count; ++i) {
 | |
|     nsAutoString subkeyName;
 | |
|     if (NS_FAILED(aRegBase->GetChildName(i, subkeyName))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     nsCOMPtr<nsIWindowsRegKey> subkey;
 | |
|     if (NS_FAILED(aRegBase->OpenChild(subkeyName, nsIWindowsRegKey::ACCESS_READ,
 | |
|                                       getter_AddRefs(subkey)))) {
 | |
|       continue;
 | |
|     }
 | |
| 
 | |
|     CallbackResult result = aCallback(subkeyName, subkey);
 | |
|     if (result == CallbackResult::Continue) {
 | |
|       continue;
 | |
|     } else if (result == CallbackResult::Stop) {
 | |
|       break;
 | |
|     } else {
 | |
|       MOZ_ASSERT_UNREACHABLE("Unexpected CallbackResult.");
 | |
|     }
 | |
|   }
 | |
| }
 | |
| 
 | |
| }  // anonymous namespace
 | |
| 
 | |
| InstallLocationComparator::InstallLocationComparator(const nsAString& aFilePath)
 | |
|     : mFilePath(aFilePath) {}
 | |
| 
 | |
| int InstallLocationComparator::operator()(
 | |
|     const InstallLocationT& aLocation) const {
 | |
|   // Firstly we check whether mFilePath begins with aLocation.
 | |
|   // If yes, mFilePath is a part of the target installation,
 | |
|   // so we return 0 showing match.
 | |
|   const nsAString& location = aLocation.first();
 | |
|   size_t locationLen = location.Length();
 | |
|   if (locationLen <= mFilePath.Length() &&
 | |
|       nsCaseInsensitiveStringComparator(mFilePath.BeginReading(),
 | |
|                                         location.BeginReading(), locationLen,
 | |
|                                         locationLen) == 0) {
 | |
|     return 0;
 | |
|   }
 | |
| 
 | |
|   return CompareIgnoreCase(mFilePath, location);
 | |
| }
 | |
| 
 | |
| // The InstalledApplications class behaves like Chrome's InstalledApplications,
 | |
| // which collects installed applications from two resources below.
 | |
| //
 | |
| // 1) Path strings in MSI package components
 | |
| // An MSI package is consisting of multiple components.  This class collects
 | |
| // MSI components representing a file and stores them as a hash table.
 | |
| //
 | |
| // 2) Install location paths in the InstallLocation registry value
 | |
| // If an application's installer is not MSI but sets the InstallLocation
 | |
| // registry value, we can use it to search for an application by comparing
 | |
| // a target module is located under that location path.  This class stores
 | |
| // location path strings as a sorted array so that we can binary-search it.
 | |
| class InstalledApplications final {
 | |
|   // Limit the number of entries to avoid consuming too much memory
 | |
|   constexpr static uint32_t kMaxComponents = 1000000;
 | |
|   constexpr static uint32_t kMaxInstallLocations = 1000;
 | |
| 
 | |
|   nsCOMPtr<nsIWindowsRegKey> mInstallerData;
 | |
|   nsCOMPtr<nsIInstalledApplication> mCurrentApp;
 | |
|   ComponentPathMapT mComponentPaths;
 | |
|   nsTArray<InstallLocationT> mLocations;
 | |
| 
 | |
|   void AddInstallLocation(nsIWindowsRegKey* aProductSubKey) {
 | |
|     nsString location;
 | |
|     if (NS_FAILED(
 | |
|             aProductSubKey->ReadStringValue(u"InstallLocation"_ns, location)) ||
 | |
|         location.IsEmpty()) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     if (location.Last() != u'\\') {
 | |
|       location.Append(u'\\');
 | |
|     }
 | |
| 
 | |
|     mLocations.EmplaceBack(location, this->mCurrentApp);
 | |
|   }
 | |
| 
 | |
|   void AddComponentGuid(const nsString& aPackedProductGuid,
 | |
|                         const nsString& aPackedComponentGuid) {
 | |
|     nsAutoString componentSubkey(L"Components\\");
 | |
|     componentSubkey += aPackedComponentGuid;
 | |
| 
 | |
|     // Pick a first value in the subkeys under |componentSubkey|.
 | |
|     nsString componentPath;
 | |
| 
 | |
|     EnumSubkeys(mInstallerData, [&aPackedProductGuid, &componentSubkey,
 | |
|                                  &componentPath](const nsString& aSid,
 | |
|                                                  nsIWindowsRegKey* aSidSubkey) {
 | |
|       // If we have a value in |componentPath|, the loop should
 | |
|       // have been stopped.
 | |
|       MOZ_ASSERT(componentPath.IsEmpty());
 | |
| 
 | |
|       nsCOMPtr<nsIWindowsRegKey> compKey;
 | |
|       nsresult rv =
 | |
|           aSidSubkey->OpenChild(componentSubkey, nsIWindowsRegKey::ACCESS_READ,
 | |
|                                 getter_AddRefs(compKey));
 | |
|       if (NS_FAILED(rv)) {
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       nsString compData;
 | |
|       if (NS_FAILED(compKey->ReadStringValue(aPackedProductGuid, compData))) {
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       if (!CorrectMsiComponentPath(compData)) {
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       componentPath = std::move(compData);
 | |
|       return CallbackResult::Stop;
 | |
|     });
 | |
| 
 | |
|     if (componentPath.IsEmpty()) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Use a full path as a key rather than a leaf name because
 | |
|     // the same name's module can be installed under system32
 | |
|     // and syswow64.
 | |
|     mComponentPaths.WithEntryHandle(componentPath, [this](auto&& addPtr) {
 | |
|       if (addPtr) {
 | |
|         // If the same file appeared in multiple installations, we set null
 | |
|         // for its value because there is no way to know which installation is
 | |
|         // the real owner.
 | |
|         addPtr.Data() = nullptr;
 | |
|       } else {
 | |
|         addPtr.Insert(this->mCurrentApp);
 | |
|       }
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   void AddProduct(const nsString& aProductId,
 | |
|                   nsIWindowsRegKey* aProductSubKey) {
 | |
|     nsString displayName;
 | |
|     if (NS_FAILED(
 | |
|             aProductSubKey->ReadStringValue(u"DisplayName"_ns, displayName)) ||
 | |
|         displayName.IsEmpty()) {
 | |
|       // Skip if no name is found.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     nsString publisher;
 | |
|     if (NS_SUCCEEDED(
 | |
|             aProductSubKey->ReadStringValue(u"Publisher"_ns, publisher)) &&
 | |
|         publisher.EqualsIgnoreCase("Microsoft") &&
 | |
|         publisher.EqualsIgnoreCase("Microsoft Corporation")) {
 | |
|       // Skip if the publisher is Microsoft because it's not a third-party.
 | |
|       // We don't skip an application without the publisher name.
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     mCurrentApp =
 | |
|         new InstalledApplication(std::move(displayName), std::move(publisher));
 | |
|     // Try an MSI database first because it's more accurate,
 | |
|     // then fall back to the InstallLocation key.
 | |
|     do {
 | |
|       if (!mInstallerData) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       nsAutoString packedProdGuid;
 | |
|       if (!MsiPackGuid(aProductId, packedProdGuid)) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       auto db = MsiDatabase::FromProductId(aProductId.get());
 | |
|       if (db.isNothing()) {
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       db->ExecuteSingleColumnQuery(
 | |
|           L"SELECT DISTINCT ComponentId FROM Component",
 | |
|           [this, &packedProdGuid](const wchar_t* aComponentGuid) {
 | |
|             if (this->mComponentPaths.Count() >= kMaxComponents) {
 | |
|               return MsiDatabase::CallbackResult::Stop;
 | |
|             }
 | |
| 
 | |
|             nsAutoString packedComponentGuid;
 | |
|             if (MsiPackGuid(nsDependentString(aComponentGuid),
 | |
|                             packedComponentGuid)) {
 | |
|               this->AddComponentGuid(packedProdGuid, packedComponentGuid);
 | |
|             }
 | |
| 
 | |
|             return MsiDatabase::CallbackResult::Continue;
 | |
|           });
 | |
| 
 | |
|       // We've decided to collect data from an MSI database.
 | |
|       // Exiting the function.
 | |
|       return;
 | |
|     } while (false);
 | |
| 
 | |
|     if (mLocations.Length() >= kMaxInstallLocations) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // If we cannot use an MSI database for any reason,
 | |
|     // try the InstallLocation key.
 | |
|     AddInstallLocation(aProductSubKey);
 | |
|   }
 | |
| 
 | |
|  public:
 | |
|   InstalledApplications() {
 | |
|     nsresult rv;
 | |
|     nsCOMPtr<nsIWindowsRegKey> regKey =
 | |
|         do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
 | |
|     if (NS_SUCCEEDED(rv) &&
 | |
|         NS_SUCCEEDED(regKey->Open(
 | |
|             nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
 | |
|             u"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\"
 | |
|             u"Installer\\UserData"_ns,
 | |
|             nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64))) {
 | |
|       mInstallerData.swap(regKey);
 | |
|     }
 | |
|   }
 | |
|   ~InstalledApplications() = default;
 | |
| 
 | |
|   InstalledApplications(InstalledApplications&&) = delete;
 | |
|   InstalledApplications& operator=(InstalledApplications&&) = delete;
 | |
|   InstalledApplications(const InstalledApplications&) = delete;
 | |
|   InstalledApplications& operator=(const InstalledApplications&) = delete;
 | |
| 
 | |
|   void Collect(ComponentPathMapT& aOutComponentPaths,
 | |
|                nsTArray<InstallLocationT>& aOutLocations) {
 | |
|     const nsLiteralString kUninstallKey(
 | |
|         u"Software\\Microsoft\\Windows\\CurrentVersion\\Uninstall");
 | |
| 
 | |
|     static const uint16_t sProcessor = []() -> uint16_t {
 | |
|       SYSTEM_INFO si;
 | |
|       ::GetSystemInfo(&si);
 | |
|       return si.wProcessorArchitecture;
 | |
|     }();
 | |
| 
 | |
|     nsresult rv;
 | |
|     nsCOMPtr<nsIWindowsRegKey> regKey =
 | |
|         do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     switch (sProcessor) {
 | |
|       case PROCESSOR_ARCHITECTURE_INTEL:
 | |
|         rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
 | |
|                           kUninstallKey, nsIWindowsRegKey::ACCESS_READ);
 | |
|         if (NS_SUCCEEDED(rv)) {
 | |
|           EnumSubkeys(regKey, [this](const nsString& aProductId,
 | |
|                                      nsIWindowsRegKey* aProductSubKey) {
 | |
|             this->AddProduct(aProductId, aProductSubKey);
 | |
|             return CallbackResult::Continue;
 | |
|           });
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       case PROCESSOR_ARCHITECTURE_AMD64:
 | |
|         // A 64-bit application may be installed by a 32-bit installer,
 | |
|         // or vice versa.  So we enumerate both views regardless of
 | |
|         // the process's (not processor's) bitness.
 | |
|         rv = regKey->Open(
 | |
|             nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kUninstallKey,
 | |
|             nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_64);
 | |
|         if (NS_SUCCEEDED(rv)) {
 | |
|           EnumSubkeys(regKey, [this](const nsString& aProductId,
 | |
|                                      nsIWindowsRegKey* aProductSubKey) {
 | |
|             this->AddProduct(aProductId, aProductSubKey);
 | |
|             return CallbackResult::Continue;
 | |
|           });
 | |
|         }
 | |
|         rv = regKey->Open(
 | |
|             nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE, kUninstallKey,
 | |
|             nsIWindowsRegKey::ACCESS_READ | nsIWindowsRegKey::WOW64_32);
 | |
|         if (NS_SUCCEEDED(rv)) {
 | |
|           EnumSubkeys(regKey, [this](const nsString& aProductId,
 | |
|                                      nsIWindowsRegKey* aProductSubKey) {
 | |
|             this->AddProduct(aProductId, aProductSubKey);
 | |
|             return CallbackResult::Continue;
 | |
|           });
 | |
|         }
 | |
|         break;
 | |
| 
 | |
|       default:
 | |
|         MOZ_ASSERT(false, "Unsupported CPU architecture");
 | |
|         return;
 | |
|     }
 | |
| 
 | |
|     // The "HKCU\SOFTWARE\" subtree is shared between the 32-bits and 64 bits
 | |
|     // views.  No need to enumerate wow6432node for HKCU.
 | |
|     // https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys
 | |
|     rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, kUninstallKey,
 | |
|                       nsIWindowsRegKey::ACCESS_READ);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       EnumSubkeys(regKey, [this](const nsString& aProductId,
 | |
|                                  nsIWindowsRegKey* aProductSubKey) {
 | |
|         this->AddProduct(aProductId, aProductSubKey);
 | |
|         return CallbackResult::Continue;
 | |
|       });
 | |
|     }
 | |
| 
 | |
|     aOutComponentPaths.SwapElements(mComponentPaths);
 | |
| 
 | |
|     mLocations.Sort([](const InstallLocationT& aA, const InstallLocationT& aB) {
 | |
|       return CompareIgnoreCase(aA.first(), aB.first());
 | |
|     });
 | |
|     aOutLocations.SwapElements(mLocations);
 | |
|   }
 | |
| };
 | |
| 
 | |
| class KnownModule final {
 | |
|   static KnownModule sKnownExtensions[static_cast<int>(KnownModuleType::Last)];
 | |
| 
 | |
|   static bool GetInprocServerDllPathFromGuid(const GUID& aGuid,
 | |
|                                              nsAString& aResult) {
 | |
|     nsAutoStringN<60> subkey;
 | |
|     subkey.AppendPrintf(
 | |
|         "CLSID\\{%08lx-%04x-%04x-%02x%02x-%02x%02x%02x%02x%02x%02x}\\"
 | |
|         "InProcServer32",
 | |
|         aGuid.Data1, aGuid.Data2, aGuid.Data3, aGuid.Data4[0], aGuid.Data4[1],
 | |
|         aGuid.Data4[2], aGuid.Data4[3], aGuid.Data4[4], aGuid.Data4[5],
 | |
|         aGuid.Data4[6], aGuid.Data4[7]);
 | |
| 
 | |
|     nsresult rv;
 | |
|     nsCOMPtr<nsIWindowsRegKey> regKey =
 | |
|         do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, subkey,
 | |
|                       nsIWindowsRegKey::ACCESS_READ);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return false;
 | |
|     }
 | |
| 
 | |
|     rv = regKey->ReadStringValue(u""_ns, aResult);
 | |
|     return NS_SUCCEEDED(rv);
 | |
|   }
 | |
| 
 | |
|   enum class HandlerType {
 | |
|     // For this type of handler, multiple extensions can be registered as
 | |
|     // subkeys under the handler subkey.
 | |
|     Multi,
 | |
|     // For this type of handler, a single extension can be registered as
 | |
|     // the default value of the handler subkey.
 | |
|     Single,
 | |
|   };
 | |
| 
 | |
|   HandlerType mHandlerType;
 | |
|   nsLiteralString mSubkeyName;
 | |
| 
 | |
|   using CallbackT = std::function<void(const nsString&, KnownModuleType)>;
 | |
| 
 | |
|   void EnumInternal(nsIWindowsRegKey* aRegBase, KnownModuleType aType,
 | |
|                     const CallbackT& aCallback) const {
 | |
|     nsCOMPtr<nsIWindowsRegKey> shexType;
 | |
|     if (NS_FAILED(aRegBase->OpenChild(mSubkeyName,
 | |
|                                       nsIWindowsRegKey::ACCESS_READ,
 | |
|                                       getter_AddRefs(shexType)))) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     switch (mHandlerType) {
 | |
|       case HandlerType::Single: {
 | |
|         nsAutoString valData;
 | |
|         GUID guid;
 | |
|         if (NS_FAILED(shexType->ReadStringValue(u""_ns, valData)) ||
 | |
|             FAILED(::CLSIDFromString(valData.get(), &guid))) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         nsAutoString dllPath;
 | |
|         if (!GetInprocServerDllPathFromGuid(guid, dllPath)) {
 | |
|           return;
 | |
|         }
 | |
| 
 | |
|         aCallback(dllPath, aType);
 | |
|         break;
 | |
|       }
 | |
| 
 | |
|       case HandlerType::Multi:
 | |
|         EnumSubkeys(shexType, [aType, &aCallback](const nsString& aSubKeyName,
 | |
|                                                   nsIWindowsRegKey* aSubKey) {
 | |
|           GUID guid;
 | |
|           HRESULT hr = ::CLSIDFromString(aSubKeyName.get(), &guid);
 | |
|           if (hr == CO_E_CLASSSTRING) {
 | |
|             // If the key's name is not a GUID, the default value of the key
 | |
|             // may be a GUID.
 | |
|             nsAutoString valData;
 | |
|             if (NS_SUCCEEDED(aSubKey->ReadStringValue(u""_ns, valData))) {
 | |
|               hr = ::CLSIDFromString(valData.get(), &guid);
 | |
|             }
 | |
|           }
 | |
| 
 | |
|           if (FAILED(hr)) {
 | |
|             return CallbackResult::Continue;
 | |
|           }
 | |
| 
 | |
|           nsAutoString dllPath;
 | |
|           if (!GetInprocServerDllPathFromGuid(guid, dllPath)) {
 | |
|             return CallbackResult::Continue;
 | |
|           }
 | |
| 
 | |
|           aCallback(dllPath, aType);
 | |
|           return CallbackResult::Continue;
 | |
|         });
 | |
|         break;
 | |
| 
 | |
|       default:
 | |
|         MOZ_ASSERT_UNREACHABLE("Unexpected KnownModule::Type.");
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   static void Enum(nsIWindowsRegKey* aRegBase, KnownModuleType aType,
 | |
|                    const CallbackT& aCallback) {
 | |
|     sKnownExtensions[static_cast<int>(aType)].EnumInternal(aRegBase, aType,
 | |
|                                                            aCallback);
 | |
|   }
 | |
| 
 | |
|   KnownModule(HandlerType aHandlerType, nsLiteralString aSubkeyName)
 | |
|       : mHandlerType(aHandlerType), mSubkeyName(aSubkeyName) {}
 | |
| 
 | |
|  public:
 | |
|   static void EnumAll(const CallbackT& aCallback) {
 | |
|     nsresult rv;
 | |
|     nsCOMPtr<nsIWindowsRegKey> regKey =
 | |
|         do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Icon Overlay Handlers are registered under HKLM only.
 | |
|     // No need to look at HKCU.
 | |
|     rv = regKey->Open(
 | |
|         nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
 | |
|         u"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer"_ns,
 | |
|         nsIWindowsRegKey::ACCESS_READ);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       Enum(regKey, KnownModuleType::IconOverlay, aCallback);
 | |
|     }
 | |
| 
 | |
|     // IMEs can be enumerated by
 | |
|     // ITfInputProcessorProfiles::EnumInputProcessorInfo, but enumerating
 | |
|     // the registry key is easier.
 | |
|     // The "HKLM\Software\Microsoft\CTF\TIP" subtree is shared between
 | |
|     // the 32-bits and 64 bits views.
 | |
|     // https://docs.microsoft.com/en-us/windows/win32/winprog64/shared-registry-keys
 | |
|     // This logic cannot detect legacy (TSF-unaware) IMEs.
 | |
|     rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_LOCAL_MACHINE,
 | |
|                       u"Software\\Microsoft\\CTF"_ns,
 | |
|                       nsIWindowsRegKey::ACCESS_READ);
 | |
|     if (NS_SUCCEEDED(rv)) {
 | |
|       Enum(regKey, KnownModuleType::Ime, aCallback);
 | |
|     }
 | |
| 
 | |
|     // Because HKCR is a merged view of HKLM\Software\Classes and
 | |
|     // HKCU\Software\Classes, looking at HKCR covers both per-machine
 | |
|     // and per-user extensions.
 | |
|     rv = regKey->Open(nsIWindowsRegKey::ROOT_KEY_CLASSES_ROOT, u""_ns,
 | |
|                       nsIWindowsRegKey::ACCESS_READ);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     EnumSubkeys(regKey, [&aCallback](const nsString& aSubKeyName,
 | |
|                                      nsIWindowsRegKey* aSubKey) {
 | |
|       if (aSubKeyName.EqualsIgnoreCase("DesktopBackground") ||
 | |
|           aSubKeyName.EqualsIgnoreCase("AudioCD")) {
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       if (aSubKeyName.EqualsIgnoreCase("Directory")) {
 | |
|         nsCOMPtr<nsIWindowsRegKey> regBackground;
 | |
|         if (NS_SUCCEEDED(aSubKey->OpenChild(u"Background\\shellex"_ns,
 | |
|                                             nsIWindowsRegKey::ACCESS_READ,
 | |
|                                             getter_AddRefs(regBackground)))) {
 | |
|           Enum(regBackground, KnownModuleType::ContextMenuHandler, aCallback);
 | |
|         }
 | |
|       } else if (aSubKeyName.EqualsIgnoreCase("Network")) {
 | |
|         nsCOMPtr<nsIWindowsRegKey> regNetworkTypes;
 | |
|         if (NS_SUCCEEDED(aSubKey->OpenChild(u"Type"_ns,
 | |
|                                             nsIWindowsRegKey::ACCESS_READ,
 | |
|                                             getter_AddRefs(regNetworkTypes)))) {
 | |
|           EnumSubkeys(
 | |
|               regNetworkTypes,
 | |
|               [&aCallback](const nsString&, nsIWindowsRegKey* aRegNetworkType) {
 | |
|                 nsCOMPtr<nsIWindowsRegKey> regNetworkTypeShex;
 | |
|                 if (NS_FAILED(aRegNetworkType->OpenChild(
 | |
|                         u"shellex"_ns, nsIWindowsRegKey::ACCESS_READ,
 | |
|                         getter_AddRefs(regNetworkTypeShex)))) {
 | |
|                   return CallbackResult::Continue;
 | |
|                 }
 | |
| 
 | |
|                 Enum(regNetworkTypeShex, KnownModuleType::ContextMenuHandler,
 | |
|                      aCallback);
 | |
|                 Enum(regNetworkTypeShex, KnownModuleType::PropertySheetHandler,
 | |
|                      aCallback);
 | |
|                 return CallbackResult::Continue;
 | |
|               });
 | |
|         }
 | |
|       }
 | |
| 
 | |
|       nsCOMPtr<nsIWindowsRegKey> regShex;
 | |
|       if (NS_FAILED(aSubKey->OpenChild(u"shellex"_ns,
 | |
|                                        nsIWindowsRegKey::ACCESS_READ,
 | |
|                                        getter_AddRefs(regShex)))) {
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       Enum(regShex, KnownModuleType::ContextMenuHandler, aCallback);
 | |
|       Enum(regShex, KnownModuleType::PropertySheetHandler, aCallback);
 | |
| 
 | |
|       if (aSubKeyName.EqualsIgnoreCase("AllFileSystemObjects") ||
 | |
|           aSubKeyName.EqualsIgnoreCase("Network") ||
 | |
|           aSubKeyName.EqualsIgnoreCase("NetShare") ||
 | |
|           aSubKeyName.EqualsIgnoreCase("NetServer") ||
 | |
|           aSubKeyName.EqualsIgnoreCase("DVD")) {
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       if (aSubKeyName.EqualsIgnoreCase("Directory")) {
 | |
|         Enum(regShex, KnownModuleType::CopyHookHandler, aCallback);
 | |
|         Enum(regShex, KnownModuleType::DragDropHandler, aCallback);
 | |
|         return CallbackResult::Continue;
 | |
|       } else if (aSubKeyName.EqualsIgnoreCase("Drive")) {
 | |
|         Enum(regShex, KnownModuleType::DragDropHandler, aCallback);
 | |
|         return CallbackResult::Continue;
 | |
|       } else if (aSubKeyName.EqualsIgnoreCase("Folder")) {
 | |
|         Enum(regShex, KnownModuleType::DragDropHandler, aCallback);
 | |
|         return CallbackResult::Continue;
 | |
|       } else if (aSubKeyName.EqualsIgnoreCase("Printers")) {
 | |
|         Enum(regShex, KnownModuleType::CopyHookHandler, aCallback);
 | |
|         return CallbackResult::Continue;
 | |
|       }
 | |
| 
 | |
|       Enum(regShex, KnownModuleType::DataHandler, aCallback);
 | |
|       Enum(regShex, KnownModuleType::DropHandler, aCallback);
 | |
|       Enum(regShex, KnownModuleType::IconHandler, aCallback);
 | |
|       Enum(regShex, KnownModuleType::PropertyHandler, aCallback);
 | |
|       Enum(regShex, KnownModuleType::InfotipHandler, aCallback);
 | |
|       return CallbackResult::Continue;
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   KnownModule() = delete;
 | |
|   KnownModule(KnownModule&&) = delete;
 | |
|   KnownModule& operator=(KnownModule&&) = delete;
 | |
|   KnownModule(const KnownModule&) = delete;
 | |
|   KnownModule& operator=(const KnownModule&) = delete;
 | |
| };
 | |
| 
 | |
| KnownModule KnownModule::sKnownExtensions[] = {
 | |
|     {HandlerType::Multi, u"TIP"_ns},
 | |
|     {HandlerType::Multi, u"ShellIconOverlayIdentifiers"_ns},
 | |
|     {HandlerType::Multi, u"ContextMenuHandlers"_ns},
 | |
|     {HandlerType::Multi, u"CopyHookHandlers"_ns},
 | |
|     {HandlerType::Multi, u"DragDropHandlers"_ns},
 | |
|     {HandlerType::Multi, u"PropertySheetHandlers"_ns},
 | |
|     {HandlerType::Single, u"DataHandler"_ns},
 | |
|     {HandlerType::Single, u"DropHandler"_ns},
 | |
|     {HandlerType::Single, u"IconHandler"_ns},
 | |
|     {HandlerType::Single, u"{00021500-0000-0000-C000-000000000046}"_ns},
 | |
|     {HandlerType::Single, u"PropertyHandler"_ns},
 | |
| };
 | |
| 
 | |
| namespace mozilla {
 | |
| 
 | |
| static StaticRefPtr<AboutThirdParty> sSingleton;
 | |
| 
 | |
| NS_IMPL_ISUPPORTS(InstalledApplication, nsIInstalledApplication);
 | |
| NS_IMPL_ISUPPORTS(AboutThirdParty, nsIAboutThirdParty);
 | |
| 
 | |
| InstalledApplication::InstalledApplication(nsString&& aAppName,
 | |
|                                            nsString&& aPublisher)
 | |
|     : mName(std::move(aAppName)), mPublisher(std::move(aPublisher)) {}
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| InstalledApplication::GetName(nsAString& aResult) {
 | |
|   aResult = mName;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| InstalledApplication::GetPublisher(nsAString& aResult) {
 | |
|   aResult = mPublisher;
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| /*static*/
 | |
| already_AddRefed<AboutThirdParty> AboutThirdParty::GetSingleton() {
 | |
|   if (!sSingleton) {
 | |
|     sSingleton = new AboutThirdParty;
 | |
|     ClearOnShutdown(&sSingleton);
 | |
|   }
 | |
| 
 | |
|   return do_AddRef(sSingleton);
 | |
| }
 | |
| 
 | |
| AboutThirdParty::AboutThirdParty()
 | |
|     : mPromise(new BackgroundThreadPromise::Private(__func__)) {}
 | |
| 
 | |
| void AboutThirdParty::AddKnownModule(const nsString& aPath,
 | |
|                                      KnownModuleType aType) {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
| 
 | |
|   const uint32_t flag = 1u << static_cast<uint32_t>(aType);
 | |
|   mKnownModules.WithEntryHandle(nt::GetLeafName(aPath), [flag](auto&& addPtr) {
 | |
|     if (addPtr) {
 | |
|       addPtr.Data() |= flag;
 | |
|     } else {
 | |
|       addPtr.Insert(flag);
 | |
|     }
 | |
|   });
 | |
| }
 | |
| 
 | |
| void AboutThirdParty::BackgroundThread() {
 | |
|   MOZ_ASSERT(!NS_IsMainThread());
 | |
|   MOZ_ASSERT(mWorkerState == WorkerState::Running);
 | |
| 
 | |
|   KnownModule::EnumAll(
 | |
|       [self = RefPtr{this}](const nsString& aDllPath, KnownModuleType aType) {
 | |
|         self->AddKnownModule(aDllPath, aType);
 | |
|       });
 | |
| 
 | |
|   InstalledApplications apps;
 | |
|   apps.Collect(mComponentPaths, mLocations);
 | |
| 
 | |
|   mWorkerState = WorkerState::Done;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP AboutThirdParty::LookupModuleType(const nsAString& aLeafName,
 | |
|                                                 uint32_t* aResult) {
 | |
|   constexpr uint32_t kShellExtensions =
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::IconOverlay) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::ContextMenuHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::CopyHookHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::DragDropHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::PropertySheetHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::DataHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::DropHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::IconHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::InfotipHandler) |
 | |
|       1u << static_cast<uint32_t>(KnownModuleType::PropertyHandler);
 | |
| 
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   *aResult = 0;
 | |
|   if (mWorkerState != WorkerState::Done) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   uint32_t flags;
 | |
|   if (!mKnownModules.Get(aLeafName, &flags)) {
 | |
|     *aResult = nsIAboutThirdParty::ModuleType_Unknown;
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   if (flags & (1u << static_cast<uint32_t>(KnownModuleType::Ime))) {
 | |
|     *aResult |= nsIAboutThirdParty::ModuleType_IME;
 | |
|   }
 | |
| 
 | |
|   if (flags & kShellExtensions) {
 | |
|     *aResult |= nsIAboutThirdParty::ModuleType_ShellExtension;
 | |
|   }
 | |
| 
 | |
|   return NS_OK;
 | |
| }
 | |
| NS_IMETHODIMP AboutThirdParty::LookupApplication(
 | |
|     const nsAString& aModulePath, nsIInstalledApplication** aResult) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   *aResult = nullptr;
 | |
|   if (mWorkerState != WorkerState::Done) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   const nsDependentSubstring leaf = nt::GetLeafName(aModulePath);
 | |
|   if (leaf.IsEmpty()) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   // Look up the component path's map first because it's more accurate
 | |
|   // than the location's array.
 | |
|   nsCOMPtr<nsIInstalledApplication> app = mComponentPaths.Get(aModulePath);
 | |
|   if (app) {
 | |
|     app.forget(aResult);
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   auto bounds = EqualRange(mLocations, 0, mLocations.Length(),
 | |
|                            InstallLocationComparator(aModulePath));
 | |
| 
 | |
|   // If more than one application includes the module, we return null
 | |
|   // because there is no way to know which is the real owner.
 | |
|   if (bounds.second() - bounds.first() != 1) {
 | |
|     return NS_OK;
 | |
|   }
 | |
| 
 | |
|   app = mLocations[bounds.first()].second();
 | |
|   app.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| RefPtr<BackgroundThreadPromise> AboutThirdParty::CollectSystemInfoAsync() {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   // Allow only the first call to start a background task.
 | |
|   if (mWorkerState.compareExchange(WorkerState::Init, WorkerState::Running)) {
 | |
|     nsCOMPtr<nsIRunnable> runnable = NS_NewRunnableFunction(
 | |
|         "AboutThirdParty::BackgroundThread", [self = RefPtr{this}]() mutable {
 | |
|           self->BackgroundThread();
 | |
|           NS_DispatchToMainThread(NS_NewRunnableFunction(
 | |
|               "AboutThirdParty::BackgroundThread Done",
 | |
|               [self]() { self->mPromise->Resolve(true, __func__); }));
 | |
|         });
 | |
| 
 | |
|     nsresult rv =
 | |
|         NS_DispatchBackgroundTask(runnable.forget(), NS_DISPATCH_NORMAL);
 | |
|     if (NS_FAILED(rv)) {
 | |
|       mPromise->Reject(rv, __func__);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   return mPromise;
 | |
| }
 | |
| 
 | |
| NS_IMETHODIMP
 | |
| AboutThirdParty::CollectSystemInfo(JSContext* aCx, dom::Promise** aResult) {
 | |
|   MOZ_ASSERT(NS_IsMainThread());
 | |
| 
 | |
|   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
 | |
|   MOZ_ASSERT(global);
 | |
| 
 | |
|   ErrorResult result;
 | |
|   RefPtr<dom::Promise> promise(dom::Promise::Create(global, result));
 | |
|   if (NS_WARN_IF(result.Failed())) {
 | |
|     return result.StealNSResult();
 | |
|   }
 | |
| 
 | |
|   CollectSystemInfoAsync()->Then(
 | |
|       GetMainThreadSerialEventTarget(), __func__,
 | |
|       [promise](bool) { promise->MaybeResolve(JS::NullHandleValue); },
 | |
|       [promise](nsresult aRv) { promise->MaybeReject(aRv); });
 | |
| 
 | |
|   promise.forget(aResult);
 | |
|   return NS_OK;
 | |
| }
 | |
| 
 | |
| }  // namespace mozilla
 | 
