diff options
Diffstat (limited to '')
-rw-r--r-- | toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp | 341 |
1 files changed, 341 insertions, 0 deletions
diff --git a/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp new file mode 100644 index 0000000000..98cff7db40 --- /dev/null +++ b/toolkit/system/windowsPackageManager/nsWindowsPackageManager.cpp @@ -0,0 +1,341 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsWindowsPackageManager.h" +#include "mozilla/Logging.h" +#include "mozilla/WindowsVersion.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "mozilla/mscom/EnsureMTA.h" +#ifndef __MINGW32__ +# include <comutil.h> +# include <wrl.h> +# include <windows.applicationmodel.store.h> +# include <windows.management.deployment.h> +# include <windows.services.store.h> +#endif // __MINGW32__ +#include "nsError.h" +#include "nsString.h" +#include "nsISupportsPrimitives.h" +#include "nsCOMPtr.h" +#include "nsComponentManagerUtils.h" +#include "nsXPCOMCID.h" +#include "json/json.h" + +#ifndef __MINGW32__ // WinRT headers not yet supported by MinGW +using namespace Microsoft::WRL; +using namespace Microsoft::WRL::Wrappers; +using namespace ABI::Windows; +using namespace ABI::Windows::Foundation; +using namespace ABI::Windows::Management; +using namespace ABI::Windows::Services::Store; +#endif + +// Campaign IDs are stored in a JSON data structure under this key +// for installs done without the user being signed in to the Microsoft +// store. +#define CAMPAIGN_ID_JSON_FIELD_NAME "customPolicyField1" + +namespace mozilla { +namespace toolkit { +namespace system { + +NS_IMPL_ISUPPORTS(nsWindowsPackageManager, nsIWindowsPackageManager) + +NS_IMETHODIMP +nsWindowsPackageManager::FindUserInstalledPackages( + const nsTArray<nsString>& aNamePrefixes, nsTArray<nsString>& aPackages) { +#ifdef __MINGW32__ + return NS_ERROR_NOT_IMPLEMENTED; +#else + // The classes we're using are only available beginning with Windows 10 + if (!mozilla::IsWin10OrLater()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + ComPtr<IInspectable> pmInspectable; + ComPtr<Deployment::IPackageManager> pm; + HRESULT hr = RoActivateInstance( + HStringReference( + RuntimeClass_Windows_Management_Deployment_PackageManager) + .Get(), + &pmInspectable); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + hr = pmInspectable.As(&pm); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + + ComPtr<Collections::IIterable<ApplicationModel::Package*> > pkgs; + hr = pm->FindPackagesByUserSecurityId(NULL, &pkgs); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + ComPtr<Collections::IIterator<ApplicationModel::Package*> > iterator; + hr = pkgs->First(&iterator); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + boolean hasCurrent; + hr = iterator->get_HasCurrent(&hasCurrent); + while (SUCCEEDED(hr) && hasCurrent) { + ComPtr<ApplicationModel::IPackage> package; + hr = iterator->get_Current(&package); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + ComPtr<ApplicationModel::IPackageId> packageId; + hr = package->get_Id(&packageId); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + HString rawName; + hr = packageId->get_FamilyName(rawName.GetAddressOf()); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + unsigned int tmp; + nsString name(rawName.GetRawBuffer(&tmp)); + for (uint32_t i = 0; i < aNamePrefixes.Length(); i++) { + if (name.Find(aNamePrefixes.ElementAt(i)) != kNotFound) { + aPackages.AppendElement(name); + break; + } + } + hr = iterator->MoveNext(&hasCurrent); + } + return NS_OK; +#endif // __MINGW32__ +} + +NS_IMETHODIMP +nsWindowsPackageManager::GetInstalledDate(uint64_t* ts) { +#ifdef __MINGW32__ + return NS_ERROR_NOT_IMPLEMENTED; +#else + // The classes we're using are only available beginning with Windows 10 + if (!mozilla::IsWin10OrLater()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + ComPtr<ApplicationModel::IPackageStatics> pkgStatics; + HRESULT hr = RoGetActivationFactory( + HStringReference(RuntimeClass_Windows_ApplicationModel_Package).Get(), + IID_PPV_ARGS(&pkgStatics)); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + + ComPtr<ApplicationModel::IPackage> package; + hr = pkgStatics->get_Current(&package); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + + ComPtr<ApplicationModel::IPackage3> package3; + hr = package.As(&package3); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + + DateTime installedDate; + hr = package3->get_InstalledDate(&installedDate); + if (!SUCCEEDED(hr)) { + return NS_ERROR_FAILURE; + } + + *ts = installedDate.UniversalTime; + return NS_OK; +#endif // __MINGW32__ +} + +NS_IMETHODIMP +nsWindowsPackageManager::GetCampaignId(nsAString& aCampaignId) { +#ifdef __MINGW32__ + return NS_ERROR_NOT_IMPLEMENTED; +#else + // The classes we're using are only available beginning with Windows 10, + // and this is only relevant for MSIX packaged builds. + if (!mozilla::IsWin10OrLater() || !mozilla::HasPackageIdentity()) { + return NS_ERROR_NOT_IMPLEMENTED; + } + + ComPtr<IStoreContextStatics> scStatics = nullptr; + HRESULT hr = RoGetActivationFactory( + HStringReference(RuntimeClass_Windows_Services_Store_StoreContext).Get(), + IID_PPV_ARGS(&scStatics)); + /* Per + * https://docs.microsoft.com/en-us/windows/uwp/publish/create-a-custom-app-promotion-campaign#programmatically-retrieve-the-custom-campaign-id-for-an-app + * there are three ways to find a campaign ID. + * 1) If the user was logged into the Microsoft Store when they installed + * it will be available in a SKU. + * 2) If they were not logged in, it will be available through the + * StoreAppLicense + * + * There's also a third way, in theory, to retrieve it on very old versions of + * Windows 10 (1511 or earlier). However, these versions don't appear to be + * able to use the Windows Store at all anymore - so there's no point in + * supporting that scenario. + * + */ + if (!SUCCEEDED(hr) || scStatics == nullptr) return NS_ERROR_FAILURE; + ComPtr<IStoreContext> storeContext = nullptr; + hr = scStatics->GetDefault(&storeContext); + if (!SUCCEEDED(hr) || storeContext == nullptr) return NS_ERROR_FAILURE; + + ComPtr<IAsyncOperation<StoreProductResult*> > asyncSpr = nullptr; + + { + nsAutoHandle event(CreateEventW(nullptr, true, false, nullptr)); + bool asyncOpSucceeded = false; + + // Despite the documentation indicating otherwise, the async operations + // and callbacks used here don't seem to work outside of a COM MTA. + mozilla::mscom::EnsureMTA( + [&event, &asyncOpSucceeded, &hr, &storeContext, &asyncSpr]() -> void { + auto callback = + Callback<IAsyncOperationCompletedHandler<StoreProductResult*> >( + [&asyncOpSucceeded, &event]( + IAsyncOperation<StoreProductResult*>* asyncInfo, + AsyncStatus status) -> HRESULT { + asyncOpSucceeded = status == AsyncStatus::Completed; + return SetEvent(event.get()); + }); + + hr = storeContext->GetStoreProductForCurrentAppAsync(&asyncSpr); + if (!SUCCEEDED(hr) || asyncSpr == nullptr) { + asyncOpSucceeded = false; + return; + } + hr = asyncSpr->put_Completed(callback.Get()); + if (!SUCCEEDED(hr)) { + asyncOpSucceeded = false; + return; + } + + DWORD ret = WaitForSingleObject(event.get(), 30000); + if (ret != WAIT_OBJECT_0) { + asyncOpSucceeded = false; + } + }); + if (!asyncOpSucceeded) return NS_ERROR_FAILURE; + } + + ComPtr<IStoreProductResult> productResult = nullptr; + hr = asyncSpr->GetResults(&productResult); + if (!SUCCEEDED(hr) || productResult == nullptr) return NS_ERROR_FAILURE; + + ComPtr<IStoreProduct> product = nullptr; + hr = productResult->get_Product(&product); + if (!SUCCEEDED(hr) || product == nullptr) return NS_ERROR_FAILURE; + ComPtr<Collections::IVectorView<StoreSku*> > skus = nullptr; + hr = product->get_Skus(&skus); + if (!SUCCEEDED(hr) || skus == nullptr) return NS_ERROR_FAILURE; + + unsigned int size; + hr = skus->get_Size(&size); + if (!SUCCEEDED(hr)) return NS_ERROR_FAILURE; + + for (unsigned int i = 0; i < size; i++) { + ComPtr<IStoreSku> sku = nullptr; + hr = skus->GetAt(i, &sku); + if (!SUCCEEDED(hr) || sku == nullptr) return NS_ERROR_FAILURE; + + boolean isInUserCollection = false; + hr = sku->get_IsInUserCollection(&isInUserCollection); + if (!SUCCEEDED(hr) || !isInUserCollection) continue; + + ComPtr<IStoreCollectionData> scd = nullptr; + hr = sku->get_CollectionData(&scd); + if (!SUCCEEDED(hr) || scd == nullptr) continue; + + HString campaignId; + hr = scd->get_CampaignId(campaignId.GetAddressOf()); + if (!SUCCEEDED(hr)) continue; + + unsigned int tmp; + aCampaignId.Assign(campaignId.GetRawBuffer(&tmp)); + if (aCampaignId.Length() > 0) { + aCampaignId.AppendLiteral("&msstoresignedin=true"); + } + } + + // There's various points above that could exit without a failure. + // If we get here without a campaignId we may as well just check + // the AppStoreLicense. + if (aCampaignId.IsEmpty()) { + ComPtr<IAsyncOperation<StoreAppLicense*> > asyncSal = nullptr; + bool asyncOpSucceeded = false; + nsAutoHandle event(CreateEventW(nullptr, true, false, nullptr)); + mozilla::mscom::EnsureMTA( + [&event, &asyncOpSucceeded, &hr, &storeContext, &asyncSal]() -> void { + auto callback = + Callback<IAsyncOperationCompletedHandler<StoreAppLicense*> >( + [&asyncOpSucceeded, &event]( + IAsyncOperation<StoreAppLicense*>* asyncInfo, + AsyncStatus status) -> HRESULT { + asyncOpSucceeded = status == AsyncStatus::Completed; + return SetEvent(event.get()); + }); + + hr = storeContext->GetAppLicenseAsync(&asyncSal); + if (!SUCCEEDED(hr) || asyncSal == nullptr) { + asyncOpSucceeded = false; + return; + } + hr = asyncSal->put_Completed(callback.Get()); + if (!SUCCEEDED(hr)) { + asyncOpSucceeded = false; + return; + } + + DWORD ret = WaitForSingleObject(event.get(), 30000); + if (ret != WAIT_OBJECT_0) { + asyncOpSucceeded = false; + } + }); + if (!asyncOpSucceeded) return NS_ERROR_FAILURE; + + ComPtr<IStoreAppLicense> license = nullptr; + hr = asyncSal->GetResults(&license); + if (!SUCCEEDED(hr) || license == nullptr) return NS_ERROR_FAILURE; + + HString extendedData; + hr = license->get_ExtendedJsonData(extendedData.GetAddressOf()); + if (!SUCCEEDED(hr)) return NS_ERROR_FAILURE; + + Json::Value jsonData; + Json::Reader jsonReader; + + unsigned int tmp; + nsAutoString key(extendedData.GetRawBuffer(&tmp)); + if (!jsonReader.parse(NS_ConvertUTF16toUTF8(key).get(), jsonData, false)) { + return NS_ERROR_FAILURE; + } + + if (jsonData.isMember(CAMPAIGN_ID_JSON_FIELD_NAME) && + jsonData[CAMPAIGN_ID_JSON_FIELD_NAME].isString()) { + aCampaignId.Assign( + NS_ConvertUTF8toUTF16( + jsonData[CAMPAIGN_ID_JSON_FIELD_NAME].asString().c_str()) + .get()); + if (aCampaignId.Length() > 0) { + aCampaignId.AppendLiteral("&msstoresignedin=false"); + } + } + } + + // No matter what happens in either block above, if they don't exit with a + // failure we managed to successfully pull the campaignId from somewhere + // (even if its empty). + return NS_OK; + +#endif // __MINGW32__ +} + +} // namespace system +} // namespace toolkit +} // namespace mozilla |