From 872701a149520b41e3fc7fececf04aadc910039b Mon Sep 17 00:00:00 2001 From: Peter Shafton Date: Tue, 25 Nov 2025 12:09:27 -0800 Subject: [PATCH 1/3] Add support for adding a payload to a package --- src/inc/internal/AppxBundleWriter.hpp | 173 +++---- src/inc/internal/ZipObjectWriter.hpp | 145 +++--- src/msix/pack/AppxBundleWriter.cpp | 646 +++++++++++++------------- src/msix/pack/AppxPackageWriter.cpp | 518 ++++++++++----------- src/msix/pack/ZipObjectWriter.cpp | 348 +++++++------- 5 files changed, 922 insertions(+), 908 deletions(-) diff --git a/src/inc/internal/AppxBundleWriter.hpp b/src/inc/internal/AppxBundleWriter.hpp index 82fa589fa..3ba7bde01 100644 --- a/src/inc/internal/AppxBundleWriter.hpp +++ b/src/inc/internal/AppxBundleWriter.hpp @@ -1,86 +1,87 @@ -// -// Copyright (C) 2019 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// -#pragma once - -#include "AppxPackaging.hpp" -#include "ComHelper.hpp" -#include "DirectoryObject.hpp" -#include "AppxBlockMapWriter.hpp" -#include "ContentTypeWriter.hpp" -#include "ZipObjectWriter.hpp" -#include "BundleWriterHelper.hpp" -#include "BundleManifestWriter.hpp" -#include "AppxPackageInfo.hpp" - -// internal interface -// {ca90bcd9-78a2-4773-820c-0b687de49f99} -#ifndef WIN32 -interface IBundleWriter : public IUnknown -#else -#include "Unknwn.h" -#include "Objidl.h" -class IBundleWriter : public IUnknown -#endif -{ -public: - virtual void ProcessBundlePayload(const MSIX::ComPtr& from, bool flatBundle) = 0; - virtual void ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) = 0; - virtual void ProcessExternalPackages(std::map externalPackagesList) = 0; -}; -MSIX_INTERFACE(IBundleWriter, 0xca90bcd9,0x78a2,0x4773,0x82,0x0c,0x0b,0x68,0x7d,0xe4,0x9f,0x99); - -namespace MSIX { - class AppxBundleWriter final : public ComClass - { - public: - AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion); - ~AppxBundleWriter() {}; - - // IBundleWriter - void ProcessBundlePayload(const ComPtr& from, bool flatBundle) override; - void ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) override; - void ProcessExternalPackages(std::map externalPackagesList) override; - - // IAppxBundleWriter - HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept override; - HRESULT STDMETHODCALLTYPE Close() noexcept override; - - // IAppxBundleWriter4 - HRESULT STDMETHODCALLTYPE AddPackageReference(LPCWSTR fileName, IStream* inputStream, - BOOL isDefaultApplicablePackage) noexcept override; - HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, - BOOL isDefaultApplicablePackage) noexcept override; - HRESULT STDMETHODCALLTYPE AddExternalPackageReference(LPCWSTR fileName, IStream* inputStream, - BOOL isDefaultApplicablePackage) noexcept override; - - protected: - typedef enum - { - Open = 1, - Closed = 2, - Failed = 3 - } - WriterState; - - void AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, - bool addToBlockMap, const char* contentType, bool forceContentTypeOverride = false); - - void AddPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage); - - void AddExternalPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage); - - void ValidateAndAddPayloadFile(const std::string& name, IStream* stream, APPX_COMPRESSION_OPTION compressionOpt, const char* contentType); - - void ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt); - - WriterState m_state; - ComPtr m_factory; - ComPtr m_zipWriter; - BlockMapWriter m_blockMapWriter; - ContentTypeWriter m_contentTypeWriter; - BundleWriterHelper m_bundleWriterHelper; - }; -} - +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "AppxPackaging.hpp" +#include "ComHelper.hpp" +#include "DirectoryObject.hpp" +#include "AppxBlockMapWriter.hpp" +#include "ContentTypeWriter.hpp" +#include "ZipObjectWriter.hpp" +#include "BundleWriterHelper.hpp" +#include "BundleManifestWriter.hpp" +#include "AppxPackageInfo.hpp" + +// internal interface +// {ca90bcd9-78a2-4773-820c-0b687de49f99} +#ifndef WIN32 +interface IBundleWriter : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IBundleWriter : public IUnknown +#endif +{ +public: + virtual void ProcessBundlePayload(const MSIX::ComPtr& from, bool flatBundle) = 0; + virtual void ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) = 0; + virtual void ProcessExternalPackages(std::map externalPackagesList) = 0; +}; +MSIX_INTERFACE(IBundleWriter, 0xca90bcd9,0x78a2,0x4773,0x82,0x0c,0x0b,0x68,0x7d,0xe4,0x9f,0x99); + +namespace MSIX { + class AppxBundleWriter final : public ComClass + { + public: + AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion); + ~AppxBundleWriter() {}; + + // IBundleWriter + void ProcessBundlePayload(const ComPtr& from, bool flatBundle) override; + void ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) override; + void ProcessExternalPackages(std::map externalPackagesList) override; + + // IAppxBundleWriter + HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept override; + HRESULT STDMETHODCALLTYPE Close() noexcept override; + + // IAppxBundleWriter4 + HRESULT STDMETHODCALLTYPE AddPackageReference(LPCWSTR fileName, IStream* inputStream, + BOOL isDefaultApplicablePackage) noexcept override; + HRESULT STDMETHODCALLTYPE AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, + BOOL isDefaultApplicablePackage) noexcept override; + HRESULT STDMETHODCALLTYPE AddExternalPackageReference(LPCWSTR fileName, IStream* inputStream, + BOOL isDefaultApplicablePackage) noexcept override; + + protected: + typedef enum + { + Open = 1, + Closed = 2, + Failed = 3 + } + WriterState; + + // Returns the offset of the file in the bundle + std::uint64_t AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride = false); + + void AddPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage); + + void AddExternalPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage); + + void ValidateAndAddPayloadFile(const std::string& name, IStream* stream, APPX_COMPRESSION_OPTION compressionOpt, const char* contentType); + + void ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt); + + WriterState m_state; + ComPtr m_factory; + ComPtr m_zipWriter; + BlockMapWriter m_blockMapWriter; + ContentTypeWriter m_contentTypeWriter; + BundleWriterHelper m_bundleWriterHelper; + }; +} + diff --git a/src/inc/internal/ZipObjectWriter.hpp b/src/inc/internal/ZipObjectWriter.hpp index 33b35417b..5a583bf89 100644 --- a/src/inc/internal/ZipObjectWriter.hpp +++ b/src/inc/internal/ZipObjectWriter.hpp @@ -1,73 +1,74 @@ -// -// Copyright (C) 2019 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// -#pragma once - -#include "AppxPackaging.hpp" -#include "Exceptions.hpp" -#include "ComHelper.hpp" -#include "ZipObject.hpp" - -#include -#include -#include -#include - -#include - -// {350dd671-0c40-4cd7-9a5b-27456d604bd0} -#ifndef WIN32 -interface IZipWriter : public IUnknown -#else -#include "Unknwn.h" -#include "Objidl.h" -class IZipWriter : public IUnknown -#endif -{ -public: - // Writes the lfh header to the stream and return the size of the header - virtual std::pair> PrepareToAddFile(const std::string& name, bool isCompressed) = 0; - - // Ends the file, rewrites the LFH or writes data descriptor and adds an entry - // to the central directories map - virtual void EndFile(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize, bool forceDataDescriptor) = 0; - - // Ends zip file by writing the central directory records, zip64 locator, - // zip64 end of central directory and the end of central directories. - virtual void Close() = 0; -}; -MSIX_INTERFACE(IZipWriter, 0x350dd671,0x0c40,0x4cd7,0x9a,0x5b,0x27,0x45,0x6d,0x60,0x4b,0xd0); - -namespace MSIX { - - class ZipObjectWriter final : public ComClass, ZipObject - { - public: - ZipObjectWriter(const ComPtr& stream); - - ZipObjectWriter(const ComPtr& storageObject); - - // IStorage methods - std::vector GetFileNames(FileNameOptions options) override; - ComPtr GetFile(const std::string& fileName) override; - std::string GetFileName() override { NOTIMPLEMENTED }; - - // IZipWriter - std::pair> PrepareToAddFile(const std::string& name, bool isCompressed) override; - void EndFile(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize, bool forceDataDescriptor) override; - void Close() override; - - protected: - enum class State - { - ReadyForLfhOrClose, - ReadyForFile, - Closed, - }; - - State m_state = State::ReadyForLfhOrClose; - std::pair m_lastLFH; - std::vector m_fileNameSequence; - }; +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// +#pragma once + +#include "AppxPackaging.hpp" +#include "Exceptions.hpp" +#include "ComHelper.hpp" +#include "ZipObject.hpp" + +#include +#include +#include +#include +#include + +#include + +// {350dd671-0c40-4cd7-9a5b-27456d604bd0} +#ifndef WIN32 +interface IZipWriter : public IUnknown +#else +#include "Unknwn.h" +#include "Objidl.h" +class IZipWriter : public IUnknown +#endif +{ +public: + // Writes the lfh header to the stream and return the offset of the header, size of the header and the stream + virtual std::tuple> PrepareToAddFile(const std::string& name, bool isCompressed) = 0; + + // Ends the file, rewrites the LFH or writes data descriptor and adds an entry + // to the central directories map + virtual void EndFile(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize, bool forceDataDescriptor) = 0; + + // Ends zip file by writing the central directory records, zip64 locator, + // zip64 end of central directory and the end of central directories. + virtual void Close() = 0; +}; +MSIX_INTERFACE(IZipWriter, 0x350dd671,0x0c40,0x4cd7,0x9a,0x5b,0x27,0x45,0x6d,0x60,0x4b,0xd0); + +namespace MSIX { + + class ZipObjectWriter final : public ComClass, ZipObject + { + public: + ZipObjectWriter(const ComPtr& stream); + + ZipObjectWriter(const ComPtr& storageObject); + + // IStorage methods + std::vector GetFileNames(FileNameOptions options) override; + ComPtr GetFile(const std::string& fileName) override; + std::string GetFileName() override { NOTIMPLEMENTED }; + + // IZipWriter + std::tuple> PrepareToAddFile(const std::string& name, bool isCompressed) override; + void EndFile(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize, bool forceDataDescriptor) override; + void Close() override; + + protected: + enum class State + { + ReadyForLfhOrClose, + ReadyForFile, + Closed, + }; + + State m_state = State::ReadyForLfhOrClose; + std::pair m_lastLFH; + std::vector m_fileNameSequence; + }; } \ No newline at end of file diff --git a/src/msix/pack/AppxBundleWriter.cpp b/src/msix/pack/AppxBundleWriter.cpp index 050d53b44..108391a09 100644 --- a/src/msix/pack/AppxBundleWriter.cpp +++ b/src/msix/pack/AppxBundleWriter.cpp @@ -1,317 +1,329 @@ -// -// Copyright (C) 2019 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -#include "AppxPackaging.hpp" -#include "AppxBundleWriter.hpp" -#include "MsixErrors.hpp" -#include "Exceptions.hpp" -#include "ContentType.hpp" -#include "Encoding.hpp" -#include "ZipObjectWriter.hpp" -#include "AppxManifestObject.hpp" -#include "ScopeExit.hpp" -#include "FileNameValidation.hpp" -#include "StringHelper.hpp" -#include "VectorStream.hpp" - -#include -#include - -namespace MSIX { - - AppxBundleWriter::AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion) - : m_factory(factory), m_zipWriter(zip) - { - m_state = WriterState::Open; - if(bundleVersion == 0) - { - // The generated version number has the format: YYYY.MMDD.hhmm.0 - std::time_t t = std::time(nullptr); - std::tm tm = *std::gmtime(&t); - std::stringstream ss; - ss << std::put_time(&tm, "%Y.%m%d.%H%M.0"); - this->m_bundleWriterHelper.SetBundleVersion(ConvertVersionStringToUint64(ss.str())); - } - else - { - this->m_bundleWriterHelper.SetBundleVersion(bundleVersion); - } - } - - // IBundleWriter - void AppxBundleWriter::ProcessBundlePayload(const ComPtr& from, bool flatBundle) - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - - auto fileMap = from->GetFilesByLastModDate(); - for (const auto& file : fileMap) - { - if (!(FileNameValidation::IsFootPrintFile(file.second, true))) - { - std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); - auto contentType = ContentType::GetContentTypeByExtension(ext); - auto stream = from.As()->GetFile(file.second); - - if (flatBundle) - { - ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(file.second).c_str(), stream.Get(), false)); - } - } - } - - failState.release(); - } - - void AppxBundleWriter::ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - - std::map::iterator fileListIterator; - for (fileListIterator = fileList.begin(); fileListIterator != fileList.end(); fileListIterator++) - { - std::string inputPath = fileListIterator->second; - std::string outputPath = fileListIterator->first; - - if (!(FileNameValidation::IsFootPrintFile(inputPath, true))) - { - std::string ext = Helper::tolower(inputPath.substr(inputPath.find_last_of(".") + 1)); - auto contentType = ContentType::GetContentTypeByExtension(ext); - auto stream = ComPtr::Make(inputPath, FileStream::Mode::READ); - - if (flatBundle) - { - ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(outputPath).c_str(), stream.Get(), false)); - } - } - } - failState.release(); - } - - void AppxBundleWriter::ProcessExternalPackages(std::map externalPackagesList) - { - std::map::iterator externalPackagesIterator; - for (externalPackagesIterator = externalPackagesList.begin(); externalPackagesIterator != externalPackagesList.end(); externalPackagesIterator++) - { - std::string inputPath = externalPackagesIterator->second; - std::string outputPath = externalPackagesIterator->first; - - if (!(FileNameValidation::IsFootPrintFile(inputPath, true))) - { - auto inputStream = ComPtr::Make(inputPath, FileStream::Mode::READ); - ThrowHrIfFailed(AddExternalPackageReference(utf8_to_wstring(outputPath).c_str(), inputStream.Get(), false)); - } - } - } - - // IAppxBundleWriter - HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept try - { - // TODO: implement - NOTIMPLEMENTED; - } CATCH_RETURN(); - - HRESULT STDMETHODCALLTYPE AppxBundleWriter::Close() noexcept try - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - - //Process AppxBundleManifest.xml and add it to the bundle - m_bundleWriterHelper.EndBundleManifest(); - - auto bundleManifestStream = m_bundleWriterHelper.GetBundleManifestStream(); - auto bundleManifestContentType = ContentType::GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST); - AddFileToPackage(APPXBUNDLEMANIFEST_XML, bundleManifestStream.Get(), true, true, bundleManifestContentType.c_str()); - - // Close blockmap and add it to the bundle - m_blockMapWriter.Close(); - auto blockMapStream = m_blockMapWriter.GetStream(); - auto blockMapContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP); - AddFileToPackage(APPXBLOCKMAP_XML, blockMapStream.Get(), true, false, blockMapContentType.c_str()); - - // Close content types and add it to the bundle - m_contentTypeWriter.Close(); - auto contentTypeStream = m_contentTypeWriter.GetStream(); - AddFileToPackage(CONTENT_TYPES_XML, contentTypeStream.Get(), true, false, nullptr); - - m_zipWriter->Close(); - failState.release(); - m_state = WriterState::Closed; - return static_cast(Error::OK); - } CATCH_RETURN(); - - // IAppxBundleWriter4 - HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPackageReference(LPCWSTR fileName, - IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try - { - this->AddPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); - return static_cast(Error::OK); - } CATCH_RETURN(); - - void AppxBundleWriter::AddPackageReferenceInternal(std::string fileName, IStream* packageStream, - bool isDefaultApplicablePackage) - { - auto appxFactory = m_factory.As(); - - ComPtr reader; - ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream, &reader)); - - std::uint64_t packageStreamSize = this->m_bundleWriterHelper.GetStreamSize(packageStream); - - this->m_bundleWriterHelper.AddPackage(fileName, reader.Get(), 0, packageStreamSize, isDefaultApplicablePackage); - } - - HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, - BOOL isDefaultApplicablePackage) noexcept try - { - // TODO: implement - NOTIMPLEMENTED; - } CATCH_RETURN(); - - HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddExternalPackageReference(LPCWSTR fileName, - IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try - { - this->AddExternalPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); - return static_cast(Error::OK); - } CATCH_RETURN(); - - void AppxBundleWriter::AddExternalPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage) - { - auto appxFactory = m_factory.As(); - - ComPtr manifestReader; - HRESULT hr = appxFactory->CreateManifestReader(packageStream, &manifestReader); - if(SUCCEEDED(hr)) - { - this->m_bundleWriterHelper.AddExternalPackageReferenceFromManifest(fileName, manifestReader.Get(), isDefaultApplicablePackage); - return; - } - - ComPtr packageReader; - hr = appxFactory->CreatePackageReader(packageStream, &packageReader); - if(SUCCEEDED(hr)) - { - ComPtr manifestReader; - ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); - this->m_bundleWriterHelper.AddExternalPackageReferenceFromManifest(fileName, manifestReader.Get(), isDefaultApplicablePackage); - return; - } - - ThrowErrorAndLog(Error::InvalidData, "The data is invalid."); - } - - void AppxBundleWriter::ValidateAndAddPayloadFile(const std::string& name, IStream* stream, - APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) - { - ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); - ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); - ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); - ValidateCompressionOption(compressionOpt); - AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); - } - - void AppxBundleWriter::AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, - bool addToBlockMap, const char* contentType, bool forceContentTypeOverride) - { - std::string opcFileName; - // Don't encode [Content Type].xml - if (contentType != nullptr) - { - opcFileName = Encoding::EncodeFileName(name); - } - else - { - opcFileName = name; - } - auto fileInfo = m_zipWriter->PrepareToAddFile(opcFileName, toCompress); - - // Add content type to [Content Types].xml - if (contentType != nullptr) - { - m_contentTypeWriter.AddContentType(name, contentType, forceContentTypeOverride); - } - - // This might be called with external IStream implementations. Don't rely on internal implementation of FileStream - LARGE_INTEGER start = { 0 }; - ULARGE_INTEGER end = { 0 }; - ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end)); - ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr)); - std::uint64_t uncompressedSize = static_cast(end.QuadPart); - - // Add file to block map. - if (addToBlockMap) - { - m_blockMapWriter.AddFile(name, uncompressedSize, fileInfo.first); - } - - auto& zipFileStream = fileInfo.second; - - std::uint64_t bytesToRead = uncompressedSize; - std::uint32_t crc = 0; - while (bytesToRead > 0) - { - // Calculate the size of the next block to add - std::uint32_t blockSize = (bytesToRead > DefaultBlockSize) ? DefaultBlockSize : static_cast(bytesToRead); - bytesToRead -= blockSize; - - // read block from stream - std::vector block; - block.resize(blockSize); - ULONG bytesRead; - ThrowHrIfFailed(stream->Read(static_cast(block.data()), static_cast(blockSize), &bytesRead)); - ThrowErrorIfNot(Error::FileRead, (static_cast(blockSize) == bytesRead), "Read stream file failed"); - crc = crc32(crc, block.data(), static_cast(block.size())); - - // Write block and compress if needed - ULONG bytesWritten = 0; - ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast(block.size()), &bytesWritten)); - - // Add block to blockmap - if (addToBlockMap) - { - m_blockMapWriter.AddBlock(block, bytesWritten, toCompress); - } - - } - - if (toCompress) - { - // Put the stream termination on - std::vector buffer; - ULONG bytesWritten = 0; - ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast(buffer.size()), &bytesWritten)); - } - - // Close File element - if (addToBlockMap) - { - m_blockMapWriter.CloseFile(); - } - - // This could be the compressed or uncompressed size - auto streamSize = zipFileStream.As()->GetSize(); - m_zipWriter->EndFile(crc, streamSize, uncompressedSize, true); - } - - void AppxBundleWriter::ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt) - { - bool result = ((compressionOpt == APPX_COMPRESSION_OPTION_NONE) || - (compressionOpt == APPX_COMPRESSION_OPTION_NORMAL) || - (compressionOpt == APPX_COMPRESSION_OPTION_MAXIMUM) || - (compressionOpt == APPX_COMPRESSION_OPTION_FAST) || - (compressionOpt == APPX_COMPRESSION_OPTION_SUPERFAST)); - ThrowErrorIfNot(Error::InvalidParameter, result, "Invalid compression option."); - } -} +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "AppxPackaging.hpp" +#include "AppxBundleWriter.hpp" +#include "MsixErrors.hpp" +#include "Exceptions.hpp" +#include "ContentType.hpp" +#include "Encoding.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxManifestObject.hpp" +#include "ScopeExit.hpp" +#include "FileNameValidation.hpp" +#include "StringHelper.hpp" +#include "VectorStream.hpp" + +#include +#include + +namespace MSIX { + + AppxBundleWriter::AppxBundleWriter(IMsixFactory* factory, const ComPtr& zip, std::uint64_t bundleVersion) + : m_factory(factory), m_zipWriter(zip) + { + m_state = WriterState::Open; + if(bundleVersion == 0) + { + // The generated version number has the format: YYYY.MMDD.hhmm.0 + std::time_t t = std::time(nullptr); + std::tm tm = *std::gmtime(&t); + std::stringstream ss; + ss << std::put_time(&tm, "%Y.%m%d.%H%M.0"); + this->m_bundleWriterHelper.SetBundleVersion(ConvertVersionStringToUint64(ss.str())); + } + else + { + this->m_bundleWriterHelper.SetBundleVersion(bundleVersion); + } + } + + // IBundleWriter + void AppxBundleWriter::ProcessBundlePayload(const ComPtr& from, bool flatBundle) + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + auto fileMap = from->GetFilesByLastModDate(); + for (const auto& file : fileMap) + { + if (!(FileNameValidation::IsFootPrintFile(file.second, true))) + { + std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); + auto contentType = ContentType::GetContentTypeByExtension(ext); + auto stream = from.As()->GetFile(file.second); + + if (flatBundle) + { + ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(file.second).c_str(), stream.Get(), false)); + } + } + } + + failState.release(); + } + + void AppxBundleWriter::ProcessBundlePayloadFromMappingFile(std::map fileList, bool flatBundle) + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + std::map::iterator fileListIterator; + for (fileListIterator = fileList.begin(); fileListIterator != fileList.end(); fileListIterator++) + { + std::string inputPath = fileListIterator->second; + std::string outputPath = fileListIterator->first; + + if (!(FileNameValidation::IsFootPrintFile(inputPath, true))) + { + std::string ext = Helper::tolower(inputPath.substr(inputPath.find_last_of(".") + 1)); + auto contentType = ContentType::GetContentTypeByExtension(ext); + auto stream = ComPtr::Make(inputPath, FileStream::Mode::READ); + + if (flatBundle) + { + ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(outputPath).c_str(), stream.Get(), false)); + } + } + } + failState.release(); + } + + void AppxBundleWriter::ProcessExternalPackages(std::map externalPackagesList) + { + std::map::iterator externalPackagesIterator; + for (externalPackagesIterator = externalPackagesList.begin(); externalPackagesIterator != externalPackagesList.end(); externalPackagesIterator++) + { + std::string inputPath = externalPackagesIterator->second; + std::string outputPath = externalPackagesIterator->first; + + if (!(FileNameValidation::IsFootPrintFile(inputPath, true))) + { + auto inputStream = ComPtr::Make(inputPath, FileStream::Mode::READ); + ThrowHrIfFailed(AddExternalPackageReference(utf8_to_wstring(outputPath).c_str(), inputStream.Get(), false)); + } + } + } + + // IAppxBundleWriter + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream) noexcept try + { + return AddPayloadPackage(fileName, packageStream, FALSE); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::Close() noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + //Process AppxBundleManifest.xml and add it to the bundle + m_bundleWriterHelper.EndBundleManifest(); + + auto bundleManifestStream = m_bundleWriterHelper.GetBundleManifestStream(); + auto bundleManifestContentType = ContentType::GetBundlePayloadFileContentType(APPX_BUNDLE_FOOTPRINT_FILE_TYPE_MANIFEST); + AddFileToPackage(APPXBUNDLEMANIFEST_XML, bundleManifestStream.Get(), true, true, bundleManifestContentType.c_str()); + + // Close blockmap and add it to the bundle + m_blockMapWriter.Close(); + auto blockMapStream = m_blockMapWriter.GetStream(); + auto blockMapContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP); + AddFileToPackage(APPXBLOCKMAP_XML, blockMapStream.Get(), true, false, blockMapContentType.c_str()); + + // Close content types and add it to the bundle + m_contentTypeWriter.Close(); + auto contentTypeStream = m_contentTypeWriter.GetStream(); + AddFileToPackage(CONTENT_TYPES_XML, contentTypeStream.Get(), true, false, nullptr); + + m_zipWriter->Close(); + failState.release(); + m_state = WriterState::Closed; + return static_cast(Error::OK); + } CATCH_RETURN(); + + // IAppxBundleWriter4 + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPackageReference(LPCWSTR fileName, + IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try + { + this->AddPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); + return static_cast(Error::OK); + } CATCH_RETURN(); + + void AppxBundleWriter::AddPackageReferenceInternal(std::string fileName, IStream* packageStream, + bool isDefaultApplicablePackage) + { + auto appxFactory = m_factory.As(); + + ComPtr reader; + ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream, &reader)); + + std::uint64_t packageStreamSize = this->m_bundleWriterHelper.GetStreamSize(packageStream); + + this->m_bundleWriterHelper.AddPackage(fileName, reader.Get(), 0, packageStreamSize, isDefaultApplicablePackage); + } + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddPayloadPackage(LPCWSTR fileName, IStream* packageStream, + BOOL isDefaultApplicablePackage) noexcept try + { + auto appxFactory = m_factory.As(); + ComPtr reader; + ThrowHrIfFailed(appxFactory->CreatePackageReader(packageStream, &reader)); + + std::string name = wstring_to_utf8(fileName); + std::uint64_t offset = AddFileToPackage(name, packageStream, false, true, nullptr); + + // Reset stream for reader safety + LARGE_INTEGER start = { 0 }; + ThrowHrIfFailed(packageStream->Seek(start, StreamBase::Reference::START, nullptr)); + + std::uint64_t size = m_bundleWriterHelper.GetStreamSize(packageStream); + m_bundleWriterHelper.AddPackage(name, reader.Get(), offset, size, !!isDefaultApplicablePackage); + return static_cast(Error::OK); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxBundleWriter::AddExternalPackageReference(LPCWSTR fileName, + IStream* inputStream, BOOL isDefaultApplicablePackage) noexcept try + { + this->AddExternalPackageReferenceInternal(wstring_to_utf8(fileName), inputStream, !!isDefaultApplicablePackage); + return static_cast(Error::OK); + } CATCH_RETURN(); + + void AppxBundleWriter::AddExternalPackageReferenceInternal(std::string fileName, IStream* packageStream, bool isDefaultApplicablePackage) + { + auto appxFactory = m_factory.As(); + + ComPtr manifestReader; + HRESULT hr = appxFactory->CreateManifestReader(packageStream, &manifestReader); + if(SUCCEEDED(hr)) + { + this->m_bundleWriterHelper.AddExternalPackageReferenceFromManifest(fileName, manifestReader.Get(), isDefaultApplicablePackage); + return; + } + + ComPtr packageReader; + hr = appxFactory->CreatePackageReader(packageStream, &packageReader); + if(SUCCEEDED(hr)) + { + ComPtr manifestReader; + ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); + this->m_bundleWriterHelper.AddExternalPackageReferenceFromManifest(fileName, manifestReader.Get(), isDefaultApplicablePackage); + return; + } + + ThrowErrorAndLog(Error::InvalidData, "The data is invalid."); + } + + void AppxBundleWriter::ValidateAndAddPayloadFile(const std::string& name, IStream* stream, + APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) + { + ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); + ValidateCompressionOption(compressionOpt); + AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); + } + + std::uint64_t AppxBundleWriter::AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride) + { + std::string opcFileName; + // Don't encode [Content Type].xml + if (contentType != nullptr) + { + opcFileName = Encoding::EncodeFileName(name); + } + else + { + opcFileName = name; + } + auto fileInfo = m_zipWriter->PrepareToAddFile(opcFileName, toCompress); + + // Add content type to [Content Types].xml + if (contentType != nullptr) + { + m_contentTypeWriter.AddContentType(name, contentType, forceContentTypeOverride); + } + + // This might be called with external IStream implementations. Don't rely on internal implementation of FileStream + LARGE_INTEGER start = { 0 }; + ULARGE_INTEGER end = { 0 }; + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end)); + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr)); + std::uint64_t uncompressedSize = static_cast(end.QuadPart); + + // Add file to block map. + if (addToBlockMap) + { + m_blockMapWriter.AddFile(name, uncompressedSize, std::get<1>(fileInfo)); + } + + auto& zipFileStream = std::get<2>(fileInfo); + + std::uint64_t bytesToRead = uncompressedSize; + std::uint32_t crc = 0; + while (bytesToRead > 0) + { + // Calculate the size of the next block to add + std::uint32_t blockSize = (bytesToRead > DefaultBlockSize) ? DefaultBlockSize : static_cast(bytesToRead); + bytesToRead -= blockSize; + + // read block from stream + std::vector block; + block.resize(blockSize); + ULONG bytesRead; + ThrowHrIfFailed(stream->Read(static_cast(block.data()), static_cast(blockSize), &bytesRead)); + ThrowErrorIfNot(Error::FileRead, (static_cast(blockSize) == bytesRead), "Read stream file failed"); + crc = crc32(crc, block.data(), static_cast(block.size())); + + // Write block and compress if needed + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast(block.size()), &bytesWritten)); + + // Add block to blockmap + if (addToBlockMap) + { + m_blockMapWriter.AddBlock(block, bytesWritten, toCompress); + } + + } + + if (toCompress) + { + // Put the stream termination on + std::vector buffer; + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast(buffer.size()), &bytesWritten)); + } + + // Close File element + if (addToBlockMap) + { + m_blockMapWriter.CloseFile(); + } + + // This could be the compressed or uncompressed size + auto streamSize = zipFileStream.As()->GetSize(); + m_zipWriter->EndFile(crc, streamSize, uncompressedSize, true); + return std::get<0>(fileInfo); + } + + void AppxBundleWriter::ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt) + { + bool result = ((compressionOpt == APPX_COMPRESSION_OPTION_NONE) || + (compressionOpt == APPX_COMPRESSION_OPTION_NORMAL) || + (compressionOpt == APPX_COMPRESSION_OPTION_MAXIMUM) || + (compressionOpt == APPX_COMPRESSION_OPTION_FAST) || + (compressionOpt == APPX_COMPRESSION_OPTION_SUPERFAST)); + ThrowErrorIfNot(Error::InvalidParameter, result, "Invalid compression option."); + } +} diff --git a/src/msix/pack/AppxPackageWriter.cpp b/src/msix/pack/AppxPackageWriter.cpp index fc47d4cb1..429ad9702 100644 --- a/src/msix/pack/AppxPackageWriter.cpp +++ b/src/msix/pack/AppxPackageWriter.cpp @@ -1,259 +1,259 @@ -// -// Copyright (C) 2019 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -#include "AppxPackaging.hpp" -#include "AppxPackageWriter.hpp" -#include "MsixErrors.hpp" -#include "Exceptions.hpp" -#include "ContentType.hpp" -#include "Encoding.hpp" -#include "ZipObjectWriter.hpp" -#include "AppxManifestObject.hpp" -#include "ScopeExit.hpp" -#include "FileNameValidation.hpp" -#include "StringHelper.hpp" - -#include -#include -#include -#include -#include - -namespace MSIX { - - AppxPackageWriter::AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip, bool enableFileHash) : m_factory(factory), m_zipWriter(zip) - { - if (enableFileHash) - { - m_blockMapWriter.EnableFileHash(); - } - m_state = WriterState::Open; - } - - // IPackageWriter - void AppxPackageWriter::PackPayloadFiles(const ComPtr& from) - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - - auto fileMap = from->GetFilesByLastModDate(); - for(const auto& file : fileMap) - { - // If any footprint file is present, ignore it. We only require the AppxManifest.xml - // and any other will be ignored and a new one will be created for the package. - if(!(FileNameValidation::IsFootPrintFile(file.second, false) || FileNameValidation::IsReservedFolder(file.second))) - { - std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); - auto contentType = ContentType::GetContentTypeByExtension(ext); - auto stream = from.As()->GetFile(file.second); - ValidateAndAddPayloadFile(file.second, stream.Get(), contentType.GetCompressionOpt(), contentType.GetContentType().c_str()); - } - } - failState.release(); - } - - // IAppxPackageWriter - HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFile(LPCWSTR fileName, LPCWSTR contentType, - APPX_COMPRESSION_OPTION compressionOption, IStream *inputStream) noexcept try - { - return AddPayloadFile(wstring_to_utf8(fileName).c_str(), wstring_to_utf8(contentType).c_str(), - compressionOption, inputStream); - } CATCH_RETURN(); - - HRESULT STDMETHODCALLTYPE AppxPackageWriter::Close(IStream* manifest) noexcept try - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - - ComPtr manifestStream(manifest); - - // Process AppxManifest.xml - // If the creating the AppxManifestObject succeeds, then the stream is valid. - auto manifestObj = ComPtr::Make(m_factory.Get(), manifestStream.Get()); - auto manifestContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_MANIFEST); - AddFileToPackage(APPXMANIFEST_XML, manifestStream.Get(), true, true, manifestContentType.c_str()); - - // Close blockmap and add it to package - m_blockMapWriter.Close(); - auto blockMapStream = m_blockMapWriter.GetStream(); - auto blockMapContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP); - AddFileToPackage(APPXBLOCKMAP_XML, blockMapStream.Get(), true, false, blockMapContentType.c_str()); - - // Close content types and add it to package - m_contentTypeWriter.Close(); - auto contentTypeStream = m_contentTypeWriter.GetStream(); - AddFileToPackage(CONTENT_TYPES_XML, contentTypeStream.Get(), true, false, nullptr); - - m_zipWriter->Close(); - failState.release(); - m_state = WriterState::Closed; - return static_cast(Error::OK); - } CATCH_RETURN(); - - // IAppxPackageWriterUtf8 - HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFile(LPCSTR fileName, LPCSTR contentType, - APPX_COMPRESSION_OPTION compressionOption, IStream* inputStream) noexcept try - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - ComPtr stream(inputStream); - ValidateAndAddPayloadFile(fileName, stream.Get(), compressionOption, contentType); - failState.release(); - return static_cast(Error::OK); - } CATCH_RETURN(); - - // IAppxPackageWriter3 - HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFiles(UINT32 fileCount, - APPX_PACKAGE_WRITER_PAYLOAD_STREAM* payloadFiles, UINT64 memoryLimit) noexcept try - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - // TODO: use memoryLimit for how many files are going to be added - for(UINT32 i = 0; i < fileCount; i++) - { - std::string fileName = wstring_to_utf8(payloadFiles[i].fileName); - ComPtr stream(payloadFiles[i].inputStream); - std::string contentType = wstring_to_utf8(payloadFiles[i].contentType); - ValidateAndAddPayloadFile(fileName, stream.Get(), payloadFiles[i].compressionOption, contentType.c_str()); - } - failState.release(); - return static_cast(Error::OK); - } CATCH_RETURN(); - - // IAppxPackageWriter3Utf8 - HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFiles(UINT32 fileCount, - APPX_PACKAGE_WRITER_PAYLOAD_STREAM_UTF8* payloadFiles, UINT64 memoryLimit) noexcept try - { - ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); - auto failState = MSIX::scope_exit([this] - { - this->m_state = WriterState::Failed; - }); - // TODO: use memoryLimit for how many files are going to be added - for(UINT32 i = 0; i < fileCount; i++) - { - ComPtr stream(payloadFiles[i].inputStream); - ValidateAndAddPayloadFile(payloadFiles[i].fileName, stream.Get(), payloadFiles[i].compressionOption, payloadFiles[i].contentType); - } - failState.release(); - return static_cast(Error::OK); - } CATCH_RETURN(); - - void AppxPackageWriter::ValidateAndAddPayloadFile(const std::string& name, IStream* stream, - APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) - { - ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); - ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); - ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); - ValidateCompressionOption(compressionOpt); - AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); - } - - void AppxPackageWriter::AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, - bool addToBlockMap, const char* contentType, bool forceContentTypeOverride) - { - std::string opcFileName; - // Don't encode [Content Type].xml - if (contentType != nullptr) - { - opcFileName = Encoding::EncodeFileName(name); - } - else - { - opcFileName = name; - } - auto fileInfo = m_zipWriter->PrepareToAddFile(opcFileName, toCompress); - - // Add content type to [Content Types].xml - if (contentType != nullptr) - { - m_contentTypeWriter.AddContentType(name, contentType, forceContentTypeOverride); - } - - // This might be called with external IStream implementations. Don't rely on internal implementation of FileStream - LARGE_INTEGER start = { 0 }; - ULARGE_INTEGER end = { 0 }; - ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end)); - ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr)); - std::uint64_t uncompressedSize = static_cast(end.QuadPart); - - // Add file to block map. - if (addToBlockMap) - { - m_blockMapWriter.AddFile(name, uncompressedSize, fileInfo.first); - } - - auto& zipFileStream = fileInfo.second; - - std::uint64_t bytesToRead = uncompressedSize; - std::uint32_t crc = 0; - while (bytesToRead > 0) - { - // Calculate the size of the next block to add - std::uint32_t blockSize = (bytesToRead > DefaultBlockSize) ? DefaultBlockSize : static_cast(bytesToRead); - bytesToRead -= blockSize; - - // read block from stream - std::vector block; - block.resize(blockSize); - ULONG bytesRead; - ThrowHrIfFailed(stream->Read(static_cast(block.data()), static_cast(blockSize), &bytesRead)); - ThrowErrorIfNot(Error::FileRead, (static_cast(blockSize) == bytesRead), "Read stream file failed"); - crc = crc32(crc, block.data(), static_cast(block.size())); - - // Write block and compress if needed - ULONG bytesWritten = 0; - ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast(block.size()), &bytesWritten)); - - // Add block to blockmap - if (addToBlockMap) - { - m_blockMapWriter.AddBlock(block, bytesWritten, toCompress); - } - - } - - if (toCompress) - { - // Put the stream termination on - std::vector buffer; - ULONG bytesWritten = 0; - ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast(buffer.size()), &bytesWritten)); - } - - // Close File element - if (addToBlockMap) - { - m_blockMapWriter.CloseFile(); - } - - // This could be the compressed or uncompressed size - auto streamSize = zipFileStream.As()->GetSize(); - m_zipWriter->EndFile(crc, streamSize, uncompressedSize, true); - } - - void AppxPackageWriter::ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt) - { - bool result = ((compressionOpt == APPX_COMPRESSION_OPTION_NONE) || - (compressionOpt == APPX_COMPRESSION_OPTION_NORMAL) || - (compressionOpt == APPX_COMPRESSION_OPTION_MAXIMUM) || - (compressionOpt == APPX_COMPRESSION_OPTION_FAST) || - (compressionOpt == APPX_COMPRESSION_OPTION_SUPERFAST)); - ThrowErrorIfNot(Error::InvalidParameter, result, "Invalid compression option."); - } - -} +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "AppxPackaging.hpp" +#include "AppxPackageWriter.hpp" +#include "MsixErrors.hpp" +#include "Exceptions.hpp" +#include "ContentType.hpp" +#include "Encoding.hpp" +#include "ZipObjectWriter.hpp" +#include "AppxManifestObject.hpp" +#include "ScopeExit.hpp" +#include "FileNameValidation.hpp" +#include "StringHelper.hpp" + +#include +#include +#include +#include +#include + +namespace MSIX { + + AppxPackageWriter::AppxPackageWriter(IMsixFactory* factory, const ComPtr& zip, bool enableFileHash) : m_factory(factory), m_zipWriter(zip) + { + if (enableFileHash) + { + m_blockMapWriter.EnableFileHash(); + } + m_state = WriterState::Open; + } + + // IPackageWriter + void AppxPackageWriter::PackPayloadFiles(const ComPtr& from) + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + auto fileMap = from->GetFilesByLastModDate(); + for(const auto& file : fileMap) + { + // If any footprint file is present, ignore it. We only require the AppxManifest.xml + // and any other will be ignored and a new one will be created for the package. + if(!(FileNameValidation::IsFootPrintFile(file.second, false) || FileNameValidation::IsReservedFolder(file.second))) + { + std::string ext = Helper::tolower(file.second.substr(file.second.find_last_of(".") + 1)); + auto contentType = ContentType::GetContentTypeByExtension(ext); + auto stream = from.As()->GetFile(file.second); + ValidateAndAddPayloadFile(file.second, stream.Get(), contentType.GetCompressionOpt(), contentType.GetContentType().c_str()); + } + } + failState.release(); + } + + // IAppxPackageWriter + HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFile(LPCWSTR fileName, LPCWSTR contentType, + APPX_COMPRESSION_OPTION compressionOption, IStream *inputStream) noexcept try + { + return AddPayloadFile(wstring_to_utf8(fileName).c_str(), wstring_to_utf8(contentType).c_str(), + compressionOption, inputStream); + } CATCH_RETURN(); + + HRESULT STDMETHODCALLTYPE AppxPackageWriter::Close(IStream* manifest) noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + + ComPtr manifestStream(manifest); + + // Process AppxManifest.xml + // If the creating the AppxManifestObject succeeds, then the stream is valid. + auto manifestObj = ComPtr::Make(m_factory.Get(), manifestStream.Get()); + auto manifestContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_MANIFEST); + AddFileToPackage(APPXMANIFEST_XML, manifestStream.Get(), true, true, manifestContentType.c_str()); + + // Close blockmap and add it to package + m_blockMapWriter.Close(); + auto blockMapStream = m_blockMapWriter.GetStream(); + auto blockMapContentType = ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP); + AddFileToPackage(APPXBLOCKMAP_XML, blockMapStream.Get(), true, false, blockMapContentType.c_str()); + + // Close content types and add it to package + m_contentTypeWriter.Close(); + auto contentTypeStream = m_contentTypeWriter.GetStream(); + AddFileToPackage(CONTENT_TYPES_XML, contentTypeStream.Get(), true, false, nullptr); + + m_zipWriter->Close(); + failState.release(); + m_state = WriterState::Closed; + return static_cast(Error::OK); + } CATCH_RETURN(); + + // IAppxPackageWriterUtf8 + HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFile(LPCSTR fileName, LPCSTR contentType, + APPX_COMPRESSION_OPTION compressionOption, IStream* inputStream) noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + ComPtr stream(inputStream); + ValidateAndAddPayloadFile(fileName, stream.Get(), compressionOption, contentType); + failState.release(); + return static_cast(Error::OK); + } CATCH_RETURN(); + + // IAppxPackageWriter3 + HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFiles(UINT32 fileCount, + APPX_PACKAGE_WRITER_PAYLOAD_STREAM* payloadFiles, UINT64 memoryLimit) noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + // TODO: use memoryLimit for how many files are going to be added + for(UINT32 i = 0; i < fileCount; i++) + { + std::string fileName = wstring_to_utf8(payloadFiles[i].fileName); + ComPtr stream(payloadFiles[i].inputStream); + std::string contentType = wstring_to_utf8(payloadFiles[i].contentType); + ValidateAndAddPayloadFile(fileName, stream.Get(), payloadFiles[i].compressionOption, contentType.c_str()); + } + failState.release(); + return static_cast(Error::OK); + } CATCH_RETURN(); + + // IAppxPackageWriter3Utf8 + HRESULT STDMETHODCALLTYPE AppxPackageWriter::AddPayloadFiles(UINT32 fileCount, + APPX_PACKAGE_WRITER_PAYLOAD_STREAM_UTF8* payloadFiles, UINT64 memoryLimit) noexcept try + { + ThrowErrorIf(Error::InvalidState, m_state != WriterState::Open, "Invalid package writer state"); + auto failState = MSIX::scope_exit([this] + { + this->m_state = WriterState::Failed; + }); + // TODO: use memoryLimit for how many files are going to be added + for(UINT32 i = 0; i < fileCount; i++) + { + ComPtr stream(payloadFiles[i].inputStream); + ValidateAndAddPayloadFile(payloadFiles[i].fileName, stream.Get(), payloadFiles[i].compressionOption, payloadFiles[i].contentType); + } + failState.release(); + return static_cast(Error::OK); + } CATCH_RETURN(); + + void AppxPackageWriter::ValidateAndAddPayloadFile(const std::string& name, IStream* stream, + APPX_COMPRESSION_OPTION compressionOpt, const char* contentType) + { + ThrowErrorIfNot(Error::InvalidParameter, FileNameValidation::IsFileNameValid(name), "Invalid file name"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsFootPrintFile(name, false), "Trying to add footprint file to package"); + ThrowErrorIf(Error::InvalidParameter, FileNameValidation::IsReservedFolder(name), "Trying to add file in reserved folder"); + ValidateCompressionOption(compressionOpt); + AddFileToPackage(name, stream, compressionOpt != APPX_COMPRESSION_OPTION_NONE, true, contentType); + } + + void AppxPackageWriter::AddFileToPackage(const std::string& name, IStream* stream, bool toCompress, + bool addToBlockMap, const char* contentType, bool forceContentTypeOverride) + { + std::string opcFileName; + // Don't encode [Content Type].xml + if (contentType != nullptr) + { + opcFileName = Encoding::EncodeFileName(name); + } + else + { + opcFileName = name; + } + auto fileInfo = m_zipWriter->PrepareToAddFile(opcFileName, toCompress); + + // Add content type to [Content Types].xml + if (contentType != nullptr) + { + m_contentTypeWriter.AddContentType(name, contentType, forceContentTypeOverride); + } + + // This might be called with external IStream implementations. Don't rely on internal implementation of FileStream + LARGE_INTEGER start = { 0 }; + ULARGE_INTEGER end = { 0 }; + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::END, &end)); + ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr)); + std::uint64_t uncompressedSize = static_cast(end.QuadPart); + + // Add file to block map. + if (addToBlockMap) + { + m_blockMapWriter.AddFile(name, uncompressedSize, std::get<1>(fileInfo)); + } + + auto& zipFileStream = std::get<2>(fileInfo); + + std::uint64_t bytesToRead = uncompressedSize; + std::uint32_t crc = 0; + while (bytesToRead > 0) + { + // Calculate the size of the next block to add + std::uint32_t blockSize = (bytesToRead > DefaultBlockSize) ? DefaultBlockSize : static_cast(bytesToRead); + bytesToRead -= blockSize; + + // read block from stream + std::vector block; + block.resize(blockSize); + ULONG bytesRead; + ThrowHrIfFailed(stream->Read(static_cast(block.data()), static_cast(blockSize), &bytesRead)); + ThrowErrorIfNot(Error::FileRead, (static_cast(blockSize) == bytesRead), "Read stream file failed"); + crc = crc32(crc, block.data(), static_cast(block.size())); + + // Write block and compress if needed + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast(block.size()), &bytesWritten)); + + // Add block to blockmap + if (addToBlockMap) + { + m_blockMapWriter.AddBlock(block, bytesWritten, toCompress); + } + + } + + if (toCompress) + { + // Put the stream termination on + std::vector buffer; + ULONG bytesWritten = 0; + ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast(buffer.size()), &bytesWritten)); + } + + // Close File element + if (addToBlockMap) + { + m_blockMapWriter.CloseFile(); + } + + // This could be the compressed or uncompressed size + auto streamSize = zipFileStream.As()->GetSize(); + m_zipWriter->EndFile(crc, streamSize, uncompressedSize, true); + } + + void AppxPackageWriter::ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt) + { + bool result = ((compressionOpt == APPX_COMPRESSION_OPTION_NONE) || + (compressionOpt == APPX_COMPRESSION_OPTION_NORMAL) || + (compressionOpt == APPX_COMPRESSION_OPTION_MAXIMUM) || + (compressionOpt == APPX_COMPRESSION_OPTION_FAST) || + (compressionOpt == APPX_COMPRESSION_OPTION_SUPERFAST)); + ThrowErrorIfNot(Error::InvalidParameter, result, "Invalid compression option."); + } + +} diff --git a/src/msix/pack/ZipObjectWriter.cpp b/src/msix/pack/ZipObjectWriter.cpp index 68e4f6a51..c7bd0c3df 100644 --- a/src/msix/pack/ZipObjectWriter.cpp +++ b/src/msix/pack/ZipObjectWriter.cpp @@ -1,175 +1,175 @@ -// -// Copyright (C) 2019 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -#include "ZipObjectWriter.hpp" -#include "ZipObject.hpp" -#include "MsixErrors.hpp" -#include "Exceptions.hpp" -#include "ZipFileStream.hpp" -#include "DeflateStream.hpp" -#include "StreamHelper.hpp" -#include "Encoding.hpp" - -namespace MSIX { - - // We only use this for writting. If we ever decide to validate it, it needs to move to - // ZipObject and ZipObjectReader must validate it - class DataDescriptor final : public Meta::StructuredObject< - Meta::Field4Bytes, // 0 - data descriptor header signature 4 bytes(0x08074b50) - Meta::Field4Bytes, // 1 - crc -32 4 bytes - Meta::Field8Bytes, // 2 - compressed size 8 bytes(zip64) - Meta::Field8Bytes // 3 - uncompressed size 8 bytes(zip64) - > - { - public: - DataDescriptor(std::uint32_t crc, std::uint64_t compressSize, std::uint64_t uncompressSize) - { - Field<0>() = static_cast(Signatures::DataDescriptor); - Field<1>() = crc; - Field<2>() = compressSize; - Field<3>() = uncompressSize; - } - }; - - ZipObjectWriter::ZipObjectWriter(const ComPtr& stream) : ZipObject(stream) - { - } - - // This is used for editing a package (aka signing) - ZipObjectWriter::ZipObjectWriter(const ComPtr& storageObject) : ZipObject(storageObject) - { - // The storage object provided should had already initialize all the data. - ThrowErrorIfNot(Error::Zip64EOCDRecord, m_endCentralDirectoryRecord.GetIsZip64(), - "Editing non zip64 packages not supported"); - - // Move the stream at the start of central directory record so we can start overwritting. - // Central directory data in already in m_centralDirectories. - LARGE_INTEGER pos = {0}; - pos.QuadPart = m_zip64EndOfCentralDirectory.GetOffsetStartOfCD(); - ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr)); - } - - // IStorage - std::vector ZipObjectWriter::GetFileNames(FileNameOptions options) - { - // TODO: implement - NOTIMPLEMENTED; - } - - ComPtr ZipObjectWriter::GetFile(const std::string& fileName) - { - // TODO: implement - NOTIMPLEMENTED; - } - - // IZipWriter - std::pair> ZipObjectWriter::PrepareToAddFile(const std::string& name, bool isCompressed) - { - ThrowErrorIf(Error::InvalidState, m_state != ZipObjectWriter::State::ReadyForLfhOrClose, "Invalid zip writer state"); - - auto result = m_centralDirectories.find(name); - if (result != m_centralDirectories.end()) - { - auto message = "Adding duplicated file " + Encoding::DecodeFileName(name) + "to package"; - ThrowErrorAndLog(Error::DuplicateFile, message.c_str()); - } - - // Get position were the lfh is going to be written - ULARGE_INTEGER pos = {0}; - ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &pos)); - - // track the sequence of file names to sort the central directory upon Close - m_fileNameSequence.push_back(name); - - // Write lfh - LocalFileHeader lfh; - lfh.SetData(name, isCompressed); - lfh.WriteTo(m_stream); - - m_lastLFH = std::make_pair(static_cast(pos.QuadPart), std::move(lfh)); - m_state = ZipObjectWriter::State::ReadyForFile; - - ComPtr zipStream = ComPtr::Make(name, isCompressed, m_stream.Get()); - if (isCompressed) - { - zipStream = ComPtr::Make(zipStream); - } - - return std::make_pair(static_cast(m_lastLFH.second.Size()), std::move(zipStream)); - } - - void ZipObjectWriter::EndFile(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize, bool forceDataDescriptor) - { - ThrowErrorIf(Error::InvalidState, m_state != ZipObjectWriter::State::ReadyForFile, "Invalid zip writer state"); - - if (forceDataDescriptor || - compressedSize > MaxSizeToNotUseDataDescriptor || - uncompressedSize > MaxSizeToNotUseDataDescriptor) - { - // Create and write data descriptor - DataDescriptor descriptor = DataDescriptor(crc, compressedSize, uncompressedSize); - descriptor.WriteTo(m_stream); - } - else - { - // The sizes can fit in the LFH, rewrite it with the new data - Helper::StreamPositionReset resetAfterLFHWrite{ m_stream.Get() }; - - LARGE_INTEGER lfhLocation; - lfhLocation.QuadPart = static_cast(m_lastLFH.first); - ThrowHrIfFailed(m_stream->Seek(lfhLocation, StreamBase::Reference::START, nullptr)); - - // We cannot change the size of the LFH, ensure that we don't accidentally - size_t currentSize = m_lastLFH.second.Size(); - m_lastLFH.second.SetData(crc, compressedSize, uncompressedSize); - ThrowErrorIf(Error::Unexpected, currentSize != m_lastLFH.second.Size(), "Cannot change the LFH size when updating it"); - - m_lastLFH.second.WriteTo(m_stream); - } - - // Create and add cdh to map - CentralDirectoryFileHeader cdh; - cdh.SetData(m_lastLFH.second.GetFileName(), crc, compressedSize, uncompressedSize, m_lastLFH.first, m_lastLFH.second.GetCompressionMethod(), forceDataDescriptor); - m_centralDirectories.insert(std::make_pair(m_lastLFH.second.GetFileName(), std::move(cdh))); - m_state = ZipObjectWriter::State::ReadyForLfhOrClose; - } - - void ZipObjectWriter::Close() - { - ThrowErrorIf(Error::InvalidState, m_state != ZipObjectWriter::State::ReadyForLfhOrClose, "Invalid zip writer state"); - // Write central directories - ULARGE_INTEGER startOfCdh = {0}; - ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &startOfCdh)); - std::size_t cdhsSize = 0; - for (const auto& fileName : m_fileNameSequence) - { - auto it = m_centralDirectories.find(fileName); - if (it != m_centralDirectories.end()) - { - auto& cdh = it->second; - cdhsSize += cdh.Size(); - cdh.WriteTo(m_stream); - } - } - m_fileNameSequence.clear(); - - // Write zip64 end of cds - ULARGE_INTEGER startOfZip64EndOfCds = {0}; - ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &startOfZip64EndOfCds)); - m_zip64EndOfCentralDirectory.SetData(m_centralDirectories.size(), static_cast(cdhsSize), - static_cast(startOfCdh.QuadPart)); - m_zip64EndOfCentralDirectory.WriteTo(m_stream); - - // Write zip64 locator - m_zip64Locator.SetData(static_cast(startOfZip64EndOfCds.QuadPart)); - m_zip64Locator.WriteTo(m_stream); - - // Because we only use zip64, EndCentralDirectoryRecord never changes - m_endCentralDirectoryRecord.WriteTo(m_stream); - - m_state = ZipObjectWriter::State::Closed; - } - +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "ZipObjectWriter.hpp" +#include "ZipObject.hpp" +#include "MsixErrors.hpp" +#include "Exceptions.hpp" +#include "ZipFileStream.hpp" +#include "DeflateStream.hpp" +#include "StreamHelper.hpp" +#include "Encoding.hpp" + +namespace MSIX { + + // We only use this for writting. If we ever decide to validate it, it needs to move to + // ZipObject and ZipObjectReader must validate it + class DataDescriptor final : public Meta::StructuredObject< + Meta::Field4Bytes, // 0 - data descriptor header signature 4 bytes(0x08074b50) + Meta::Field4Bytes, // 1 - crc -32 4 bytes + Meta::Field8Bytes, // 2 - compressed size 8 bytes(zip64) + Meta::Field8Bytes // 3 - uncompressed size 8 bytes(zip64) + > + { + public: + DataDescriptor(std::uint32_t crc, std::uint64_t compressSize, std::uint64_t uncompressSize) + { + Field<0>() = static_cast(Signatures::DataDescriptor); + Field<1>() = crc; + Field<2>() = compressSize; + Field<3>() = uncompressSize; + } + }; + + ZipObjectWriter::ZipObjectWriter(const ComPtr& stream) : ZipObject(stream) + { + } + + // This is used for editing a package (aka signing) + ZipObjectWriter::ZipObjectWriter(const ComPtr& storageObject) : ZipObject(storageObject) + { + // The storage object provided should had already initialize all the data. + ThrowErrorIfNot(Error::Zip64EOCDRecord, m_endCentralDirectoryRecord.GetIsZip64(), + "Editing non zip64 packages not supported"); + + // Move the stream at the start of central directory record so we can start overwritting. + // Central directory data in already in m_centralDirectories. + LARGE_INTEGER pos = {0}; + pos.QuadPart = m_zip64EndOfCentralDirectory.GetOffsetStartOfCD(); + ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr)); + } + + // IStorage + std::vector ZipObjectWriter::GetFileNames(FileNameOptions options) + { + // TODO: implement + NOTIMPLEMENTED; + } + + ComPtr ZipObjectWriter::GetFile(const std::string& fileName) + { + // TODO: implement + NOTIMPLEMENTED; + } + + // IZipWriter + std::tuple> ZipObjectWriter::PrepareToAddFile(const std::string& name, bool isCompressed) + { + ThrowErrorIf(Error::InvalidState, m_state != ZipObjectWriter::State::ReadyForLfhOrClose, "Invalid zip writer state"); + + auto result = m_centralDirectories.find(name); + if (result != m_centralDirectories.end()) + { + auto message = "Adding duplicated file " + Encoding::DecodeFileName(name) + "to package"; + ThrowErrorAndLog(Error::DuplicateFile, message.c_str()); + } + + // Get position were the lfh is going to be written + ULARGE_INTEGER pos = {0}; + ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &pos)); + + // track the sequence of file names to sort the central directory upon Close + m_fileNameSequence.push_back(name); + + // Write lfh + LocalFileHeader lfh; + lfh.SetData(name, isCompressed); + lfh.WriteTo(m_stream); + + m_lastLFH = std::make_pair(static_cast(pos.QuadPart), std::move(lfh)); + m_state = ZipObjectWriter::State::ReadyForFile; + + ComPtr zipStream = ComPtr::Make(name, isCompressed, m_stream.Get()); + if (isCompressed) + { + zipStream = ComPtr::Make(zipStream); + } + + return std::make_tuple(m_lastLFH.first, static_cast(m_lastLFH.second.Size()), std::move(zipStream)); + } + + void ZipObjectWriter::EndFile(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize, bool forceDataDescriptor) + { + ThrowErrorIf(Error::InvalidState, m_state != ZipObjectWriter::State::ReadyForFile, "Invalid zip writer state"); + + if (forceDataDescriptor || + compressedSize > MaxSizeToNotUseDataDescriptor || + uncompressedSize > MaxSizeToNotUseDataDescriptor) + { + // Create and write data descriptor + DataDescriptor descriptor = DataDescriptor(crc, compressedSize, uncompressedSize); + descriptor.WriteTo(m_stream); + } + else + { + // The sizes can fit in the LFH, rewrite it with the new data + Helper::StreamPositionReset resetAfterLFHWrite{ m_stream.Get() }; + + LARGE_INTEGER lfhLocation; + lfhLocation.QuadPart = static_cast(m_lastLFH.first); + ThrowHrIfFailed(m_stream->Seek(lfhLocation, StreamBase::Reference::START, nullptr)); + + // We cannot change the size of the LFH, ensure that we don't accidentally + size_t currentSize = m_lastLFH.second.Size(); + m_lastLFH.second.SetData(crc, compressedSize, uncompressedSize); + ThrowErrorIf(Error::Unexpected, currentSize != m_lastLFH.second.Size(), "Cannot change the LFH size when updating it"); + + m_lastLFH.second.WriteTo(m_stream); + } + + // Create and add cdh to map + CentralDirectoryFileHeader cdh; + cdh.SetData(m_lastLFH.second.GetFileName(), crc, compressedSize, uncompressedSize, m_lastLFH.first, m_lastLFH.second.GetCompressionMethod(), forceDataDescriptor); + m_centralDirectories.insert(std::make_pair(m_lastLFH.second.GetFileName(), std::move(cdh))); + m_state = ZipObjectWriter::State::ReadyForLfhOrClose; + } + + void ZipObjectWriter::Close() + { + ThrowErrorIf(Error::InvalidState, m_state != ZipObjectWriter::State::ReadyForLfhOrClose, "Invalid zip writer state"); + // Write central directories + ULARGE_INTEGER startOfCdh = {0}; + ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &startOfCdh)); + std::size_t cdhsSize = 0; + for (const auto& fileName : m_fileNameSequence) + { + auto it = m_centralDirectories.find(fileName); + if (it != m_centralDirectories.end()) + { + auto& cdh = it->second; + cdhsSize += cdh.Size(); + cdh.WriteTo(m_stream); + } + } + m_fileNameSequence.clear(); + + // Write zip64 end of cds + ULARGE_INTEGER startOfZip64EndOfCds = {0}; + ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &startOfZip64EndOfCds)); + m_zip64EndOfCentralDirectory.SetData(m_centralDirectories.size(), static_cast(cdhsSize), + static_cast(startOfCdh.QuadPart)); + m_zip64EndOfCentralDirectory.WriteTo(m_stream); + + // Write zip64 locator + m_zip64Locator.SetData(static_cast(startOfZip64EndOfCds.QuadPart)); + m_zip64Locator.WriteTo(m_stream); + + // Because we only use zip64, EndCentralDirectoryRecord never changes + m_endCentralDirectoryRecord.WriteTo(m_stream); + + m_state = ZipObjectWriter::State::Closed; + } + } \ No newline at end of file From 9d496a1750600a38b43c98c1656f0695913131c9 Mon Sep 17 00:00:00 2001 From: Peter Shafton Date: Tue, 25 Nov 2025 18:52:32 -0800 Subject: [PATCH 2/3] Fix for Non Flat pack issues --- src/msix/pack/AppxBundleWriter.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/msix/pack/AppxBundleWriter.cpp b/src/msix/pack/AppxBundleWriter.cpp index 108391a09..c5c508dd3 100644 --- a/src/msix/pack/AppxBundleWriter.cpp +++ b/src/msix/pack/AppxBundleWriter.cpp @@ -62,6 +62,10 @@ namespace MSIX { { ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(file.second).c_str(), stream.Get(), false)); } + else + { + ThrowHrIfFailed(AddPayloadPackage(utf8_to_wstring(file.second).c_str(), stream.Get(), false)); + } } } @@ -92,6 +96,10 @@ namespace MSIX { { ThrowHrIfFailed(AddPackageReference(utf8_to_wstring(outputPath).c_str(), stream.Get(), false)); } + else + { + ThrowHrIfFailed(AddPayloadPackage(utf8_to_wstring(outputPath).c_str(), stream.Get(), false)); + } } } failState.release(); From 3d8dd46b9a68a3dab15347b4bbecb06ebc92ab10 Mon Sep 17 00:00:00 2001 From: Peter Shafton Date: Wed, 26 Nov 2025 09:35:29 -0800 Subject: [PATCH 3/3] Properly include the Offset in the package --- src/msix/pack/BundleManifestWriter.cpp | 666 ++++++++++++------------- src/msix/pack/BundleWriterHelper.cpp | 608 +++++++++++----------- 2 files changed, 637 insertions(+), 637 deletions(-) diff --git a/src/msix/pack/BundleManifestWriter.cpp b/src/msix/pack/BundleManifestWriter.cpp index 908f3bcfb..f098fe8cb 100644 --- a/src/msix/pack/BundleManifestWriter.cpp +++ b/src/msix/pack/BundleManifestWriter.cpp @@ -1,333 +1,333 @@ -// -// Copyright (C) 2019 Microsoft. All rights reserved. -// See LICENSE file in the project root for full license information. -// - -#include "XmlWriter.hpp" -#include "BundleManifestWriter.hpp" -#include "Crypto.hpp" -#include "StringHelper.hpp" -#include "AppxManifestObject.hpp" - -#include - -namespace MSIX { - - static const char* bundleManifestElement = "Bundle"; - static const char* schemaVersionAttribute = "SchemaVersion"; - static const char* Win2019SchemaVersion = "5.0"; - static const char* identityManifestElement = "Identity"; - static const char* nameAttribute = "Name"; - static const char* publisherAttribute = "Publisher"; - static const char* versionAttribute = "Version"; - static const char* packagesManifestElement = "Packages"; - static const char* packageManifestElement = "Package"; - static const char* packageTypeAttribute = "Type"; - static const char* packageArchitectureAttribute = "Architecture"; - static const char* packageResourceIdAttribute = "ResourceId"; - static const char* fileNameAttribute = "FileName"; - static const char* resourcesManifestElement = "Resources"; - static const char* resourceManifestElement = "Resource"; - static const char* resourceLanguageAttribute = "Language"; - static const char* dependenciesManifestElementWithoutPrefix = "Dependencies"; - static const char* targetDeviceFamilyManifestElementWithoutPrefix = "TargetDeviceFamily"; - static const char* tdfMinVersionAttribute = "MinVersion"; - static const char* tdfMaxVersionTestedAttribute = "MaxVersionTested"; - static const char* ApplicationPackageType = "application"; - static const char* ResourcePackageType = "resource"; - static const char* optionalBundleManifestElement = "OptionalBundle"; - - static const char* NamespaceAlias = "b"; - static const char* Namespace = "http://schemas.microsoft.com/appx/2013/bundle"; - static const char* Namespace2016Alias = "b2"; - static const char* Namespace2016 = "http://schemas.microsoft.com/appx/2016/bundle"; - static const char* Namespace2017Alias = "b3"; - static const char* Namespace2017 = "http://schemas.microsoft.com/appx/2017/bundle"; - static const char* Namespace2018Alias = "b4"; - static const char* Namespace2018 = "http://schemas.microsoft.com/appx/2018/bundle"; - static const char* Namespace2019Alias = "b5"; - static const char* Namespace2019 = "http://schemas.microsoft.com/appx/2019/bundle"; - - BundleManifestWriter::BundleManifestWriter() : m_xmlWriter(XmlWriter(bundleManifestElement)) - { - currentState = Uninitialized; - } - - void BundleManifestWriter::StartBundleManifest(std::string targetXmlNamespace, std::string name, - std::string publisher, std::uint64_t version) - { - this->targetXmlNamespace = targetXmlNamespace; - StartBundleElement(); - WriteIdentityElement(name, publisher, version); - StartPackagesElement(); - currentState = BundleManifestStarted; - } - - void BundleManifestWriter::StartBundleElement() - { - m_xmlWriter.AddAttribute(xmlnsAttribute, this->targetXmlNamespace); - m_xmlWriter.AddAttribute(schemaVersionAttribute, Win2019SchemaVersion); - - std::string bundle2018QName = GetQualifiedName(xmlnsAttribute, Namespace2018Alias); - m_xmlWriter.AddAttribute(bundle2018QName, Namespace2018); - - std::string bundle2019QName = GetQualifiedName(xmlnsAttribute, Namespace2019Alias); - m_xmlWriter.AddAttribute(bundle2019QName, Namespace2019); - - std::string ignorableNamespaces; - ignorableNamespaces.append(Namespace2018Alias); - ignorableNamespaces.append(" "); - ignorableNamespaces.append(Namespace2019Alias); - m_xmlWriter.AddAttribute("IgnorableNamespaces", ignorableNamespaces); - } - - void BundleManifestWriter::WriteIdentityElement(std::string name, std::string publisher, std::uint64_t version) - { - m_xmlWriter.StartElement(identityManifestElement); - - m_xmlWriter.AddAttribute(nameAttribute, name); - m_xmlWriter.AddAttribute(publisherAttribute, publisher); - - std::string versionString = MSIX::ConvertVersionToString(version); - m_xmlWriter.AddAttribute(versionAttribute, versionString); - - m_xmlWriter.CloseElement(); - } - - void BundleManifestWriter::StartPackagesElement() - { - m_xmlWriter.StartElement(packagesManifestElement); - } - - void BundleManifestWriter::AddPackage(PackageInfo packageInfo) - { - WritePackageElement(packageInfo); - currentState = PackagesAdded; - } - - void BundleManifestWriter::WritePackageElement(PackageInfo packageInfo) - { - m_xmlWriter.StartElement(packageManifestElement); - - std::string packageTypeString; - if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) - { - packageTypeString = ApplicationPackageType; - } - else if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE) - { - packageTypeString = ResourcePackageType; - } - m_xmlWriter.AddAttribute(packageTypeAttribute, packageTypeString); - - std::string versionString = MSIX::ConvertVersionToString(packageInfo.version); - m_xmlWriter.AddAttribute(versionAttribute, versionString); - - if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) - { - m_xmlWriter.AddAttribute(packageArchitectureAttribute, packageInfo.architecture); - } - - if (!packageInfo.resourceId.empty() && (packageInfo.resourceId.size() > 0)) - { - m_xmlWriter.AddAttribute(packageResourceIdAttribute, packageInfo.resourceId); - } - - if(!packageInfo.fileName.empty()) - { - m_xmlWriter.AddAttribute(fileNameAttribute, packageInfo.fileName); - } - - if(packageInfo.offset > 0) - { - //TODO: not applicable for flat bundle - } - - if (packageInfo.size > 0 && packageInfo.offset > 0) - { - //TODO: not applicable for flat bundles - } - - //WriteResourcesElement - WriteResourcesElement(packageInfo.resources.Get()); - - //WriteDependenciesElement - WriteDependenciesElement(packageInfo.tdfs.Get()); - - //End Package Tag - m_xmlWriter.CloseElement(); - } - - void BundleManifestWriter::WriteResourcesElement(IAppxManifestQualifiedResourcesEnumerator* resources) - { - BOOL hasResources = FALSE; - ThrowHrIfFailed(resources->GetHasCurrent(&hasResources)); - - if (hasResources) - { - //Start Resources element - m_xmlWriter.StartElement(resourcesManifestElement); - - BOOL hasNext = FALSE; - ThrowHrIfFailed(resources->GetHasCurrent(&hasNext)); - while (hasNext) - { - ComPtr resource; - ThrowHrIfFailed(resources->GetCurrent(&resource)); - - //Start Resource element - m_xmlWriter.StartElement(resourceManifestElement); - - auto qualifiedResourceInternal = resource.As(); - std::string languageString = qualifiedResourceInternal->GetLanguage(); - if (!languageString.empty()) - { - m_xmlWriter.AddAttribute(resourceLanguageAttribute, languageString); - } - - //TODO:: Write scale and dxfeaturelevel attributes - - //End Resource element - m_xmlWriter.CloseElement(); - - ThrowHrIfFailed(resources->MoveNext(&hasNext)); - } - - //End Resources element - m_xmlWriter.CloseElement(); - } - } - - void BundleManifestWriter::WriteDependenciesElement(IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) - { - BOOL hasNext = FALSE; - ThrowHrIfFailed(tdfs->GetHasCurrent(&hasNext)); - - if (hasNext) - { - std::string dependencyQName = GetElementName(Namespace2018, Namespace2018Alias, dependenciesManifestElementWithoutPrefix); - m_xmlWriter.StartElement(dependencyQName); - - while (hasNext) - { - ComPtr tdf; - ThrowHrIfFailed(tdfs->GetCurrent(&tdf)); - - //Start TargetDeviceFamily manifest element - std::string tdfQName = GetElementName(Namespace2018, Namespace2018Alias, targetDeviceFamilyManifestElementWithoutPrefix); - m_xmlWriter.StartElement(tdfQName); - - auto targetDeviceFamilyInternal = tdf.As(); - std::string name = targetDeviceFamilyInternal->GetName(); - m_xmlWriter.AddAttribute(nameAttribute, name); - - //Get minversion - UINT64 minVersion; - ThrowHrIfFailed(tdf->GetMinVersion(&minVersion)); - std::string minVerionString = MSIX::ConvertVersionToString(minVersion); - m_xmlWriter.AddAttribute(tdfMinVersionAttribute, minVerionString); - - //Get maxversiontested - UINT64 maxVersionTested; - ThrowHrIfFailed(tdf->GetMaxVersionTested(&maxVersionTested)); - std::string maxVersionTestedString = MSIX::ConvertVersionToString(maxVersionTested); - m_xmlWriter.AddAttribute(tdfMaxVersionTestedAttribute, maxVersionTestedString); - - //End TargetDeviceFamily manifest element - m_xmlWriter.CloseElement(); - - ThrowHrIfFailed(tdfs->MoveNext(&hasNext)); - } - - //End Dependencies Tag - m_xmlWriter.CloseElement(); - } - } - - void BundleManifestWriter::AddOptionalBundle(OptionalBundleInfo bundleInfo) - { - EndPackagesElementIfNecessary(); - WriteOptionalBundleElement(bundleInfo); - currentState = OptionalBundlesAdded; - } - - // Writes an OptionalBundle element, which can have one or more Package elements inside. - // - void BundleManifestWriter::WriteOptionalBundleElement(OptionalBundleInfo bundleInfo) - { - ThrowErrorIf(Error::InvalidParameter, bundleInfo.name.empty(), "One or more arguments are invalid"); - ThrowErrorIf(Error::InvalidParameter, bundleInfo.publisher.empty(), "One or more arguments are invalid"); - - m_xmlWriter.StartElement(optionalBundleManifestElement); - m_xmlWriter.AddAttribute(nameAttribute, bundleInfo.name); - m_xmlWriter.AddAttribute(publisherAttribute, bundleInfo.publisher); - - if (bundleInfo.version > 0) - { - std::string versionString = MSIX::ConvertVersionToString(bundleInfo.version); - m_xmlWriter.AddAttribute(versionAttribute, versionString); - } - - if(!bundleInfo.fileName.empty()) - { - m_xmlWriter.AddAttribute(fileNameAttribute, bundleInfo.fileName); - } - - for(size_t i = 0; i < bundleInfo.optionalPackages.size(); i++) - { - WritePackageElement(bundleInfo.optionalPackages[i]); - } - - //End OptionalBundle Tag - m_xmlWriter.CloseElement(); - } - - void BundleManifestWriter::EndBundleManifest() - { - EndPackagesElementIfNecessary(); - EndBundleElement(); - currentState = BundleManifestEnded; - } - - void BundleManifestWriter::EndPackagesElementIfNecessary() - { - if (currentState == PackagesAdded) - { - EndPackagesElement(); - } - } - - void BundleManifestWriter::EndPackagesElement() - { - m_xmlWriter.CloseElement(); - } - - void BundleManifestWriter::EndBundleElement() - { - m_xmlWriter.CloseElement(); - } - - std::string BundleManifestWriter::GetElementName(std::string targetNamespace, std::string targetNamespaceAlias, std::string name) - { - std::string qualifiedName; - if ((this->targetXmlNamespace.compare(targetNamespace) != 0) && (!targetNamespaceAlias.empty())) - { - qualifiedName = GetQualifiedName(targetNamespaceAlias, name); - } - else - { - qualifiedName = name; - } - return qualifiedName; - } - - std::string BundleManifestWriter::GetQualifiedName(std::string namespaceAlias, std::string name) - { - std::string output; - output.append(namespaceAlias); - output.append(xmlNamespaceDelimiter); - output.append(name); - return output; - } -} - +// +// Copyright (C) 2019 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include "XmlWriter.hpp" +#include "BundleManifestWriter.hpp" +#include "Crypto.hpp" +#include "StringHelper.hpp" +#include "AppxManifestObject.hpp" + +#include + +namespace MSIX { + + static const char* bundleManifestElement = "Bundle"; + static const char* schemaVersionAttribute = "SchemaVersion"; + static const char* Win2019SchemaVersion = "5.0"; + static const char* identityManifestElement = "Identity"; + static const char* nameAttribute = "Name"; + static const char* publisherAttribute = "Publisher"; + static const char* versionAttribute = "Version"; + static const char* packagesManifestElement = "Packages"; + static const char* packageManifestElement = "Package"; + static const char* packageTypeAttribute = "Type"; + static const char* packageArchitectureAttribute = "Architecture"; + static const char* packageResourceIdAttribute = "ResourceId"; + static const char* fileNameAttribute = "FileName"; + static const char* offsetAttribute = "Offset"; + static const char* sizeAttribute = "Size"; + static const char* resourcesManifestElement = "Resources"; + static const char* resourceManifestElement = "Resource"; + static const char* resourceLanguageAttribute = "Language"; + static const char* dependenciesManifestElementWithoutPrefix = "Dependencies"; + static const char* targetDeviceFamilyManifestElementWithoutPrefix = "TargetDeviceFamily"; + static const char* tdfMinVersionAttribute = "MinVersion"; + static const char* tdfMaxVersionTestedAttribute = "MaxVersionTested"; + static const char* ApplicationPackageType = "application"; + static const char* ResourcePackageType = "resource"; + static const char* optionalBundleManifestElement = "OptionalBundle"; + + static const char* NamespaceAlias = "b"; + static const char* Namespace = "http://schemas.microsoft.com/appx/2013/bundle"; + static const char* Namespace2016Alias = "b2"; + static const char* Namespace2016 = "http://schemas.microsoft.com/appx/2016/bundle"; + static const char* Namespace2017Alias = "b3"; + static const char* Namespace2017 = "http://schemas.microsoft.com/appx/2017/bundle"; + static const char* Namespace2018Alias = "b4"; + static const char* Namespace2018 = "http://schemas.microsoft.com/appx/2018/bundle"; + static const char* Namespace2019Alias = "b5"; + static const char* Namespace2019 = "http://schemas.microsoft.com/appx/2019/bundle"; + + BundleManifestWriter::BundleManifestWriter() : m_xmlWriter(XmlWriter(bundleManifestElement)) + { + currentState = Uninitialized; + } + + void BundleManifestWriter::StartBundleManifest(std::string targetXmlNamespace, std::string name, + std::string publisher, std::uint64_t version) + { + this->targetXmlNamespace = targetXmlNamespace; + StartBundleElement(); + WriteIdentityElement(name, publisher, version); + StartPackagesElement(); + currentState = BundleManifestStarted; + } + + void BundleManifestWriter::StartBundleElement() + { + m_xmlWriter.AddAttribute(xmlnsAttribute, this->targetXmlNamespace); + m_xmlWriter.AddAttribute(schemaVersionAttribute, Win2019SchemaVersion); + + std::string bundle2018QName = GetQualifiedName(xmlnsAttribute, Namespace2018Alias); + m_xmlWriter.AddAttribute(bundle2018QName, Namespace2018); + + std::string bundle2019QName = GetQualifiedName(xmlnsAttribute, Namespace2019Alias); + m_xmlWriter.AddAttribute(bundle2019QName, Namespace2019); + + std::string ignorableNamespaces; + ignorableNamespaces.append(Namespace2018Alias); + ignorableNamespaces.append(" "); + ignorableNamespaces.append(Namespace2019Alias); + m_xmlWriter.AddAttribute("IgnorableNamespaces", ignorableNamespaces); + } + + void BundleManifestWriter::WriteIdentityElement(std::string name, std::string publisher, std::uint64_t version) + { + m_xmlWriter.StartElement(identityManifestElement); + + m_xmlWriter.AddAttribute(nameAttribute, name); + m_xmlWriter.AddAttribute(publisherAttribute, publisher); + + std::string versionString = MSIX::ConvertVersionToString(version); + m_xmlWriter.AddAttribute(versionAttribute, versionString); + + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::StartPackagesElement() + { + m_xmlWriter.StartElement(packagesManifestElement); + } + + void BundleManifestWriter::AddPackage(PackageInfo packageInfo) + { + WritePackageElement(packageInfo); + currentState = PackagesAdded; + } + + void BundleManifestWriter::WritePackageElement(PackageInfo packageInfo) + { + m_xmlWriter.StartElement(packageManifestElement); + + std::string packageTypeString; + if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + packageTypeString = ApplicationPackageType; + } + else if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_RESOURCE) + { + packageTypeString = ResourcePackageType; + } + m_xmlWriter.AddAttribute(packageTypeAttribute, packageTypeString); + + std::string versionString = MSIX::ConvertVersionToString(packageInfo.version); + m_xmlWriter.AddAttribute(versionAttribute, versionString); + + if(packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + m_xmlWriter.AddAttribute(packageArchitectureAttribute, packageInfo.architecture); + } + + if (!packageInfo.resourceId.empty() && (packageInfo.resourceId.size() > 0)) + { + m_xmlWriter.AddAttribute(packageResourceIdAttribute, packageInfo.resourceId); + } + + if(packageInfo.fileName.empty()) + { + // If the file name is empty, we shouldn't be here. + ThrowError(Error::Unexpected); + } + m_xmlWriter.AddAttribute(fileNameAttribute, packageInfo.fileName); + + if(packageInfo.size > 0) + { + m_xmlWriter.AddAttribute(offsetAttribute, std::to_string(packageInfo.offset)); + m_xmlWriter.AddAttribute(sizeAttribute, std::to_string(packageInfo.size)); + } + + //WriteResourcesElement + WriteResourcesElement(packageInfo.resources.Get()); + + //WriteDependenciesElement + WriteDependenciesElement(packageInfo.tdfs.Get()); + + //End Package Tag + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::WriteResourcesElement(IAppxManifestQualifiedResourcesEnumerator* resources) + { + BOOL hasResources = FALSE; + ThrowHrIfFailed(resources->GetHasCurrent(&hasResources)); + + if (hasResources) + { + //Start Resources element + m_xmlWriter.StartElement(resourcesManifestElement); + + BOOL hasNext = FALSE; + ThrowHrIfFailed(resources->GetHasCurrent(&hasNext)); + while (hasNext) + { + ComPtr resource; + ThrowHrIfFailed(resources->GetCurrent(&resource)); + + //Start Resource element + m_xmlWriter.StartElement(resourceManifestElement); + + auto qualifiedResourceInternal = resource.As(); + std::string languageString = qualifiedResourceInternal->GetLanguage(); + if (!languageString.empty()) + { + m_xmlWriter.AddAttribute(resourceLanguageAttribute, languageString); + } + + //TODO:: Write scale and dxfeaturelevel attributes + + //End Resource element + m_xmlWriter.CloseElement(); + + ThrowHrIfFailed(resources->MoveNext(&hasNext)); + } + + //End Resources element + m_xmlWriter.CloseElement(); + } + } + + void BundleManifestWriter::WriteDependenciesElement(IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) + { + BOOL hasNext = FALSE; + ThrowHrIfFailed(tdfs->GetHasCurrent(&hasNext)); + + if (hasNext) + { + std::string dependencyQName = GetElementName(Namespace2018, Namespace2018Alias, dependenciesManifestElementWithoutPrefix); + m_xmlWriter.StartElement(dependencyQName); + + while (hasNext) + { + ComPtr tdf; + ThrowHrIfFailed(tdfs->GetCurrent(&tdf)); + + //Start TargetDeviceFamily manifest element + std::string tdfQName = GetElementName(Namespace2018, Namespace2018Alias, targetDeviceFamilyManifestElementWithoutPrefix); + m_xmlWriter.StartElement(tdfQName); + + auto targetDeviceFamilyInternal = tdf.As(); + std::string name = targetDeviceFamilyInternal->GetName(); + m_xmlWriter.AddAttribute(nameAttribute, name); + + //Get minversion + UINT64 minVersion; + ThrowHrIfFailed(tdf->GetMinVersion(&minVersion)); + std::string minVerionString = MSIX::ConvertVersionToString(minVersion); + m_xmlWriter.AddAttribute(tdfMinVersionAttribute, minVerionString); + + //Get maxversiontested + UINT64 maxVersionTested; + ThrowHrIfFailed(tdf->GetMaxVersionTested(&maxVersionTested)); + std::string maxVersionTestedString = MSIX::ConvertVersionToString(maxVersionTested); + m_xmlWriter.AddAttribute(tdfMaxVersionTestedAttribute, maxVersionTestedString); + + //End TargetDeviceFamily manifest element + m_xmlWriter.CloseElement(); + + ThrowHrIfFailed(tdfs->MoveNext(&hasNext)); + } + + //End Dependencies Tag + m_xmlWriter.CloseElement(); + } + } + + void BundleManifestWriter::AddOptionalBundle(OptionalBundleInfo bundleInfo) + { + EndPackagesElementIfNecessary(); + WriteOptionalBundleElement(bundleInfo); + currentState = OptionalBundlesAdded; + } + + // Writes an OptionalBundle element, which can have one or more Package elements inside. + // + void BundleManifestWriter::WriteOptionalBundleElement(OptionalBundleInfo bundleInfo) + { + ThrowErrorIf(Error::InvalidParameter, bundleInfo.name.empty(), "One or more arguments are invalid"); + ThrowErrorIf(Error::InvalidParameter, bundleInfo.publisher.empty(), "One or more arguments are invalid"); + + m_xmlWriter.StartElement(optionalBundleManifestElement); + m_xmlWriter.AddAttribute(nameAttribute, bundleInfo.name); + m_xmlWriter.AddAttribute(publisherAttribute, bundleInfo.publisher); + + if (bundleInfo.version > 0) + { + std::string versionString = MSIX::ConvertVersionToString(bundleInfo.version); + m_xmlWriter.AddAttribute(versionAttribute, versionString); + } + + if(!bundleInfo.fileName.empty()) + { + m_xmlWriter.AddAttribute(fileNameAttribute, bundleInfo.fileName); + } + + for(size_t i = 0; i < bundleInfo.optionalPackages.size(); i++) + { + WritePackageElement(bundleInfo.optionalPackages[i]); + } + + //End OptionalBundle Tag + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::EndBundleManifest() + { + EndPackagesElementIfNecessary(); + EndBundleElement(); + currentState = BundleManifestEnded; + } + + void BundleManifestWriter::EndPackagesElementIfNecessary() + { + if (currentState == PackagesAdded) + { + EndPackagesElement(); + } + } + + void BundleManifestWriter::EndPackagesElement() + { + m_xmlWriter.CloseElement(); + } + + void BundleManifestWriter::EndBundleElement() + { + m_xmlWriter.CloseElement(); + } + + std::string BundleManifestWriter::GetElementName(std::string targetNamespace, std::string targetNamespaceAlias, std::string name) + { + std::string qualifiedName; + if ((this->targetXmlNamespace.compare(targetNamespace) != 0) && (!targetNamespaceAlias.empty())) + { + qualifiedName = GetQualifiedName(targetNamespaceAlias, name); + } + else + { + qualifiedName = name; + } + return qualifiedName; + } + + std::string BundleManifestWriter::GetQualifiedName(std::string namespaceAlias, std::string name) + { + std::string output; + output.append(namespaceAlias); + output.append(xmlNamespaceDelimiter); + output.append(name); + return output; + } +} + diff --git a/src/msix/pack/BundleWriterHelper.cpp b/src/msix/pack/BundleWriterHelper.cpp index 598672eac..6d5400a43 100644 --- a/src/msix/pack/BundleWriterHelper.cpp +++ b/src/msix/pack/BundleWriterHelper.cpp @@ -1,305 +1,305 @@ -#include "BundleWriterHelper.hpp" - -namespace MSIX { - - BundleWriterHelper::BundleWriterHelper() - { - this->bundleVersion = 0; - this->hasExternalPackages = false; - this->hasDefaultOrNeutralResources = false; - } - - std::uint64_t BundleWriterHelper::GetStreamSize(IStream* stream) - { - STATSTG stat; - ThrowHrIfFailed(stream->Stat(&stat, 1)); - - return stat.cbSize.QuadPart; - } - - void BundleWriterHelper::AddPackage(std::string fileName, IAppxPackageReader* packageReader, - std::uint64_t bundleOffset, std::uint64_t packageSize, bool isDefaultApplicableResource) - { - ComPtr packageId; - APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; - ComPtr resources; - ComPtr tdfs; - - GetValidatedPackageData(fileName, packageReader, &packageType, &packageId, &resources, &tdfs); - - AddValidatedPackageData(fileName, bundleOffset, packageSize, packageType, packageId, - isDefaultApplicableResource, resources.Get(), tdfs.Get()); - } - - void BundleWriterHelper::GetValidatedPackageData( - std::string fileName, - IAppxPackageReader* packageReader, - APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE* packageType, - IAppxManifestPackageId** packageId, - IAppxManifestQualifiedResourcesEnumerator** resources, - IAppxManifestTargetDeviceFamiliesEnumerator** tdfs) - { - *packageId = nullptr; - *resources = nullptr; - *tdfs = nullptr; - - ComPtr loadedPackageId; - APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE loadedPackageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; - ComPtr loadedResources; - ComPtr loadedTdfs; - - ComPtr manifestReader; - ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); - ThrowHrIfFailed(manifestReader->GetPackageId(&loadedPackageId)); - - ComPtr manifestReader3; - ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); - ThrowHrIfFailed(manifestReader3->GetQualifiedResources(&loadedResources)); - ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&loadedTdfs)); - - auto packageIdInternal = loadedPackageId.As(); - - loadedPackageType = this->m_validationHelper.GetPayloadPackageType(manifestReader.Get(), fileName); - this->m_validationHelper.AddPackage(loadedPackageType, packageIdInternal.Get(), fileName); - this->m_validationHelper.ValidateOSVersion(manifestReader.Get(), fileName); - - ValidateNameAndPublisher(packageIdInternal.Get(), fileName); - - if (loadedPackageType == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) - { - this->m_validationHelper.ValidateApplicationElement(manifestReader.Get(), fileName); - if (loadedTdfs.Get() != nullptr) - { - ComPtr tdfCopy; - ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&tdfCopy)); - this->m_validationHelper.ValidateTargetDeviceFamiliesFromManifestPackageId(packageIdInternal.Get(), tdfCopy.Get(), fileName); - } - } - - *packageType = loadedPackageType; - *packageId = loadedPackageId.Detach(); - *resources = loadedResources.Detach(); - - if (loadedTdfs.Get() != nullptr) - { - *tdfs = loadedTdfs.Detach(); - } - } - - void BundleWriterHelper::ValidateNameAndPublisher(IAppxManifestPackageIdInternal* packageId, - std::string filename) - { - if(this->mainPackageName.empty()) - { - this->mainPackageName = packageId->GetName(); - this->mainPackagePublisher = packageId->GetPublisher(); - } - else - { - std::string packageName = packageId->GetName(); - - if ((this->mainPackageName.compare(packageName)) != 0) - { - std::string packageFullName = packageId->GetPackageFullName(); - ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); - } - - std::string publisherName = packageId->GetPublisher(); - if ((this->mainPackagePublisher.compare(publisherName)) != 0) - { - std::string packageFullName = packageId->GetPackageFullName(); - ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); - } - } - } - - void BundleWriterHelper::AddValidatedPackageData( - std::string fileName, - std::uint64_t bundleOffset, - std::uint64_t packageSize, - APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType, - ComPtr packageId, - bool isDefaultApplicablePackage, - IAppxManifestQualifiedResourcesEnumerator* resources, - IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) - { - auto innerPackageIdInternal = packageId.As(); - - PackageInfo packageInfo; - packageInfo.type = packageType; - packageInfo.architecture = innerPackageIdInternal->GetArchitecture(); - UINT64 version; - ThrowHrIfFailed(packageId->GetVersion(&version)); - packageInfo.version = version; - packageInfo.resourceId = innerPackageIdInternal->GetResourceId(); - packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; - packageInfo.resources = resources; - packageInfo.fileName = fileName; - packageInfo.size = packageSize; - packageInfo.offset = bundleOffset; - packageInfo.tdfs = tdfs; - - AddPackageInfoToVector(this->payloadPackages, packageInfo); - } - - void BundleWriterHelper::AddPackageInfoToVector(std::vector& packagesVector, - PackageInfo packageInfo) - { - packagesVector.push_back(packageInfo); - - if (packageInfo.offset == 0) - { - this->hasExternalPackages = true; - } - - if (packageInfo.isDefaultApplicablePackage) - { - this->hasDefaultOrNeutralResources = true; - } - - BOOL hasResources = FALSE; - ThrowHrIfFailed(packageInfo.resources->GetHasCurrent(&hasResources)); - if (!hasResources) - { - this->hasDefaultOrNeutralResources = true; - } - } - - void BundleWriterHelper::AddExternalPackageReferenceFromManifest(std::string fileName, IAppxManifestReader* manifestReader, - bool isDefaultApplicablePackage) - { - ComPtr manifestReader5; - ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader5))); - ComPtr mainPackageDependencies; - ThrowHrIfFailed(manifestReader5->GetMainPackageDependencies(&mainPackageDependencies)); - BOOL hasMoreMainPackageDependencies = FALSE; - ThrowHrIfFailed(mainPackageDependencies->GetHasCurrent(&hasMoreMainPackageDependencies)); - - // Validation: this must be an optional package with a main package dependency with the same - // name and publisher as the actual main packages added to the bundle - ThrowErrorIfNot(Error::AppxManifestSemanticError, hasMoreMainPackageDependencies, "The Appx package's manifest is invalid."); - ComPtr mainPackage; - ThrowHrIfFailed(mainPackageDependencies->GetCurrent(&mainPackage)); - - auto mainPackageInternal = mainPackage.As(); - - if(this->mainPackageName.empty()) - { - this->mainPackageName = mainPackageInternal->GetName(); - this->mainPackagePublisher = mainPackageInternal->GetPublisher(); - } - else - { - std::string packageName = mainPackageInternal->GetName(); - if(packageName.compare(this->mainPackageName) != 0) - { - ThrowErrorAndLog(Error::AppxManifestSemanticError, "The Appx package's manifest is invalid."); - } - - std::string publisher = mainPackageInternal->GetPublisher(); - if(publisher.compare(this->mainPackagePublisher) != 0) - { - ThrowErrorAndLog(Error::AppxManifestSemanticError, "The Appx package's manifest is invalid."); - } - } - - // Validation: this cannot be an optional package with multiple main package dependencies - ThrowHrIfFailed(mainPackageDependencies->MoveNext(&hasMoreMainPackageDependencies)); - ThrowErrorIf(Error::AppxManifestSemanticError, hasMoreMainPackageDependencies, "The Appx package's manifest is invalid."); - - // Populate a new PackageInfo entry with info from the optional package's manifest - ComPtr packageId1; - ThrowHrIfFailed(manifestReader->GetPackageId(&packageId1)); - - auto packageId = packageId1.As(); - - std::string bundleFamilyName = packageId->GetPackageFamilyName(); - std::map::iterator optBundlesIterator = optionalBundles.find(bundleFamilyName); - if(optBundlesIterator == optionalBundles.end()) - { - OptionalBundleInfo newBundleInfo; - newBundleInfo.name = packageId->GetName(); - newBundleInfo.publisher = packageId->GetPublisher(); - newBundleInfo.version = 0; - - optionalBundles.insert(std::pair(bundleFamilyName, newBundleInfo)); - optBundlesIterator = optionalBundles.find(bundleFamilyName); - } - - PackageInfo packageInfo; - packageInfo.type = this->m_validationHelper.GetPayloadPackageType(manifestReader, fileName); - this->m_validationHelper.AddPackage(packageInfo.type, packageId.Get(), fileName); - if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) - { - this->m_validationHelper.ValidateApplicationElement(manifestReader, fileName); - } - - packageInfo.architecture = packageId->GetArchitecture(); - UINT64 version; - ThrowHrIfFailed(packageId1->GetVersion(&version)); - packageInfo.version = version; - packageInfo.resourceId = packageId->GetResourceId(); - - ComPtr manifestReader2; - ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader2))); - - packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; - ThrowHrIfFailed(manifestReader2->GetQualifiedResources(&packageInfo.resources)); - packageInfo.fileName = fileName; - packageInfo.offset = 0; - packageInfo.size = 0; - - ComPtr manifestReader3; - ThrowHrIfFailed(manifestReader2->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); - manifestReader3->GetTargetDeviceFamilies(&packageInfo.tdfs); - - AddPackageInfoToVector(optBundlesIterator->second.optionalPackages, packageInfo); - } - - void BundleWriterHelper::EndBundleManifest() - { - // A bundle must contain at least one app package. It's an error to Close - // the writer without having added one. - bool result = this->m_validationHelper.ContainsApplicationPackage(); - if (!result) - { - ThrowErrorAndLog(Error::AppxManifestSemanticError, "The bundle must contain at least one app package targeting a known processor architecture."); - } - - std::string targetXmlNamespace = "http://schemas.microsoft.com/appx/2013/bundle"; - bool isPre2018BundleManifest = true; - - // Only use new 2018 bundle schema if the bundle contains more than 1 neutral app packages - if (this->m_validationHelper.ContainsMultipleNeutralAppPackages()) - { - targetXmlNamespace = "http://schemas.microsoft.com/appx/2018/bundle"; - isPre2018BundleManifest = false; - } - else if (this->hasDefaultOrNeutralResources) - { - targetXmlNamespace = "http://schemas.microsoft.com/appx/2017/bundle"; - } - else if ((this->optionalBundles.size() > 0) || this->hasExternalPackages) - { - targetXmlNamespace = "http://schemas.microsoft.com/appx/2016/bundle"; - } - - this->m_validationHelper.ValidateContainsMultipleNeutralAppPackages(isPre2018BundleManifest); - m_bundleManifestWriter.StartBundleManifest(targetXmlNamespace, this->mainPackageName, - this->mainPackagePublisher, this->bundleVersion); - - for(std::size_t i = 0; i < this->payloadPackages.size(); i++) - { - m_bundleManifestWriter.AddPackage(payloadPackages[i]); - } - - std::map::iterator optionalBundleIterator = optionalBundles.begin(); - while(optionalBundleIterator != optionalBundles.end()) - { - m_bundleManifestWriter.AddOptionalBundle(optionalBundleIterator->second); - optionalBundleIterator++; - } - - m_bundleManifestWriter.EndBundleManifest(); - } +#include "BundleWriterHelper.hpp" + +namespace MSIX { + + BundleWriterHelper::BundleWriterHelper() + { + this->bundleVersion = 0; + this->hasExternalPackages = false; + this->hasDefaultOrNeutralResources = false; + } + + std::uint64_t BundleWriterHelper::GetStreamSize(IStream* stream) + { + STATSTG stat; + ThrowHrIfFailed(stream->Stat(&stat, 1)); + + return stat.cbSize.QuadPart; + } + + void BundleWriterHelper::AddPackage(std::string fileName, IAppxPackageReader* packageReader, + std::uint64_t bundleOffset, std::uint64_t packageSize, bool isDefaultApplicableResource) + { + ComPtr packageId; + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; + ComPtr resources; + ComPtr tdfs; + + GetValidatedPackageData(fileName, packageReader, &packageType, &packageId, &resources, &tdfs); + + AddValidatedPackageData(fileName, bundleOffset, packageSize, packageType, packageId, + isDefaultApplicableResource, resources.Get(), tdfs.Get()); + } + + void BundleWriterHelper::GetValidatedPackageData( + std::string fileName, + IAppxPackageReader* packageReader, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE* packageType, + IAppxManifestPackageId** packageId, + IAppxManifestQualifiedResourcesEnumerator** resources, + IAppxManifestTargetDeviceFamiliesEnumerator** tdfs) + { + *packageId = nullptr; + *resources = nullptr; + *tdfs = nullptr; + + ComPtr loadedPackageId; + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE loadedPackageType = APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE::APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION; + ComPtr loadedResources; + ComPtr loadedTdfs; + + ComPtr manifestReader; + ThrowHrIfFailed(packageReader->GetManifest(&manifestReader)); + ThrowHrIfFailed(manifestReader->GetPackageId(&loadedPackageId)); + + ComPtr manifestReader3; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); + ThrowHrIfFailed(manifestReader3->GetQualifiedResources(&loadedResources)); + ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&loadedTdfs)); + + auto packageIdInternal = loadedPackageId.As(); + + loadedPackageType = this->m_validationHelper.GetPayloadPackageType(manifestReader.Get(), fileName); + this->m_validationHelper.AddPackage(loadedPackageType, packageIdInternal.Get(), fileName); + this->m_validationHelper.ValidateOSVersion(manifestReader.Get(), fileName); + + ValidateNameAndPublisher(packageIdInternal.Get(), fileName); + + if (loadedPackageType == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + this->m_validationHelper.ValidateApplicationElement(manifestReader.Get(), fileName); + if (loadedTdfs.Get() != nullptr) + { + ComPtr tdfCopy; + ThrowHrIfFailed(manifestReader3->GetTargetDeviceFamilies(&tdfCopy)); + this->m_validationHelper.ValidateTargetDeviceFamiliesFromManifestPackageId(packageIdInternal.Get(), tdfCopy.Get(), fileName); + } + } + + *packageType = loadedPackageType; + *packageId = loadedPackageId.Detach(); + *resources = loadedResources.Detach(); + + if (loadedTdfs.Get() != nullptr) + { + *tdfs = loadedTdfs.Detach(); + } + } + + void BundleWriterHelper::ValidateNameAndPublisher(IAppxManifestPackageIdInternal* packageId, + std::string filename) + { + if(this->mainPackageName.empty()) + { + this->mainPackageName = packageId->GetName(); + this->mainPackagePublisher = packageId->GetPublisher(); + } + else + { + std::string packageName = packageId->GetName(); + + if ((this->mainPackageName.compare(packageName)) != 0) + { + std::string packageFullName = packageId->GetPackageFullName(); + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); + } + + std::string publisherName = packageId->GetPublisher(); + if ((this->mainPackagePublisher.compare(publisherName)) != 0) + { + std::string packageFullName = packageId->GetPackageFullName(); + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The package is not valid in the bundle because it has a different package family name than other packages in the bundle."); + } + } + } + + void BundleWriterHelper::AddValidatedPackageData( + std::string fileName, + std::uint64_t bundleOffset, + std::uint64_t packageSize, + APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE packageType, + ComPtr packageId, + bool isDefaultApplicablePackage, + IAppxManifestQualifiedResourcesEnumerator* resources, + IAppxManifestTargetDeviceFamiliesEnumerator* tdfs) + { + auto innerPackageIdInternal = packageId.As(); + + PackageInfo packageInfo; + packageInfo.type = packageType; + packageInfo.architecture = innerPackageIdInternal->GetArchitecture(); + UINT64 version; + ThrowHrIfFailed(packageId->GetVersion(&version)); + packageInfo.version = version; + packageInfo.resourceId = innerPackageIdInternal->GetResourceId(); + packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; + packageInfo.resources = resources; + packageInfo.fileName = fileName; + packageInfo.size = packageSize; + packageInfo.offset = bundleOffset; + packageInfo.tdfs = tdfs; + + AddPackageInfoToVector(this->payloadPackages, packageInfo); + } + + void BundleWriterHelper::AddPackageInfoToVector(std::vector& packagesVector, + PackageInfo packageInfo) + { + packagesVector.push_back(packageInfo); + + if (packageInfo.offset == 0 && packageInfo.size == 0) + { + this->hasExternalPackages = true; + } + + if (packageInfo.isDefaultApplicablePackage) + { + this->hasDefaultOrNeutralResources = true; + } + + BOOL hasResources = FALSE; + ThrowHrIfFailed(packageInfo.resources->GetHasCurrent(&hasResources)); + if (!hasResources) + { + this->hasDefaultOrNeutralResources = true; + } + } + + void BundleWriterHelper::AddExternalPackageReferenceFromManifest(std::string fileName, IAppxManifestReader* manifestReader, + bool isDefaultApplicablePackage) + { + ComPtr manifestReader5; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader5))); + ComPtr mainPackageDependencies; + ThrowHrIfFailed(manifestReader5->GetMainPackageDependencies(&mainPackageDependencies)); + BOOL hasMoreMainPackageDependencies = FALSE; + ThrowHrIfFailed(mainPackageDependencies->GetHasCurrent(&hasMoreMainPackageDependencies)); + + // Validation: this must be an optional package with a main package dependency with the same + // name and publisher as the actual main packages added to the bundle + ThrowErrorIfNot(Error::AppxManifestSemanticError, hasMoreMainPackageDependencies, "The Appx package's manifest is invalid."); + ComPtr mainPackage; + ThrowHrIfFailed(mainPackageDependencies->GetCurrent(&mainPackage)); + + auto mainPackageInternal = mainPackage.As(); + + if(this->mainPackageName.empty()) + { + this->mainPackageName = mainPackageInternal->GetName(); + this->mainPackagePublisher = mainPackageInternal->GetPublisher(); + } + else + { + std::string packageName = mainPackageInternal->GetName(); + if(packageName.compare(this->mainPackageName) != 0) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The Appx package's manifest is invalid."); + } + + std::string publisher = mainPackageInternal->GetPublisher(); + if(publisher.compare(this->mainPackagePublisher) != 0) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The Appx package's manifest is invalid."); + } + } + + // Validation: this cannot be an optional package with multiple main package dependencies + ThrowHrIfFailed(mainPackageDependencies->MoveNext(&hasMoreMainPackageDependencies)); + ThrowErrorIf(Error::AppxManifestSemanticError, hasMoreMainPackageDependencies, "The Appx package's manifest is invalid."); + + // Populate a new PackageInfo entry with info from the optional package's manifest + ComPtr packageId1; + ThrowHrIfFailed(manifestReader->GetPackageId(&packageId1)); + + auto packageId = packageId1.As(); + + std::string bundleFamilyName = packageId->GetPackageFamilyName(); + std::map::iterator optBundlesIterator = optionalBundles.find(bundleFamilyName); + if(optBundlesIterator == optionalBundles.end()) + { + OptionalBundleInfo newBundleInfo; + newBundleInfo.name = packageId->GetName(); + newBundleInfo.publisher = packageId->GetPublisher(); + newBundleInfo.version = 0; + + optionalBundles.insert(std::pair(bundleFamilyName, newBundleInfo)); + optBundlesIterator = optionalBundles.find(bundleFamilyName); + } + + PackageInfo packageInfo; + packageInfo.type = this->m_validationHelper.GetPayloadPackageType(manifestReader, fileName); + this->m_validationHelper.AddPackage(packageInfo.type, packageId.Get(), fileName); + if (packageInfo.type == APPX_BUNDLE_PAYLOAD_PACKAGE_TYPE_APPLICATION) + { + this->m_validationHelper.ValidateApplicationElement(manifestReader, fileName); + } + + packageInfo.architecture = packageId->GetArchitecture(); + UINT64 version; + ThrowHrIfFailed(packageId1->GetVersion(&version)); + packageInfo.version = version; + packageInfo.resourceId = packageId->GetResourceId(); + + ComPtr manifestReader2; + ThrowHrIfFailed(manifestReader->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader2))); + + packageInfo.isDefaultApplicablePackage = isDefaultApplicablePackage; + ThrowHrIfFailed(manifestReader2->GetQualifiedResources(&packageInfo.resources)); + packageInfo.fileName = fileName; + packageInfo.offset = 0; + packageInfo.size = 0; + + ComPtr manifestReader3; + ThrowHrIfFailed(manifestReader2->QueryInterface(UuidOfImpl::iid, reinterpret_cast(&manifestReader3))); + manifestReader3->GetTargetDeviceFamilies(&packageInfo.tdfs); + + AddPackageInfoToVector(optBundlesIterator->second.optionalPackages, packageInfo); + } + + void BundleWriterHelper::EndBundleManifest() + { + // A bundle must contain at least one app package. It's an error to Close + // the writer without having added one. + bool result = this->m_validationHelper.ContainsApplicationPackage(); + if (!result) + { + ThrowErrorAndLog(Error::AppxManifestSemanticError, "The bundle must contain at least one app package targeting a known processor architecture."); + } + + std::string targetXmlNamespace = "http://schemas.microsoft.com/appx/2013/bundle"; + bool isPre2018BundleManifest = true; + + // Only use new 2018 bundle schema if the bundle contains more than 1 neutral app packages + if (this->m_validationHelper.ContainsMultipleNeutralAppPackages()) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2018/bundle"; + isPre2018BundleManifest = false; + } + else if (this->hasDefaultOrNeutralResources) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2017/bundle"; + } + else if ((this->optionalBundles.size() > 0) || this->hasExternalPackages) + { + targetXmlNamespace = "http://schemas.microsoft.com/appx/2016/bundle"; + } + + this->m_validationHelper.ValidateContainsMultipleNeutralAppPackages(isPre2018BundleManifest); + m_bundleManifestWriter.StartBundleManifest(targetXmlNamespace, this->mainPackageName, + this->mainPackagePublisher, this->bundleVersion); + + for(std::size_t i = 0; i < this->payloadPackages.size(); i++) + { + m_bundleManifestWriter.AddPackage(payloadPackages[i]); + } + + std::map::iterator optionalBundleIterator = optionalBundles.begin(); + while(optionalBundleIterator != optionalBundles.end()) + { + m_bundleManifestWriter.AddOptionalBundle(optionalBundleIterator->second); + optionalBundleIterator++; + } + + m_bundleManifestWriter.EndBundleManifest(); + } } \ No newline at end of file