From f06b41340af75cd4a091e4cf59bdffa8320d8e14 Mon Sep 17 00:00:00 2001 From: Nir Bar Date: Tue, 2 Jan 2024 18:49:08 +0200 Subject: [PATCH] Extensions can create and extract containers using bundle extensions. --- .../inc/BundleExtension.h | 82 +++++++ .../bextutil/inc/BextBaseBundleExtension.h | 51 +++++ .../inc/BextBaseBundleExtensionProc.h | 74 ++++++- src/api/burn/bextutil/inc/IBundleExtension.h | 30 +++ src/api/wix/WixToolset.Data/ErrorMessages.cs | 6 + .../Symbols/WixBundleContainerSymbol.cs | 8 + .../BaseBurnContainerExtension.cs | 87 ++++++++ .../IBurnContainerExtension.cs | 34 +++ src/burn/engine/burnextension.cpp | 202 ++++++++++++++++++ src/burn/engine/burnextension.h | 31 +++ src/burn/engine/container.cpp | 57 ++++- src/burn/engine/container.h | 16 +- src/burn/engine/manifest.cpp | 2 +- .../Bind/BindBundleCommand.cs | 7 +- src/wix/WixToolset.Core.Burn/BundleBackend.cs | 8 +- .../Bundles/CreateBurnManifestCommand.cs | 7 + .../Bundles/CreateNonUXContainers.cs | 32 ++- 17 files changed, 719 insertions(+), 15 deletions(-) create mode 100644 src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs create mode 100644 src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs diff --git a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h index be76a1a50..ea48a298a 100644 --- a/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h +++ b/src/api/burn/WixToolset.BootstrapperCore.Native/inc/BundleExtension.h @@ -9,6 +9,12 @@ extern "C" { enum BUNDLE_EXTENSION_MESSAGE { BUNDLE_EXTENSION_MESSAGE_SEARCH, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_NEXT_STREAM, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_FILE, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_BUFFER, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_SKIP_STREAM, + BUNDLE_EXTENSION_MESSAGE_CONTAINER_CLOSE, }; typedef struct _BUNDLE_EXTENSION_SEARCH_ARGS @@ -23,6 +29,82 @@ typedef struct _BUNDLE_EXTENSION_SEARCH_RESULTS DWORD cbSize; } BUNDLE_EXTENSION_SEARCH_RESULTS; + +// Container ops arguments +typedef struct _BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS +{ + DWORD cbSize; + LPCWSTR wzContainerId; + LPCWSTR wzFilePath; +} BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS +{ + DWORD cbSize; + // String allocated using SysAllocString on input, expected to be allocated using same method on return + BSTR *psczStreamName; +} BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS +{ + DWORD cbSize; + LPVOID pContext; + LPCWSTR wzFileName; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS +{ + DWORD cbSize; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS +{ + DWORD cbSize; + // Buffer must be allocated with CoTaskMemAlloc() + LPBYTE *ppbBuffer; + SIZE_T *pcbBuffer; +} BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_RESULTS +{ + DWORD cbSize; +} BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_RESULTS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS +{ + DWORD cbSize; + LPVOID pContext; +} BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS; + +typedef struct _BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS +{ + DWORD cbSize; +} BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS; + extern "C" typedef HRESULT(WINAPI *PFN_BUNDLE_EXTENSION_PROC)( __in BUNDLE_EXTENSION_MESSAGE message, __in const LPVOID pvArgs, diff --git a/src/api/burn/bextutil/inc/BextBaseBundleExtension.h b/src/api/burn/bextutil/inc/BextBaseBundleExtension.h index a302702ea..c56e746d5 100644 --- a/src/api/burn/bextutil/inc/BextBaseBundleExtension.h +++ b/src/api/burn/bextutil/inc/BextBaseBundleExtension.h @@ -67,6 +67,57 @@ class CBextBaseBundleExtension : public IBundleExtension return E_NOTIMPL; } + virtual STDMETHODIMP ContainerOpen( + __in LPCWSTR /*wzContainerId*/, + __in LPCWSTR /*wzFilePath*/, + __out LPVOID* /*pContext*/ + ) + { + return E_NOTIMPL; + } + + // Implementor should keep the stream name in the contex, to release it when done + virtual STDMETHODIMP ContainerNextStream( + __in LPVOID /*pContext*/, + __inout_z LPWSTR* /*psczStreamName*/ + ) + { + return E_NOTIMPL; + } + + virtual STDMETHODIMP ContainerStreamToFile( + __in LPVOID /*pContext*/, + __in_z LPCWSTR /*wzFileName*/ + ) + { + return E_NOTIMPL; + } + + // Not really needed because it is only used to read the manifest by the engine, and that is always a cab. + virtual STDMETHODIMP ContainerStreamToBuffer( + __in LPVOID /*pContext*/, + __out BYTE** /*ppbBuffer*/, + __out SIZE_T* /*pcbBuffer*/ + ) + { + return E_NOTIMPL; + } + + virtual STDMETHODIMP ContainerSkipStream( + __in LPVOID /*pContext*/ + ) + { + return E_NOTIMPL; + } + + // Don't forget to release everything in the context + virtual STDMETHODIMP ContainerClose( + __in LPVOID /*pContext*/ + ) + { + return E_NOTIMPL; + } + virtual STDMETHODIMP BundleExtensionProc( __in BUNDLE_EXTENSION_MESSAGE /*message*/, __in const LPVOID /*pvArgs*/, diff --git a/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h b/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h index f71e3b924..b32c5629b 100644 --- a/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h +++ b/src/api/burn/bextutil/inc/BextBaseBundleExtensionProc.h @@ -18,6 +18,60 @@ static HRESULT BextBaseBEProcSearch( return pBE->Search(pArgs->wzId, pArgs->wzVariable); } +static HRESULT BextBaseBEProcContainerOpen( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS* pResults + ) +{ + return pBE->ContainerOpen(pArgs->wzContainerId, pArgs->wzFilePath, &pResults->pContext); +} + +static HRESULT BextBaseBEProcContainerNextStream( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS* pResults + ) +{ + return pBE->ContainerNextStream(pArgs->pContext, pResults->psczStreamName); +} + +static HRESULT BextBaseBEProcContainerStreamToFile( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS* /*pResults*/ + ) +{ + return pBE->ContainerStreamToFile(pArgs->pContext, pArgs->wzFileName); +} + +static HRESULT BextBaseBEProcContainerStreamToBuffer( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS* pResults + ) +{ + return pBE->ContainerStreamToBuffer(pArgs->pContext, pResults->ppbBuffer, pResults->pcbBuffer); +} + +static HRESULT BextBaseBEProcContainerSkipStream( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_SKIP_STREAM_RESULTS* /*pResults*/ + ) +{ + return pBE->ContainerSkipStream(pArgs->pContext); +} + +static HRESULT BextBaseBEProcContainerClose( + __in IBundleExtension* pBE, + __in BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS* pArgs, + __inout BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS* /*pResults*/ + ) +{ + return pBE->ContainerClose(pArgs->pContext); +} + /******************************************************************* BextBaseBundleExtensionProc - requires pvContext to be of type IBundleExtension. Provides a default mapping between the message based @@ -33,7 +87,7 @@ static HRESULT WINAPI BextBaseBundleExtensionProc( { IBundleExtension* pBE = reinterpret_cast(pvContext); HRESULT hr = pBE->BundleExtensionProc(message, pvArgs, pvResults, pvContext); - + if (E_NOTIMPL == hr) { switch (message) @@ -41,6 +95,24 @@ static HRESULT WINAPI BextBaseBundleExtensionProc( case BUNDLE_EXTENSION_MESSAGE_SEARCH: hr = BextBaseBEProcSearch(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN: + hr = BextBaseBEProcContainerOpen(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_NEXT_STREAM: + hr = BextBaseBEProcContainerNextStream(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_FILE: + hr = BextBaseBEProcContainerStreamToFile(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_BUFFER: + hr = BextBaseBEProcContainerStreamToBuffer(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_SKIP_STREAM: + hr = BextBaseBEProcContainerSkipStream(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; + case BUNDLE_EXTENSION_MESSAGE_CONTAINER_CLOSE: + hr = BextBaseBEProcContainerClose(pBE, reinterpret_cast(pvArgs), reinterpret_cast(pvResults)); + break; } } diff --git a/src/api/burn/bextutil/inc/IBundleExtension.h b/src/api/burn/bextutil/inc/IBundleExtension.h index 7516c11be..cbae50fbe 100644 --- a/src/api/burn/bextutil/inc/IBundleExtension.h +++ b/src/api/burn/bextutil/inc/IBundleExtension.h @@ -9,6 +9,36 @@ DECLARE_INTERFACE_IID_(IBundleExtension, IUnknown, "93123C9D-796B-4FCD-A507-6EDE __in LPCWSTR wzVariable ) = 0; + STDMETHOD(ContainerOpen)( + __in LPCWSTR wzContainerId, + __in LPCWSTR wzFilePath, + __out LPVOID *ppContext + ) = 0; + + STDMETHOD(ContainerNextStream)( + __in LPVOID pContext, + __inout_z LPWSTR* psczStreamName + ) = 0; + + STDMETHOD(ContainerStreamToFile)( + __in LPVOID pContext, + __in_z LPCWSTR wzFileName + ) = 0; + + STDMETHOD(ContainerStreamToBuffer)( + __in LPVOID pContext, + __out BYTE** ppbBuffer, + __out SIZE_T* pcbBuffer + ) = 0; + + STDMETHOD(ContainerSkipStream)( + __in LPVOID pContext + ) = 0; + + STDMETHOD(ContainerClose)( + __in LPVOID pContext + ) = 0; + // BundleExtensionProc - The PFN_BUNDLE_EXTENSION_PROC can call this method to give the BundleExtension raw access to the callback from the engine. // This might be used to help the BundleExtension support more than one version of the engine. STDMETHOD(BundleExtensionProc)( diff --git a/src/api/wix/WixToolset.Data/ErrorMessages.cs b/src/api/wix/WixToolset.Data/ErrorMessages.cs index e7c886134..be9e7a5c9 100644 --- a/src/api/wix/WixToolset.Data/ErrorMessages.cs +++ b/src/api/wix/WixToolset.Data/ErrorMessages.cs @@ -2266,6 +2266,11 @@ private static Message Message(SourceLineNumber sourceLineNumber, Ids id, Resour return new Message(sourceLineNumber, MessageLevel.Error, (int)id, resourceManager, resourceName, args); } + public static Message MissingContainerExtension(SourceLineNumber sourceLineNumber, string containerId, string bundleExtensionRef) + { + return Message(sourceLineNumber, Ids.MissingContainerExtension, "Container '{0}' has BundleExtensionRef set to '{1}', which could not be resolved to a container extension.", containerId, bundleExtensionRef); + } + public enum Ids { UnexpectedException = 1, @@ -2657,6 +2662,7 @@ public enum Ids MsiTransactionInvalidPackage2 = 412, ExpectedAttributeOrElementWithOtherAttribute = 413, ExpectedAttributeOrElementWithoutOtherAttribute = 414, + MissingContainerExtension = 415, } } } diff --git a/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs b/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs index 80beda0a0..e969ab18b 100644 --- a/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs +++ b/src/api/wix/WixToolset.Data/Symbols/WixBundleContainerSymbol.cs @@ -17,6 +17,7 @@ public static partial class SymbolDefinitions new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.Hash), IntermediateFieldType.String), new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.AttachedContainerIndex), IntermediateFieldType.Number), new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.WorkingPath), IntermediateFieldType.String), + new IntermediateFieldDefinition(nameof(WixBundleContainerSymbolFields.BundleExtensionRef), IntermediateFieldType.String), }, typeof(WixBundleContainerSymbol)); } @@ -35,6 +36,7 @@ public enum WixBundleContainerSymbolFields Hash, AttachedContainerIndex, WorkingPath, + BundleExtensionRef, } /// @@ -99,5 +101,11 @@ public string WorkingPath get => (string)this.Fields[(int)WixBundleContainerSymbolFields.WorkingPath]; set => this.Set((int)WixBundleContainerSymbolFields.WorkingPath, value); } + + public string BundleExtensionRef + { + get => (string)this.Fields[(int)WixBundleContainerSymbolFields.BundleExtensionRef]; + set => this.Set((int)WixBundleContainerSymbolFields.BundleExtensionRef, value); + } } } diff --git a/src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs b/src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs new file mode 100644 index 000000000..97eac4f3d --- /dev/null +++ b/src/api/wix/WixToolset.Extensibility/BaseBurnContainerExtension.cs @@ -0,0 +1,87 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Extensibility +{ + using System.Collections.Generic; + using System.IO; + using System.Security.Cryptography; + using System.Text; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + using WixToolset.Extensibility.Services; + + /// + /// Base class for creating a Burn container extension. + /// + public abstract class BaseBurnContainerExtension : IBurnContainerExtension + { + /// + /// Context for use by the extension. + /// + protected IBindContext Context { get; private set; } + + /// + /// Messaging for use by the extension. + /// + protected IMessaging Messaging { get; private set; } + + /// + /// Backend helper for use by the extension. + /// + protected IBurnBackendHelper BackendHelper { get; private set; } + + /// + /// Collection of bundle extension IDs that this container extension handles. + /// + public abstract IReadOnlyCollection ContainerExtensionIds { get; } + + /// + /// Called at the beginning of the binding phase. + /// + public virtual void PreBackendBind(IBindContext context) + { + this.Context = context; + this.Messaging = context.ServiceProvider.GetService(); + this.BackendHelper = context.ServiceProvider.GetService(); + } + + /// + /// Called during bind phase to create a container + /// Implementors must set to the container file's SHA512, and after creating the container + /// + /// The container symbol. + /// Collection of payloads that should be compressed in the container. + /// SHA512 hash of the container file. + /// File size of the container file. + public abstract void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads, out string sha512, out long size); + + /// + /// Helper method to calculate SHA512 and size of the container + /// + /// + /// + /// + protected void CalculateHashAndSize(string containerPath, out string sha512, out long size) + { + byte[] hashBytes; + + var fileInfo = new FileInfo(containerPath); + using (var managed = new SHA512CryptoServiceProvider()) + { + using (var stream = fileInfo.OpenRead()) + { + hashBytes = managed.ComputeHash(stream); + } + } + + var sb = new StringBuilder(hashBytes.Length * 2); + for (var i = 0; i < hashBytes.Length; i++) + { + sb.AppendFormat("{0:X2}", hashBytes[i]); + } + + sha512 = sb.ToString(); + size = fileInfo.Length; + } + } +} diff --git a/src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs b/src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs new file mode 100644 index 000000000..51f8ed736 --- /dev/null +++ b/src/api/wix/WixToolset.Extensibility/IBurnContainerExtension.cs @@ -0,0 +1,34 @@ +// Copyright (c) .NET Foundation and contributors. All rights reserved. Licensed under the Microsoft Reciprocal License. See LICENSE.TXT file in the project root for full license information. + +namespace WixToolset.Extensibility +{ + using System.Collections.Generic; + using WixToolset.Data.Symbols; + using WixToolset.Extensibility.Data; + + /// + /// Interface for container extensions. + /// + public interface IBurnContainerExtension + { + /// + /// Collection of bundle extension IDs that this container extension handles. + /// + IReadOnlyCollection ContainerExtensionIds { get; } + + /// + /// Called at the beginning of the binding phase. + /// + void PreBackendBind(IBindContext context); + + /// + /// Called during bind phase to create a container + /// Implementors must set to the container file's SHA512, and after creating the container + /// + /// The container symbol. + /// Collection of payloads that should be compressed in the container. + /// SHA512 hash of the container file. + /// File size of the container file. + void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads, out string sha512, out long size); + } +} diff --git a/src/burn/engine/burnextension.cpp b/src/burn/engine/burnextension.cpp index ee4b15422..4736ac2a4 100644 --- a/src/burn/engine/burnextension.cpp +++ b/src/burn/engine/burnextension.cpp @@ -250,6 +250,208 @@ EXTERN_C BEEAPI BurnExtensionPerformSearch( return hr; } +EXTERN_C BEEAPI BurnExtensionContainerOpen( + __in BURN_EXTENSION* pExtension, + __in LPCWSTR wzContainerId, + __in LPCWSTR wzFilePath, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_OPEN_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_OPEN_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.wzContainerId = wzContainerId; + args.wzFilePath = wzFilePath; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_OPEN, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' open container '%ls' failed.", pExtension->sczId, wzFilePath); + + pContext->Bex.pExtensionContext = results.pContext; + +LExit: + return hr; +} + +BEEAPI BurnExtensionContainerNextStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR* psczStreamName +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_NEXT_STREAM_RESULTS results = { }; + BSTR sczStreamName = nullptr; + + if (psczStreamName && *psczStreamName) + { + sczStreamName = ::SysAllocString(*psczStreamName); + ExitOnNull(sczStreamName, hr, E_FAIL, "Failed to allocate sys string"); + } + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + results.psczStreamName = &sczStreamName; + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_NEXT_STREAM, &args, &results); + if (hr != E_NOMOREITEMS) + { + ExitOnFailure(hr, "BundleExtension '%ls' failed to move to next stream.", pExtension->sczId); + + if (psczStreamName) + { + if (sczStreamName) + { + hr = StrAllocString(psczStreamName, sczStreamName, 0); + ExitOnFailure(hr, "Failed to copy string"); + } + else + { + ReleaseNullStr(*psczStreamName); + } + } + } + +LExit: + if (sczStreamName) + { + ::SysFreeString(sczStreamName); + } + + return hr; +} + +BEEAPI BurnExtensionContainerStreamToFile( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_FILE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + args.wzFileName = wzFileName; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_FILE, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to extract file '%ls'.", pExtension->sczId, wzFileName); + +LExit: + return hr; +} + +BEEAPI BurnExtensionContainerStreamToBuffer( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout LPBYTE* ppbBuffer, + __inout SIZE_T* pcbBuffer +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS results = { }; + LPBYTE pbBuffer = nullptr; + SIZE_T cbBuffer = 0; + errno_t err = 0; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + results.ppbBuffer = &pbBuffer; + results.pcbBuffer = &cbBuffer; + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_STREAM_TO_BUFFER, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to extract stream to buffer.", pExtension->sczId); + + if (pbBuffer) + { + if (ppbBuffer && *ppbBuffer) + { + LPVOID pv = MemReAlloc(*ppbBuffer, cbBuffer, FALSE); + ExitOnNull(pv, hr, E_OUTOFMEMORY, "Failed to reallocate memory."); + + *ppbBuffer = (LPBYTE)pv; + *pcbBuffer = cbBuffer; + } + else + { + *ppbBuffer = (LPBYTE)MemAlloc(cbBuffer, FALSE); + ExitOnNull(*ppbBuffer, hr, E_OUTOFMEMORY, "Failed to allocate memory."); + + *pcbBuffer = cbBuffer; + } + + err = ::memcpy_s(*ppbBuffer, cbBuffer, pbBuffer, cbBuffer); + ExitOnNull(!err, hr, HRESULT_FROM_WIN32(err), "Failed to copy memory"); + } + else if (ppbBuffer && *ppbBuffer) + { + ReleaseNullMem(*ppbBuffer); + *pcbBuffer = 0; + } + +LExit: + if (pbBuffer) + { + ::CoTaskMemFree(pbBuffer); + } + + return hr; +} + +BEEAPI BurnExtensionContainerSkipStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_STREAM_TO_BUFFER_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_SKIP_STREAM, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to skip stream.", pExtension->sczId); + +LExit: + return hr; +} + +BEEAPI BurnExtensionContainerClose( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext +) +{ + HRESULT hr = S_OK; + BUNDLE_EXTENSION_CONTAINER_CLOSE_ARGS args = { }; + BUNDLE_EXTENSION_CONTAINER_CLOSE_RESULTS results = { }; + + args.cbSize = sizeof(args); + args.pContext = pContext->Bex.pExtensionContext; + + results.cbSize = sizeof(results); + + hr = SendRequiredBextMessage(pExtension, BUNDLE_EXTENSION_MESSAGE_CONTAINER_CLOSE, &args, &results); + ExitOnFailure(hr, "BundleExtension '%ls' failed to close container.", pExtension->sczId); + +LExit: + return hr; +} + static HRESULT SendRequiredBextMessage( __in BURN_EXTENSION* pExtension, __in BUNDLE_EXTENSION_MESSAGE message, diff --git a/src/burn/engine/burnextension.h b/src/burn/engine/burnextension.h index 3529ef38a..2ba8a87f4 100644 --- a/src/burn/engine/burnextension.h +++ b/src/burn/engine/burnextension.h @@ -55,6 +55,37 @@ BEEAPI BurnExtensionPerformSearch( __in LPWSTR wzSearchId, __in LPWSTR wzVariable ); +BEEAPI BurnExtensionContainerOpen( + __in BURN_EXTENSION* pExtension, + __in LPCWSTR wzContainerId, + __in LPCWSTR wzFilePath, + __in BURN_CONTAINER_CONTEXT* pContext + ); +BEEAPI BurnExtensionContainerNextStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout_z LPWSTR *psczStreamName + ); +BEEAPI BurnExtensionContainerStreamToFile( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __in_z LPCWSTR wzFileName + ); +BEEAPI BurnExtensionContainerStreamToBuffer( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext, + __inout LPBYTE * ppbBuffer, + __inout SIZE_T * pcbBuffer + ); +BEEAPI BurnExtensionContainerSkipStream( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext + ); +BEEAPI BurnExtensionContainerClose( + __in BURN_EXTENSION* pExtension, + __in BURN_CONTAINER_CONTEXT* pContext + ); + #if defined(__cplusplus) } #endif diff --git a/src/burn/engine/container.cpp b/src/burn/engine/container.cpp index e6b915325..70826c53a 100644 --- a/src/burn/engine/container.cpp +++ b/src/burn/engine/container.cpp @@ -7,7 +7,8 @@ extern "C" HRESULT ContainersParseFromXml( __in BURN_CONTAINERS* pContainers, - __in IXMLDOMNode* pixnBundle + __in IXMLDOMNode* pixnBundle, + __in BURN_EXTENSIONS* pBurnExtensions ) { HRESULT hr = S_OK; @@ -44,13 +45,41 @@ extern "C" HRESULT ContainersParseFromXml( hr = XmlNextElement(pixnNodes, &pixnNode, NULL); ExitOnFailure(hr, "Failed to get next node."); - // TODO: Read type from manifest. Today only CABINET is supported. - pContainer->type = BURN_CONTAINER_TYPE_CABINET; - // @Id hr = XmlGetAttributeEx(pixnNode, L"Id", &pContainer->sczId); ExitOnRequiredXmlQueryFailure(hr, "Failed to get @Id."); + // @Type + pContainer->type = BURN_CONTAINER_TYPE_CABINET; // Default + hr = XmlGetAttributeEx(pixnNode, L"Type", &scz); + ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get @Type."); + if (fXmlFound && scz && *scz) + { + if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"Extension", -1)) + { + pContainer->type = BURN_CONTAINER_TYPE_EXTENSION; + } + else if (CSTR_EQUAL == ::CompareStringW(LOCALE_INVARIANT, 0, scz, -1, L"Cabinet", -1)) + { + pContainer->type = BURN_CONTAINER_TYPE_CABINET; + } + else + { + hr = E_INVALIDDATA; + ExitOnFailure(hr, "Unsupported container type '%ls'.", scz); + } + } + + if (BURN_CONTAINER_TYPE_EXTENSION == pContainer->type) + { + // @ExtensionId + hr = XmlGetAttributeEx(pixnNode, L"ExtensionId", &scz); + ExitOnRequiredXmlQueryFailure(hr, "Failed to get @ExtensionId."); + + hr = BurnExtensionFindById(pBurnExtensions, scz, &pContainer->pExtension); + ExitOnRootFailure(hr, "Failed to find bundle extension '%ls' for container '%ls'", scz, pContainer->sczId); + } + // @Attached hr = XmlGetYesNoAttribute(pixnNode, L"Attached", &pContainer->fAttached); ExitOnOptionalXmlQueryFailure(hr, fXmlFound, "Failed to get @Attached."); @@ -260,6 +289,10 @@ extern "C" HRESULT ContainerOpen( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractOpen(pContext, wzFilePath); break; + case BURN_CONTAINER_TYPE_EXTENSION: + pContext->Bex.pExtension = pContainer->pExtension; + hr = BurnExtensionContainerOpen(pContainer->pExtension, pContainer->sczId, wzFilePath, pContext); + break; } ExitOnFailure(hr, "Failed to open container."); @@ -279,6 +312,9 @@ extern "C" HRESULT ContainerNextStream( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractNextStream(pContext, psczStreamName); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerNextStream(pContext->Bex.pExtension, pContext, psczStreamName); + break; } //LExit: @@ -297,6 +333,9 @@ extern "C" HRESULT ContainerStreamToFile( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractStreamToFile(pContext, wzFileName); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerStreamToFile(pContext->Bex.pExtension, pContext, wzFileName); + break; } //LExit: @@ -316,6 +355,9 @@ extern "C" HRESULT ContainerStreamToBuffer( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractStreamToBuffer(pContext, ppbBuffer, pcbBuffer); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerStreamToBuffer(pContext->Bex.pExtension, pContext, ppbBuffer, pcbBuffer); + break; default: *ppbBuffer = NULL; @@ -337,6 +379,9 @@ extern "C" HRESULT ContainerSkipStream( case BURN_CONTAINER_TYPE_CABINET: hr = CabExtractSkipStream(pContext); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerSkipStream(pContext->Bex.pExtension, pContext); + break; } //LExit: @@ -356,6 +401,10 @@ extern "C" HRESULT ContainerClose( hr = CabExtractClose(pContext); ExitOnFailure(hr, "Failed to close cabinet."); break; + case BURN_CONTAINER_TYPE_EXTENSION: + hr = BurnExtensionContainerClose(pContext->Bex.pExtension, pContext); + ExitOnFailure(hr, "Failed to close cabinet."); + break; } LExit: diff --git a/src/burn/engine/container.h b/src/burn/engine/container.h index a38afa90a..fc4d2948d 100644 --- a/src/burn/engine/container.h +++ b/src/burn/engine/container.h @@ -32,6 +32,9 @@ extern "C" { // __in void* pCookie // ); +// Forward declarations +typedef struct _BURN_EXTENSION BURN_EXTENSION; +typedef struct _BURN_EXTENSIONS BURN_EXTENSIONS; // constants @@ -40,6 +43,7 @@ enum BURN_CONTAINER_TYPE BURN_CONTAINER_TYPE_NONE, BURN_CONTAINER_TYPE_CABINET, BURN_CONTAINER_TYPE_SEVENZIP, + BURN_CONTAINER_TYPE_EXTENSION, }; enum BURN_CAB_OPERATION @@ -81,6 +85,8 @@ typedef struct _BURN_CONTAINER DWORD64 qwAttachedOffset; BOOL fActuallyAttached; // indicates whether an attached container is attached or missing. + BURN_EXTENSION* pExtension; + // mutable members BOOL fPlanned; LPWSTR sczSourcePath; @@ -127,6 +133,12 @@ typedef struct _BURN_CONTAINER_CONTEXT_CABINET DWORD cVirtualFilePointers; } BURN_CONTAINER_CONTEXT_CABINET; +typedef struct _BURN_CONTAINER_CONTEXT_BEX +{ + BURN_EXTENSION* pExtension; + LPVOID pExtensionContext; +} BURN_CONTAINER_CONTEXT_BEX; + typedef struct _BURN_CONTAINER_CONTEXT { HANDLE hFile; @@ -143,6 +155,7 @@ typedef struct _BURN_CONTAINER_CONTEXT union { BURN_CONTAINER_CONTEXT_CABINET Cabinet; + BURN_CONTAINER_CONTEXT_BEX Bex; }; } BURN_CONTAINER_CONTEXT; @@ -152,7 +165,8 @@ typedef struct _BURN_CONTAINER_CONTEXT HRESULT ContainersParseFromXml( __in BURN_CONTAINERS* pContainers, - __in IXMLDOMNode* pixnBundle + __in IXMLDOMNode* pixnBundle, + __in BURN_EXTENSIONS* pBurnExtensions ); HRESULT ContainersInitialize( __in BURN_CONTAINERS* pContainers, diff --git a/src/burn/engine/manifest.cpp b/src/burn/engine/manifest.cpp index c0d67c192..01d5490b9 100644 --- a/src/burn/engine/manifest.cpp +++ b/src/burn/engine/manifest.cpp @@ -133,7 +133,7 @@ static HRESULT ParseFromXml( ExitOnFailure(hr, "Failed to parse update."); // parse containers - hr = ContainersParseFromXml(&pEngineState->containers, pixeBundle); + hr = ContainersParseFromXml(&pEngineState->containers, pixeBundle, &pEngineState->extensions); ExitOnFailure(hr, "Failed to parse containers."); // parse payloads diff --git a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs index 370364d18..0ddb68574 100644 --- a/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bind/BindBundleCommand.cs @@ -24,7 +24,7 @@ namespace WixToolset.Core.Burn /// internal class BindBundleCommand { - public BindBundleCommand(IBindContext context, IEnumerable backedExtensions) + public BindBundleCommand(IBindContext context, IEnumerable backedExtensions, IEnumerable containerExtensions) { this.ServiceProvider = context.ServiceProvider; @@ -44,6 +44,7 @@ public BindBundleCommand(IBindContext context, IEnumerable BackendExtensions { get; } + private IEnumerable ContainerExtensions { get; } + private Intermediate Output { get; } private string OutputPath { get; } @@ -443,7 +446,7 @@ public void Execute() WixBundleContainerSymbol uxContainer; IEnumerable uxPayloads; { - var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); + var command = new CreateNonUXContainers(this.BackendHelper, this.Messaging, this.ContainerExtensions, bundleApplicationDllSymbol, containers.Values, payloadSymbols, this.IntermediateFolder, layoutDirectory, this.DefaultCompressionLevel); command.Execute(); fileTransfers.AddRange(command.FileTransfers); diff --git a/src/wix/WixToolset.Core.Burn/BundleBackend.cs b/src/wix/WixToolset.Core.Burn/BundleBackend.cs index cf1971f6e..160bb2991 100644 --- a/src/wix/WixToolset.Core.Burn/BundleBackend.cs +++ b/src/wix/WixToolset.Core.Burn/BundleBackend.cs @@ -13,13 +13,19 @@ public IBindResult Bind(IBindContext context) var extensionManager = context.ServiceProvider.GetService(); var backendExtensions = extensionManager.GetServices(); + var containerExtensions = extensionManager.GetServices(); foreach (var extension in backendExtensions) { extension.PreBackendBind(context); } - var command = new BindBundleCommand(context, backendExtensions); + foreach (var extension in containerExtensions) + { + extension.PreBackendBind(context); + } + + var command = new BindBundleCommand(context, backendExtensions, containerExtensions); command.Execute(); var result = context.ServiceProvider.GetService(); diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs index 0a11ea3aa..80ca6b47f 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateBurnManifestCommand.cs @@ -732,6 +732,13 @@ private void WriteBurnManifestContainerAttributes(XmlTextWriter writer, string e writer.WriteAttributeString("Attached", "yes"); writer.WriteAttributeString("Primary", "yes"); } + + // Extension container + if (!String.IsNullOrEmpty(container.BundleExtensionRef)) + { + writer.WriteAttributeString("Type", "Extension"); + writer.WriteAttributeString("ExtensionId", container.BundleExtensionRef); + } } private void WriteBurnManifestPayload(XmlTextWriter writer, WixBundlePayloadSymbol payload) diff --git a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs index 8e83408a6..a2a6dc331 100644 --- a/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs +++ b/src/wix/WixToolset.Core.Burn/Bundles/CreateNonUXContainers.cs @@ -10,12 +10,13 @@ namespace WixToolset.Core.Burn.Bundles using WixToolset.Data; using WixToolset.Data.Burn; using WixToolset.Data.Symbols; + using WixToolset.Extensibility; using WixToolset.Extensibility.Data; using WixToolset.Extensibility.Services; internal class CreateNonUXContainers { - public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) + public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, IEnumerable containerExtensions, WixBootstrapperApplicationDllSymbol bootstrapperApplicationDllSymbol, IEnumerable containerSymbols, Dictionary payloadSymbols, string intermediateFolder, string layoutFolder, CompressionLevel? defaultCompressionLevel) { this.BackendHelper = backendHelper; this.Messaging = messaging; @@ -25,6 +26,7 @@ public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, this.IntermediateFolder = intermediateFolder; this.LayoutFolder = layoutFolder; this.DefaultCompressionLevel = defaultCompressionLevel; + this.ContainerExtensions = containerExtensions; } public IEnumerable FileTransfers { get; private set; } @@ -41,6 +43,8 @@ public CreateNonUXContainers(IBackendHelper backendHelper, IMessaging messaging, private IMessaging Messaging { get; } + private IEnumerable ContainerExtensions { get; } + private WixBootstrapperApplicationDllSymbol BootstrapperApplicationDllSymbol { get; } private Dictionary PayloadSymbols { get; } @@ -137,11 +141,29 @@ public void Execute() private void CreateContainer(WixBundleContainerSymbol container, IEnumerable containerPayloads) { - var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); - command.Execute(); + if (String.IsNullOrEmpty(container.BundleExtensionRef)) + { + var command = new CreateContainerCommand(containerPayloads, container.WorkingPath, this.DefaultCompressionLevel); + command.Execute(); + + container.Hash = command.Hash; + container.Size = command.Size; + return; + } + + container.Hash = null; + container.Size = 0; + IBurnContainerExtension containerExtension = this.ContainerExtensions.FirstOrDefault(ce => (ce.ContainerExtensionIds != null) && ce.ContainerExtensionIds.Contains(container.BundleExtensionRef)); + if (containerExtension == null) + { + this.Messaging.Write(ErrorMessages.MissingContainerExtension(container.SourceLineNumbers, container.Id.Id, container.BundleExtensionRef)); + return; + } + + containerExtension.CreateContainer(container, containerPayloads, out string sha512, out long size); - container.Hash = command.Hash; - container.Size = command.Size; + container.Hash = sha512; + container.Size = size; } } }