diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-19 00:47:55 +0000 |
commit | 26a029d407be480d791972afb5975cf62c9360a6 (patch) | |
tree | f435a8308119effd964b339f76abb83a57c29483 /toolkit/components/aboutthirdparty/AboutThirdParty.cpp | |
parent | Initial commit. (diff) | |
download | firefox-26a029d407be480d791972afb5975cf62c9360a6.tar.xz firefox-26a029d407be480d791972afb5975cf62c9360a6.zip |
Adding upstream version 124.0.1.upstream/124.0.1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/aboutthirdparty/AboutThirdParty.cpp')
-rw-r--r-- | toolkit/components/aboutthirdparty/AboutThirdParty.cpp | 923 |
1 files changed, 923 insertions, 0 deletions
diff --git a/toolkit/components/aboutthirdparty/AboutThirdParty.cpp b/toolkit/components/aboutthirdparty/AboutThirdParty.cpp new file mode 100644 index 0000000000..73cef0d531 --- /dev/null +++ b/toolkit/components/aboutthirdparty/AboutThirdParty.cpp @@ -0,0 +1,923 @@ +/* -*- 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 "base/command_line.h" +#include "base/string_util.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/dom/Promise.h" +#include "mozilla/DynamicBlocklist.h" +#include "mozilla/GeckoArgs.h" +#include "mozilla/NativeNt.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/WinDllServices.h" +#include "MsiDatabase.h" +#include "nsAppRunner.h" +#include "nsComponentManagerUtils.h" +#include "nsIWindowsRegKey.h" +#include "nsThreadUtils.h" + +#include <objbase.h> + +using namespace mozilla; + +template <> +bool DllBlockInfo::IsValidDynamicBlocklistEntry() const { + if (!mName.Buffer || !mName.Length || mName.Length > mName.MaximumLength) { + return false; + } + MOZ_ASSERT(mMaxVersion == DllBlockInfo::ALL_VERSIONS, + "dynamic blocklist does not allow custom version"); + MOZ_ASSERT(mFlags == DllBlockInfoFlags::FLAGS_DEFAULT, + "dynamic blocklist does not allow custom flags"); + return true; +} + +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."); + } + } +} + +Span<const DllBlockInfo> GetDynamicBlocklistSpan( + RefPtr<DllServices>&& aDllSvc) { + if (!aDllSvc) { + return nullptr; + } + + nt::SharedSection* sharedSection = aDllSvc->GetSharedSection(); + if (!sharedSection) { + return nullptr; + } + + return sharedSection->GetDynamicBlocklist(); +} + +} // 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); + + auto cleanup = MakeScopeExit( + [self = RefPtr{this}] { self->mWorkerState = WorkerState::Done; }); + + RefPtr<DllServices> dllSvc(DllServices::Get()); + if (!dllSvc) { + // Probably we're shutting down. Bail out before expensive tasks. + return; + } + + KnownModule::EnumAll( + [self = RefPtr{this}](const nsString& aDllPath, KnownModuleType aType) { + self->AddKnownModule(aDllPath, aType); + }); + + InstalledApplications apps; + apps.Collect(mComponentPaths, mLocations); + +#if defined(MOZ_LAUNCHER_PROCESS) + Span<const DllBlockInfo> blocklist = + GetDynamicBlocklistSpan(std::move(dllSvc)); + for (const auto& info : blocklist) { + if (!info.IsValidDynamicBlocklistEntry()) { + break; + } + + nsString name(info.mName.Buffer, info.mName.Length / sizeof(wchar_t)); + mDynamicBlocklist.Insert(name); + mDynamicBlocklistAtLaunch.Insert(std::move(name)); + } +#endif // defined(MOZ_LAUNCHER_PROCESS) +} + +NS_IMETHODIMP AboutThirdParty::LookupModuleType(const nsAString& aLeafName, + uint32_t* aResult) { + static_assert(static_cast<uint32_t>(KnownModuleType::Last) <= 32, + "Too many flags in KnownModuleType"); + 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; + } + +#if defined(MOZ_LAUNCHER_PROCESS) + if (mDynamicBlocklist.Contains(aLeafName)) { + *aResult |= nsIAboutThirdParty::ModuleType_BlockedByUser; + } + if (mDynamicBlocklistAtLaunch.Contains(aLeafName)) { + *aResult |= nsIAboutThirdParty::ModuleType_BlockedByUserAtLaunch; + } +#endif + + 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; +} + +NS_IMETHODIMP AboutThirdParty::GetIsDynamicBlocklistAvailable( + bool* aIsDynamicBlocklistAvailable) { + *aIsDynamicBlocklistAvailable = + !GetDynamicBlocklistSpan(DllServices::Get()).IsEmpty(); + return NS_OK; +} + +NS_IMETHODIMP AboutThirdParty::GetIsDynamicBlocklistDisabled( + bool* aIsDynamicBlocklistDisabled) { + *aIsDynamicBlocklistDisabled = IsDynamicBlocklistDisabled( + gSafeMode, CommandLine::ForCurrentProcess()->HasSwitch(UTF8ToWide( + mozilla::geckoargs::sDisableDynamicDllBlocklist.sMatch))); + return NS_OK; +} + +NS_IMETHODIMP AboutThirdParty::UpdateBlocklist(const nsAString& aLeafName, + bool aNewBlockStatus, + JSContext* aCx, + dom::Promise** aResult) { +#if defined(MOZ_LAUNCHER_PROCESS) + 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(); + } + + auto returnPromise = MakeScopeExit([&] { promise.forget(aResult); }); + + if (aNewBlockStatus) { + mDynamicBlocklist.Insert(aLeafName); + } else { + mDynamicBlocklist.Remove(aLeafName); + } + + auto newTask = MakeUnique<DynamicBlocklistWriter>(promise, mDynamicBlocklist); + if (!newTask->IsReady()) { + promise->MaybeReject(NS_ERROR_CANNOT_CONVERT_DATA); + return NS_OK; + } + + UniquePtr<DynamicBlocklistWriter> oldTask( + mPendingWriter.exchange(newTask.release())); + if (oldTask) { + oldTask->Cancel(); + } + + nsresult rv = NS_DispatchBackgroundTask( + NS_NewRunnableFunction(__func__, + [self = RefPtr{this}]() { + UniquePtr<DynamicBlocklistWriter> task( + self->mPendingWriter.exchange(nullptr)); + if (task) { + task->Run(); + } + }), + NS_DISPATCH_NORMAL); + if (NS_FAILED(rv)) { + promise->MaybeReject(rv); + } + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif // defined(MOZ_LAUNCHER_PROCESS) +} + +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 |