Merge pull request #191 from microsoft/packaging
This commit is contained in:
Коммит
bfaf56ac40
|
@ -1,2 +1,11 @@
|
|||
preview/MsixCore/Tests/*.msix filter=lfs diff=lfs merge=lfs -text
|
||||
preview/MsixCore/Tests/*.appx filter=lfs diff=lfs merge=lfs -text
|
||||
|
||||
# Mark third party code as vendored
|
||||
lib/openssl/* linguist-vendored
|
||||
lib/xerces/* linguist-vendored
|
||||
lib/zlib/* linguist-vendored
|
||||
lib/catch2/* linguist-vendored
|
||||
|
||||
# Test xml should have LF line endings on checkout
|
||||
src/test/testData/pack/input/AppxManifest.xml text eol=lf
|
||||
|
|
|
@ -399,6 +399,7 @@ lib/zlib
|
|||
# Files generated by CMake
|
||||
src/inc/MSIXResource.hpp
|
||||
src/msix/MSIXResource.cpp
|
||||
src/msix/common/MSIXResource.cpp
|
||||
|
||||
.vscode/
|
||||
|
||||
|
|
131
CMakeLists.txt
131
CMakeLists.txt
|
@ -21,28 +21,20 @@ if(POLICY CMP0077)
|
|||
cmake_policy(SET CMP0077 NEW)
|
||||
endif()
|
||||
|
||||
# Set build options
|
||||
option(WIN32 "Build for Win32" OFF)
|
||||
option(MACOS "Build for MacOS" OFF)
|
||||
option(IOS "Build for iOS" OFF)
|
||||
option(AOSP "Build for Android" OFF)
|
||||
option(LINUX "Build for Linux" OFF)
|
||||
|
||||
option(USE_VALIDATION_PARSER "Turn on to validates using the resouce schemas. Default (OFF) validates XML files are just valid XML" OFF)
|
||||
option(USE_SHARED_ZLIB "Choose the type of dependency for zlib, Use the -DUSE_SHARED_ZLIB=on to have a shared dependency. Default is 'off' (static)" OFF)
|
||||
option(USE_STATIC_MSVC "Windows only. Pass /MT as a compiler flag to use the staic version of the run-time library. Default is 'off' (dynamic)" OFF)
|
||||
option(SKIP_BUNDLES "Removes bundle functionality from the MSIX SDK. Default is 'off'" OFF)
|
||||
|
||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel. Use the -DCMAKE_BUILD_TYPE=[option] to specify.")
|
||||
set(XML_PARSER "" CACHE STRING "Choose the type of parser, options are: [xerces, msxml6, javaxml]. Use the -DXML_PARSER=[option] to specify.")
|
||||
set(CRYPTO_LIB "" CACHE STRING "Choose the cryptography library to use, options are: [openssl, crypt32]. Use the -DCRYPTO_LIB=[option] to specify.")
|
||||
|
||||
# Default version is 0.0.0
|
||||
set(VERSION_MAJOR "0")
|
||||
set(VERSION_MINOR "0")
|
||||
set(VERSION_PATCH "0")
|
||||
set(GIT_BRANCH_NAME "master")
|
||||
|
||||
# CMake useful variables
|
||||
set(MSIX_PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(MSIX_BINARY_ROOT ${CMAKE_CURRENT_BINARY_DIR})
|
||||
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
|
||||
|
||||
## Git (and its revision)
|
||||
find_package(Git) # QUIET) # if we don't find git or FindGit.cmake is not on the system we ignore it.
|
||||
|
||||
|
@ -91,6 +83,8 @@ set(MSIX_VERSION ${VERSION_MAJOR}.${VERSION_MINOR}.${VERSION_PATCH})
|
|||
message(STATUS "MSIX Packaging SDK version ${MSIX_VERSION}")
|
||||
message(STATUS "MSIX Packaging SDK branch name ${GIT_BRANCH_NAME}")
|
||||
add_definitions(-DSDK_VERSION="${MSIX_VERSION}")
|
||||
# Validates CMake options.
|
||||
include(msix_options)
|
||||
|
||||
# Configure Package.nuspec
|
||||
if(WIN32)
|
||||
|
@ -101,22 +95,20 @@ elseif(IOS)
|
|||
set(MSIX_NUGET_NAME "Microsoft.MSIX.Packaging.iOS")
|
||||
elseif(AOSP)
|
||||
set(MSIX_NUGET_NAME "Microsoft.MSIX.Packaging.AOSP")
|
||||
elseif(LINUX)
|
||||
set(MSIX_NUGET_NAME "Microsoft.MSIX.Packaging.Linux")
|
||||
else()
|
||||
set(MSIX_NUGET_NAME "Microsoft.MSIX.Packaging") # cmake ..
|
||||
set(MSIX_NUGET_NAME "Microsoft.MSIX.Packaging.Linux")
|
||||
endif()
|
||||
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Package.nuspec.cmakein ${CMAKE_CURRENT_BINARY_DIR}/Package.nuspec CRLF)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/Microsoft.MSIX.Packaging.targets ${CMAKE_BINARY_DIR}/build/native/${MSIX_NUGET_NAME}.targets)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/Package.nuspec.cmakein ${MSIX_BINARY_ROOT}/Package.nuspec CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/Microsoft.MSIX.Packaging.targets ${MSIX_BINARY_ROOT}/build/native/${MSIX_NUGET_NAME}.targets)
|
||||
message(STATUS "Package.Nuspec created")
|
||||
message(STATUS "--------------------------------")
|
||||
|
||||
# Configure license txt
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/build/LICENSE)
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/THIRD\ PARTY\ CODE\ NOTICE ${CMAKE_BINARY_DIR}/build/THIRD\ PARTY\ CODE\ NOTICE)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/LICENSE ${MSIX_BINARY_ROOT}/build/LICENSE)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/THIRD\ PARTY\ CODE\ NOTICE ${MSIX_BINARY_ROOT}/build/THIRD\ PARTY\ CODE\ NOTICE)
|
||||
message(STATUS "LICENSE created")
|
||||
message(STATUS "--------------------------------")
|
||||
message(STATUS "--------------------------------")
|
||||
|
||||
# Configure certificates
|
||||
# list each certificate by name that is to be published in the nuget package
|
||||
|
@ -129,71 +121,10 @@ list(APPEND CERTS_TO_PUBLISH
|
|||
Microsoft_MarketPlace_PCA_2011.cer
|
||||
)
|
||||
foreach(CERT_TO_PUBLISH ${CERTS_TO_PUBLISH})
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/resources/certs/${CERT_TO_PUBLISH} ${CMAKE_BINARY_DIR}/build/certs/${CERT_TO_PUBLISH})
|
||||
configure_file(${MSIX_PROJECT_ROOT}/resources/certs/${CERT_TO_PUBLISH} ${MSIX_BINARY_ROOT}/build/certs/${CERT_TO_PUBLISH})
|
||||
endforeach()
|
||||
message(STATUS "Certificates published")
|
||||
message(STATUS "--------------------------------")
|
||||
|
||||
# Enforce that target platform is specified.
|
||||
if((NOT WIN32) AND (NOT MACOS) AND (NOT IOS) AND (NOT AOSP) AND (NOT LINUX))
|
||||
message(STATUS "You must specify one of: [WIN32|MACOS|IOS|AOSP|LINUX]" )
|
||||
message(STATUS "For example, use cmake -DWIN32=on .." )
|
||||
return()
|
||||
else()
|
||||
if(WIN32)
|
||||
message(STATUS "Building for WIN32")
|
||||
if(USE_STATIC_MSVC)
|
||||
# By default these flags have /MD set. Modified it to use /MT instead.
|
||||
foreach(buildType RELEASE MINSIZEREL RELWITHDEBINFO)
|
||||
set(cxxFlag "CMAKE_CXX_FLAGS_${buildType}")
|
||||
string(REPLACE "/MD" "/MT" ${cxxFlag} "${${cxxFlag}}")
|
||||
endforeach()
|
||||
set(cxxFlagDebug "CMAKE_CXX_FLAGS_DEBUG")
|
||||
string(REPLACE "/MDd" "/MTd" ${cxxFlagDebug} "${${cxxFlagDebug}}")
|
||||
endif()
|
||||
endif()
|
||||
if(MACOS)
|
||||
message(STATUS "Building for MacOS")
|
||||
endif()
|
||||
if(IOS)
|
||||
message(STATUS "Building for iOS")
|
||||
if(IOS_DEPLOYMENT_TARGET VERSION_LESS 10.0)
|
||||
message(FATAL_ERROR "Unsupported iOS version: ${IOS_DEPLOYMENT_TARGET}, this project requires at least iOS version 10.0")
|
||||
endif()
|
||||
set(PLATFORM_APPLE 1)
|
||||
endif()
|
||||
if(AOSP)
|
||||
message(STATUS "Building for Android")
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
if(LINUX)
|
||||
message(STATUS "Building for Linux")
|
||||
# Static libraries must be position independent to be linked with a shared object.
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT XML_PARSER)
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx(msxml6.h HAVE_MSXML6)
|
||||
if(HAVE_MSXML6)
|
||||
set(XML_PARSER msxml6 CACHE STRING "Using XML Parser: msxml6" FORCE)
|
||||
elseif (AOSP)
|
||||
set(XML_PARSER javaxml CACHE STRING "Using XML Parser: javaxml" FORCE)
|
||||
else()
|
||||
set(XML_PARSER xerces CACHE STRING "Using XML Parser: xerces" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT CRYPTO_LIB)
|
||||
include(CheckIncludeFileCXX)
|
||||
check_include_file_cxx(wincrypt.h HAVE_CRYPT32)
|
||||
if(HAVE_CRYPT32)
|
||||
set(CRYPTO_LIB crypt32 CACHE STRING "Using Crypto Lib: crypt32" FORCE)
|
||||
else()
|
||||
set(CRYPTO_LIB openssl CACHE STRING "Using Crypto Lib: openssl" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
message(STATUS "--------------------------------")
|
||||
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
|
@ -232,20 +163,18 @@ if((MACOS) OR (IOS))
|
|||
set(CMAKE_CXX_CREATE_SHARED_LIBRARY
|
||||
"${CMAKE_CXX_CREATE_SHARED_LIBRARY}"
|
||||
"${DSYMUTIL_PROGRAM} <TARGET>")
|
||||
endif ()
|
||||
endif()
|
||||
endif()
|
||||
if(IOS)
|
||||
if(IOS_DEPLOYMENT_TARGET VERSION_LESS 10.0)
|
||||
message(FATAL_ERROR "Unsupported iOS version: ${IOS_DEPLOYMENT_TARGET}, this project requires at least iOS version 10.0")
|
||||
endif()
|
||||
endif()
|
||||
elseif(AOSP OR LINUX)
|
||||
# Static libraries must be position independent to be linked with a shared object.
|
||||
set(CMAKE_POSITION_INDEPENDENT_CODE ON)
|
||||
endif()
|
||||
|
||||
# CMake useful variables
|
||||
set(CMAKE_PROJECT_ROOT ${CMAKE_CURRENT_SOURCE_DIR})
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/bin")
|
||||
set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
|
||||
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/lib")
|
||||
|
||||
# Create a bin directory for samples and msixtest to be self contained
|
||||
set(MSIX_SAMPLE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/samples")
|
||||
set(MSIX_TEST_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}/msixtest")
|
||||
|
||||
# Mac needed variables
|
||||
# [TODO: adapt as needed]
|
||||
set(CMAKE_MACOSX_RPATH ON)
|
||||
|
@ -255,15 +184,9 @@ set(CMAKE_MACOSX_RPATH ON)
|
|||
#set(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE)
|
||||
|
||||
add_subdirectory(lib)
|
||||
message(STATUS " ")
|
||||
message(STATUS "--------------------------------")
|
||||
message(STATUS "MSIX Packaging SDK")
|
||||
message(STATUS "--------------------------------")
|
||||
message(STATUS "libs processed")
|
||||
add_subdirectory(src)
|
||||
message(STATUS "src processed")
|
||||
ADD_DEPENDENCIES(SRC LIBS)
|
||||
message(STATUS "dependencies added")
|
||||
add_subdirectory(sample)
|
||||
message(STATUS "sample processed")
|
||||
message(STATUS "DONE!")
|
||||
|
|
107
README.md
107
README.md
|
@ -2,24 +2,24 @@
|
|||
Copyright (c) 2019 Microsoft Corp. All rights reserved.
|
||||
|
||||
## Description
|
||||
The MSIX SDK project is an effort to enable developers on a variety of platforms to unpack
|
||||
packages for the purposes of distribution from either the Microsoft Store, or their own content distribution networks.
|
||||
The MSIX SDK project is an effort to enable developers on a variety of platforms to pack and unpack
|
||||
packages for the purposes of distribution from either the Microsoft Store, or their own content distribution networks.
|
||||
|
||||
The MSIX Packaging APIs that a client app would use to interact with .msix/.appx packages are a subset of those
|
||||
documented [here](https://msdn.microsoft.com/en-us/library/windows/desktop/hh446766(v=vs.85).aspx).
|
||||
|
||||
## Overview
|
||||
The MSIX SDK project includes cross platform API support for unpacking of .msix/.appx packages
|
||||
The MSIX SDK project includes cross platform API support for packing and unpacking of .msix/.appx packages
|
||||
|
||||
| | |
|
||||
|--------------------------------------|---------------------------------|
|
||||
| **msix** | A shared library (DLL on Win32, dylib on macOS, SO on Linux and Android) that exports a subset of the functionality contained within appxpackaging.dll on Windows. See [here](https://msdn.microsoft.com/en-us/library/windows/desktop/hh446766(v=vs.85).aspx) for additional details.<br />On all platforms instead of CoCreating IAppxFactory, a C-style export: CoCreateAppxFactory is provided. Similarly, the CoCreateAppxBundleFactory export is equivalent as CoCreating IAppxBundleFactory.<br /><br /> The 'UnpackPackage' and 'UnpackBundle' exports that provide a simplified unpackage implementation. See the [samples directory](sample) for usage of the SDK.|
|
||||
| **makemsix** | A command line wrapper over the UnpackPackage and UnpackBundle implementations. This tool exists primarily as a means of validating the implementation of the MSIX SDK internal routines and is compiled for Win32, macOS, and Linux platforms.|
|
||||
| **msix** | A shared library (DLL on Win32, dylib on macOS, SO on Linux and Android) that exports a subset of the functionality contained within appxpackaging.dll on Windows. See [here](https://msdn.microsoft.com/en-us/library/windows/desktop/hh446766(v=vs.85).aspx) for additional details.<br />On all platforms instead of CoCreating IAppxFactory, a C-style export: CoCreateAppxFactory is provided. Similarly, the CoCreateAppxBundleFactory export is equivalent as CoCreating IAppxBundleFactory.<br /><br /> The 'UnpackPackage' and 'UnpackBundle' exports that provide a simplified unpackage implementation. Similarly, PackPackage provides a simplified package implementation. See the [samples directory](sample) for usage of the SDK.|
|
||||
| **makemsix** | A command line wrapper over the MSIX library entrypoints. makemsix supports pack and unpack. Use the -? to get information about the options supported.|
|
||||
|
||||
Guidance on how to package your app contents and construct your app manifest such that it can take advantage of the cross platform support of this SDK is [here](tdf-guidance.md).
|
||||
|
||||
## Release Notes
|
||||
Release notes on the latest features and performance improvements made to the SDK are listed [here](https://docs.microsoft.com/en-us/windows/msix/msix-sdk/release-notes/sdk-release-notes-1.6)
|
||||
Release notes on the latest features and performance improvements made to the SDK are listed [here](https://docs.microsoft.com/en-us/windows/msix/msix-sdk/release-notes/sdk-release-notes-1.7)
|
||||
|
||||
## Setup Instructions
|
||||
1. Clone the repository:
|
||||
|
@ -41,8 +41,7 @@ more of the following dependencies may be statically linked into the binary:
|
|||
* [OpenSSL Tag OpenSSL_1_0_2q Commit 5707219a6aae8052cb98aa361d115be01b8fd894](https://github.com/openssl/openssl/releases/tag/OpenSSL_1_0_2q)
|
||||
* [Android NDK](https://developer.android.com/ndk)
|
||||
|
||||
For convinience, Zlib, Xerces-C and OpenSSL are git-subtrees that are mapped in under the lib folder of this project. Edits on top
|
||||
of these subtrees for build related optimizations are tracked within this repository. OpenSSL is only used on non-Windows platforms
|
||||
For convinience, Zlib, Xerces-C and OpenSSL are git-subtrees that are mapped in under the lib folder of this project. Edits on top of these subtrees for build related optimizations are tracked within this repository.
|
||||
|
||||
The Android NDK is only required for targeting the Android platform.
|
||||
|
||||
|
@ -84,22 +83,22 @@ See [cmake-Xcode-integration](https://www.johnlamp.net/cmake-tutorial-2-ide-inte
|
|||
## Build
|
||||
### On Windows using Visual Studio nmake:
|
||||
```
|
||||
makewin.cmd <x86|x64> -mt
|
||||
|
||||
This will start MSVC environment calling vcvarsall.bat <arch>, clean the output directory, call cmake and nmake. The latest Visual Studio version is obtained by calling vswhere.exe
|
||||
makewin.cmd <x86|x64> [options]
|
||||
```
|
||||
This will start MSVC environment calling vcvarsall.bat <arch>, clean the output directory, call cmake and nmake. The latest Visual Studio version is obtained by calling vswhere.exe
|
||||
|
||||
|
||||
### On Mac using make:
|
||||
```
|
||||
./makemac
|
||||
./makeios
|
||||
./makemac [options]
|
||||
./makeios [options]
|
||||
```
|
||||
|
||||
### On Linux using make:
|
||||
```
|
||||
./makelinux
|
||||
./makeaosp
|
||||
```
|
||||
./makelinux [options]
|
||||
./makeaosp [options]
|
||||
```
|
||||
|
||||
### How to compile for Android on Windows:
|
||||
|
||||
|
@ -123,36 +122,50 @@ To compile, run the following command from the android folder:
|
|||
ninja
|
||||
```
|
||||
|
||||
### Enable pack features
|
||||
|
||||
By default, pack is *NOT* turn on in the build scripts and is not supported for mobile devices. Use the --pack option in the build scripts or pass -DMSIX_PACK=on to the CMake command to enable it.
|
||||
|
||||
## Build Status
|
||||
The following native platforms are in development now:
|
||||
|
||||
### Windows
|
||||
||master|
|
||||
|---|---|
|
||||
**Debug x32**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20debug_32)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Debug x64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20debug_64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20release_32)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20release_64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32 Validation Parser**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20release_32_validation_parser)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64 Validation Parser**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20release_64_validation_parser)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32 Xerces**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20release_32_xerces)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64 Xerces**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&configuration=Windows%20release_64_xerces)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Debug x32**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20debug_32_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Debug x64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20debug_64_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_32_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_64_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32 Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_32_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64 Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_64_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32 With Validation Parser**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_32_validation_parser)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64 With Validation Parser**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_64_validation_parser)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Debug x32 With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20debug_32_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Debug x64 With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20debug_64_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32 With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_32_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64 With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_64_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x32 Xerces With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_32_xerces)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
**Release x64 Xerces With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Windows%20CI?branchName=master&jobName=Windows&configuration=Windows%20release_64_xerces)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=64&branchName=master)|
|
||||
|
||||
Built in the Azure Pipelines Hosted VS2017 pool. See specifications [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/win/Vs2017-Server2016-Readme.md)
|
||||
|
||||
### macOS
|
||||
||master|
|
||||
|---|---|
|
||||
**Debug**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20debug)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
**Release**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
**Debug**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20debug_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
**Release**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
**Release Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
**Debug With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20debug_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
**Release With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20macOS%20CI?branchName=master&jobName=macOS&configuration=macOS%20release_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=69&branchName=master)|
|
||||
|
||||
Built in the Azure Pipelines macOS pool. See specification [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/macos/macos-10.14-Readme.md)
|
||||
|
||||
### iOS
|
||||
||master|
|
||||
|---|---|
|
||||
**Debug emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20iOS%20CI?branchName=master&jobName=iOS&configuration=iOS%20debug_x86)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=74&branchName=master)|
|
||||
**Release emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20iOS%20CI?branchName=master&jobName=iOS&configuration=iOS%20release_x86)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=74&branchName=master)|
|
||||
**Debug Emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20iOS%20CI?branchName=master&jobName=iOS&configuration=iOS%20debug_x86)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=74&branchName=master)|
|
||||
**Release Emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20iOS%20CI?branchName=master&jobName=iOS&configuration=iOS%20release_x86)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=74&branchName=master)|
|
||||
**Release Emulator Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20iOS%20CI?branchName=master&jobName=iOS&configuration=iOS%20release_x86_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=74&branchName=master)
|
||||
**Release arm64**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20iOS%20CI?branchName=master&jobName=iOS&configuration=iOS%20release_arm64)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=74&branchName=master)
|
||||
|
||||
Built in the Azure Pipelines macOS pool. See specification [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/macos/macos-10.14-Readme.md)
|
||||
|
@ -160,24 +173,29 @@ Built in the Azure Pipelines macOS pool. See specification [here](https://github
|
|||
### Android
|
||||
||master|
|
||||
|---|---|
|
||||
**Debug emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20debug_emulator)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
**Release emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20release_emulator)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
**Debug Emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20debug_emulator)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
**Release Emulator**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20release_emulator)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
**Release Emulator Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20release_emulator_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
**Release arm**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20release_arm)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
**Release arm Without Bundle support**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20aosp%20CI?branchName=master&jobName=AOSP&configuration=AOSP%20release_arm_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=76&branchName=master)|
|
||||
|
||||
Built in the Azure Pipelines macOS pool. See specification [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/macos/macos-10.14-Readme.md)
|
||||
|
||||
### Linux
|
||||
||master|
|
||||
|---|---|
|
||||
**Debug**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20debug)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Release**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20release)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Release Validation Parser**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20release_xerces_validation_parser)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Debug**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20debug_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Release**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20release_nopack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Release Without Bundle Support|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20release_nobundle)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Release With Validation Parser**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20release_validation_parser)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Debug With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20debug_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
**Release With Pack**|[![Build Status](https://dev.azure.com/ms/msix-packaging/_apis/build/status/msix-packaging%20Linux%20CI?branchName=master&jobName=Linux&configuration=Linux%20release_pack)](https://dev.azure.com/ms/msix-packaging/_build/latest?definitionId=72&branchName=master)|
|
||||
|
||||
Built in the Azure Pipelines Hosted Ubuntu 1604. See specification [here](https://github.com/Microsoft/azure-pipelines-image-generation/blob/master/images/linux/Ubuntu1604-README.md)
|
||||
|
||||
## Windows 7 support
|
||||
The MSIX SDK is fully supported and tested on Windows 7. However, an Application Manifest **_MUST_** be included to any executable that is expected to run on Windows 7 and uses msix.dll. Specifically, the Application Manifest **_MUST_** include the supportedOS flags for Windows 7. The manifest is not included on msix.dll because the compat manifest doesn't matter on DLLs.
|
||||
See the [manifest](manifest.cmakein) that is used for makemsix and samples of this project as example. The Windows 7 machine might also require the [Microsoft Visual C++ Redistributable](https://www.visualstudio.com/downloads/) binaries installed to run properly.
|
||||
See the [manifest](manifest.cmakein) that is used for makemsix and samples of this project as example. The Windows 7 machine might also require the [Microsoft Visual C++ Redistributable](https://www.visualstudio.com/downloads/) binaries installed to run properly. Alternatively, build msix.dll with makewin.cmd <x86|x64> -mt [options] to use static version of the runtime library and don't require the redistributables.
|
||||
|
||||
## Android support
|
||||
The MSIX SDK minimum supported for Android is API Level 19.
|
||||
|
@ -187,24 +205,19 @@ The default level for the SDK level is 24 because we use the [Configuration clas
|
|||
We recommend using the [makeaosp](makeaosp) script to build for Android on non-Windows devices.
|
||||
|
||||
## Testing
|
||||
Unit tests should be run on builds that have the "Release" or "RelWithDebug" CMAKE switch.
|
||||
msixtest uses Catch2 as testing framework. msixtest is either an executable or a shared library, depending on the platform. It has a single entrypoint msixtest_main that takes argc and argv, as main, plus the path were the test packages are located. The shared library is used for our mobile test apps, while non-mobile just forwards the arguments to msixtest_main. It requires msix.dll to be build with "Release" or "RelWithDebInfo" CMake switch.
|
||||
|
||||
First build the project, then:
|
||||
|
||||
On Windows:
|
||||
From within powershell, navigate to test\Win32, and run ".\Win32.ps1"
|
||||
### Testing for non-mobile devices:
|
||||
Go to the build directory and run msixtes\msixtest.exe. You can run an specific test by running msixtest [test name]. By default, the test will only output the failling tests, use -s to output successfull tests.
|
||||
|
||||
On Mac & Linux:
|
||||
From within bash, navigate to test/MacOS-Linux, and run "./MacOS-Linux-Etc.sh [Apple|Linux]"
|
||||
### Testing on mobile devices:
|
||||
#### iOS
|
||||
First build the project for iOS, then launch xCode and load src/test/mobile/iOSBVT.xcworkspace, compile the test app, and then launch the iPhone simulator. You can also run "testios.sh -p iOSBVT/iOSBVT.xcodeproj" from src/test/mobile.
|
||||
|
||||
Testing on mobile platforms:
|
||||
|
||||
On iOS :
|
||||
First build the project for iOS, then launch xCode and load test/mobile/iOSBVT.xcworkspace, compile the test app,
|
||||
and then launch the iPhone simulator. You can also run "./testios.sh" from test/MacOS-Linux.
|
||||
|
||||
On Android:
|
||||
From within bash, navigate to test/MacOS-Linux, and run "./testaosponmac.sh". The test assumes there's an Android emulator named Nexus_5X_API_19_x86 and the build output is on a .vs directory at the root of the project.
|
||||
#### Android:
|
||||
From within bash, navigate to src/test/mobile, and run "./testaosponmac.sh".
|
||||
|
||||
## Releasing
|
||||
If you are the current maintainer of this project:
|
||||
|
|
|
@ -0,0 +1,117 @@
|
|||
# Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
# See LICENSE file in the project root for full license information.
|
||||
# Validates CMake options for the MSIX SDK.
|
||||
|
||||
option(WIN32 "Build for Win32" OFF)
|
||||
option(MACOS "Build for MacOS" OFF)
|
||||
option(IOS "Build for iOS" OFF)
|
||||
option(AOSP "Build for Android" OFF)
|
||||
option(LINUX "Build for Linux" OFF)
|
||||
|
||||
option(USE_VALIDATION_PARSER "Turn on to validates using the resouce schemas. Default (OFF) validates XML files are just valid XML" OFF)
|
||||
option(USE_SHARED_ZLIB "Choose the type of dependency for zlib, Use the -DUSE_SHARED_ZLIB=on to have a shared dependency. Default is 'off' (static)" OFF)
|
||||
option(USE_STATIC_MSVC "Windows only. Pass /MT as a compiler flag to use the staic version of the run-time library. Default is 'off' (dynamic)" OFF)
|
||||
option(SKIP_BUNDLES "Removes bundle functionality from the MSIX SDK. Default is 'off'" OFF)
|
||||
option(MSIX_PACK "Include packaging features for the MSIX SDK. Not supported for mobile. Default is 'off'" OFF)
|
||||
option(USE_MSIX_SDK_ZLIB "Use zlib implementation under lib/zlib. If off, uses inbox compression library. For Windows and Linux this is no-opt." OFF)
|
||||
|
||||
set(CMAKE_BUILD_TYPE Debug CACHE STRING "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel. Use the -DCMAKE_BUILD_TYPE=[option] to specify.")
|
||||
set(XML_PARSER "" CACHE STRING "Choose the type of parser, options are: [xerces, msxml6, javaxml]. Use the -DXML_PARSER=[option] to specify.")
|
||||
set(CRYPTO_LIB "" CACHE STRING "Choose the cryptography library to use, options are: [openssl, crypt32]. Use the -DCRYPTO_LIB=[option] to specify.")
|
||||
|
||||
# Enforce that target platform is specified.
|
||||
if((NOT WIN32) AND (NOT MACOS) AND (NOT IOS) AND (NOT AOSP) AND (NOT LINUX))
|
||||
message(FATAL_ERROR "You must specify one of: -D[WIN32|MACOS|IOS|AOSP|LINUX]=on")
|
||||
endif()
|
||||
|
||||
if(USE_STATIC_MSVC)
|
||||
if(NOT WIN32)
|
||||
message(FATAL_ERROR "-DUSE_STATIC_MSVC=on can only be used for Windows")
|
||||
endif()
|
||||
# By default these flags have /MD set. Modified it to use /MT instead.
|
||||
foreach(buildType RELEASE MINSIZEREL RELWITHDEBINFO)
|
||||
set(cxxFlag "CMAKE_CXX_FLAGS_${buildType}")
|
||||
string(REPLACE "/MD" "/MT" ${cxxFlag} "${${cxxFlag}}")
|
||||
endforeach()
|
||||
set(cxxFlagDebug "CMAKE_CXX_FLAGS_DEBUG")
|
||||
string(REPLACE "/MDd" "/MTd" ${cxxFlagDebug} "${${cxxFlagDebug}}")
|
||||
endif()
|
||||
|
||||
# Set xml parser if not set
|
||||
if(NOT XML_PARSER)
|
||||
if(WIN32)
|
||||
set(XML_PARSER msxml6 CACHE STRING "XML Parser not defined. Using msxml6" FORCE)
|
||||
elseif(AOSP)
|
||||
set(XML_PARSER javaxml CACHE STRING "XML Parser not defined. Using javaxml" FORCE)
|
||||
elseif(MAC)
|
||||
if(MSIX_PACK)
|
||||
set(XML_PARSER xerces CACHE STRING "XML Parser not defined. Using xerces" FORCE)
|
||||
else()
|
||||
set(XML_PARSER applexml CACHE STRING "XML Parser not defined. Using applexml" FORCE)
|
||||
endif()
|
||||
elseif(IOS)
|
||||
set(XML_PARSER applexml CACHE STRING "XML Parser not defined. Using applexml" FORCE)
|
||||
else()
|
||||
set(XML_PARSER xerces CACHE STRING "XML Parser not defined. Using xerces" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Set crypto library if not set
|
||||
if(NOT CRYPTO_LIB)
|
||||
if(WIN32)
|
||||
set(CRYPTO_LIB crypt32 CACHE STRING "Crypto Lib not defined. Using crypt32" FORCE)
|
||||
else()
|
||||
set(CRYPTO_LIB openssl CACHE STRING "Crypto Lib not defined. Using openssl" FORCE)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Validates PACK options are correct
|
||||
if(MSIX_PACK)
|
||||
if(AOSP OR IOS)
|
||||
message(FATAL_ERROR "Packaging is not supported for mobile devices.")
|
||||
elseif(MAC)
|
||||
if(NOT USE_MSIX_SDK_ZLIB)
|
||||
message(FATAL_ERROR "Using libCompression APIs and packaging features is not supported. Use -DUSE_MSIX_SDK_ZLIB=on")
|
||||
endif()
|
||||
if(NOT (XML_PARSER MATCHES xerces))
|
||||
message(FATAL_ERROR "Xerces is the only supported parser for MacOS pack. Use -DXML_PARSER=xerces")
|
||||
endif()
|
||||
endif()
|
||||
if(NOT USE_VALIDATION_PARSER)
|
||||
message(FATAL_ERROR "Packaging requires validation parser. Use -DUSE_VALIDATION_PARSER=on")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Compression
|
||||
set(COMPRESSION_LIB "zlib")
|
||||
if(((IOS) OR (MACOS)) AND (NOT USE_MSIX_SDK_ZLIB))
|
||||
set(COMPRESSION_LIB "libCompression")
|
||||
elseif((AOSP) AND (NOT USE_MSIX_SDK_ZLIB))
|
||||
set(COMPRESSION_LIB "inbox zlib")
|
||||
endif()
|
||||
|
||||
message(STATUS "MSIX SDK options validation")
|
||||
message(STATUS "Configuration:")
|
||||
if(WIN32)
|
||||
message(STATUS "\tPlatform = WIN32")
|
||||
elseif(MACOS)
|
||||
message(STATUS "\tPlatform = MacOS")
|
||||
elseif(IOS)
|
||||
message(STATUS "\tPlatform = iOS")
|
||||
message(STATUS "\tPlatform = iOS")
|
||||
elseif(AOSP)
|
||||
message(STATUS "\tPlatform = Android")
|
||||
else()
|
||||
message(STATUS "\tPlatform = Linux")
|
||||
endif()
|
||||
|
||||
message(STATUS "\tPackaging support = ${MSIX_PACK}")
|
||||
if(SKIP_BUNDLES)
|
||||
message(STATUS "\tBundle support = off")
|
||||
else()
|
||||
message(STATUS "\tBundle support = on")
|
||||
endif()
|
||||
|
||||
message(STATUS "\tCompression library = ${COMPRESSION_LIB}")
|
||||
message(STATUS "\tXML Parser = ${XML_PARSER} with validation parser ${USE_VALIDATION_PARSER}")
|
||||
message(STATUS "\tCrypto library = ${CRYPTO_LIB}")
|
|
@ -11,7 +11,7 @@ set(RESOURCES_APPXTYPES)
|
|||
set(RESOURCES_APPXMANIFEST)
|
||||
set(RESOURCES_APPXBUNDLEMANIFEST)
|
||||
|
||||
set(RESOURCES_DIR "${CMAKE_PROJECT_ROOT}/resources")
|
||||
set(RESOURCES_DIR "${MSIX_PROJECT_ROOT}/resources")
|
||||
|
||||
if(CRYPTO_LIB MATCHES openssl) # Only OpenSSL needs to carry the certificates.
|
||||
list(APPEND RESOURCES_CERTS
|
||||
|
@ -36,6 +36,7 @@ if ((XML_PARSER MATCHES msxml6) OR (XML_PARSER MATCHES xerces))
|
|||
endif()
|
||||
|
||||
if(USE_VALIDATION_PARSER)
|
||||
add_definitions(-DVALIDATING=1)
|
||||
# Schemas are defined in triplets in the form of
|
||||
# <namespace> <alias> <file location relative to root/resources>
|
||||
list(APPEND CONTENT_TYPES
|
||||
|
@ -307,12 +308,12 @@ foreach(FILE ${RESOURCES_CERTS})
|
|||
endforeach()
|
||||
|
||||
execute_process(
|
||||
COMMAND ${CMAKE_COMMAND} -E tar cvf "${CMAKE_BINARY_DIR}/resources.zip" --format=zip -- ${FILES_TO_ZIP}
|
||||
COMMAND ${CMAKE_COMMAND} -E tar cvf "${MSIX_BINARY_ROOT}/resources.zip" --format=zip -- ${FILES_TO_ZIP}
|
||||
WORKING_DIRECTORY "${RESOURCES_DIR}"
|
||||
OUTPUT_QUIET
|
||||
)
|
||||
|
||||
file(READ "${CMAKE_BINARY_DIR}/resources.zip" RESOURCE_HEX HEX)
|
||||
file(READ "${MSIX_BINARY_ROOT}/resources.zip" RESOURCE_HEX HEX)
|
||||
# Create a list by matching every 2 charactes. CMake separates lists with ;
|
||||
string(REGEX MATCHALL ".." RESOURCE_HEX_LIST "${RESOURCE_HEX}")
|
||||
list(LENGTH RESOURCE_HEX_LIST RESOURCE_LENGTH)
|
||||
|
@ -338,5 +339,5 @@ foreach(FILE ${RESOURCES_CERTS})
|
|||
string(APPEND CERTS_HPP result.push_back(std::make_pair(\"${FILE}\", std::move(factory->GetResource(\"${FILE}\")))) ";\n\t\t\t\t")
|
||||
endforeach()
|
||||
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/src/inc/MSIXResource.hpp.cmakein ${CMAKE_PROJECT_ROOT}/src/inc/MSIXResource.hpp CRLF)
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/src/msix/MSIXResource.cpp.cmakein ${CMAKE_PROJECT_ROOT}/src/msix/MSIXResource.cpp CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/src/inc/MSIXResource.hpp.cmakein ${MSIX_PROJECT_ROOT}/src/inc/MSIXResource.hpp CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/src/msix/common/MSIXResource.cpp.cmakein ${MSIX_PROJECT_ROOT}/src/msix/common/MSIXResource.cpp CRLF)
|
||||
|
|
|
@ -24,7 +24,7 @@ endif()
|
|||
|
||||
# Xerces
|
||||
if(XML_PARSER MATCHES xerces)
|
||||
message(STATUS "XML_PARSER defined. Configuring XERCES-C parser for inclusion as static library." )
|
||||
message(STATUS "Configuring XERCES-C parser for inclusion as static library." )
|
||||
set(BUILD_SHARED_LIBS OFF CACHE BOOL "Only build static lib" FORCE)
|
||||
set(network OFF CACHE BOOL "Disable network support" FORCE)
|
||||
set(XERCES_TESTS OFF CACHE BOOL "Don't build test or samples" FORCE)
|
||||
|
|
37
makeaosp.sh
37
makeaosp.sh
|
@ -9,23 +9,22 @@ sdk=
|
|||
sdkver=24
|
||||
dataCompressionLib=NDK_libz
|
||||
bundle=off
|
||||
xmlparserLib=javaxml
|
||||
xmlparser="-DXML_PARSER=javaxml"
|
||||
xmlparser=javaxml
|
||||
validationParser=off
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "usage: makeaosp [-ndk ndk_path] [-arch arch] [-ndkver ndk_version] [-sdk sdk_path] [-sdkver sdk_version] [-b buildType] [-xzlib] [-sb] [-parser-xerces]"
|
||||
echo $'\t' "-ndk Path to Android NDK. Default $ANDROID_NDK_ROOT or $ANDROID_NDK"
|
||||
echo $'\t' "-ndkver Android NDK version. Default/minimum 19."
|
||||
echo $'\t' "-sdk Path to Android SDK. Default $ANDROID_HOME."
|
||||
echo $'\t' "-sdkver Android SDK version. Default/minimum 24."
|
||||
echo $'\t' "-arch Architecture ABI. Default x86"
|
||||
echo $'\t' "-b Build type. Default MinSizeRel"
|
||||
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libz.so"
|
||||
echo $'\t' "-parser-xerces Use xerces xml parser instead of default javaxml"
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "--validation-parser, -vp Enable XML schema validation."
|
||||
echo "usage: makeaosp [options]"
|
||||
echo $'\t' "-ndk ndk_path Path to Android NDK. Default $ANDROID_NDK_ROOT or $ANDROID_NDK"
|
||||
echo $'\t' "-ndkver ndk_version Android NDK version. Default/minimum 19."
|
||||
echo $'\t' "-sdk sdk_path Path to Android SDK. Default $ANDROID_HOME."
|
||||
echo $'\t' "-sdkver sdk_version Android SDK version. Default/minimum 24."
|
||||
echo $'\t' "-arch arch Architecture ABI. Default x86"
|
||||
echo $'\t' "-b build_type Default MinSizeRel"
|
||||
echo $'\t' "-xzlib Use MSIX SDK Zlib insctead of inbox libz.so"
|
||||
echo $'\t' "-parser-xerces Use xerces xml parser instead of default javaxml"
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "--validation-parser|-vp Enable XML schema validation."
|
||||
}
|
||||
|
||||
printsetup()
|
||||
|
@ -37,7 +36,7 @@ printsetup()
|
|||
echo "Architecture:" $arch
|
||||
echo "Build Type:" $build
|
||||
echo "Zlib:" $dataCompressionLib
|
||||
echo "parser:" $xmlparserLib
|
||||
echo "parser:" $xmlparser
|
||||
echo "Skip bundle support:" $bundle
|
||||
echo "Validation parser:" $validationParser
|
||||
}
|
||||
|
@ -62,8 +61,8 @@ while [ "$1" != "" ]; do
|
|||
-xzlib ) dataCompressionLib=MSIX_SDK_zlib
|
||||
zlib="-DUSE_MSIX_SDK_ZLIB=on"
|
||||
;;
|
||||
-parser-xerces ) xmlparserLib=xerces
|
||||
xmlparser="-DXML_PARSER=xerces"
|
||||
-parser-xerces )
|
||||
xmlparser=xerces
|
||||
;;
|
||||
-sdk ) shift
|
||||
sdk=$1
|
||||
|
@ -114,8 +113,8 @@ find . -name *msix* -d | xargs rm -r
|
|||
|
||||
echo "cmake -DCMAKE_SYSTEM_NAME=Android -DCMAKE_ANDROID_NDK="$ndk "-DCMAKE_SYSTEM_VERSION="$version "-DANDROID_SDK="$sdk
|
||||
echo "-DANDROID_SDK_VERSION="$sdkver "-DCMAKE_ANDROID_ARCH_ABI="$arch "-DCMAKE_ANDROID_NDK_TOOLCHAIN_VERSION=clang"
|
||||
echo "-DCMAKE_ANDROID_STL_TYPE=c++_shared -DCMAKE_BUILD_TYPE="$build "-DSKIP_BUNDLES="$bundle $xmlparser "-DUSE_VALIDATION_PARSER="$validationParser
|
||||
echo $zlib "-DAOSP=on .."
|
||||
echo "-DCMAKE_ANDROID_STL_TYPE=c++_shared -DCMAKE_BUILD_TYPE="$build "-DSKIP_BUNDLES="$bundle "-DXML_PARSER="$xmlparser
|
||||
echo "-DUSE_VALIDATION_PARSER="$validationParser $zlib "-DAOSP=on .."
|
||||
cmake -DCMAKE_SYSTEM_NAME=Android \
|
||||
-DCMAKE_ANDROID_NDK="$ndk" \
|
||||
-DCMAKE_SYSTEM_VERSION="$version" \
|
||||
|
@ -126,7 +125,7 @@ cmake -DCMAKE_SYSTEM_NAME=Android \
|
|||
-DCMAKE_ANDROID_STL_TYPE=c++_shared \
|
||||
-DCMAKE_BUILD_TYPE="$build" \
|
||||
-DSKIP_BUNDLES=$bundle \
|
||||
$xmlparser \
|
||||
-DXML_PARSER=$xmlparser \
|
||||
-DUSE_VALIDATION_PARSER=$validationParser \
|
||||
$zlib -DAOSP=on ..
|
||||
make
|
||||
|
|
32
makeios.sh
32
makeios.sh
|
@ -4,19 +4,18 @@ build=MinSizeRel
|
|||
arch=x86_64
|
||||
dataCompressionLib=libcompression
|
||||
bundle=off
|
||||
xmlparserLib=applexml
|
||||
xmlparser="-DXML_PARSER=applexml"
|
||||
xmlparser=applexml
|
||||
validationParser=off
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "usage: makemac [-b buildType] [-arch] [-xzlib]"
|
||||
echo $'\t' "-b Build type. Default MinSizeRel"
|
||||
echo $'\t' "-arch OSX Architecture. Default x86_64 (simulator)"
|
||||
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libCompression api. Default on iOS is libCompression."
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "-parser-xerces Use xerces xml parser instead of default apple xml parser."
|
||||
echo $'\t' "--validation-parser, -vp Enable XML schema validation."
|
||||
echo "usage: makemac [options]"
|
||||
echo $'\t' "-b build_type Default MinSizeRel"
|
||||
echo $'\t' "-arch arch OSX Architecture. Default x86_64 (simulator)"
|
||||
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libCompression api. Default on iOS is libCompression."
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "-parser-xerces Use xerces xml parser instead of default apple xml parser."
|
||||
echo $'\t' "--validation-parser|-vp Enable XML schema validation."
|
||||
}
|
||||
|
||||
printsetup()
|
||||
|
@ -25,7 +24,7 @@ printsetup()
|
|||
echo "Architecture:" $arch
|
||||
echo "Data Compression library:" $dataCompressionLib
|
||||
echo "Skip bundle support:" $bundle
|
||||
echo "parser:" $xmlparserLib
|
||||
echo "Parser:" $xmlparser
|
||||
echo "Validation parser:" $validationParser
|
||||
}
|
||||
|
||||
|
@ -41,7 +40,7 @@ while [ "$1" != "" ]; do
|
|||
zlib="-DUSE_MSIX_SDK_ZLIB=on"
|
||||
;;
|
||||
-parser-xerces ) xmlparserLib=xerces
|
||||
xmlparser="-DXML_PARSER=xerces"
|
||||
xmlparser=xerces
|
||||
;;
|
||||
-sb ) bundle="on"
|
||||
;;
|
||||
|
@ -65,6 +64,13 @@ cd .vs
|
|||
# clean up any old builds of msix modules
|
||||
find . -name *msix* -d | xargs rm -r
|
||||
|
||||
echo "cmake -DCMAKE_BUILD_TYPE="$build $zlib "-DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake -DCMAKE_OSX_ARCHITECTURES="$arch $xmlparser "-DUSE_VALIDATION_PARSER="$validationParser "-DSKIP_BUNDLES="$bundle "-DIOS=on .."
|
||||
cmake -DCMAKE_BUILD_TYPE=$build $zlib -DIOS=on -DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake -DCMAKE_OSX_ARCHITECTURES=$arch $xmlparser -DUSE_VALIDATION_PARSER=$validationParser -DSKIP_BUNDLES=$bundle -DIOS=on ..
|
||||
echo "cmake -DCMAKE_BUILD_TYPE="$build $zlib "-DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake -DCMAKE_OSX_ARCHITECTURES="$arch
|
||||
echo "-DXML_PARSER="$xmlparser "-DUSE_VALIDATION_PARSER="$validationParser "-DSKIP_BUNDLES="$bundle "-DIOS=on .."
|
||||
cmake -DCMAKE_BUILD_TYPE=$build \
|
||||
-DCMAKE_TOOLCHAIN_FILE=../cmake/ios.cmake \
|
||||
-DCMAKE_OSX_ARCHITECTURES=$arch \
|
||||
-DXML_PARSER=$xmlparser \
|
||||
-DUSE_VALIDATION_PARSER=$validationParser \
|
||||
-DSKIP_BUNDLES=$bundle \
|
||||
$zlib -DIOS=on ..
|
||||
make
|
||||
|
|
24
makelinux.sh
24
makelinux.sh
|
@ -3,13 +3,15 @@
|
|||
build=MinSizeRel
|
||||
bundle=off
|
||||
validationParser=off
|
||||
pack=off
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "usage: makelinux [-b buildType] [-sb]"
|
||||
echo $'\t' "-b Build type. Default MinSizeRel"
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "--validation-parser, -vp Enable XML schema validation."
|
||||
echo "usage: makelinux [options]"
|
||||
echo $'\t' "-b build_type Default MinSizeRel"
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "--validation-parser|-vp Enable XML schema validation."
|
||||
echo $'\t' "--pack Include packaging features. Sets validation parser on."
|
||||
}
|
||||
|
||||
printsetup()
|
||||
|
@ -17,6 +19,7 @@ printsetup()
|
|||
echo "Build Type:" $build
|
||||
echo "Skip bundle support:" $bundle
|
||||
echo "Validation parser:" $validationParser
|
||||
echo "Pack support:" $pack
|
||||
}
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
|
@ -33,6 +36,9 @@ while [ "$1" != "" ]; do
|
|||
;;
|
||||
-vp ) validationParser=on
|
||||
;;
|
||||
--pack ) pack=on
|
||||
validationParser=on
|
||||
;;
|
||||
* ) usage
|
||||
exit 1
|
||||
esac
|
||||
|
@ -46,6 +52,12 @@ cd .vs
|
|||
# clean up any old builds of msix modules
|
||||
find . -depth -name *msix* | xargs -0 -r rm -rf
|
||||
|
||||
echo "cmake -DCMAKE_BUILD_TYPE="$build "-DSKIP_BUNDLES="$bundle "-DUSE_VALIDATION_PARSER="$validationParser "-DCMAKE_TOOLCHAIN_FILE=../cmake/linux.cmake -DLINUX=on .."
|
||||
cmake -DCMAKE_BUILD_TYPE=$build -DSKIP_BUNDLES=$bundle -DUSE_VALIDATION_PARSER=$validationParser -DCMAKE_TOOLCHAIN_FILE=../cmake/linux.cmake -DLINUX=on ..
|
||||
echo "cmake -DCMAKE_BUILD_TYPE="$build "-DSKIP_BUNDLES="$bundle "-DUSE_VALIDATION_PARSER="$validationParser
|
||||
echo "-DCMAKE_TOOLCHAIN_FILE=../cmake/linux.cmake" "-DMSIX_PACK="$pack "-DLINUX=on .."
|
||||
cmake -DCMAKE_BUILD_TYPE=$build \
|
||||
-DSKIP_BUNDLES=$bundle \
|
||||
-DUSE_VALIDATION_PARSER=$validationParser \
|
||||
-DCMAKE_TOOLCHAIN_FILE=../cmake/linux.cmake \
|
||||
-DMSIX_PACK=$pack \
|
||||
-DLINUX=on ..
|
||||
make
|
||||
|
|
50
makemac.sh
50
makemac.sh
|
@ -3,20 +3,21 @@
|
|||
build=MinSizeRel
|
||||
dataCompressionLib=libcompression
|
||||
bundle=off
|
||||
xmlparserLib=applexml
|
||||
xmlparser="-DXML_PARSER=applexml"
|
||||
addressSanitizerFlag=off
|
||||
xmlparser=applexml
|
||||
addressSanitizer=off
|
||||
validationParser=off
|
||||
pack=off
|
||||
|
||||
usage()
|
||||
{
|
||||
echo "usage: makemac [-b buildType] [-xzlib] [-parser-xerces] [-asan]"
|
||||
echo $'\t' "-b Build type. Default MinSizeRel"
|
||||
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libCompression api. Default on MacOS is libCompression."
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "-parser-xerces Use xerces xml parser instead of default apple xml parser."
|
||||
echo $'\t' "-asan Turn on address sanitizer for memory corruption detection."
|
||||
echo $'\t' "--validation-parser, -vp Enable XML schema validation."
|
||||
echo "usage: makemac [options]"
|
||||
echo $'\t' "-b build_type Default MinSizeRel"
|
||||
echo $'\t' "-xzlib Use MSIX SDK Zlib instead of inbox libCompression api. Default on MacOS is libCompression."
|
||||
echo $'\t' "-sb Skip bundle support."
|
||||
echo $'\t' "-parser-xerces Use xerces xml parser instead of default apple xml parser."
|
||||
echo $'\t' "-asan Turn on address sanitizer for memory corruption detection."
|
||||
echo $'\t' "--validation-parser|-vp Enable XML schema validation."
|
||||
echo $'\t' "--pack Include packaging features. Uses MSIX SDK Zlib and Xerces with validation parser on."
|
||||
}
|
||||
|
||||
printsetup()
|
||||
|
@ -24,9 +25,10 @@ printsetup()
|
|||
echo "Build Type:" $build
|
||||
echo "Data Compression library:" $dataCompressionLib
|
||||
echo "Skip bundle support:" $bundle
|
||||
echo "parser:" $xmlparserLib
|
||||
echo "parser:" $xmlparser
|
||||
echo "Address Sanitizer:" $addressSanitizerFlag
|
||||
echo "Validation parser:" $validationParser
|
||||
echo "Pack support:" $pack
|
||||
}
|
||||
|
||||
while [ "$1" != "" ]; do
|
||||
|
@ -37,11 +39,9 @@ while [ "$1" != "" ]; do
|
|||
-xzlib )dataCompressionLib=MSIX_SDK_zlib
|
||||
zlib="-DUSE_MSIX_SDK_ZLIB=on"
|
||||
;;
|
||||
-parser-xerces ) xmlparserLib=xerces
|
||||
xmlparser="-DXML_PARSER=xerces"
|
||||
;;
|
||||
-asan ) addressSanitizerFlag=on
|
||||
addressSanitizer="-DASAN=on"
|
||||
-parser-xerces ) xmlparser=xerces
|
||||
;;
|
||||
-asan ) addressSanitizer=on
|
||||
;;
|
||||
-sb ) bundle="on"
|
||||
;;
|
||||
|
@ -49,6 +49,12 @@ while [ "$1" != "" ]; do
|
|||
;;
|
||||
-vp ) validationParser=on
|
||||
;;
|
||||
--pack ) pack=on
|
||||
dataCompressionLib=MSIX_SDK_zlib
|
||||
zlib="-DUSE_MSIX_SDK_ZLIB=on"
|
||||
xmlparser=xerces
|
||||
validationParser=on
|
||||
;;
|
||||
-h ) usage
|
||||
exit
|
||||
;;
|
||||
|
@ -65,6 +71,14 @@ cd .vs
|
|||
# clean up any old builds of msix modules
|
||||
find . -name *msix* -d | xargs rm -r
|
||||
|
||||
echo "cmake -DCMAKE_BUILD_TYPE="$build $zlib "-DSKIP_BUNDLES="$bundle $xmlparser $addressSanitizer "-DUSE_VALIDATION_PARSER="$validationParser "-DMACOS=on .."
|
||||
cmake -DCMAKE_BUILD_TYPE=$build $zlib -DSKIP_BUNDLES=$bundle $xmlparser $addressSanitizer -DUSE_VALIDATION_PARSER=$validationParser -DMACOS=on ..
|
||||
echo "cmake -DCMAKE_BUILD_TYPE="$build $zlib "-DSKIP_BUNDLES="$bundle
|
||||
echo "-DXML_PARSER="$xmlparser "-DASAN="$addressSanitizer "-DUSE_VALIDATION_PARSER="$validationParser
|
||||
echo "-DMSIX_PACK="$pack "-DMACOS=on .."
|
||||
cmake -DCMAKE_BUILD_TYPE=$build \
|
||||
-DXML_PARSER=$xmlparser \
|
||||
-DSKIP_BUNDLES=$bundle \
|
||||
-DASAN=$addressSanitizer \
|
||||
-DUSE_VALIDATION_PARSER=$validationParser \
|
||||
-DMSIX_PACK=$pack \
|
||||
$zlib -DMACOS=on ..
|
||||
make
|
||||
|
|
31
makewin.cmd
31
makewin.cmd
|
@ -34,6 +34,7 @@ set parser="-DXML_PARSER=msxml6"
|
|||
set crypto="-DCRYPTO_LIB=crypt32"
|
||||
set msvc="-DUSE_STATIC_MSVC=off"
|
||||
set bundle="-DSKIP_BUNDLES=off"
|
||||
set pack="-DMSIX_PACK=off"
|
||||
|
||||
:parseArgs
|
||||
if /I "%~2" == "--debug" (
|
||||
|
@ -42,6 +43,12 @@ if /I "%~2" == "--debug" (
|
|||
if /I "%~2" == "-d" (
|
||||
set build="Debug"
|
||||
)
|
||||
if /I "%~2" == "--symbols" (
|
||||
set build="RelWithDebInfo"
|
||||
)
|
||||
if /I "%~2" == "-sym" (
|
||||
set build="RelWithDebInfo"
|
||||
)
|
||||
if /I "%~2" == "--parser-xerces" (
|
||||
set parser="-DXML_PARSER=xerces"
|
||||
)
|
||||
|
@ -75,6 +82,10 @@ if /I "%~2" == "--skip-bundles" (
|
|||
if /I "%~2" == "-sb" (
|
||||
set bundle="-DSKIP_BUNDLES=on"
|
||||
)
|
||||
if /I "%~2" == "--pack" (
|
||||
set pack="-DMSIX_PACK=on"
|
||||
set validationParser="-DUSE_VALIDATION_PARSER=on"
|
||||
)
|
||||
shift /2
|
||||
if not "%~2"=="" goto parseArgs
|
||||
|
||||
|
@ -83,8 +94,8 @@ cd .vs
|
|||
if exist CMakeFiles rd /s /q CMakeFiles
|
||||
if exist CMakeCache.txt del CMakeCache.txt
|
||||
|
||||
echo cmake -DWIN32=on -DCMAKE_BUILD_TYPE=%build% %validationParser% %zlib% %parser% %crypto% %msvc% %bundle% -G"NMake Makefiles" ..
|
||||
cmake -DWIN32=on -DCMAKE_BUILD_TYPE=%build% %validationParser% %zlib% %parser% %crypto% %msvc% %bundle% -G"NMake Makefiles" ..
|
||||
echo cmake -DWIN32=on -DCMAKE_BUILD_TYPE=%build% %validationParser% %zlib% %parser% %crypto% %msvc% %bundle% %pack% -G"NMake Makefiles" ..
|
||||
cmake -DWIN32=on -DCMAKE_BUILD_TYPE=%build% %validationParser% %zlib% %parser% %crypto% %msvc% %bundle% %pack% -G"NMake Makefiles" ..
|
||||
nmake /NOLOGO
|
||||
|
||||
goto Exit
|
||||
|
@ -97,12 +108,14 @@ echo Helper to build the MSIX SDK for Windows. Assumes the user has a version of
|
|||
echo:
|
||||
echo Options
|
||||
echo --debug, -d = Build chk binary.
|
||||
echo --parser-xerces, -px = use Xerces-C parser. Default MSXML6.
|
||||
echo --validation-parser, -vp = enable XML schema validation.
|
||||
echo --shared-zlib, -sz = don't statically link zlib.
|
||||
echo --crypto-openssl, -co = use OpenSSL crypto [currently for testing]. Default Crypt32.
|
||||
echo -mt = use compiler flag /MT to use static version of the run-time library.
|
||||
echo --skip-bundles, -sb = turn off bundle support.
|
||||
echo --help, -h, /? = print this usage information and exit.
|
||||
echo --symbols, -sym = Produce symbols for release binaries.
|
||||
echo --parser-xerces, -px = Use Xerces-C parser. Default MSXML6.
|
||||
echo --validation-parser, -vp = Enable XML schema validation.
|
||||
echo --shared-zlib, -sz = Don't statically link zlib.
|
||||
echo --crypto-openssl, -co = Use OpenSSL crypto [currently for testing]. Default Crypt32.
|
||||
echo -mt = Use compiler flag /MT to use static version of the run-time library.
|
||||
echo --skip-bundles, -sb = Turn off bundle support.
|
||||
echo --pack = Include packaging features. Sets validation parser on.
|
||||
echo --help, -h, /? = Print this usage information and exit.
|
||||
:Exit
|
||||
EXIT /B 0
|
||||
|
|
|
@ -9,6 +9,7 @@ pr:
|
|||
include:
|
||||
- master
|
||||
- release_v*
|
||||
- packaging
|
||||
paths:
|
||||
include:
|
||||
- cmake/*
|
||||
|
|
|
@ -9,6 +9,7 @@ pr:
|
|||
include:
|
||||
- master
|
||||
- release_v*
|
||||
- packaging
|
||||
paths:
|
||||
include:
|
||||
- cmake/*
|
||||
|
|
|
@ -9,6 +9,7 @@ pr:
|
|||
include:
|
||||
- master
|
||||
- release_v*
|
||||
- packaging
|
||||
paths:
|
||||
include:
|
||||
- cmake/*
|
||||
|
@ -33,10 +34,10 @@ jobs:
|
|||
strategy:
|
||||
# TODO: add builds using xerces if needed.
|
||||
matrix:
|
||||
debug:
|
||||
debug_nopack:
|
||||
_arguments: -b Debug
|
||||
_artifact: LINUXchk
|
||||
release:
|
||||
release_nopack:
|
||||
_arguments: -b MinSizeRel
|
||||
_artifact: LINUX
|
||||
release_nobundle:
|
||||
|
@ -45,6 +46,12 @@ jobs:
|
|||
release_validation_parser:
|
||||
_arguments: -b MinSizeRel -vp
|
||||
_artifact: LINUX-ValidationParser
|
||||
release_pack:
|
||||
_arguments: -b MinSizeRel --pack
|
||||
_artifact: LINUX-pack
|
||||
debug_pack:
|
||||
_arguments: -b Debug --pack
|
||||
_artifact: LINUXchk-pack
|
||||
|
||||
steps:
|
||||
- task: Bash@3
|
||||
|
|
|
@ -9,6 +9,7 @@ pr:
|
|||
include:
|
||||
- master
|
||||
- release_v*
|
||||
- packaging
|
||||
paths:
|
||||
include:
|
||||
- cmake/*
|
||||
|
@ -33,15 +34,21 @@ jobs:
|
|||
strategy:
|
||||
# TODO: add builds using xerces if needed.
|
||||
matrix:
|
||||
debug:
|
||||
debug_nopack:
|
||||
_arguments: -b Debug
|
||||
_artifact: MACOSchk
|
||||
release:
|
||||
release_nopack:
|
||||
_arguments: -b MinSizeRel
|
||||
_artifact: MACOS
|
||||
release_nobundle:
|
||||
_arguments: -b MinSizeRel -sb
|
||||
_artifact: MACOS-nobundle
|
||||
release_pack:
|
||||
_arguments: -b MinSizeRel --pack
|
||||
_artifact: MACOSchk-pack
|
||||
debug_pack:
|
||||
_arguments: -b Debug --pack
|
||||
_artifact: MACOSchk-pack
|
||||
steps:
|
||||
- task: Bash@3
|
||||
displayName: Build
|
||||
|
|
|
@ -9,6 +9,7 @@ pr:
|
|||
include:
|
||||
- master
|
||||
- release_v*
|
||||
- packaging
|
||||
paths:
|
||||
include:
|
||||
- cmake/*
|
||||
|
@ -33,16 +34,16 @@ jobs:
|
|||
strategy:
|
||||
# TODO: add debug for validation parser and xerces if needed.
|
||||
matrix:
|
||||
debug_32:
|
||||
debug_32_nopack:
|
||||
_arguments: x86 -d
|
||||
_artifact: WIN32chk
|
||||
debug_64:
|
||||
debug_64_nopack:
|
||||
_arguments: x64 -d
|
||||
_artifact: WIN32-x64chk
|
||||
release_32:
|
||||
release_32_nopack:
|
||||
_arguments: x86
|
||||
_artifact: WIN32
|
||||
release_64:
|
||||
release_64_nopack:
|
||||
_arguments: x64
|
||||
_artifact: WIN32-x64
|
||||
release_32_validation_parser:
|
||||
|
@ -52,10 +53,10 @@ jobs:
|
|||
_arguments: x64 --validation-parser
|
||||
_artifact: WIN32-x64ValidationParser
|
||||
release_32_xerces:
|
||||
_arguments: x86 --parser-xerces
|
||||
_arguments: x86 --parser-xerces --pack
|
||||
_artifact: WIN32Xerces
|
||||
release_64_xerces:
|
||||
_arguments: x64 --parser-xerces
|
||||
_arguments: x64 --parser-xerces --pack
|
||||
_artifact: WIN32-x64Xerces
|
||||
release_32_nobundle:
|
||||
_arguments: x86 -sb
|
||||
|
@ -63,6 +64,18 @@ jobs:
|
|||
release_64_nobundle:
|
||||
_arguments: x64 -sb
|
||||
_artifact: WIN32-x64-nobundle
|
||||
release_32_pack:
|
||||
_arguments: x86 --pack
|
||||
_artifact: WIN32-pack
|
||||
release_64_pack:
|
||||
_arguments: x64 --pack
|
||||
_artifact: WIN32-pack
|
||||
debug_32_pack:
|
||||
_arguments: x86 -d --pack
|
||||
_artifact: WIN32chk-pack
|
||||
debug_64_pack:
|
||||
_arguments: x86 -d --pack
|
||||
_artifact: WIN32chk-pack
|
||||
|
||||
steps:
|
||||
- task: BatchScript@1
|
||||
|
|
|
@ -8,61 +8,9 @@
|
|||
|
||||
#include <iostream>
|
||||
|
||||
#define RETURN_IF_FAILED(a) \
|
||||
{ HRESULT __hr = a; \
|
||||
if (FAILED(__hr)) \
|
||||
{ return __hr; } \
|
||||
}
|
||||
#include "Helpers.hpp"
|
||||
|
||||
// Stripped down ComPtr provided for those platforms that do not already have a ComPtr class.
|
||||
template <class T>
|
||||
class ComPtr
|
||||
{
|
||||
public:
|
||||
// default ctor
|
||||
ComPtr() = default;
|
||||
ComPtr(T* ptr) : m_ptr(ptr) { InternalAddRef(); }
|
||||
|
||||
~ComPtr() { InternalRelease(); }
|
||||
inline T* operator->() const { return m_ptr; }
|
||||
inline T* Get() const { return m_ptr; }
|
||||
|
||||
inline T** operator&()
|
||||
{ InternalRelease();
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
T* m_ptr = nullptr;
|
||||
|
||||
inline void InternalAddRef() { if (m_ptr) { m_ptr->AddRef(); } }
|
||||
inline void InternalRelease()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
if (temp)
|
||||
{ m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Or you can use what-ever allocator/deallocator is best for your platform...
|
||||
LPVOID STDMETHODCALLTYPE MyAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
void STDMETHODCALLTYPE MyFree(LPVOID pv) { std::free(pv); }
|
||||
|
||||
// Helper class to free string buffers obtained from the packaging APIs.
|
||||
template<typename T>
|
||||
class Text
|
||||
{
|
||||
public:
|
||||
T** operator&() { return &content; }
|
||||
~Text() { Cleanup(); }
|
||||
T* Get() { return content; }
|
||||
|
||||
T* content = nullptr;
|
||||
protected:
|
||||
void Cleanup() { if (content) { MyFree(content); content = nullptr; } }
|
||||
};
|
||||
using namespace MsixSample::Helper;
|
||||
|
||||
int Help()
|
||||
{
|
||||
|
@ -264,4 +212,3 @@ int main(int argc, char* argv[])
|
|||
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ project (BundleSample)
|
|||
|
||||
if(WIN32)
|
||||
set(DESCRIPTION "BundleSample manifest")
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
set(MANIFEST ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest)
|
||||
endif()
|
||||
|
||||
|
@ -15,7 +15,7 @@ add_executable(${PROJECT_NAME}
|
|||
${MANIFEST}
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/src/msix)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_BINARY_ROOT}/src/msix ../inc)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} msix)
|
||||
|
||||
|
|
|
@ -3,12 +3,24 @@
|
|||
|
||||
cmake_minimum_required(VERSION 3.8.0 FATAL_ERROR)
|
||||
|
||||
# For windows copy the library to the msix and samples directory
|
||||
if(WIN32)
|
||||
add_custom_target(samples ALL
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "bin/msix.dll" "samples/msix.dll"
|
||||
WORKING_DIRECTORY "${MSIX_BINARY_ROOT}"
|
||||
DEPENDS msix)
|
||||
else()
|
||||
add_custom_target(samples ALL DEPENDS msix)
|
||||
endif()
|
||||
|
||||
set(MSIX_SAMPLE_OUTPUT_DIRECTORY "${MSIX_BINARY_ROOT}/samples")
|
||||
|
||||
add_subdirectory(ExtractContentsSample)
|
||||
add_subdirectory(BundleSample)
|
||||
add_subdirectory(OverrideStreamSample)
|
||||
add_subdirectory(OverrideLanguageSample)
|
||||
|
||||
add_dependencies(ExtractContentsSample msix)
|
||||
add_dependencies(BundleSample msix)
|
||||
add_dependencies(OverrideStreamSample msix)
|
||||
add_dependencies(OverrideLanguageSample msix)
|
||||
if (MSIX_PACK)
|
||||
add_subdirectory(PackSample)
|
||||
add_dependencies(PackSample msix)
|
||||
endif()
|
||||
|
|
|
@ -6,7 +6,7 @@ project (ExtractContentsSample)
|
|||
|
||||
if(WIN32)
|
||||
set(DESCRIPTION "ExtractContentsSample manifest")
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
set(MANIFEST ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest)
|
||||
endif()
|
||||
|
||||
|
@ -15,7 +15,7 @@ add_executable(${PROJECT_NAME}
|
|||
${MANIFEST}
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/src/msix)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_BINARY_ROOT}/src/msix ../inc)
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} msix)
|
||||
|
||||
|
|
|
@ -14,75 +14,12 @@
|
|||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
#ifdef WIN32
|
||||
#define UNICODE
|
||||
#include <windows.h>
|
||||
#else
|
||||
// required posix-specific headers
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "MSIXWindows.hpp"
|
||||
|
||||
#define RETURN_IF_FAILED(a) \
|
||||
{ HRESULT __hr = a; \
|
||||
if (FAILED(__hr)) \
|
||||
{ return __hr; } \
|
||||
}
|
||||
#include "Helpers.hpp"
|
||||
|
||||
// Stripped down ComPtr provided for those platforms that do not already have a ComPtr class.
|
||||
template <class T>
|
||||
class ComPtr
|
||||
{
|
||||
public:
|
||||
// default ctor
|
||||
ComPtr() = default;
|
||||
ComPtr(T* ptr) : m_ptr(ptr) { InternalAddRef(); }
|
||||
|
||||
~ComPtr() { InternalRelease(); }
|
||||
inline T* operator->() const { return m_ptr; }
|
||||
inline T* Get() const { return m_ptr; }
|
||||
|
||||
inline T** operator&()
|
||||
{ InternalRelease();
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
T* m_ptr = nullptr;
|
||||
|
||||
inline void InternalAddRef() { if (m_ptr) { m_ptr->AddRef(); } }
|
||||
inline void InternalRelease()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
if (temp)
|
||||
{ m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::string utf16_to_utf8(const std::wstring& utf16string)
|
||||
{
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8<wchar_t>>{}.to_bytes(utf16string.data());
|
||||
std::string result(converted.begin(), converted.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring utf8_to_utf16(const std::string& utf8string)
|
||||
{
|
||||
// see https://connect.microsoft.com/VisualStudio/feedback/details/1403302/unresolved-external-when-using-codecvt-utf8
|
||||
#ifdef WIN32
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<unsigned short>, unsigned short>{}.from_bytes(utf8string.data());
|
||||
#else
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(utf8string.data());
|
||||
#endif
|
||||
std::wstring result(converted.begin(), converted.end());
|
||||
return result;
|
||||
}
|
||||
using namespace MsixSample::Helper;
|
||||
|
||||
// describes an option to a command that the user may specify
|
||||
struct Option
|
||||
|
@ -101,12 +38,6 @@ struct Option
|
|||
// Tracks the state of the current parse operation as well as implements input validation
|
||||
struct State
|
||||
{
|
||||
bool SkipManifestValidation()
|
||||
{
|
||||
validationOptions = static_cast<MSIX_VALIDATION_OPTION>(validationOptions | MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPAPPXMANIFEST);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipSignature()
|
||||
{
|
||||
validationOptions = static_cast<MSIX_VALIDATION_OPTION>(validationOptions | MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPSIGNATURE);
|
||||
|
@ -176,76 +107,6 @@ void Error(char* toolName)
|
|||
std::cout << toolName << ": error : Missing required options. Use '-?' for more details." << std::endl;
|
||||
}
|
||||
|
||||
#ifdef WIN32
|
||||
// TODO: paths coming in SHOULD have platform-appropriate path separators
|
||||
void replace(std::wstring& input, const wchar_t oldchar, const wchar_t newchar)
|
||||
{
|
||||
std::size_t found = input.find_first_of(oldchar);
|
||||
while (found != std::string::npos)
|
||||
{
|
||||
input[found] = newchar;
|
||||
found = input.find_first_of(oldchar, found+1);
|
||||
}
|
||||
}
|
||||
|
||||
int mkdirp(std::wstring& utf16Path)
|
||||
{
|
||||
replace(utf16Path, L'/', L'\\');
|
||||
for (std::size_t i = 0; i < utf16Path.size(); i++)
|
||||
{
|
||||
if (utf16Path[i] == L'\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (utf16Path[i] == L'\\')
|
||||
{
|
||||
// Temporarily set string to terminate at the '\' character
|
||||
// to obtain name of the subdirectory to create
|
||||
utf16Path[i] = L'\0';
|
||||
|
||||
if (!CreateDirectory(utf16Path.c_str(), nullptr))
|
||||
{
|
||||
int lastError = static_cast<int>(GetLastError());
|
||||
|
||||
// It is normal for CreateDirectory to fail if the subdirectory
|
||||
// already exists. Other errors should not be ignored.
|
||||
if (lastError != ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
return lastError;
|
||||
}
|
||||
}
|
||||
// Restore original string
|
||||
utf16Path[i] = L'\\'; /* TODO: paths coming in SHOULD have platform-appropriate path separators */
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// not all POSIX implementations provide an implementation of mkdirp
|
||||
int mkdirp(std::wstring& utf16Path)
|
||||
{
|
||||
std::string utf8Path = utf16_to_utf8(utf16Path);
|
||||
auto lastSlash = utf8Path.find_last_of("/");
|
||||
std::string path = utf8Path.substr(0, lastSlash);
|
||||
char* p = const_cast<char*>(path.c_str());
|
||||
if (*p == '/') { p++; }
|
||||
while (*p != '\0')
|
||||
{
|
||||
while (*p != '\0' && *p != '/') { p++; }
|
||||
|
||||
char v = *p;
|
||||
*p = '\0';
|
||||
if (-1 == mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) && errno != EEXIST)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
*p = v;
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
template<typename Type>
|
||||
struct FootprintFilesType
|
||||
{
|
||||
|
@ -299,24 +160,6 @@ HRESULT GetOutputStream(LPCWSTR path, LPCWSTR fileName, IStream** stream)
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// Or you can use what-ever allocator/deallocator is best for your platform...
|
||||
LPVOID STDMETHODCALLTYPE MyAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
void STDMETHODCALLTYPE MyFree(LPVOID pv) { std::free(pv); }
|
||||
|
||||
// Helper class to free string buffers obtained from the packaging APIs.
|
||||
template<typename T>
|
||||
class Text
|
||||
{
|
||||
public:
|
||||
T** operator&() { return &content; }
|
||||
~Text() { Cleanup(); }
|
||||
T* Get() { return content; }
|
||||
|
||||
T* content = nullptr;
|
||||
protected:
|
||||
void Cleanup() { if (content) { MyFree(content); content = nullptr; } }
|
||||
};
|
||||
|
||||
//
|
||||
// Creates a cross-plat app package.
|
||||
//
|
||||
|
@ -644,4 +487,4 @@ int main(int argc, char* argv[])
|
|||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ project (OverrideLanguageSample)
|
|||
|
||||
if(WIN32)
|
||||
set(DESCRIPTION "OverrideLanguageSample manifest")
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
set(MANIFEST ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest)
|
||||
endif()
|
||||
|
||||
|
@ -15,7 +15,7 @@ add_executable(${PROJECT_NAME}
|
|||
${MANIFEST}
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/src/msix)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_BINARY_ROOT}/src/msix ../inc)
|
||||
|
||||
if (LINUX OR AOSP)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE -latomic)
|
||||
|
|
|
@ -9,89 +9,9 @@
|
|||
#include <iostream>
|
||||
#include <atomic>
|
||||
|
||||
#define RETURN_IF_FAILED(a) \
|
||||
{ HRESULT __hr = a; \
|
||||
if (FAILED(__hr)) \
|
||||
{ return __hr; } \
|
||||
}
|
||||
#include "Helpers.hpp"
|
||||
|
||||
// Stripped down ComPtr provided for those platforms that do not already have a ComPtr class.
|
||||
template <class T>
|
||||
class ComPtr
|
||||
{
|
||||
public:
|
||||
// default ctor
|
||||
ComPtr() = default;
|
||||
ComPtr(T* ptr) : m_ptr(ptr) { InternalAddRef(); }
|
||||
|
||||
// For use instead of ComPtr<T> t(new Foo(...)); given that the class has an Initialize function
|
||||
template<class U, class... Args>
|
||||
static HRESULT MakeAndInitialize(T** result, Args&&... args)
|
||||
{
|
||||
ComPtr<U> inner(new U());
|
||||
RETURN_IF_FAILED(inner->Initialize(std::forward<Args>(args)...));
|
||||
RETURN_IF_FAILED(inner->QueryInterface(UuidOfImpl<T>::iid, reinterpret_cast<void**>(result)));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// For use instead of ComPtr<T> t(new Foo(...));
|
||||
template<class U, class... Args>
|
||||
static ComPtr<T> Make(Args&&... args)
|
||||
{
|
||||
ComPtr<T> result;
|
||||
result.m_ptr = new U(std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
|
||||
~ComPtr() { InternalRelease(); }
|
||||
inline T* operator->() const { return m_ptr; }
|
||||
inline T* Get() const { return m_ptr; }
|
||||
|
||||
inline T** operator&()
|
||||
{
|
||||
InternalRelease();
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
T* Detach()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
protected:
|
||||
T* m_ptr = nullptr;
|
||||
|
||||
inline void InternalAddRef() { if (m_ptr) { m_ptr->AddRef(); } }
|
||||
inline void InternalRelease()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
if (temp)
|
||||
{
|
||||
m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Or you can use what-ever allocator/deallocator is best for your platform...
|
||||
LPVOID STDMETHODCALLTYPE MyAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
void STDMETHODCALLTYPE MyFree(LPVOID pv) { std::free(pv); }
|
||||
|
||||
// Helper class to free string buffers obtained from the packaging APIs.
|
||||
template<typename T>
|
||||
class Text
|
||||
{
|
||||
public:
|
||||
T** operator&() { return &content; }
|
||||
~Text() { Cleanup(); }
|
||||
T* Get() { return content; }
|
||||
|
||||
T* content = nullptr;
|
||||
protected:
|
||||
void Cleanup() { if (content) { MyFree(content); content = nullptr; } }
|
||||
};
|
||||
using namespace MsixSample::Helper;
|
||||
|
||||
int Help()
|
||||
{
|
||||
|
|
|
@ -6,7 +6,7 @@ project (OverrideStreamSample)
|
|||
|
||||
if(WIN32)
|
||||
set(DESCRIPTION "OverrideStreamSample manifest")
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/manifest.cmakein ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
set(MANIFEST ${MSIX_SAMPLE_OUTPUT_DIRECTORY}/${PROJECT_NAME}.exe.manifest)
|
||||
endif()
|
||||
|
||||
|
@ -15,7 +15,7 @@ add_executable(${PROJECT_NAME}
|
|||
${MANIFEST}
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/src/msix)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_BINARY_ROOT}/src/msix ../inc)
|
||||
|
||||
if (LINUX OR AOSP)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE -latomic)
|
||||
|
|
|
@ -20,114 +20,16 @@
|
|||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
#include "Helpers.hpp"
|
||||
|
||||
#define RETURN_IF_FAILED(a) \
|
||||
{ HRESULT __hr = a; \
|
||||
if (FAILED(__hr)) \
|
||||
{ return __hr; } \
|
||||
}
|
||||
|
||||
// Stripped down ComPtr provided for those platforms that do not already have a ComPtr class.
|
||||
template <class T>
|
||||
class ComPtr
|
||||
{
|
||||
public:
|
||||
// default ctor
|
||||
ComPtr() = default;
|
||||
ComPtr(T* ptr) : m_ptr(ptr) { InternalAddRef(); }
|
||||
|
||||
// For use instead of ComPtr<T> t(new Foo(...)); given that the class has an Initialize function
|
||||
template<class U, class... Args>
|
||||
static HRESULT MakeAndInitialize(T** result, Args&&... args)
|
||||
{
|
||||
ComPtr<U> inner(new U());
|
||||
RETURN_IF_FAILED(inner->Initialize(std::forward<Args>(args)...));
|
||||
RETURN_IF_FAILED(inner->QueryInterface(UuidOfImpl<T>::iid, reinterpret_cast<void**>(result)));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// For use instead of ComPtr<T> t(new Foo(...));
|
||||
template<class U, class... Args>
|
||||
static ComPtr<T> Make(Args&&... args)
|
||||
{
|
||||
ComPtr<T> result;
|
||||
result.m_ptr = new U(std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
|
||||
~ComPtr() { InternalRelease(); }
|
||||
inline T* operator->() const { return m_ptr; }
|
||||
inline T* Get() const { return m_ptr; }
|
||||
|
||||
inline T** operator&()
|
||||
{ InternalRelease();
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
T* Detach()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
protected:
|
||||
T* m_ptr = nullptr;
|
||||
|
||||
inline void InternalAddRef() { if (m_ptr) { m_ptr->AddRef(); } }
|
||||
inline void InternalRelease()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
if (temp)
|
||||
{ m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Or you can use what-ever allocator/deallocator is best for your platform...
|
||||
LPVOID STDMETHODCALLTYPE MyAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
void STDMETHODCALLTYPE MyFree(LPVOID pv) { std::free(pv); }
|
||||
|
||||
// Helper class to free string buffers obtained from the packaging APIs.
|
||||
template<typename T>
|
||||
class Text
|
||||
{
|
||||
public:
|
||||
T** operator&() { return &content; }
|
||||
~Text() { Cleanup(); }
|
||||
T* Get() { return content; }
|
||||
|
||||
T* content = nullptr;
|
||||
protected:
|
||||
void Cleanup() { if (content) { MyFree(content); content = nullptr; } }
|
||||
};
|
||||
|
||||
std::string utf16_to_utf8(const std::wstring& utf16string)
|
||||
{
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8<wchar_t>>{}.to_bytes(utf16string.data());
|
||||
std::string result(converted.begin(), converted.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring utf8_to_utf16(const std::string& utf8string)
|
||||
{
|
||||
// see https://connect.microsoft.com/VisualStudio/feedback/details/1403302/unresolved-external-when-using-codecvt-utf8
|
||||
#ifdef WIN32
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<unsigned short>, unsigned short>{}.from_bytes(utf8string.data());
|
||||
#else
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(utf8string.data());
|
||||
#endif
|
||||
std::wstring result(converted.begin(), converted.end());
|
||||
return result;
|
||||
}
|
||||
using namespace MsixSample::Helper;
|
||||
|
||||
int Help()
|
||||
{
|
||||
std::cout << std::endl;
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "------" << std::endl;
|
||||
std::cout << "\t" << "OverrideStreamSample.cpp -p <flat bundle> -rp <relative path of payload packages> -d <output directory>" << std::endl;
|
||||
std::cout << "\t" << "OverrideStreamSample -p <flat bundle> -rp <relative path of payload packages> -d <output directory>" << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Description:" << std::endl;
|
||||
std::cout << "------------" << std::endl;
|
||||
|
@ -144,7 +46,7 @@ class MyStream final : public IStream
|
|||
public:
|
||||
enum Mode { READ = 0, WRITE, APPEND, READ_UPDATE, WRITE_UPDATE, APPEND_UPDATE };
|
||||
|
||||
// These are the same values as STREAM_SEEK. See
|
||||
// These are the same values as STREAM_SEEK. See
|
||||
// https://msdn.microsoft.com/en-us/library/windows/desktop/aa380359(v=vs.85).aspx for additional details.
|
||||
enum Reference { START = SEEK_SET, CURRENT = SEEK_CUR, END = SEEK_END };
|
||||
|
||||
|
@ -185,8 +87,8 @@ public:
|
|||
return S_OK;
|
||||
}
|
||||
|
||||
// IUnknown.
|
||||
// This is the loong way. Look at ComClass<> in src\inc\ComHelper.hpp for an example on how to avoid implementing IUnknown without pain.
|
||||
// IUnknown.
|
||||
// This is the loong way. Look at ComClass<> in src\inc\ComHelper.hpp for an example on how to avoid implementing IUnknown with pain.
|
||||
virtual ULONG STDMETHODCALLTYPE AddRef() noexcept override
|
||||
{
|
||||
return ++m_ref;
|
||||
|
@ -307,7 +209,7 @@ public:
|
|||
if (bytesRead) { bytesRead->QuadPart = read; }
|
||||
if (bytesWritten) { bytesWritten->QuadPart = written;}
|
||||
return S_OK;
|
||||
}
|
||||
}
|
||||
|
||||
// For more information see: https://docs.microsoft.com/en-us/windows/desktop/api/objidl/nn-objidl-istream
|
||||
virtual HRESULT STDMETHODCALLTYPE SetSize(ULARGE_INTEGER) noexcept override { return E_NOTIMPL; }
|
||||
|
@ -410,76 +312,6 @@ protected:
|
|||
std::atomic<std::uint32_t> m_ref;
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
// TODO: paths coming in SHOULD have platform-appropriate path separators
|
||||
void replace(std::wstring& input, const wchar_t oldchar, const wchar_t newchar)
|
||||
{
|
||||
std::size_t found = input.find_first_of(oldchar);
|
||||
while (found != std::string::npos)
|
||||
{
|
||||
input[found] = newchar;
|
||||
found = input.find_first_of(oldchar, found+1);
|
||||
}
|
||||
}
|
||||
|
||||
int mkdirp(std::wstring& utf16Path)
|
||||
{
|
||||
replace(utf16Path, L'/', L'\\');
|
||||
for (std::size_t i = 0; i < utf16Path.size(); i++)
|
||||
{
|
||||
if (utf16Path[i] == L'\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (utf16Path[i] == L'\\')
|
||||
{
|
||||
// Temporarily set string to terminate at the '\' character
|
||||
// to obtain name of the subdirectory to create
|
||||
utf16Path[i] = L'\0';
|
||||
|
||||
if (!CreateDirectory(utf16Path.c_str(), nullptr))
|
||||
{
|
||||
int lastError = static_cast<int>(GetLastError());
|
||||
|
||||
// It is normal for CreateDirectory to fail if the subdirectory
|
||||
// already exists. Other errors should not be ignored.
|
||||
if (lastError != ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
return lastError;
|
||||
}
|
||||
}
|
||||
// Restore original string
|
||||
utf16Path[i] = L'\\'; /* TODO: paths coming in SHOULD have platform-appropriate path separators */
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// not all POSIX implementations provide an implementation of mkdirp
|
||||
int mkdirp(std::wstring& utf16Path)
|
||||
{
|
||||
std::string utf8Path = utf16_to_utf8(utf16Path);
|
||||
auto lastSlash = utf8Path.find_last_of("/");
|
||||
std::string path = utf8Path.substr(0, lastSlash);
|
||||
char* p = const_cast<char*>(path.c_str());
|
||||
if (*p == '/') { p++; }
|
||||
while (*p != '\0')
|
||||
{
|
||||
while (*p != '\0' && *p != '/') { p++; }
|
||||
|
||||
char v = *p;
|
||||
*p = '\0';
|
||||
if (-1 == mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) && errno != EEXIST)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
*p = v;
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
HRESULT GetOutputStream(LPCWSTR path, LPCWSTR fileName, IStream** stream)
|
||||
{
|
||||
const int MaxFileNameLength = 200;
|
||||
|
|
|
@ -0,0 +1,24 @@
|
|||
# Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
# See LICENSE file in the project root for full license information.
|
||||
|
||||
cmake_minimum_required(VERSION 3.8.0 FATAL_ERROR)
|
||||
project (PackSample)
|
||||
|
||||
# Define two variables in order not to repeat ourselves.
|
||||
set(BINARY_NAME PackSample)
|
||||
|
||||
if(WIN32)
|
||||
set(DESCRIPTION "PackSample manifest")
|
||||
configure_file(${MSIX_PROJECT_ROOT}/manifest.cmakein ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.exe.manifest CRLF)
|
||||
set(MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/${BINARY_NAME}.exe.manifest)
|
||||
endif()
|
||||
|
||||
add_executable(${BINARY_NAME}
|
||||
PackSample.cpp
|
||||
${MANIFEST}
|
||||
)
|
||||
|
||||
target_include_directories(${BINARY_NAME} PRIVATE ${MSIX_BINARY_ROOT}/src/msix ../inc)
|
||||
|
||||
add_dependencies(${BINARY_NAME} msix)
|
||||
target_link_libraries(${BINARY_NAME} PUBLIC msix)
|
|
@ -0,0 +1,320 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#ifdef WIN32
|
||||
#include <experimental/filesystem>
|
||||
using namespace std::experimental::filesystem;
|
||||
#else
|
||||
#include <queue>
|
||||
#include <fts.h>
|
||||
#include <dirent.h>
|
||||
#endif
|
||||
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "MSIXWindows.hpp"
|
||||
|
||||
#include "Helpers.hpp"
|
||||
|
||||
using namespace MsixSample::Helper;
|
||||
|
||||
int Help()
|
||||
{
|
||||
std::cout << std::endl;
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "------" << std::endl;
|
||||
std::cout << "\t" << "PackSample -d <directory> -p <output package> " << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Description:" << std::endl;
|
||||
std::cout << "------------" << std::endl;
|
||||
std::cout << "\tSample to show the usage of the AppxPackaging APIs. Takes a directory and" << std::endl;
|
||||
std::cout << "\tcreates an app package using the AppxPackaging APIs. The sample assumes " << std::endl;
|
||||
std::cout << "\t the AppxManifest.xml is in the directory provided." << std::endl;
|
||||
std::cout << std::endl;
|
||||
return 0;
|
||||
}
|
||||
|
||||
// This is the same "cheat code" we use internally if no compression options is provided
|
||||
// you can use your own logic for which files to compress or not.
|
||||
// In this sample, if an extension is not listed here we default to APPX_COMPRESSION_OPTION_NORMAL
|
||||
static const std::map<std::string, APPX_COMPRESSION_OPTION> extToContentType =
|
||||
{
|
||||
{ "atom", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "appx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "b64", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "cab", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "doc", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "dot", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "docm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "docx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "dotm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "dotx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "dll", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "dtd", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "exe", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "gz", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "java", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "json", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "p7s", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "pdf", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "ps", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "potm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "potx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "ppam", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "ppsm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "ppsx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "ppt", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "pot", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "pps", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "ppa", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "pptm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "pptx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "rar", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "rss", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "soap", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "tar", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xaml", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xap", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xbap", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xhtml", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xlam", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xls", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xlt", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xla", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xlsb", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xlsm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xlsx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xltm", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xltx", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "xsl", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xslt", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "zip", APPX_COMPRESSION_OPTION_NONE },
|
||||
// Text types
|
||||
{ "c", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "cpp", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "cs", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "css", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "csv", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "h", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "htm", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "html", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "js", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "rtf", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "sct", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "txt", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xml", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "xsd", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
// Audio types
|
||||
{ "aiff", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "au", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "m4a", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "mid", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "mp3", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "smf", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "wav", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "wma", APPX_COMPRESSION_OPTION_NONE },
|
||||
// Image types
|
||||
{ "bmp", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "emf", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "gif", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "ico", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "jpg", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "jpeg", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "png", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "svg", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "tif", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "tiff", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
{ "wmf", APPX_COMPRESSION_OPTION_NORMAL },
|
||||
// Video types
|
||||
{ "avi", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "mpeg", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "mpg", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "mov", APPX_COMPRESSION_OPTION_NONE },
|
||||
{ "wmv", APPX_COMPRESSION_OPTION_NONE }
|
||||
};
|
||||
|
||||
#ifdef WIN32
|
||||
std::vector<std::string> GetAllFilesInDirectory(const std::string& directory)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
for (const auto& file : recursive_directory_iterator(directory))
|
||||
{
|
||||
if (!is_directory(file))
|
||||
{
|
||||
files.push_back(file.path().string());
|
||||
}
|
||||
}
|
||||
return files;
|
||||
}
|
||||
#else
|
||||
std::vector<std::string> GetAllFilesInDirectory(const std::string& directory)
|
||||
{
|
||||
std::vector<std::string> files;
|
||||
std::queue<std::string> directories;
|
||||
directories.push(directory);
|
||||
|
||||
static std::string dot(".");
|
||||
static std::string dotdot("..");
|
||||
do
|
||||
{
|
||||
auto root = directories.front();
|
||||
directories.pop();
|
||||
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(root.c_str()), closedir);
|
||||
if (dir.get() != nullptr)
|
||||
{
|
||||
struct dirent* dp;
|
||||
while((dp = readdir(dir.get())) != nullptr)
|
||||
{
|
||||
std::string fileName = std::string(dp->d_name);
|
||||
std::string child = root + "/" + fileName;
|
||||
if (dp->d_type == DT_DIR)
|
||||
{
|
||||
if ((fileName != dot) && (fileName != dotdot))
|
||||
{
|
||||
directories.push(child);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
files.push_back(child);
|
||||
}
|
||||
}
|
||||
}
|
||||
} while (!directories.empty());
|
||||
return files;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Add payload files to the package and returns the AppxManifest stream
|
||||
HRESULT AddPayloadFilesAndGetManifestStream(IAppxPackageWriterUtf8* packageWriter, const std::string& directory, IStream** appxManifestStream)
|
||||
{
|
||||
*appxManifestStream = nullptr;
|
||||
auto payloadFiles = GetAllFilesInDirectory(directory);
|
||||
for (const auto& file : payloadFiles)
|
||||
{
|
||||
// Remove the top level directory from the name
|
||||
std::string name = file.substr(directory.size()+1);
|
||||
if (!IsFootPrintFile(name))
|
||||
{
|
||||
std::cout << "Packing payload file: " << name << std::endl;
|
||||
ComPtr<IStream> stream;
|
||||
RETURN_IF_FAILED(CreateStreamOnFile(const_cast<char*>(file.c_str()), true, &stream));
|
||||
|
||||
std::string ext = name.substr(name.find_last_of('.')+1);
|
||||
APPX_COMPRESSION_OPTION compressOpt = APPX_COMPRESSION_OPTION_NORMAL; // default
|
||||
auto compression = extToContentType.find(ext);
|
||||
if (compression != extToContentType.end())
|
||||
{
|
||||
compressOpt = compression->second;
|
||||
}
|
||||
|
||||
RETURN_IF_FAILED(packageWriter->AddPayloadFile(
|
||||
name.c_str(),
|
||||
"application/fake-content-type", // sample :)
|
||||
compressOpt,
|
||||
stream.Get()
|
||||
));
|
||||
}
|
||||
else if(IsAppxManifest(name))
|
||||
{
|
||||
RETURN_IF_FAILED(CreateStreamOnFile(const_cast<char*>(file.c_str()), true, appxManifestStream));
|
||||
}
|
||||
}
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
HRESULT PackPackage(const std::string& package, const std::string& directory)
|
||||
{
|
||||
// Initialize the factory.
|
||||
ComPtr<IAppxFactory> appxFactory;
|
||||
RETURN_IF_FAILED(CoCreateAppxFactoryWithHeap(
|
||||
MyAllocate,
|
||||
MyFree,
|
||||
MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPSIGNATURE,
|
||||
&appxFactory));
|
||||
|
||||
// Create output stream
|
||||
ComPtr<IStream> outputStream;
|
||||
RETURN_IF_FAILED(CreateStreamOnFile(const_cast<char*>(package.c_str()), false, &outputStream));
|
||||
|
||||
// Create the package writer. Note, APPX_PACKAGE_SETTINGS is not implemented for the MSIX SDK
|
||||
// we always create a zip64 and use SHA256 as hash method.
|
||||
ComPtr<IAppxPackageWriter> packageWriter;
|
||||
RETURN_IF_FAILED(appxFactory->CreatePackageWriter(outputStream.Get(), nullptr, &packageWriter));
|
||||
|
||||
// The MSIX SDK includes Ut8 variants for convenience for non-Windows platforms. We use them in this sample.
|
||||
ComPtr<IAppxPackageWriterUtf8> packageWriterUtf8;
|
||||
RETURN_IF_FAILED(packageWriter->QueryInterface(UuidOfImpl<IAppxPackageWriterUtf8>::iid, reinterpret_cast<void**>(&packageWriterUtf8)));
|
||||
|
||||
// The sample assumes that AppxManifest.xml is in the directory.
|
||||
ComPtr<IStream> manifestStream;
|
||||
RETURN_IF_FAILED(AddPayloadFilesAndGetManifestStream(packageWriterUtf8.Get(), directory, &manifestStream));
|
||||
if (manifestStream.Get() == nullptr)
|
||||
{
|
||||
std::cout << "Error: AppxManifest.xml not found" << std::endl;
|
||||
return E_FAIL;
|
||||
}
|
||||
|
||||
// Close the package
|
||||
RETURN_IF_FAILED(packageWriter->Close(manifestStream.Get()));
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
int index = 1;
|
||||
std::string package;
|
||||
std::string directory;
|
||||
while (index < argc)
|
||||
{
|
||||
if (strcmp(argv[index], "-p") == 0)
|
||||
{
|
||||
package = argv[++index];
|
||||
index++;
|
||||
}
|
||||
else if (strcmp(argv[index], "-d") == 0)
|
||||
{
|
||||
directory = argv[++index];
|
||||
index++;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Help();
|
||||
}
|
||||
}
|
||||
if (package.empty() || directory.empty())
|
||||
{
|
||||
return Help();
|
||||
}
|
||||
|
||||
HRESULT hr = PackPackage(package, directory);
|
||||
|
||||
if (hr != S_OK)
|
||||
{
|
||||
remove(package.c_str());
|
||||
std::cout << "Error: " << std::hex << hr << " while extracting the appx package" <<std::endl;
|
||||
Text<char> text;
|
||||
auto logResult = GetLogTextUTF8(MyAllocate, &text);
|
||||
if (0 == logResult)
|
||||
{
|
||||
std::cout << "LOG:" << std::endl << text.content << std::endl;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "UNABLE TO GET LOG WITH HR=" << std::hex << logResult << std::endl;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << "Success: package " << package << " created" << std::endl;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
|
@ -0,0 +1,216 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
// This is a helper file for the samples. Includes useful RAII wrappers, flow control
|
||||
// macros and other common functions used by the samples.
|
||||
|
||||
#include <codecvt>
|
||||
#include <string>
|
||||
#include <algorithm>
|
||||
#include <locale>
|
||||
|
||||
#ifdef WIN32
|
||||
#define UNICODE
|
||||
#include <windows.h>
|
||||
#else
|
||||
// required posix-specific headers
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <unistd.h>
|
||||
#endif
|
||||
|
||||
// Flow control macros
|
||||
#define RETURN_IF_FAILED(a) \
|
||||
{ HRESULT __hr = a; \
|
||||
if (FAILED(__hr)) \
|
||||
{ return __hr; } \
|
||||
}
|
||||
|
||||
namespace MsixSample { namespace Helper {
|
||||
|
||||
// Allocators
|
||||
LPVOID STDMETHODCALLTYPE MyAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
void STDMETHODCALLTYPE MyFree(LPVOID pv) { std::free(pv); }
|
||||
|
||||
// Stripped down ComPtr provided for those platforms that do not already have a ComPtr class.
|
||||
template <class T>
|
||||
class ComPtr
|
||||
{
|
||||
public:
|
||||
// default ctor
|
||||
ComPtr() = default;
|
||||
ComPtr(T* ptr) : m_ptr(ptr) { InternalAddRef(); }
|
||||
|
||||
~ComPtr() { InternalRelease(); }
|
||||
|
||||
// For use instead of ComPtr<T> t(new Foo(...)); given that the class has an Initialize function
|
||||
template<class U, class... Args>
|
||||
static HRESULT MakeAndInitialize(T** result, Args&&... args)
|
||||
{
|
||||
ComPtr<U> inner(new U());
|
||||
RETURN_IF_FAILED(inner->Initialize(std::forward<Args>(args)...));
|
||||
RETURN_IF_FAILED(inner->QueryInterface(UuidOfImpl<T>::iid, reinterpret_cast<void**>(result)));
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
// For use instead of ComPtr<T> t(new Foo(...));
|
||||
template<class U, class... Args>
|
||||
static ComPtr<T> Make(Args&&... args)
|
||||
{
|
||||
ComPtr<T> result;
|
||||
result.m_ptr = new U(std::forward<Args>(args)...);
|
||||
return result;
|
||||
}
|
||||
|
||||
inline T* operator->() const { return m_ptr; }
|
||||
inline T* Get() const { return m_ptr; }
|
||||
T* Detach()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
m_ptr = nullptr;
|
||||
return temp;
|
||||
}
|
||||
|
||||
inline T** operator&()
|
||||
{ InternalRelease();
|
||||
return &m_ptr;
|
||||
}
|
||||
|
||||
protected:
|
||||
T* m_ptr = nullptr;
|
||||
|
||||
inline void InternalAddRef() { if (m_ptr) { m_ptr->AddRef(); } }
|
||||
inline void InternalRelease()
|
||||
{
|
||||
T* temp = m_ptr;
|
||||
if (temp)
|
||||
{ m_ptr = nullptr;
|
||||
temp->Release();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Helper class to free string buffers obtained from the packaging APIs.
|
||||
template<typename T>
|
||||
class Text
|
||||
{
|
||||
public:
|
||||
T** operator&() { return &content; }
|
||||
~Text() { Cleanup(); }
|
||||
T* Get() { return content; }
|
||||
|
||||
T* content = nullptr;
|
||||
protected:
|
||||
void Cleanup() { if (content) { MyFree(content); content = nullptr; } }
|
||||
};
|
||||
|
||||
// Useful string convertions functions
|
||||
std::string utf16_to_utf8(const std::wstring& utf16string)
|
||||
{
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8<wchar_t>>{}.to_bytes(utf16string.data());
|
||||
std::string result(converted.begin(), converted.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
std::wstring utf8_to_utf16(const std::string& utf8string)
|
||||
{
|
||||
// see https://connect.microsoft.com/VisualStudio/feedback/details/1403302/unresolved-external-when-using-codecvt-utf8
|
||||
#ifdef WIN32
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<unsigned short>, unsigned short>{}.from_bytes(utf8string.data());
|
||||
#else
|
||||
auto converted = std::wstring_convert<std::codecvt_utf8_utf16<char16_t>, char16_t>{}.from_bytes(utf8string.data());
|
||||
#endif
|
||||
std::wstring result(converted.begin(), converted.end());
|
||||
return result;
|
||||
}
|
||||
|
||||
// Directory helpers
|
||||
#ifdef WIN32
|
||||
// TODO: paths coming in SHOULD have platform-appropriate path separators
|
||||
void replace(std::wstring& input, const wchar_t oldchar, const wchar_t newchar)
|
||||
{
|
||||
std::size_t found = input.find_first_of(oldchar);
|
||||
while (found != std::string::npos)
|
||||
{
|
||||
input[found] = newchar;
|
||||
found = input.find_first_of(oldchar, found+1);
|
||||
}
|
||||
}
|
||||
|
||||
int mkdirp(std::wstring& utf16Path)
|
||||
{
|
||||
replace(utf16Path, L'/', L'\\');
|
||||
for (std::size_t i = 0; i < utf16Path.size(); i++)
|
||||
{
|
||||
if (utf16Path[i] == L'\0')
|
||||
{
|
||||
break;
|
||||
}
|
||||
else if (utf16Path[i] == L'\\')
|
||||
{
|
||||
// Temporarily set string to terminate at the '\' character
|
||||
// to obtain name of the subdirectory to create
|
||||
utf16Path[i] = L'\0';
|
||||
|
||||
if (!CreateDirectory(utf16Path.c_str(), nullptr))
|
||||
{
|
||||
int lastError = static_cast<int>(GetLastError());
|
||||
|
||||
// It is normal for CreateDirectory to fail if the subdirectory
|
||||
// already exists. Other errors should not be ignored.
|
||||
if (lastError != ERROR_ALREADY_EXISTS)
|
||||
{
|
||||
return lastError;
|
||||
}
|
||||
}
|
||||
// Restore original string
|
||||
utf16Path[i] = L'\\'; /* TODO: paths coming in SHOULD have platform-appropriate path separators */
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#else
|
||||
// not all POSIX implementations provide an implementation of mkdirp
|
||||
int mkdirp(std::wstring& utf16Path)
|
||||
{
|
||||
std::string utf8Path = utf16_to_utf8(utf16Path);
|
||||
auto lastSlash = utf8Path.find_last_of("/");
|
||||
std::string path = utf8Path.substr(0, lastSlash);
|
||||
char* p = const_cast<char*>(path.c_str());
|
||||
if (*p == '/') { p++; }
|
||||
while (*p != '\0')
|
||||
{
|
||||
while (*p != '\0' && *p != '/') { p++; }
|
||||
|
||||
char v = *p;
|
||||
*p = '\0';
|
||||
if (-1 == mkdir(path.c_str(), S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) && errno != EEXIST)
|
||||
{
|
||||
return errno;
|
||||
}
|
||||
*p = v;
|
||||
p++;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Footprint files helpers
|
||||
bool IsFootPrintFile(std::string normalized)
|
||||
{
|
||||
std::transform(normalized.begin(), normalized.end(), normalized.begin(), ::tolower);
|
||||
return ((normalized == "appxmanifest.xml") ||
|
||||
(normalized == "appxsignature.p7x") ||
|
||||
(normalized == "appxblockmap.xml") ||
|
||||
(normalized == "[content_types].xml") ||
|
||||
(normalized.rfind("appxmetadata", 0) != std::string::npos) ||
|
||||
(normalized.rfind("microsoft.system.package.metadata", 0) != std::string::npos));
|
||||
}
|
||||
|
||||
bool IsAppxManifest(std::string normalized)
|
||||
{
|
||||
std::transform(normalized.begin(), normalized.end(), normalized.begin(), ::tolower);
|
||||
return (normalized == "appxmanifest.xml");
|
||||
}
|
||||
} }
|
|
@ -3,7 +3,7 @@
|
|||
# See LICENSE file in the project root for full license information.
|
||||
|
||||
cmake_minimum_required(VERSION 3.8.0 FATAL_ERROR)
|
||||
add_custom_target(SRC)
|
||||
|
||||
add_subdirectory(msix)
|
||||
add_subdirectory(makemsix)
|
||||
add_subdirectory(test)
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "XmlWriter.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
static const std::uint32_t DefaultBlockSize = 65536;
|
||||
|
||||
class BlockMapWriter final
|
||||
{
|
||||
public:
|
||||
BlockMapWriter();
|
||||
|
||||
void AddFile(const std::string& name, std::uint64_t uncompressedSize, std::uint32_t lfh);
|
||||
void AddBlock(const std::vector<std::uint8_t>& block, ULONG size, bool isCompressed);
|
||||
void CloseFile();
|
||||
void Close();
|
||||
ComPtr<IStream> GetStream() { return m_xmlWriter.GetStream(); }
|
||||
|
||||
protected:
|
||||
XmlWriter m_xmlWriter;
|
||||
};
|
||||
}
|
|
@ -13,8 +13,32 @@
|
|||
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <array>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
// names of footprint files.
|
||||
#define APPXBLOCKMAP_XML "AppxBlockMap.xml"
|
||||
#define APPXMANIFEST_XML "AppxManifest.xml"
|
||||
#define CODEINTEGRITY_CAT "AppxMetadata/CodeIntegrity.cat"
|
||||
#define APPXSIGNATURE_P7X "AppxSignature.p7x"
|
||||
#define CONTENT_TYPES_XML "[Content_Types].xml"
|
||||
#define APPXBUNDLEMANIFEST_XML "AppxMetadata/AppxBundleManifest.xml"
|
||||
|
||||
static const std::array<const char*, 4> footprintFiles =
|
||||
{ APPXMANIFEST_XML,
|
||||
APPXBLOCKMAP_XML,
|
||||
APPXSIGNATURE_P7X,
|
||||
CODEINTEGRITY_CAT,
|
||||
};
|
||||
|
||||
static const std::array<const char*, 3> bundleFootprintFiles =
|
||||
{
|
||||
APPXBUNDLEMANIFEST_XML,
|
||||
APPXBLOCKMAP_XML,
|
||||
APPXSIGNATURE_P7X,
|
||||
};
|
||||
|
||||
class AppxFactory final : public ComClass<AppxFactory, IMsixFactory, IAppxFactory, IXmlFactory, IAppxBundleFactory, IMsixFactoryOverrides, IAppxFactoryUtf8>
|
||||
{
|
||||
public:
|
||||
|
|
|
@ -290,6 +290,8 @@ namespace MSIX {
|
|||
HRESULT STDMETHODCALLTYPE GetDocumentElement(IMsixElement** documentElement) noexcept override;
|
||||
|
||||
protected:
|
||||
std::vector<std::string> GetCapabilities(APPX_CAPABILITY_CLASS_TYPE capabilityClass);
|
||||
|
||||
ComPtr<IMsixFactory> m_factory;
|
||||
ComPtr<IStream> m_stream;
|
||||
ComPtr<IAppxManifestPackageId> m_packageId;
|
||||
|
|
|
@ -23,6 +23,7 @@
|
|||
#include "AppxFactory.hpp"
|
||||
#include "AppxPackageInfo.hpp"
|
||||
#include "AppxManifestObject.hpp"
|
||||
#include "DirectoryObject.hpp"
|
||||
|
||||
// internal interface
|
||||
// {51b2c456-aaa9-46d6-8ec9-298220559189}
|
||||
|
@ -35,7 +36,7 @@ class IPackage : public IUnknown
|
|||
#endif
|
||||
{
|
||||
public:
|
||||
virtual void Unpack(MSIX_PACKUNPACK_OPTION options, const MSIX::ComPtr<IStorageObject>& to) = 0;
|
||||
virtual void Unpack(MSIX_PACKUNPACK_OPTION options, const MSIX::ComPtr<IDirectoryObject>& to) = 0;
|
||||
virtual std::vector<std::string>& GetFootprintFiles() = 0;
|
||||
};
|
||||
MSIX_INTERFACE(IPackage, 0x51b2c456,0xaaa9,0x46d6,0x8e,0xc9,0x29,0x82,0x20,0x55,0x91,0x89);
|
||||
|
@ -105,7 +106,7 @@ namespace MSIX {
|
|||
}
|
||||
|
||||
// internal IPackage methods
|
||||
void Unpack(MSIX_PACKUNPACK_OPTION options, const ComPtr<IStorageObject>& to) override;
|
||||
void Unpack(MSIX_PACKUNPACK_OPTION options, const ComPtr<IDirectoryObject>& to) override;
|
||||
std::vector<std::string>& GetFootprintFiles() override { return m_footprintFiles; }
|
||||
|
||||
// IAppxPackageReader
|
||||
|
@ -124,10 +125,8 @@ namespace MSIX {
|
|||
// HRESULT STDMETHODCALLTYPE GetBlockMap(IAppxBlockMapReader** blockMapReader) noexcept override;
|
||||
|
||||
// IStorageObject methods
|
||||
const char* GetPathSeparator() override;
|
||||
std::vector<std::string> GetFileNames(FileNameOptions options) override;
|
||||
ComPtr<IStream> GetFile(const std::string& fileName) override;
|
||||
ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) override;
|
||||
std::string GetFileName() override;
|
||||
|
||||
// IAppxPackageReaderUtf8
|
||||
|
|
|
@ -0,0 +1,86 @@
|
|||
//
|
||||
// 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 <map>
|
||||
#include <memory>
|
||||
#include <future>
|
||||
|
||||
// internal interface
|
||||
// {32e89da5-7cbb-4443-8cf0-b84eedb51d0a}
|
||||
#ifndef WIN32
|
||||
interface IPackageWriter : public IUnknown
|
||||
#else
|
||||
#include "Unknwn.h"
|
||||
#include "Objidl.h"
|
||||
class IPackageWriter : public IUnknown
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
// TODO: add options if needed
|
||||
virtual void PackPayloadFiles(const MSIX::ComPtr<IDirectoryObject>& from) = 0;
|
||||
};
|
||||
MSIX_INTERFACE(IPackageWriter, 0x32e89da5,0x7cbb,0x4443,0x8c,0xf0,0xb8,0x4e,0xed,0xb5,0x1d,0x0a);
|
||||
|
||||
namespace MSIX {
|
||||
class AppxPackageWriter final : public ComClass<AppxPackageWriter, IPackageWriter, IAppxPackageWriter,
|
||||
IAppxPackageWriterUtf8, IAppxPackageWriter3, IAppxPackageWriter3Utf8>
|
||||
{
|
||||
public:
|
||||
AppxPackageWriter(IMsixFactory* factory, const ComPtr<IZipWriter>& zip);
|
||||
~AppxPackageWriter() {};
|
||||
|
||||
// IPackageWriter
|
||||
void PackPayloadFiles(const ComPtr<IDirectoryObject>& from) override;
|
||||
|
||||
// IAppxPackageWriter
|
||||
HRESULT STDMETHODCALLTYPE AddPayloadFile(LPCWSTR fileName, LPCWSTR contentType,
|
||||
APPX_COMPRESSION_OPTION compressionOption, IStream *inputStream) noexcept override;
|
||||
HRESULT STDMETHODCALLTYPE Close(IStream *manifest) noexcept override;
|
||||
|
||||
// IAppxPackageWriterUtf8
|
||||
HRESULT STDMETHODCALLTYPE AddPayloadFile(LPCSTR fileName, LPCSTR contentType,
|
||||
APPX_COMPRESSION_OPTION compressionOption, IStream* inputStream) noexcept override;
|
||||
|
||||
// IAppxPackageWriter3
|
||||
HRESULT STDMETHODCALLTYPE AddPayloadFiles(UINT32 fileCount, APPX_PACKAGE_WRITER_PAYLOAD_STREAM* payloadFiles,
|
||||
UINT64 memoryLimit) noexcept override;
|
||||
|
||||
// IAppxPackageWriter3Utf8
|
||||
HRESULT STDMETHODCALLTYPE AddPayloadFiles(UINT32 fileCount, APPX_PACKAGE_WRITER_PAYLOAD_STREAM_UTF8* payloadFiles,
|
||||
UINT64 memoryLimit) noexcept override;
|
||||
|
||||
protected:
|
||||
typedef enum
|
||||
{
|
||||
Open = 1,
|
||||
Closed = 2,
|
||||
Failed = 3
|
||||
}
|
||||
WriterState;
|
||||
|
||||
void ValidateAndAddPayloadFile(const std::string& name, IStream* stream,
|
||||
APPX_COMPRESSION_OPTION compressionOpt, const char* contentType);
|
||||
|
||||
void AddFileToPackage(const std::string& name, IStream* stream, bool toCompress,
|
||||
bool addToBlockMap, const char* contentType, bool forceContentTypeOverride = false);
|
||||
|
||||
void ValidateCompressionOption(APPX_COMPRESSION_OPTION compressionOpt);
|
||||
|
||||
WriterState m_state;
|
||||
ComPtr<IMsixFactory> m_factory;
|
||||
ComPtr<IZipWriter> m_zipWriter;
|
||||
BlockMapWriter m_blockMapWriter;
|
||||
ContentTypeWriter m_contentTypeWriter;
|
||||
};
|
||||
}
|
||||
|
|
@ -81,6 +81,7 @@ SpecializeUuidOfImpl(IAppxBundleReader);
|
|||
SpecializeUuidOfImpl(IAppxBundleManifestReader);
|
||||
SpecializeUuidOfImpl(IAppxBundleManifestPackageInfoEnumerator);
|
||||
SpecializeUuidOfImpl(IAppxBundleManifestPackageInfo);
|
||||
SpecializeUuidOfImpl(IAppxPackageWriter3);
|
||||
SpecializeUuidOfImpl(IAppxManifestOptionalPackageInfo);
|
||||
#endif
|
||||
|
||||
|
@ -123,6 +124,7 @@ interface IAppxBundleReader;
|
|||
interface IAppxBundleManifestReader;
|
||||
interface IAppxBundleManifestPackageInfoEnumerator;
|
||||
interface IAppxBundleManifestPackageInfo;
|
||||
interface IAppxPackageWriter3;
|
||||
interface IAppxManifestOptionalPackageInfo;
|
||||
|
||||
extern "C"{
|
||||
|
@ -1061,6 +1063,28 @@ enum tagLOCKTYPE
|
|||
};
|
||||
#endif /* __IAppxBundleManifestPackageInfo_INTERFACE_DEFINED__ */
|
||||
|
||||
#ifndef __IAppxPackageWriter3_INTERFACE_DEFINED__
|
||||
#define __IAppxPackageWriter3_INTERFACE_DEFINED__
|
||||
|
||||
typedef struct APPX_PACKAGE_WRITER_PAYLOAD_STREAM
|
||||
{
|
||||
IStream* inputStream;
|
||||
LPCWSTR fileName;
|
||||
LPCWSTR contentType;
|
||||
APPX_COMPRESSION_OPTION compressionOption;
|
||||
} APPX_PACKAGE_WRITER_PAYLOAD_STREAM;
|
||||
|
||||
// {a83aacd3-41c0-4501-b8a3-74164f50b2fd}
|
||||
MSIX_INTERFACE(IAppxPackageWriter3, 0xa83aacd3,0x41c0,0x4501,0xb8,0xa3,0x74,0x16,0x4f,0x50,0xb2,0xfd);
|
||||
interface IAppxPackageWriter3 : public IUnknown
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE AddPayloadFiles(
|
||||
/* [in] */ UINT32 fileCount,
|
||||
/* [size_is][in] */ APPX_PACKAGE_WRITER_PAYLOAD_STREAM* payloadFiles,
|
||||
/* [in] */ UINT64 memoryLimit) = 0;
|
||||
};
|
||||
#endif /* __IAppxPackageWriter3_INTERFACE_DEFINED__ */
|
||||
#ifndef __IAppxManifestOptionalPackageInfo_INTERFACE_DEFINED__
|
||||
#define __IAppxManifestOptionalPackageInfo_INTERFACE_DEFINED__
|
||||
|
||||
|
@ -1228,9 +1252,12 @@ interface IAppxManifestPackageDependencyUtf8;
|
|||
interface IAppxManifestPackageIdUtf8;
|
||||
interface IAppxManifestPropertiesUtf8;
|
||||
interface IAppxManifestQualifiedResourceUtf8;
|
||||
interface IAppxManifestCapabilitiesEnumeratorUtf8;
|
||||
interface IAppxManifestResourcesEnumeratorUtf8;
|
||||
interface IAppxManifestTargetDeviceFamilyUtf8;
|
||||
interface IAppxPackageReaderUtf8;
|
||||
interface IAppxPackageWriterUtf8;
|
||||
interface IAppxPackageWriter3Utf8;
|
||||
interface IAppxManifestOptionalPackageInfoUtf8;
|
||||
|
||||
#ifndef __IAppxBlockMapFileUtf8_INTERFACE_DEFINED__
|
||||
|
@ -1412,6 +1439,19 @@ interface IAppxManifestOptionalPackageInfoUtf8;
|
|||
};
|
||||
#endif /* __IAppxManifestQualifiedResourceUtf8_INTERFACE_DEFINED__ */
|
||||
|
||||
#ifndef __IAppxManifestCapabilitiesEnumeratorUtf8_INTERFACE_DEFINED__
|
||||
#define __IAppxManifestCapabilitiesEnumeratorUtf8_INTERFACE_DEFINED__
|
||||
|
||||
// {cc422f8e-a4d9-4f2e-bb49-ac3a5ce2a2f0}
|
||||
MSIX_INTERFACE(IAppxManifestCapabilitiesEnumeratorUtf8,0xcc422f8e,0xa4d9,0x4f2e,0xbb,0x49,0xac,0x3a,0x5c,0xe2,0xa2,0xf0);
|
||||
interface IAppxManifestCapabilitiesEnumeratorUtf8 : public IUnknown
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE GetCurrent(
|
||||
/* [retval][string][out] */ LPSTR *resource) noexcept = 0;
|
||||
};
|
||||
#endif /* __IAppxManifestCapabilitiesEnumeratorUtf8_INTERFACE_DEFINED__ */
|
||||
|
||||
#ifndef __IAppxManifestResourcesEnumeratorUtf8_INTERFACE_DEFINED__
|
||||
#define __IAppxManifestResourcesEnumeratorUtf8_INTERFACE_DEFINED__
|
||||
|
||||
|
@ -1452,6 +1492,44 @@ interface IAppxManifestOptionalPackageInfoUtf8;
|
|||
};
|
||||
#endif /* __IAppxPackageReaderUtf8_INTERFACE_DEFINED__ */
|
||||
|
||||
#ifndef __IAppxPackageWriterUtf8_INTERFACE_DEFINED__
|
||||
#define __IAppxPackageWriterUtf8_INTERFACE_DEFINED__
|
||||
|
||||
// {578ee26e-642a-4b03-aeda-8a374ff71b5b}
|
||||
MSIX_INTERFACE(IAppxPackageWriterUtf8, 0x578ee26e,0x642a,0x4b03,0xae,0xda,0x8a,0x37,0x4f,0xf7,0x1b,0x5b);
|
||||
interface IAppxPackageWriterUtf8 : public IUnknown
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE AddPayloadFile(
|
||||
/* [string][in] */ LPCSTR fileName,
|
||||
/* [string][in] */ LPCSTR contentType,
|
||||
/* [in] */ APPX_COMPRESSION_OPTION compressionOption,
|
||||
/* [in] */ IStream* inputStream) noexcept = 0;
|
||||
};
|
||||
#endif /* __IAppxPackageWriterUtf8_INTERFACE_DEFINED__ */
|
||||
|
||||
#ifndef __IAppxPackageWriter3Utf8_INTERFACE_DEFINED__
|
||||
#define __IAppxPackageWriter3Utf8_INTERFACE_DEFINED__
|
||||
|
||||
typedef struct APPX_PACKAGE_WRITER_PAYLOAD_STREAM_UTF8
|
||||
{
|
||||
IStream* inputStream;
|
||||
LPCSTR fileName;
|
||||
LPCSTR contentType;
|
||||
APPX_COMPRESSION_OPTION compressionOption;
|
||||
} APPX_PACKAGE_WRITER_PAYLOAD_STREAM_UTF8;
|
||||
|
||||
// {fc8f7fd6-3a35-49cc-b62f-ea0ee839e25e}
|
||||
MSIX_INTERFACE(IAppxPackageWriter3Utf8, 0xfc8f7fd6,0x3a35,0x49cc,0xb6,0x2f,0xea,0x0e,0xe8,0x39,0xe2,0x5e);
|
||||
interface IAppxPackageWriter3Utf8 : public IUnknown
|
||||
{
|
||||
public:
|
||||
virtual HRESULT STDMETHODCALLTYPE AddPayloadFiles(
|
||||
/* [in] */ UINT32 fileCount,
|
||||
/* [size_is][in] */ APPX_PACKAGE_WRITER_PAYLOAD_STREAM_UTF8* payloadFiles,
|
||||
/* [in] */ UINT64 memoryLimit) = 0;
|
||||
};
|
||||
#endif /* __IAppxPackageWriter3Utf8_INTERFACE_DEFINED__ */
|
||||
#ifndef __IAppxManifestOptionalPackageInfoUtf8_INTERFACE_DEFINED__
|
||||
#define __IAppxManifestOptionalPackageInfoUtf8_INTERFACE_DEFINED__
|
||||
|
||||
|
@ -1472,7 +1550,10 @@ enum MSIX_VALIDATION_OPTION
|
|||
MSIX_VALIDATION_OPTION_FULL = 0x0,
|
||||
MSIX_VALIDATION_OPTION_SKIPSIGNATURE = 0x1,
|
||||
MSIX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN = 0x2,
|
||||
MSIX_VALIDATION_OPTION_SKIPAPPXMANIFEST = 0x4,
|
||||
MSIX_VALIDATION_OPTION_SKIPAPPXMANIFEST_DEPRECATED = 0x4, // AppxManifest.xml must be always be valid.
|
||||
// If the SDK is compiled without USE_VALIDATION_PARSER,
|
||||
// no schema validation is done, but it needs to be
|
||||
// valid xml.
|
||||
} MSIX_VALIDATION_OPTION;
|
||||
|
||||
typedef /* [v1_enum] */
|
||||
|
@ -1520,6 +1601,7 @@ enum MSIX_APPLICABILITY_OPTIONS
|
|||
#define MSIX_APPLICABILITY_NONE MSIX_APPLICABILITY_OPTION_SKIPPLATFORM | \
|
||||
MSIX_APPLICABILITY_OPTION_SKIPLANGUAGE \
|
||||
|
||||
// Unpack
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackage(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
|
@ -1562,6 +1644,17 @@ MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundleFromStream(
|
|||
char* utf8Destination
|
||||
) noexcept;
|
||||
|
||||
#ifdef MSIX_PACK
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE PackPackage(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
char* directoryPath,
|
||||
char* outputPackage
|
||||
) noexcept;
|
||||
|
||||
#endif // MSIX_PACK
|
||||
|
||||
// A call to called CoCreateAppxFactory is required before start using the factory on non-windows platforms specifying
|
||||
// their allocator/de-allocator pair of preference. Failure to do this will result on E_UNEXPECTED.
|
||||
typedef LPVOID STDMETHODCALLTYPE COTASKMEMALLOC(SIZE_T cb);
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
#include "RangeStream.hpp"
|
||||
#include "HashStream.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "SHA256.hpp"
|
||||
#include "Crypto.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
|
||||
#include <string>
|
||||
|
@ -61,7 +61,7 @@ namespace MSIX {
|
|||
std::uint64_t sizeRemaining = m_streamSize;
|
||||
for (auto block = blocks.begin(); ((sizeRemaining != 0) && (block != blocks.end())); block++)
|
||||
{
|
||||
auto rangeStream = ComPtr<IStream>::Make<RangeStream>(offset, std::min(sizeRemaining, BLOCKMAP_BLOCK_SIZE), stream);
|
||||
auto rangeStream = ComPtr<IStream>::Make<RangeStream>(offset, std::min(sizeRemaining, BLOCKMAP_BLOCK_SIZE), stream.Get());
|
||||
auto hashStream = ComPtr<IStream>::Make<HashStream>(rangeStream, block->hash);
|
||||
std::uint64_t blockSize = std::min(sizeRemaining, BLOCKMAP_BLOCK_SIZE);
|
||||
|
||||
|
@ -143,9 +143,9 @@ namespace MSIX {
|
|||
} CATCH_RETURN();
|
||||
|
||||
// IStreamInternal
|
||||
std::uint64_t GetSizeOnZip() override
|
||||
std::uint64_t GetSize() override
|
||||
{ // The underlying ZipFileStream/InflateStream object knows, so go ask it.
|
||||
return m_stream.As<IStreamInternal>()->GetSizeOnZip();
|
||||
return m_stream.As<IStreamInternal>()->GetSize();
|
||||
}
|
||||
|
||||
bool IsCompressed() override
|
||||
|
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Helper file for determining content type and compression settings for adding files to a package
|
||||
#pragma once
|
||||
|
||||
#include "AppxPackaging.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
class ContentType
|
||||
{
|
||||
public:
|
||||
ContentType(std::string contentType, APPX_COMPRESSION_OPTION compressionOpt) :
|
||||
m_contentType(contentType), m_compressionOpt(compressionOpt)
|
||||
{}
|
||||
|
||||
const std::string& GetContentType() { return m_contentType; }
|
||||
APPX_COMPRESSION_OPTION GetCompressionOpt() { return m_compressionOpt; }
|
||||
|
||||
static const ContentType& GetContentTypeByExtension(std::string& ext);
|
||||
static const std::string GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE footprintFile);
|
||||
|
||||
private:
|
||||
APPX_COMPRESSION_OPTION m_compressionOpt;
|
||||
std::string m_contentType;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "XmlWriter.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
|
||||
#include <map>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
class ContentTypeWriter final
|
||||
{
|
||||
public:
|
||||
ContentTypeWriter();
|
||||
|
||||
void AddContentType(const std::string& name, const std::string& contentType, bool forceOverride = false);
|
||||
void Close();
|
||||
ComPtr<IStream> GetStream() { return m_xmlWriter.GetStream(); }
|
||||
|
||||
protected:
|
||||
void AddDefault(const std::string& ext, const std::string& contentType);
|
||||
void AddOverride(const std::string& file, const std::string& contentType);
|
||||
|
||||
std::map<std::string, std::string> m_defaultExtensions;
|
||||
XmlWriter m_xmlWriter;
|
||||
};
|
||||
}
|
|
@ -13,4 +13,10 @@ namespace MSIX {
|
|||
public:
|
||||
static bool ComputeHash(std::uint8_t *buffer, std::uint32_t cbBuffer, std::vector<uint8_t>& hash);
|
||||
};
|
||||
|
||||
class Base64
|
||||
{
|
||||
public:
|
||||
static std::string ComputeBase64(const std::vector<std::uint8_t>& buffer);
|
||||
};
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ComHelper.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <zlib.h>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
class DeflateStream final : public StreamBase
|
||||
{
|
||||
public:
|
||||
DeflateStream(const ComPtr<IStream>& stream);
|
||||
~DeflateStream();
|
||||
|
||||
// IStream
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) noexcept override;
|
||||
HRESULT STDMETHODCALLTYPE Read(void* buffer, ULONG countBytes, ULONG* bytesRead) noexcept override;
|
||||
HRESULT STDMETHODCALLTYPE Write(void const *buffer, ULONG countBytes, ULONG *bytesWritten) noexcept override;
|
||||
|
||||
// IStreamInternal
|
||||
std::uint64_t GetSize() override { return m_stream.As<IStreamInternal>()->GetSize(); }
|
||||
bool IsCompressed() override { return m_stream.As<IStreamInternal>()->IsCompressed(); }
|
||||
std::string GetName() override { return m_stream.As<IStreamInternal>()->GetName(); }
|
||||
|
||||
protected:
|
||||
std::vector<std::uint8_t> Deflate(int disposition);
|
||||
|
||||
typedef enum
|
||||
{
|
||||
Open,
|
||||
Closed,
|
||||
}
|
||||
State;
|
||||
|
||||
State m_state = State::Open;
|
||||
z_stream m_zstrm;
|
||||
ComPtr<IStream> m_stream;
|
||||
};
|
||||
}
|
|
@ -13,23 +13,55 @@
|
|||
#include "StreamBase.hpp"
|
||||
#include "StorageObject.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "FileStream.hpp"
|
||||
|
||||
// internal interface
|
||||
// {1675f000-9b74-49bb-ba31-94ed7c435c28}
|
||||
#ifndef WIN32
|
||||
interface IDirectoryObject : public IUnknown
|
||||
#else
|
||||
#include "Unknwn.h"
|
||||
#include "Objidl.h"
|
||||
class IDirectoryObject : public IUnknown
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
// Opens a stream to a file by name in the storage object. If the file does not exist and mode is read,
|
||||
// or read + update, then nullptr is returned. If the file is opened with write and it does not exist,
|
||||
// then the file is created and an empty stream to the file is handed back to the caller.
|
||||
virtual MSIX::ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) = 0;
|
||||
|
||||
// Returns a multipmap sorted by last modified time. Use multimap in the unlikely case there are two files
|
||||
// with the same last modified time.
|
||||
virtual std::multimap<std::uint64_t, std::string> GetFilesByLastModDate() = 0;
|
||||
};
|
||||
MSIX_INTERFACE(IDirectoryObject, 0x1675f000,0x9b74,0x49bb,0xba,0x31,0x94,0xed,0x7c,0x43,0x5c,0x28);
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
class DirectoryObject final : public ComClass<DirectoryObject, IStorageObject>
|
||||
class DirectoryObject final : public ComClass<DirectoryObject, IStorageObject, IDirectoryObject>
|
||||
{
|
||||
public:
|
||||
DirectoryObject(std::string root) : m_root(std::move(root)) {}
|
||||
DirectoryObject(const std::string& root, bool createRootIfNecessary = false);
|
||||
|
||||
// StorageObject methods
|
||||
const char* GetPathSeparator() override;
|
||||
// IStorageObject methods
|
||||
std::vector<std::string> GetFileNames(FileNameOptions options) override;
|
||||
ComPtr<IStream> GetFile(const std::string& fileName) override;
|
||||
ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) override;
|
||||
ComPtr<IStream> GetFile(const std::string& fileName) override
|
||||
{
|
||||
std::string file = m_root + GetPathSeparator() + fileName;
|
||||
auto fileStream = ComPtr<IStream>::Make<FileStream>(file, FileStream::Mode::READ);
|
||||
return fileStream;
|
||||
}
|
||||
std::string GetFileName() override { return m_root; }
|
||||
|
||||
// IDirectoryObject
|
||||
ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) override;
|
||||
std::multimap<std::uint64_t, std::string> GetFilesByLastModDate() override;
|
||||
|
||||
static const char* GetPathSeparator();
|
||||
|
||||
protected:
|
||||
std::string m_root;
|
||||
|
||||
};//class DirectoryObject
|
||||
}
|
||||
}
|
||||
|
|
|
@ -56,13 +56,22 @@ namespace MSIX {
|
|||
// Provides an ABI exception boundary with parameter validation
|
||||
#define CATCH_RETURN() \
|
||||
catch (MSIX::Exception& e) \
|
||||
{ return static_cast<HRESULT>(e.Code()); \
|
||||
{ \
|
||||
return static_cast<HRESULT>(e.Code()); \
|
||||
} \
|
||||
catch (std::bad_alloc&) \
|
||||
{ return static_cast<HRESULT>(MSIX::Error::OutOfMemory); \
|
||||
catch (const std::bad_alloc& e) \
|
||||
{ \
|
||||
MSIX::Global::Log::Append(e.what()); \
|
||||
return static_cast<HRESULT>(MSIX::Error::OutOfMemory); \
|
||||
} \
|
||||
catch (const std::exception& e) \
|
||||
{ \
|
||||
MSIX::Global::Log::Append(e.what()); \
|
||||
return static_cast<HRESULT>(MSIX::Error::Unexpected); \
|
||||
} \
|
||||
catch (...) \
|
||||
{ return static_cast<HRESULT>(MSIX::Error::Unexpected); \
|
||||
{ \
|
||||
return static_cast<HRESULT>(MSIX::Error::Unexpected); \
|
||||
}
|
||||
|
||||
template <typename E, class C>
|
||||
|
@ -76,7 +85,15 @@ namespace MSIX {
|
|||
#endif
|
||||
RaiseException(const int line, const char* const file, const char* details, C c)
|
||||
{
|
||||
assert(false);
|
||||
#ifdef WIN32
|
||||
#ifndef NDEBUG
|
||||
if (IsDebuggerPresent())
|
||||
#endif
|
||||
#endif
|
||||
{
|
||||
assert(false);
|
||||
}
|
||||
|
||||
std::ostringstream builder;
|
||||
if (details) { builder << details << "\n"; }
|
||||
builder << "Call failed in " << file << " on line " << line;
|
||||
|
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
// Validates payload file name is correct
|
||||
struct FileNameValidation
|
||||
{
|
||||
static bool IsFileNameValid(const std::string& name);
|
||||
static bool IsIdentifierValid(const std::string& name);
|
||||
static bool IsFootPrintFile(const std::string& fileName);
|
||||
static bool IsReservedFolder(const std::string& fileName);
|
||||
};
|
||||
}
|
|
@ -8,7 +8,7 @@
|
|||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "SHA256.hpp"
|
||||
#include "Crypto.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
|
|
@ -52,6 +52,7 @@ enum class XmlQueryName : std::uint8_t
|
|||
Package_Properties_Framework,
|
||||
Package_Properties_ResourcePackage,
|
||||
Package_Properties_SupportedUsers,
|
||||
Package_Capabilities_CustomCapability,
|
||||
};
|
||||
|
||||
// defines attribute names for use in IXmlElement:: [GetAttributeValue|GetBase64DecodedAttributeValue]
|
||||
|
@ -92,6 +93,7 @@ public:
|
|||
virtual std::string GetAttributeValue(XmlAttributeName attribute) = 0;
|
||||
virtual std::vector<std::uint8_t> GetBase64DecodedAttributeValue(XmlAttributeName attribute) = 0;
|
||||
virtual std::string GetText() = 0;
|
||||
virtual std::string GetPrefix() = 0;
|
||||
};
|
||||
MSIX_INTERFACE(IXmlElement, 0xac94449e,0x442d,0x4bed,0x8f,0xca,0x83,0x77,0x0c,0x0f,0x7e,0xe9);
|
||||
|
||||
|
|
|
@ -32,9 +32,9 @@ namespace MSIX {
|
|||
}
|
||||
|
||||
// IStreamInternal
|
||||
std::uint64_t GetSizeOnZip() override
|
||||
std::uint64_t GetSize() override
|
||||
{ // The underlying ZipFileStream object knows, so go ask it.
|
||||
return m_stream.As<IStreamInternal>()->GetSizeOnZip();
|
||||
return m_stream.As<IStreamInternal>()->GetSize();
|
||||
}
|
||||
|
||||
bool IsCompressed() override
|
||||
|
|
|
@ -276,6 +276,10 @@
|
|||
#define E_BOUNDS 0x8000000b
|
||||
#endif
|
||||
|
||||
#ifndef E_FAIL
|
||||
#define E_FAIL 0x80004005
|
||||
#endif
|
||||
|
||||
#if !defined (_SYS_GUID_OPERATORS_)
|
||||
#define _SYS_GUID_OPERATORS_
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ namespace MSIX {
|
|||
NotSupported = 0x80070032,
|
||||
InvalidParameter = 0x80070057,
|
||||
Stg_E_Invalidpointer = 0x80030009,
|
||||
InvalidState = 0x804d0003,
|
||||
|
||||
//
|
||||
// msix specific error codes
|
||||
|
@ -66,6 +67,7 @@ namespace MSIX {
|
|||
|
||||
// Blockmap semantic errors
|
||||
BlockMapSemanticError = ERROR_FACILITY + 0x0051,
|
||||
BlockMapInvalidData = ERROR_FACILITY + 0x0052,
|
||||
|
||||
// AppxManifest semantic errors
|
||||
AppxManifestSemanticError = ERROR_FACILITY + 0x0061,
|
||||
|
@ -73,6 +75,11 @@ namespace MSIX {
|
|||
// Bundle errors
|
||||
PackageIsBundle = ERROR_FACILITY + 0x0071,
|
||||
|
||||
// Deflate errors
|
||||
DeflateInitialize = ERROR_FACILITY + 0x0081,
|
||||
DeflateWrite = ERROR_FACILITY + 0x0082,
|
||||
DeflateRead = ERROR_FACILITY + 0x0083,
|
||||
|
||||
// XML parsing errors
|
||||
XmlWarning = XML_FACILITY + 0x0001,
|
||||
XmlError = XML_FACILITY + 0x0002,
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
// Helper macros for flow control. Throws Error::NotSupported when a feature
|
||||
// is not enabled.
|
||||
#include "Exceptions.hpp"
|
||||
|
||||
#ifndef THROW_IF_BUNDLE_NOT_ENABLED
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
#define THROW_IF_BUNDLE_NOT_ENABLED
|
||||
#else
|
||||
#define THROW_IF_BUNDLE_NOT_ENABLED { MSIX::RaiseException<MSIX::Exception>(__LINE__, __FILE__, "Bundle support not enabled", MSIX::Error::NotSupported); }
|
||||
#endif // BUNDLE_SUPPORT
|
||||
#endif // THROW_IF_BUNDLE_NOT_ENABLED
|
||||
|
||||
#ifndef THROW_IF_PACK_NOT_ENABLED
|
||||
#ifdef MSIX_PACK
|
||||
#define THROW_IF_PACK_NOT_ENABLED
|
||||
#else
|
||||
#define THROW_IF_PACK_NOT_ENABLED { MSIX::RaiseException<MSIX::Exception>(__LINE__, __FILE__, "Packaging support not enabled", MSIX::Error::NotSupported); }
|
||||
#endif // MSIX_PACK
|
||||
#endif // THROW_IF_PACK_NOT_ENABLED
|
|
@ -9,6 +9,7 @@
|
|||
#include <type_traits>
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
|
||||
namespace MSIX { namespace Meta {
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -45,32 +46,135 @@ class FieldBase
|
|||
public:
|
||||
FieldBase() = default;
|
||||
|
||||
size_t Size() { return sizeof(T); }
|
||||
|
||||
constexpr size_t Size() const { return sizeof(T); }
|
||||
|
||||
void GetBytes(std::vector<std::uint8_t>& bytes) const
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
for (size_t i = 0; i < Size(); ++i)
|
||||
{
|
||||
bytes.push_back(static_cast<std::uint8_t>(this->value >> (i * 8)));
|
||||
}
|
||||
}
|
||||
|
||||
FieldBase& operator=(const T& v)
|
||||
{
|
||||
value = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
T& get()
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
const T& get() const
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
T* operator &()
|
||||
{
|
||||
return &value;
|
||||
}
|
||||
|
||||
protected:
|
||||
T value;
|
||||
};
|
||||
|
||||
// Simple 2, 4, and 8 byte fields
|
||||
class Field2Bytes final : public FieldBase<std::uint16_t> { };
|
||||
class Field4Bytes final : public FieldBase<std::uint32_t> { };
|
||||
class Field8Bytes final : public FieldBase<std::uint64_t> { };
|
||||
using Field2Bytes = FieldBase<std::uint16_t>;
|
||||
using Field4Bytes = FieldBase<std::uint32_t>;
|
||||
using Field8Bytes = FieldBase<std::uint64_t>;
|
||||
|
||||
// variable length field.
|
||||
class FieldNBytes : public FieldBase<std::vector<std::uint8_t>>
|
||||
{
|
||||
public:
|
||||
size_t Size() { return this->value.size(); }
|
||||
size_t Size() const { return this->value.size(); }
|
||||
|
||||
void GetBytes(std::vector<std::uint8_t>& result) const
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
result.insert(result.end(), value.begin(), value.end());
|
||||
}
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Base type for individual serializable/deserializable optional fields //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template <class T>
|
||||
class OptionalFieldBase
|
||||
{
|
||||
public:
|
||||
OptionalFieldBase() = default;
|
||||
|
||||
constexpr size_t Size() { return (hasValue ? value.Size() : 0); }
|
||||
|
||||
void GetBytes(std::vector<std::uint8_t>& bytes)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
if (hasValue)
|
||||
{
|
||||
value.GetBytes(bytes);
|
||||
}
|
||||
}
|
||||
|
||||
OptionalFieldBase& operator=(const T& v)
|
||||
{
|
||||
hasValue = true;
|
||||
value = v;
|
||||
return *this;
|
||||
}
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return hasValue;
|
||||
}
|
||||
|
||||
operator T() const
|
||||
{
|
||||
ThrowErrorIfNot(Error::InvalidState, hasValue, "Cannot retrieve value if none is set");
|
||||
return value;
|
||||
}
|
||||
|
||||
const T& get() const
|
||||
{
|
||||
ThrowErrorIfNot(Error::InvalidState, hasValue, "Cannot retrieve value if none is set");
|
||||
return value.get();
|
||||
}
|
||||
|
||||
T* operator &()
|
||||
{
|
||||
hasValue = true;
|
||||
return &value;
|
||||
}
|
||||
|
||||
protected:
|
||||
FieldBase<T> value;
|
||||
bool hasValue = false;
|
||||
};
|
||||
|
||||
// Simple 2, 4, and 8 byte fields
|
||||
using OptionalField2Bytes = OptionalFieldBase<std::uint16_t>;
|
||||
using OptionalField4Bytes = OptionalFieldBase<std::uint32_t>;
|
||||
using OptionalField8Bytes = OptionalFieldBase<std::uint64_t>;
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Heterogeneous collection of types that are operated on as a compile-time vector //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
template <typename... Types>
|
||||
class TypeList
|
||||
{
|
||||
static constexpr std::size_t last_index { std::tuple_size<std::tuple<Types...>>::value };
|
||||
static constexpr std::size_t last_index { sizeof...(Types) };
|
||||
public:
|
||||
std::tuple<Types...> fields;
|
||||
std::tuple<Types...> fields;
|
||||
|
||||
template<std::size_t index = 0, typename FuncT, class... Args>
|
||||
inline typename std::enable_if<index == last_index, void>::type for_each(FuncT, Args&&... args) { }
|
||||
|
||||
|
@ -83,6 +187,9 @@ public:
|
|||
|
||||
template <size_t index>
|
||||
auto& Field() noexcept { return std::get<index>(fields); }
|
||||
|
||||
template <size_t index>
|
||||
const auto& Field() const noexcept { return std::get<index>(fields); }
|
||||
};
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
@ -96,10 +203,30 @@ public:
|
|||
{
|
||||
size_t result = 0;
|
||||
this->for_each([](auto& field, std::size_t index, size_t& result)
|
||||
{ result += field.Size();
|
||||
{
|
||||
result += field.Size();
|
||||
}, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> GetBytes()
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
std::vector<std::uint8_t> bytes;
|
||||
this->for_each([](auto& field, std::size_t index, std::vector<std::uint8_t>& bytes)
|
||||
{
|
||||
field.GetBytes(bytes);
|
||||
}, bytes);
|
||||
return bytes;
|
||||
}
|
||||
|
||||
void WriteTo(const ComPtr<IStream>& stream)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
auto bytes = GetBytes();
|
||||
ULONG bytesWritten = 0;
|
||||
ThrowHrIfFailed(stream->Write(bytes.data(), static_cast<ULONG>(bytes.size()), &bytesWritten));
|
||||
}
|
||||
};
|
||||
|
||||
} /* namespace Meta */ } /* namespace MSIX */
|
|
@ -6,43 +6,68 @@
|
|||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
#include <functional>
|
||||
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
// This represents a subset of a Stream
|
||||
class RangeStream : public StreamBase
|
||||
{
|
||||
public:
|
||||
RangeStream(std::uint64_t offset, std::uint64_t size, const ComPtr<IStream>& stream) :
|
||||
RangeStream(std::uint64_t offset, std::uint64_t size, IStream* stream) :
|
||||
m_offset(offset),
|
||||
m_size(size),
|
||||
m_stream(stream)
|
||||
{
|
||||
}
|
||||
|
||||
// For writing/pack
|
||||
// This simply keeps track of the amount of data written to the stream, as well as
|
||||
// limiting any Read/Seek to the data written, rather than the entire underlying stream.
|
||||
RangeStream(IStream* stream) : m_stream(stream), m_size(0)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
ULARGE_INTEGER pos = { 0 };
|
||||
ThrowHrIfFailed(m_stream->Seek({ 0 }, Reference::CURRENT, &pos));
|
||||
m_offset = pos.QuadPart;
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) noexcept override try
|
||||
{
|
||||
// Determine new range relative position
|
||||
LARGE_INTEGER newPos = { 0 };
|
||||
switch (origin)
|
||||
{
|
||||
case Reference::CURRENT:
|
||||
newPos.QuadPart = m_offset + m_relativePosition + move.QuadPart;
|
||||
newPos.QuadPart = m_relativePosition + move.QuadPart;
|
||||
break;
|
||||
case Reference::START:
|
||||
newPos.QuadPart = m_offset + move.QuadPart;
|
||||
newPos.QuadPart = move.QuadPart;
|
||||
break;
|
||||
case Reference::END:
|
||||
newPos.QuadPart = m_offset + m_size + move.QuadPart;
|
||||
newPos.QuadPart = m_size + move.QuadPart;
|
||||
break;
|
||||
}
|
||||
//TODO: We need to constrain newPos so that it can't exceed the end of the stream
|
||||
|
||||
// Constrain newPos to range relative values
|
||||
if (newPos.QuadPart < 0)
|
||||
{
|
||||
newPos.QuadPart = 0;
|
||||
}
|
||||
else if (static_cast<uint64_t>(newPos.QuadPart) > m_size)
|
||||
{
|
||||
newPos.QuadPart = m_size;
|
||||
}
|
||||
|
||||
// Add in the underlying stream offset
|
||||
newPos.QuadPart += m_offset;
|
||||
|
||||
ULARGE_INTEGER pos = { 0 };
|
||||
m_stream->Seek(newPos, Reference::START, &pos);
|
||||
ThrowHrIfFailed(m_stream->Seek(newPos, Reference::START, &pos));
|
||||
m_relativePosition = std::min(static_cast<std::uint64_t>(pos.QuadPart - m_offset), m_size);
|
||||
if (newPosition) { newPosition->QuadPart = m_relativePosition; }
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
|
@ -56,13 +81,28 @@ namespace MSIX {
|
|||
ULONG amountToRead = std::min(countBytes, static_cast<ULONG>(m_size - m_relativePosition));
|
||||
ULONG amountRead = 0;
|
||||
ThrowHrIfFailed(m_stream->Read(buffer, amountToRead, &amountRead));
|
||||
ThrowErrorIf(Error::FileRead, (amountToRead != amountRead), "Did not read as much as requesteed.");
|
||||
ThrowErrorIf(Error::FileRead, (amountToRead != amountRead), "Did not read as much as requested.");
|
||||
m_relativePosition += amountRead;
|
||||
if (bytesRead) { *bytesRead = amountRead; }
|
||||
ThrowErrorIf(Error::FileSeekOutOfRange, (m_relativePosition > m_size), "seek pointer out of bounds.");
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Write(const void *buffer, ULONG countBytes, ULONG *bytesWritten) noexcept override try
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
LARGE_INTEGER offset = { 0 };
|
||||
offset.QuadPart = m_relativePosition + m_offset;
|
||||
ThrowHrIfFailed(m_stream->Seek(offset, StreamBase::START, nullptr));
|
||||
ULONG amountWritten = 0;
|
||||
ThrowHrIfFailed(m_stream->Write(buffer, countBytes, &amountWritten));
|
||||
ThrowErrorIf(Error::FileWrite, (countBytes != amountWritten), "Did not write as much as requested.");
|
||||
m_relativePosition += amountWritten;
|
||||
m_size = std::max(m_size, m_relativePosition);
|
||||
if (bytesWritten) { *bytesWritten = amountWritten; }
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
std::uint64_t Size() { return m_size; }
|
||||
|
||||
protected:
|
||||
|
|
|
@ -0,0 +1,71 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
namespace MSIX {
|
||||
namespace details {
|
||||
|
||||
template <typename TLambda>
|
||||
class lambda_call
|
||||
{
|
||||
public:
|
||||
lambda_call(const lambda_call&) = delete;
|
||||
lambda_call& operator=(const lambda_call&) = delete;
|
||||
lambda_call& operator=(lambda_call&& other) = delete;
|
||||
|
||||
explicit lambda_call(TLambda&& lambda) noexcept : m_lambda(std::move(lambda))
|
||||
{
|
||||
static_assert(std::is_same<decltype(lambda()), void>::value, "scope_exit lambdas must not have a return value");
|
||||
static_assert(!std::is_lvalue_reference<TLambda>::value && !std::is_rvalue_reference<TLambda>::value,
|
||||
"scope_exit should only be directly used with a lambda");
|
||||
}
|
||||
|
||||
lambda_call(lambda_call&& other) noexcept : m_lambda(std::move(other.m_lambda)), m_call(other.m_call)
|
||||
{
|
||||
other.m_call = false;
|
||||
}
|
||||
|
||||
~lambda_call() noexcept
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
// Ensures the scope_exit lambda will not be called
|
||||
void release() noexcept
|
||||
{
|
||||
m_call = false;
|
||||
}
|
||||
|
||||
// Executes the scope_exit lambda immediately if not yet run; ensures it will not run again
|
||||
void reset() noexcept
|
||||
{
|
||||
if (m_call)
|
||||
{
|
||||
m_call = false;
|
||||
m_lambda();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns true if the scope_exit lambda is still going to be executed
|
||||
explicit operator bool() const noexcept
|
||||
{
|
||||
return m_call;
|
||||
}
|
||||
|
||||
protected:
|
||||
TLambda m_lambda;
|
||||
bool m_call = true;
|
||||
};
|
||||
} // details
|
||||
|
||||
// Returns an object that executes the given lambda when destroyed.
|
||||
// Capture the object with 'auto'; use reset() to execute the lambda early or release() to avoid
|
||||
// execution.
|
||||
template <typename TLambda>
|
||||
inline auto scope_exit(TLambda&& lambda) noexcept
|
||||
{
|
||||
return details::lambda_call<TLambda>(std::forward<TLambda>(lambda));
|
||||
}
|
||||
}
|
|
@ -5,7 +5,7 @@
|
|||
#pragma once
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "AppxSignature.hpp"
|
||||
#include "SHA256.hpp"
|
||||
#include "Crypto.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "FileStream.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
|
||||
#include <string>
|
||||
|
@ -41,20 +40,13 @@ interface IStorageObject : public IUnknown
|
|||
class IStorageObject : public IUnknown
|
||||
#endif
|
||||
{
|
||||
public:
|
||||
virtual const char* GetPathSeparator() = 0;
|
||||
|
||||
public:
|
||||
// Obtains a vector of UTF-8 formatted string names contained in the storage object
|
||||
virtual std::vector<std::string> GetFileNames(FileNameOptions options) = 0;
|
||||
|
||||
// Obtains a pointer to a stream representing the file that exists in the storage object
|
||||
virtual MSIX::ComPtr<IStream> GetFile(const std::string& fileName) = 0;
|
||||
|
||||
// Opens a stream to a file by name in the storage object. If the file does not exist and mode is read,
|
||||
// or read + update, then nullptr is returned. If the file is opened with write and it does not exist,
|
||||
// then the file is created and an empty stream to the file is handed back to the caller.
|
||||
virtual MSIX::ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) = 0;
|
||||
|
||||
// Returns the file name of the storage object.
|
||||
virtual std::string GetFileName() = 0;
|
||||
};
|
||||
|
|
|
@ -25,7 +25,7 @@ class IStreamInternal : public IUnknown
|
|||
#endif
|
||||
{
|
||||
public:
|
||||
virtual std::uint64_t GetSizeOnZip() = 0;
|
||||
virtual std::uint64_t GetSize() = 0;
|
||||
virtual bool IsCompressed() = 0;
|
||||
virtual std::string GetName() = 0;
|
||||
};
|
||||
|
@ -130,7 +130,7 @@ namespace MSIX {
|
|||
virtual HRESULT STDMETHODCALLTYPE Write(const void*, ULONG, ULONG*) noexcept override { return static_cast<HRESULT>(Error::NotImplemented); }
|
||||
|
||||
// IStreamInternal
|
||||
virtual std::uint64_t GetSizeOnZip() override { NOTIMPLEMENTED; }
|
||||
virtual std::uint64_t GetSize() override { NOTIMPLEMENTED; }
|
||||
virtual bool IsCompressed() override { NOTIMPLEMENTED; }
|
||||
virtual std::string GetName() override { NOTIMPLEMENTED; }
|
||||
|
||||
|
|
|
@ -6,6 +6,7 @@
|
|||
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
|
||||
#include <utility>
|
||||
|
||||
|
@ -49,5 +50,46 @@ namespace MSIX {
|
|||
ThrowHrIfFailed(stream->Seek(start, StreamBase::Reference::START, nullptr));
|
||||
return std::make_pair(streamSize, std::move(buffer));
|
||||
}
|
||||
|
||||
inline void WriteStringToStream(const ComPtr<IStream>& stream, const std::string& toWrite)
|
||||
{
|
||||
ULONG written;
|
||||
ThrowHrIfFailed(stream->Write(static_cast<const void*>(toWrite.data()), static_cast<ULONG>(toWrite.size()), &written));
|
||||
ThrowErrorIf(Error::FileWrite, (static_cast<ULONG>(toWrite.size()) != written), "write failed");
|
||||
}
|
||||
|
||||
// Reverts a stream's position on destruction
|
||||
struct StreamPositionReset
|
||||
{
|
||||
StreamPositionReset(IStream* stream) : m_stream(stream)
|
||||
{
|
||||
ThrowHrIfFailed(m_stream->Seek({ 0 }, StreamBase::Reference::CURRENT, &m_pos));
|
||||
}
|
||||
|
||||
~StreamPositionReset()
|
||||
{
|
||||
Reset();
|
||||
}
|
||||
|
||||
void Reset()
|
||||
{
|
||||
if (m_stream)
|
||||
{
|
||||
LARGE_INTEGER target;
|
||||
target.QuadPart = static_cast<LONGLONG>(m_pos.QuadPart);
|
||||
ThrowHrIfFailed(m_stream->Seek(target, StreamBase::Reference::START, nullptr));
|
||||
m_stream = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void Release()
|
||||
{
|
||||
m_stream = nullptr;
|
||||
}
|
||||
|
||||
private:
|
||||
ComPtr<IStream> m_stream;
|
||||
ULARGE_INTEGER m_pos;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -6,15 +6,27 @@
|
|||
|
||||
#include <algorithm>
|
||||
#include <string>
|
||||
#include <cwctype>
|
||||
|
||||
namespace MSIX {
|
||||
namespace Helper {
|
||||
|
||||
std::string tolower(std::string s)
|
||||
inline std::string tolower(std::string s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(), ::tolower);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::wstring towlower(std::wstring s)
|
||||
{
|
||||
std::transform(s.begin(), s.end(), s.begin(), std::towlower);
|
||||
return s;
|
||||
}
|
||||
|
||||
inline std::string toBackSlash(std::string s)
|
||||
{
|
||||
std::replace(s.begin(), s.end(), '/', '\\');
|
||||
return s;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include <Exceptions.hpp>
|
||||
#include <StreamBase.hpp>
|
||||
|
||||
#include <sstream>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
class StringStream final : public StreamBase
|
||||
{
|
||||
public:
|
||||
HRESULT STDMETHODCALLTYPE Read(void* buffer, ULONG countBytes, ULONG* bytesRead) noexcept override try
|
||||
{
|
||||
auto current = m_data.tellp();
|
||||
m_data.seekp(0, std::ios_base::end);
|
||||
auto available = m_data.tellp() - current;
|
||||
m_data.seekp(current); // rewind
|
||||
auto buf = m_data.rdbuf();
|
||||
ULONG amountToRead = std::min(countBytes, static_cast<ULONG>(available));
|
||||
if (amountToRead > 0)
|
||||
{
|
||||
buf->sgetn(static_cast<char*>(buffer),amountToRead);
|
||||
m_data.seekp(static_cast<std::ostringstream::off_type>(amountToRead), std::ios_base::cur);
|
||||
}
|
||||
if (bytesRead) { *bytesRead = amountToRead; }
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) noexcept override try
|
||||
{
|
||||
std::ios_base::seekdir dir;
|
||||
switch (origin)
|
||||
{
|
||||
case Reference::CURRENT:
|
||||
dir = std::ios_base::cur;
|
||||
break;
|
||||
case Reference::START:
|
||||
dir = std::ios_base::beg;
|
||||
break;
|
||||
case Reference::END:
|
||||
dir = std::ios_base::end;
|
||||
break;
|
||||
}
|
||||
m_data.seekp(static_cast<std::ostringstream::off_type>(move.QuadPart), dir);
|
||||
ThrowErrorIf(Error::FileWrite, m_data.rdstate() != std::ios_base::goodbit, "StringStream Seek failed");
|
||||
if (newPosition) { newPosition->QuadPart = static_cast<ULONG>(m_data.tellp()); }
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Write(const void *buffer, ULONG countBytes, ULONG *bytesWritten) noexcept override try
|
||||
{
|
||||
if (bytesWritten) { *bytesWritten = 0; }
|
||||
m_data.write(static_cast<const char*>(buffer), static_cast<std::streamsize>(countBytes));
|
||||
// std::basic_ostream::write : Characters are inserted into the output sequence until one of the following occurs:
|
||||
// exactly count characters are inserted or inserting into the output sequence fails (in which case setstate(badbit) is called)
|
||||
// If the state is std::ios_base::goodbit we know the exact number of bytes were written.
|
||||
ThrowErrorIf(Error::FileWrite, m_data.rdstate() != std::ios_base::goodbit, "StringStream Write failed");
|
||||
if (bytesWritten) { *bytesWritten = countBytes; }
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
protected:
|
||||
std::stringstream m_data;
|
||||
};
|
||||
} // namespace MSIX
|
|
@ -6,6 +6,8 @@
|
|||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <algorithm>
|
||||
|
||||
|
@ -45,6 +47,24 @@ namespace MSIX {
|
|||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE Write(const void *buffer, ULONG countBytes, ULONG *bytesWritten) noexcept override try
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
// make sure that we can allocate the buffer
|
||||
ULONG expected = static_cast<ULONG>(m_data->size()) + countBytes;
|
||||
m_data->resize(expected);
|
||||
if (countBytes > 0) { memcpy(&(m_data->at(m_offset)), buffer, countBytes); }
|
||||
m_offset = static_cast<ULONG>(m_data->size());
|
||||
ThrowErrorIf(Error::FileWrite, expected != m_offset, "Error writing to stream");
|
||||
if (bytesWritten) { *bytesWritten = countBytes; }
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
std::uint64_t GetSize() override
|
||||
{
|
||||
return static_cast<std::uint64_t>(m_data->size());
|
||||
}
|
||||
|
||||
protected:
|
||||
ULONG m_offset = 0;
|
||||
std::vector<std::uint8_t>* m_data;
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "ComHelper.hpp"
|
||||
#include "StringStream.hpp"
|
||||
|
||||
#include <stack>
|
||||
#include <string>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
// common attribute names
|
||||
static const char* xmlnsAttribute = "xmlns";
|
||||
|
||||
// This is a super light xml writer that doesn't use any xml libraries and
|
||||
// just writes to a stream the basics of an xml file.
|
||||
class XmlWriter final
|
||||
{
|
||||
public:
|
||||
typedef enum
|
||||
{
|
||||
OpenElement = 1,
|
||||
ClosedElement = 2,
|
||||
Finish = 3
|
||||
}
|
||||
State;
|
||||
|
||||
XmlWriter() = delete; // A root must be given
|
||||
|
||||
XmlWriter(const std::string& root, bool standalone = false)
|
||||
{
|
||||
m_stream = ComPtr<IStream>::Make<StringStream>();
|
||||
StartWrite(root, standalone);
|
||||
}
|
||||
|
||||
XmlWriter(const std::string& root, IStream* targetStream, bool standalone = false) : m_stream(ComPtr<IStream>(targetStream))
|
||||
{
|
||||
StartWrite(root, standalone);
|
||||
}
|
||||
|
||||
void StartElement(const std::string& name);
|
||||
void CloseElement();
|
||||
void AddAttribute(const std::string& name, const std::string& value);
|
||||
State GetState() { return m_state; }
|
||||
ComPtr<IStream> GetStream();
|
||||
|
||||
protected:
|
||||
void StartWrite(const std::string& root, bool standalone);
|
||||
void Write(const std::string& toWrite);
|
||||
void Write(const char toWrite);
|
||||
void WriteTextValue(const std::string& value);
|
||||
State m_state;
|
||||
ComPtr<IStream> m_stream;
|
||||
std::stack<std::string> m_elements;
|
||||
};
|
||||
}
|
|
@ -8,6 +8,7 @@
|
|||
#include "StreamBase.hpp"
|
||||
#include "RangeStream.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
|
||||
#include <string>
|
||||
|
||||
|
@ -17,29 +18,34 @@ namespace MSIX {
|
|||
class ZipFileStream final : public RangeStream
|
||||
{
|
||||
public:
|
||||
// TODO: define what streams to pass in on the .ctor
|
||||
// Represents an stream taken from the zip file (unpack)
|
||||
ZipFileStream(
|
||||
std::string name,
|
||||
std::string contentType,
|
||||
IMsixFactory* factory,
|
||||
bool isCompressed,
|
||||
std::uint64_t offset,
|
||||
std::uint64_t size,
|
||||
const ComPtr<IStream>& stream
|
||||
) : m_isCompressed(isCompressed), RangeStream(offset, size, stream), m_name(name), m_contentType(contentType), m_factory(factory), m_compressedSize(size)
|
||||
IStream* stream // this is the actual zip file stream
|
||||
) : m_isCompressed(isCompressed), RangeStream(offset, size, stream), m_name(std::move(name))
|
||||
{
|
||||
}
|
||||
|
||||
// Represents an stream to be added to the zip file (pack)
|
||||
ZipFileStream(
|
||||
std::string name,
|
||||
bool isCompressed,
|
||||
IStream* stream
|
||||
) : m_isCompressed(isCompressed), m_name(std::move(name)), RangeStream(stream)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
}
|
||||
|
||||
// IStreamInternal
|
||||
std::uint64_t GetSizeOnZip() override { return m_compressedSize; }
|
||||
std::uint64_t GetSize() override { return m_size; }
|
||||
bool IsCompressed() override { return m_isCompressed; }
|
||||
std::string GetName() override { return m_name; }
|
||||
|
||||
protected:
|
||||
IMsixFactory* m_factory;
|
||||
std::string m_name;
|
||||
std::string m_contentType;
|
||||
bool m_isCompressed = false;
|
||||
std::uint64_t m_compressedSize;
|
||||
};
|
||||
}
|
|
@ -1,36 +1,467 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "Exceptions.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "StorageObject.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
#include "ObjectBase.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace MSIX {
|
||||
// This represents a raw stream over a.zip file.
|
||||
class ZipObject final : public ComClass<ZipObject, IStorageObject>
|
||||
|
||||
enum class ZipVersions : std::uint16_t
|
||||
{
|
||||
Zip32DefaultVersion = 20,
|
||||
Zip64FormatExtension = 45,
|
||||
};
|
||||
|
||||
enum class GeneralPurposeBitFlags : std::uint16_t
|
||||
{
|
||||
UNSUPPORTED_0 = 0x0001, // Bit 0: If set, indicates that the file is encrypted.
|
||||
|
||||
Deflate_MaxCompress = 0x0002, // Maximum compression (-exx/-ex), otherwise, normal compression (-en)
|
||||
Deflate_FastCompress = 0x0004, // Fast (-ef), if Max+Fast then SuperFast (-es) compression
|
||||
|
||||
DataDescriptor = 0x0008, // the field's crc-32 compressed and uncompressed sizes = 0 in the local header
|
||||
// the correct values are put in the data descriptor immediately following the
|
||||
// compressed data.
|
||||
EnhancedDeflate = 0x0010,
|
||||
CompressedPatchedData = 0x0020,
|
||||
UNSUPPORTED_6 = 0x0040, // Strong encryption.
|
||||
UnUsed_7 = 0x0080, // currently unused
|
||||
UnUsed_8 = 0x0100, // currently unused
|
||||
UnUsed_9 = 0x0200, // currently unused
|
||||
UnUsed_10 = 0x0400, // currently unused
|
||||
|
||||
EncodingMustUseUTF8 = 0x0800, // Language encoding flag (EFS). File name and comments fields MUST be encoded UTF-8
|
||||
|
||||
UNSUPPORTED_12 = 0x1000, // Reserved by PKWARE for enhanced compression
|
||||
UNSUPPORTED_13 = 0x2000, // Set when encrypting the Central Directory
|
||||
UNSUPPORTED_14 = 0x4000, // Reserved by PKWARE
|
||||
UNSUPPORTED_15 = 0x8000, // Reserved by PKWARE
|
||||
};
|
||||
|
||||
constexpr GeneralPurposeBitFlags operator &(GeneralPurposeBitFlags a, GeneralPurposeBitFlags b)
|
||||
{
|
||||
return static_cast<GeneralPurposeBitFlags>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b));
|
||||
}
|
||||
|
||||
constexpr GeneralPurposeBitFlags operator |(GeneralPurposeBitFlags a, GeneralPurposeBitFlags b)
|
||||
{
|
||||
return static_cast<GeneralPurposeBitFlags>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
|
||||
}
|
||||
|
||||
constexpr GeneralPurposeBitFlags operator ~(GeneralPurposeBitFlags a)
|
||||
{
|
||||
return static_cast<GeneralPurposeBitFlags>(~static_cast<uint16_t>(a));
|
||||
}
|
||||
|
||||
enum class CompressionType : std::uint16_t
|
||||
{
|
||||
Store = 0,
|
||||
Deflate = 8,
|
||||
};
|
||||
|
||||
// from ZIP file format specification detailed in AppNote.txt
|
||||
enum class Signatures : std::uint32_t
|
||||
{
|
||||
LocalFileHeader = 0x04034b50,
|
||||
DataDescriptor = 0x08074b50,
|
||||
CentralFileHeader = 0x02014b50,
|
||||
Zip64EndOfCD = 0x06064b50,
|
||||
Zip64EndOfCDLocator = 0x07064b50,
|
||||
EndOfCentralDirectory = 0x06054b50,
|
||||
};
|
||||
|
||||
constexpr uint64_t MaxSizeToNotUseDataDescriptor = static_cast<uint64_t>(std::numeric_limits<std::uint32_t>::max() - 1);
|
||||
|
||||
template <typename T>
|
||||
inline bool IsValueInExtendedInfo(T value) noexcept
|
||||
{
|
||||
return (value == std::numeric_limits<T>::max());
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
inline bool IsValueInExtendedInfo(const Meta::FieldBase<T>& field) noexcept
|
||||
{
|
||||
return IsValueInExtendedInfo(field.get());
|
||||
}
|
||||
|
||||
class Zip64ExtendedInformation final : public Meta::StructuredObject<
|
||||
Meta::Field2Bytes, // 0 - tag for the "extra" block type 2 bytes(0x0001)
|
||||
Meta::Field2Bytes, // 1 - size of this "extra" block 2 bytes
|
||||
Meta::OptionalField8Bytes, // 2 - Original uncompressed file size 8 bytes
|
||||
Meta::OptionalField8Bytes, // 3 - Compressed file size 8 bytes
|
||||
Meta::OptionalField8Bytes, // 4 - Offset of local header record 8 bytes
|
||||
Meta::OptionalField4Bytes // 5 - number of the disk on which the file starts 4 bytes
|
||||
>
|
||||
{
|
||||
public:
|
||||
ZipObject(IMsixFactory* factory, const ComPtr<IStream>& stream);
|
||||
Zip64ExtendedInformation();
|
||||
|
||||
// IStorageObject methods
|
||||
const char* GetPathSeparator() override { return "/"; }
|
||||
std::vector<std::string> GetFileNames(FileNameOptions options) override;
|
||||
ComPtr<IStream> GetFile(const std::string& fileName) override;
|
||||
ComPtr<IStream> OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode) override { NOTIMPLEMENTED; }
|
||||
std::string GetFileName() override;
|
||||
// The incoming values are those from the central directory record. Their value there determines
|
||||
// whether we attempt to read them here.
|
||||
void Read(const ComPtr<IStream>& stream, ULARGE_INTEGER start, uint32_t uncompressedSize, uint32_t compressedSize, uint32_t offset, uint16_t disk);
|
||||
|
||||
std::uint64_t GetUncompressedSize() const { return Field<2>(); }
|
||||
std::uint64_t GetCompressedSize() const { return Field<3>(); }
|
||||
std::uint64_t GetRelativeOffsetOfLocalHeader() const { return Field<4>(); }
|
||||
std::uint32_t GetDiskStartNumber() const { return Field<5>(); }
|
||||
|
||||
void SetUncompressedSize(std::uint64_t value) noexcept { Field<2>() = value; }
|
||||
void SetCompressedSize(std::uint64_t value) noexcept { Field<3>() = value; }
|
||||
void SetRelativeOffsetOfLocalHeader(std::uint64_t value) noexcept { Field<4>() = value; }
|
||||
|
||||
bool HasAnySet() const
|
||||
{
|
||||
return (Field<2>() || Field<3>() || Field<4>() || Field<5>());
|
||||
}
|
||||
|
||||
std::vector<std::uint8_t> GetBytes()
|
||||
{
|
||||
SetSize(static_cast<uint16_t>(Size() - NonOptionalSize));
|
||||
return StructuredObject::GetBytes();
|
||||
}
|
||||
|
||||
protected:
|
||||
IMsixFactory* m_factory;
|
||||
ComPtr<IStream> m_stream;
|
||||
std::map<std::string, ComPtr<IStream>> m_streams;
|
||||
};//class ZipObject
|
||||
constexpr static size_t NonOptionalSize = 4;
|
||||
|
||||
void SetSignature(std::uint16_t value) noexcept { Field<0>() = value; }
|
||||
void SetSize(std::uint16_t value) noexcept { Field<1>() = value; }
|
||||
};
|
||||
|
||||
class CentralDirectoryFileHeader final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - central file header signature 4 bytes(0x02014b50)
|
||||
Meta::Field2Bytes, // 1 - version made by 2 bytes
|
||||
Meta::Field2Bytes, // 2 - version needed to extract 2 bytes
|
||||
Meta::Field2Bytes, // 3 - general purpose bit flag 2 bytes
|
||||
Meta::Field2Bytes, // 4 - compression method 2 bytes
|
||||
Meta::Field2Bytes, // 5 - last mod file time 2 bytes
|
||||
Meta::Field2Bytes, // 6 - last mod file date 2 bytes
|
||||
Meta::Field4Bytes, // 7 - crc - 32 4 bytes
|
||||
Meta::Field4Bytes, // 8 - compressed size 4 bytes
|
||||
Meta::Field4Bytes, // 9 - uncompressed size 4 bytes
|
||||
Meta::Field2Bytes, //10 - file name length 2 bytes
|
||||
Meta::Field2Bytes, //11 - extra field length 2 bytes
|
||||
Meta::Field2Bytes, //12 - file comment length 2 bytes
|
||||
Meta::Field2Bytes, //13 - disk number start 2 bytes
|
||||
Meta::Field2Bytes, //14 - internal file attributes 2 bytes
|
||||
Meta::Field4Bytes, //15 - external file attributes 4 bytes
|
||||
Meta::Field4Bytes, //16 - relative offset of local header 4 bytes
|
||||
Meta::FieldNBytes, //17 - file name(variable size)
|
||||
Meta::FieldNBytes, //18 - extra field(variable size)
|
||||
Meta::FieldNBytes //19 - file comment(variable size) NOT USED
|
||||
>
|
||||
{
|
||||
public:
|
||||
CentralDirectoryFileHeader();
|
||||
|
||||
void SetData(const std::string& name, std::uint32_t crc, std::uint64_t compressedSize,
|
||||
std::uint64_t uncompressedSize, std::uint64_t relativeOffset, std::uint16_t compressionMethod, bool forceDataDescriptor);
|
||||
|
||||
void Read(const ComPtr<IStream>& stream, bool isZip64);
|
||||
|
||||
GeneralPurposeBitFlags GetGeneralPurposeBitFlags() const noexcept { return static_cast<GeneralPurposeBitFlags>(Field<3>().get()); }
|
||||
|
||||
bool IsGeneralPurposeBitSet() const noexcept
|
||||
{
|
||||
return ((GetGeneralPurposeBitFlags() & GeneralPurposeBitFlags::DataDescriptor) == GeneralPurposeBitFlags::DataDescriptor);
|
||||
}
|
||||
|
||||
CompressionType GetCompressionMethod() const noexcept { return static_cast<CompressionType>(Field<4>().get()); }
|
||||
|
||||
std::uint64_t GetCompressedSize() const noexcept
|
||||
{
|
||||
if (IsValueInExtendedInfo(Field<8>()))
|
||||
{
|
||||
return m_extendedInfo.GetCompressedSize();
|
||||
}
|
||||
return static_cast<std::uint64_t>(Field<8>().get());
|
||||
}
|
||||
|
||||
std::uint64_t GetUncompressedSize() const noexcept
|
||||
{
|
||||
if (IsValueInExtendedInfo(Field<9>()))
|
||||
{
|
||||
return m_extendedInfo.GetUncompressedSize();
|
||||
}
|
||||
return static_cast<std::uint64_t>(Field<9>().get());
|
||||
}
|
||||
|
||||
std::uint64_t GetRelativeOffsetOfLocalHeader() const noexcept
|
||||
{
|
||||
if (IsValueInExtendedInfo(Field<16>()))
|
||||
{
|
||||
return m_extendedInfo.GetRelativeOffsetOfLocalHeader();
|
||||
|
||||
}
|
||||
return static_cast<std::uint64_t>(Field<16>().get());
|
||||
}
|
||||
|
||||
std::string GetFileName() const
|
||||
{
|
||||
auto data = Field<17>().get();
|
||||
return std::string(data.begin(), data.end());
|
||||
}
|
||||
|
||||
protected:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>() = value; }
|
||||
void SetVersionMadeBy(std::uint16_t value) noexcept { Field<1>() = value; }
|
||||
void SetVersionNeededToExtract(std::uint16_t value) noexcept { Field<2>() = value; }
|
||||
void SetGeneralPurposeBitFlags(std::uint16_t value) noexcept { Field<3>() = value; }
|
||||
void SetCompressionMethod(std::uint16_t value) noexcept { Field<4>() = value; }
|
||||
void SetLastModFileTime(std::uint16_t value) noexcept { Field<5>() = value; }
|
||||
void SetLastModFileDate(std::uint16_t value) noexcept { Field<6>() = value; }
|
||||
void SetCrc(std::uint32_t value) noexcept { Field<7>() = value; }
|
||||
void SetFileNameLength(std::uint16_t value) noexcept { Field<10>() = value; }
|
||||
void SetExtraFieldLength(std::uint16_t value) noexcept { Field<11>() = value; }
|
||||
void SetFileCommentLength(std::uint16_t value) noexcept { Field<12>() = value; }
|
||||
void SetDiskNumberStart(std::uint16_t value) noexcept { Field<13>() = value; }
|
||||
void SetInternalFileAttributes(std::uint16_t value) noexcept { Field<14>() = value; }
|
||||
void SetExternalFileAttributes(std::uint16_t value) noexcept { Field<15>() = value; }
|
||||
|
||||
// Values that might appear in the extended info (minus disk, which we will never set there)
|
||||
void SetCompressedSize(std::uint64_t value) noexcept
|
||||
{
|
||||
if (value > MaxSizeToNotUseDataDescriptor)
|
||||
{
|
||||
m_extendedInfo.SetCompressedSize(value);
|
||||
Field<8>() = std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
Field<8>() = static_cast<uint32_t>(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SetUncompressedSize(std::uint64_t value)noexcept
|
||||
{
|
||||
if (value > MaxSizeToNotUseDataDescriptor)
|
||||
{
|
||||
m_extendedInfo.SetUncompressedSize(value);
|
||||
Field<9>() = std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
Field<9>() = static_cast<uint32_t>(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SetRelativeOffsetOfLocalHeader(std::uint64_t value) noexcept
|
||||
{
|
||||
if (value > MaxSizeToNotUseDataDescriptor)
|
||||
{
|
||||
m_extendedInfo.SetRelativeOffsetOfLocalHeader(value);
|
||||
Field<16>() = std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
else
|
||||
{
|
||||
Field<16>() = static_cast<uint32_t>(value);
|
||||
}
|
||||
}
|
||||
|
||||
void SetFileName(const std::string& name)
|
||||
{
|
||||
SetFileNameLength(static_cast<std::uint16_t>(name.size()));
|
||||
Field<17>().get().resize(name.size(), 0);
|
||||
std::copy(name.begin(), name.end(), Field<17>().get().begin());
|
||||
}
|
||||
|
||||
void UpdateExtraField()
|
||||
{
|
||||
if (m_extendedInfo.HasAnySet())
|
||||
{
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetExtraFieldLength(static_cast<std::uint16_t>(m_extendedInfo.Size()));
|
||||
Field<18>().get() = m_extendedInfo.GetBytes();
|
||||
}
|
||||
}
|
||||
|
||||
Zip64ExtendedInformation m_extendedInfo;
|
||||
bool m_isZip64 = true;
|
||||
};
|
||||
|
||||
class LocalFileHeader final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - local file header signature 4 bytes(0x04034b50)
|
||||
Meta::Field2Bytes, // 1 - version needed to extract 2 bytes
|
||||
Meta::Field2Bytes, // 2 - general purpose bit flag 2 bytes
|
||||
Meta::Field2Bytes, // 3 - compression method 2 bytes
|
||||
Meta::Field2Bytes, // 4 - last mod file time 2 bytes
|
||||
Meta::Field2Bytes, // 5 - last mod file date 2 bytes
|
||||
Meta::Field4Bytes, // 6 - crc - 32 4 bytes
|
||||
Meta::Field4Bytes, // 7 - compressed size 4 bytes
|
||||
Meta::Field4Bytes, // 8 - uncompressed size 4 bytes
|
||||
Meta::Field2Bytes, // 9 - file name length 2 bytes
|
||||
Meta::Field2Bytes, // 10- extra field length 2 bytes
|
||||
Meta::FieldNBytes, // 11- file name (variable size)
|
||||
Meta::FieldNBytes // 12- extra field (variable size) NOT USED
|
||||
>
|
||||
{
|
||||
public:
|
||||
LocalFileHeader();
|
||||
|
||||
void SetData(const std::string& name, bool isCompressed);
|
||||
void SetData(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize);
|
||||
|
||||
void Read(const ComPtr<IStream>& stream, CentralDirectoryFileHeader& directoryEntry);
|
||||
|
||||
GeneralPurposeBitFlags GetGeneralPurposeBitFlags() const noexcept { return static_cast<GeneralPurposeBitFlags>(Field<2>().get()); }
|
||||
std::uint16_t GetCompressionMethod() const noexcept { return Field<3>(); }
|
||||
std::uint16_t GetFileNameLength() const noexcept { return Field<9>(); }
|
||||
std::string GetFileName() const
|
||||
{
|
||||
auto data = Field<11>().get();
|
||||
return std::string(data.begin(), data.end());
|
||||
}
|
||||
|
||||
protected:
|
||||
bool IsGeneralPurposeBitSet() const noexcept
|
||||
{
|
||||
return ((GetGeneralPurposeBitFlags() & GeneralPurposeBitFlags::DataDescriptor) == GeneralPurposeBitFlags::DataDescriptor);
|
||||
}
|
||||
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>() = value; }
|
||||
void SetVersionNeededToExtract(std::uint16_t value) noexcept { Field<1>() = value; }
|
||||
void SetGeneralPurposeBitFlags(std::uint16_t value) noexcept { Field<2>() = value; }
|
||||
void SetCompressionMethod(std::uint16_t value) noexcept { Field<3>() = value; }
|
||||
void SetLastModFileTime(std::uint16_t value) noexcept { Field<4>() = value; }
|
||||
void SetLastModFileDate(std::uint16_t value) noexcept { Field<5>() = value; }
|
||||
void SetCrc(std::uint32_t value) noexcept { Field<6>() = value; }
|
||||
void SetCompressedSize(std::uint32_t value) noexcept { Field<7>() = value; }
|
||||
void SetUncompressedSize(std::uint32_t value) noexcept { Field<8>() = value; }
|
||||
void SetFileNameLength(std::uint16_t value) noexcept { Field<9>() = value; }
|
||||
void SetExtraFieldLength(std::uint16_t value) noexcept { Field<10>() = value; }
|
||||
void SetFileName(const std::string& name)
|
||||
{
|
||||
SetFileNameLength(static_cast<std::uint16_t>(name.size()));
|
||||
Field<11>().get().resize(name.size(), 0);
|
||||
std::copy(name.begin(), name.end(), Field<11>().get().begin());
|
||||
}
|
||||
};
|
||||
|
||||
class Zip64EndOfCentralDirectoryRecord final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - zip64 end of central dir signature 4 bytes(0x06064b50)
|
||||
Meta::Field8Bytes, // 1 - size of zip64 end of central directory record 8 bytes
|
||||
Meta::Field2Bytes, // 2 - version made by 2 bytes
|
||||
Meta::Field2Bytes, // 3 - version needed to extract 2 bytes
|
||||
Meta::Field4Bytes, // 4 - number of this disk 4 bytes
|
||||
Meta::Field4Bytes, // 5 - number of the disk with the start of the central directory 4 bytes
|
||||
Meta::Field8Bytes, // 6 - total number of entries in the central directory on this disk 8 bytes
|
||||
Meta::Field8Bytes, // 7 - total number of entries in the central directory 8 bytes
|
||||
Meta::Field8Bytes, // 8 - size of the central directory 8 bytes
|
||||
Meta::Field8Bytes, // 9 - offset of start of central directory with respect to the
|
||||
// starting disk number 8 bytes
|
||||
Meta::FieldNBytes //10 - zip64 extensible data sector (variable size) NOT USED
|
||||
>
|
||||
{
|
||||
public:
|
||||
Zip64EndOfCentralDirectoryRecord();
|
||||
|
||||
void SetData(std::uint64_t numCentralDirs, std::uint64_t sizeCentralDir, std::uint64_t offsetStartCentralDirectory);
|
||||
|
||||
void Read(const ComPtr<IStream>& stream);
|
||||
|
||||
std::uint64_t GetTotalNumberOfEntries() const noexcept { return Field<6>(); }
|
||||
std::uint64_t GetOffsetStartOfCD() const noexcept { return Field<9>(); }
|
||||
|
||||
protected:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>() = value; }
|
||||
void SetSizeOfZip64CDRecord(std::uint64_t value) noexcept { Field<1>() = value; }
|
||||
void SetVersionMadeBy(std::uint16_t value) noexcept { Field<2>() = value; }
|
||||
void SetVersionNeededToExtract(std::uint16_t value) noexcept { Field<3>() = value; }
|
||||
void SetNumberOfThisDisk(std::uint32_t value) noexcept { Field<4>() = value; }
|
||||
void SetNumberOfTheDiskWithStartOfCD(std::uint32_t value) noexcept { Field<5>() = value; }
|
||||
void SetTotalNumberOfEntriesDisk(std::uint64_t value) noexcept
|
||||
{
|
||||
Field<6>() = value;
|
||||
Field<7>() = value;
|
||||
}
|
||||
void SetSizeOfCD(std::uint64_t value) noexcept { Field<8>() = value; }
|
||||
void SetOffsetfStartOfCD(std::uint64_t value) noexcept { Field<9>() = value; }
|
||||
};
|
||||
|
||||
class Zip64EndOfCentralDirectoryLocator final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - zip64 end of central dir locator signature 4 bytes(0x07064b50)
|
||||
Meta::Field4Bytes, // 1 - number of the disk with the start of the zip64
|
||||
// end of central directory 4 bytes
|
||||
Meta::Field8Bytes, // 2 - relative offset of the zip64 end of central
|
||||
// directory record 8 bytes
|
||||
Meta::Field4Bytes // 3 - total number of disks 4 bytes
|
||||
>
|
||||
{
|
||||
public:
|
||||
Zip64EndOfCentralDirectoryLocator();
|
||||
|
||||
void SetData(std::uint64_t zip64EndCdrOffset);
|
||||
|
||||
void Read(const ComPtr<IStream>& stream);
|
||||
|
||||
std::uint64_t GetRelativeOffset() const noexcept { return Field<2>(); }
|
||||
|
||||
protected:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>() = value; }
|
||||
void SetNumberOfDisk(std::uint32_t value) noexcept { Field<1>() = value; }
|
||||
void SetRelativeOffset(std::uint64_t value) noexcept { Field<2>() = value; }
|
||||
void SetTotalNumberOfDisks(std::uint32_t value) noexcept { Field<3>() = value; }
|
||||
};
|
||||
|
||||
class EndCentralDirectoryRecord final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - end of central dir signature 4 bytes (0x06054b50)
|
||||
Meta::Field2Bytes, // 1 - number of this disk 2 bytes
|
||||
Meta::Field2Bytes, // 2 - number of the disk with the start of the
|
||||
// central directory 2 bytes
|
||||
Meta::Field2Bytes, // 3 - total number of entries in the central
|
||||
// directory on this disk 2 bytes
|
||||
Meta::Field2Bytes, // 4 - total number of entries in the central
|
||||
// directory 2 bytes
|
||||
Meta::Field4Bytes, // 5 - size of the central directory 4 bytes
|
||||
Meta::Field4Bytes, // 6 - offset of start of central directory with
|
||||
// respect to the starting disk number 4 bytes
|
||||
Meta::Field2Bytes, // 7 - .ZIP file comment length 2 bytes
|
||||
Meta::FieldNBytes // 8 - .ZIP file comment (variable size)
|
||||
>
|
||||
{
|
||||
public:
|
||||
EndCentralDirectoryRecord();
|
||||
|
||||
void Read(const ComPtr<IStream>& stream);
|
||||
|
||||
bool GetIsZip64() const noexcept { return m_isZip64; }
|
||||
|
||||
std::uint64_t GetNumberOfCentralDirectoryEntries() noexcept { return static_cast<std::uint64_t>(Field<3>().get()); }
|
||||
std::uint64_t GetStartOfCentralDirectory() noexcept { return static_cast<std::uint64_t>(Field<6>().get()); }
|
||||
|
||||
protected:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>() = value; }
|
||||
void SetNumberOfDisk(std::uint16_t value) noexcept { Field<1>() = value; }
|
||||
void SetDiskStart(std::uint16_t value) noexcept { Field<2>() = value; }
|
||||
void SetTotalNumberOfEntries(std::uint16_t value) noexcept { Field<3>() = value; }
|
||||
void SetTotalEntriesInCentralDirectory(std::uint16_t value) noexcept { Field<4>() = value; }
|
||||
void SetSizeOfCentralDirectory(std::uint32_t value) noexcept { Field<5>() = value; }
|
||||
void SetOffsetOfCentralDirectory(std::uint32_t value) noexcept { Field<6>() = value; }
|
||||
void SetCommentLength(std::uint16_t value) noexcept { Field<7>() = value; }
|
||||
|
||||
bool m_isZip64 = true;
|
||||
};
|
||||
|
||||
class ZipObject
|
||||
{
|
||||
public:
|
||||
ZipObject(const ComPtr<IStream>& stream) : m_stream(stream) {}
|
||||
ZipObject(const ComPtr<IStorageObject>& storageObject);
|
||||
|
||||
protected:
|
||||
EndCentralDirectoryRecord m_endCentralDirectoryRecord;
|
||||
Zip64EndOfCentralDirectoryLocator m_zip64Locator;
|
||||
Zip64EndOfCentralDirectoryRecord m_zip64EndOfCentralDirectory;
|
||||
std::map<std::string, CentralDirectoryFileHeader> m_centralDirectories;
|
||||
ComPtr<IStream> m_stream;
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#pragma once
|
||||
|
||||
#include "Exceptions.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "ZipObject.hpp"
|
||||
|
||||
#include <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
|
||||
namespace MSIX {
|
||||
// This represents a raw stream over a.zip file.
|
||||
class ZipObjectReader final : public ComClass<ZipObjectReader, IStorageObject>, ZipObject
|
||||
{
|
||||
public:
|
||||
ZipObjectReader(const ComPtr<IStream>& stream);
|
||||
|
||||
// IStorageObject methods
|
||||
std::vector<std::string> GetFileNames(FileNameOptions options) override;
|
||||
ComPtr<IStream> GetFile(const std::string& fileName) override;
|
||||
std::string GetFileName() override;
|
||||
|
||||
protected:
|
||||
std::map<std::string, ComPtr<IStream>> m_streams;
|
||||
};
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
//
|
||||
// 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 <vector>
|
||||
#include <map>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
// {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<std::uint32_t, MSIX::ComPtr<IStream>> 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<ZipObjectWriter, IStorageObject, IZipWriter>, ZipObject
|
||||
{
|
||||
public:
|
||||
ZipObjectWriter(const ComPtr<IStream>& stream);
|
||||
|
||||
ZipObjectWriter(const ComPtr<IStorageObject>& storageObject);
|
||||
|
||||
// IStorage methods
|
||||
std::vector<std::string> GetFileNames(FileNameOptions options) override;
|
||||
ComPtr<IStream> GetFile(const std::string& fileName) override;
|
||||
std::string GetFileName() override { NOTIMPLEMENTED };
|
||||
|
||||
// IZipWriter
|
||||
std::pair<std::uint32_t, ComPtr<IStream>> 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<std::uint64_t, LocalFileHeader> m_lastLFH;
|
||||
};
|
||||
}
|
|
@ -5,9 +5,13 @@
|
|||
cmake_minimum_required(VERSION 3.8.0 FATAL_ERROR)
|
||||
project (makemsix)
|
||||
|
||||
if(MSIX_PACK)
|
||||
add_definitions(-DMSIX_PACK=1)
|
||||
endif()
|
||||
|
||||
if(WIN32)
|
||||
set(DESCRIPTION "makemsix manifest")
|
||||
configure_file(${CMAKE_PROJECT_ROOT}/manifest.cmakein ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
configure_file(${MSIX_PROJECT_ROOT}/manifest.cmakein ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe.manifest CRLF)
|
||||
set(MANIFEST ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}.exe.manifest)
|
||||
endif()
|
||||
|
||||
|
@ -16,7 +20,7 @@ add_executable(${PROJECT_NAME}
|
|||
${MANIFEST}
|
||||
)
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_BINARY_DIR}/src/msix)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_BINARY_ROOT}/src/msix)
|
||||
|
||||
add_dependencies(${PROJECT_NAME} msix)
|
||||
target_link_libraries(${PROJECT_NAME} msix)
|
||||
|
|
|
@ -11,277 +11,310 @@
|
|||
#include <string>
|
||||
#include <initializer_list>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
|
||||
// Describes which command the user specified
|
||||
enum class UserSpecified
|
||||
{
|
||||
Nothing,
|
||||
Help,
|
||||
Unpack,
|
||||
Unbundle
|
||||
};
|
||||
#define TOOL_HELP_COMMAND_STRING "-?"
|
||||
|
||||
// Tracks the state of the current parse operation as well as implements input validation
|
||||
struct State
|
||||
{
|
||||
bool Specify(UserSpecified spec)
|
||||
{
|
||||
if (specified != UserSpecified::Nothing || spec == UserSpecified::Help)
|
||||
{
|
||||
specified = UserSpecified::Help; // Because clearly the user needs some
|
||||
return false;
|
||||
}
|
||||
specified = spec;
|
||||
return true;
|
||||
}
|
||||
struct Invocation;
|
||||
|
||||
bool CreatePackageSubfolder()
|
||||
{
|
||||
unpackOptions = static_cast<MSIX_PACKUNPACK_OPTION>(unpackOptions | MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_CREATEPACKAGESUBFOLDER);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool UnpackWithFlatStructure()
|
||||
{
|
||||
unpackOptions = static_cast<MSIX_PACKUNPACK_OPTION>(unpackOptions | MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_UNPACKWITHFLATSTRUCTURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipManifestValidation()
|
||||
{
|
||||
validationOptions = static_cast<MSIX_VALIDATION_OPTION>(validationOptions | MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPAPPXMANIFEST);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipSignature()
|
||||
{
|
||||
validationOptions = static_cast<MSIX_VALIDATION_OPTION>(validationOptions | MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPSIGNATURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AllowSignatureOriginUnknown()
|
||||
{
|
||||
validationOptions = static_cast<MSIX_VALIDATION_OPTION>(validationOptions | MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipLanguage()
|
||||
{
|
||||
applicability = static_cast<MSIX_APPLICABILITY_OPTIONS>(applicability | MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_SKIPLANGUAGE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipPlatform()
|
||||
{
|
||||
applicability = static_cast<MSIX_APPLICABILITY_OPTIONS>(applicability | MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_SKIPPLATFORM);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipApplicability()
|
||||
{
|
||||
applicability = static_cast<MSIX_APPLICABILITY_OPTIONS>(applicability | MSIX_APPLICABILITY_NONE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetPackageName(const std::string& name)
|
||||
{
|
||||
if (!packageName.empty() || name.empty()) { return false; }
|
||||
packageName = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetDirectoryName(const std::string& name)
|
||||
{
|
||||
if (!directoryName.empty() || name.empty()) { return false; }
|
||||
directoryName = name;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Validate()
|
||||
{
|
||||
if (packageName.empty() || directoryName.empty()) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string packageName;
|
||||
std::string certName;
|
||||
std::string directoryName;
|
||||
UserSpecified specified = UserSpecified::Nothing;
|
||||
MSIX_VALIDATION_OPTION validationOptions = MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_FULL;
|
||||
MSIX_PACKUNPACK_OPTION unpackOptions = MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_NONE;
|
||||
MSIX_APPLICABILITY_OPTIONS applicability = MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_FULL;
|
||||
};
|
||||
|
||||
// describes an option to a command that the user may specify
|
||||
// Describes an option to a command that the user may specify.
|
||||
struct Option
|
||||
{
|
||||
typedef bool (*CBF)(State& state, const std::string& value);
|
||||
|
||||
Option(const std::string& name, bool param, const std::string& help, CBF callback) :
|
||||
Name(name), Help(help), Callback(callback), TakesParameter(param)
|
||||
// Constructor for flags; they can't be required and don't take parameters.
|
||||
Option(std::string name, std::string help) :
|
||||
Name(std::move(name)), Required(false), ParameterCount(0), Help(std::move(help))
|
||||
{}
|
||||
|
||||
bool operator==(const std::string& rhs) {
|
||||
Option(std::string name, std::string help, bool required, size_t parameterCount, std::string parameterName) :
|
||||
Name(std::move(name)), Required(required), ParameterCount(parameterCount), Help(std::move(help)), ParameterName(std::move(parameterName))
|
||||
{}
|
||||
|
||||
bool operator==(const std::string& rhs) const {
|
||||
return Name == rhs;
|
||||
}
|
||||
|
||||
bool TakesParameter;
|
||||
std::string Name;
|
||||
std::string Help;
|
||||
CBF Callback;
|
||||
bool Required;
|
||||
size_t ParameterCount;
|
||||
std::string ParameterName;
|
||||
};
|
||||
|
||||
// describes a command that the user may specify.
|
||||
// Describes a command that the user may specify.
|
||||
struct Command
|
||||
{
|
||||
typedef bool (*CBF)(State& state);
|
||||
using InvocationFunc = std::function<int(const Invocation&)>;
|
||||
|
||||
Command(const std::string& name, const std::string& help, CBF callback, std::vector<Option> options) :
|
||||
Name(name), Help(help), Callback(callback), Options(options)
|
||||
Command(std::string name, std::string help, std::vector<Option> options) :
|
||||
Name(std::move(name)), Help(std::move(help)), Options(std::move(options))
|
||||
{}
|
||||
|
||||
bool operator==(const std::string& rhs) {
|
||||
bool operator==(const std::string& rhs) const {
|
||||
return Name == rhs;
|
||||
}
|
||||
|
||||
// Limit strings to 76 characters
|
||||
void SetDescription(std::vector<std::string>&& description)
|
||||
{
|
||||
Description = std::move(description);
|
||||
}
|
||||
|
||||
void SetInvocationFunc(InvocationFunc func)
|
||||
{
|
||||
Invoke = func;
|
||||
}
|
||||
|
||||
void PrintHelpText(const Invocation& invocation) const;
|
||||
|
||||
std::string Name;
|
||||
std::string Help;
|
||||
std::vector<Option> Options;
|
||||
CBF Callback;
|
||||
std::vector<std::string> Description;
|
||||
InvocationFunc Invoke = nullptr;
|
||||
};
|
||||
|
||||
// Displays contextual formatted help to the user.
|
||||
int Help(char* toolName, std::vector<Command>& commands, State& state)
|
||||
// Tracks the state of the current parse operation.
|
||||
struct Invocation
|
||||
{
|
||||
bool Parse(const std::vector<Command>& commands, int argc, char* argv[]) try
|
||||
{
|
||||
if (argc < 1)
|
||||
{
|
||||
error = "Unexpected; no arguments provided, not even executable name";
|
||||
return false;
|
||||
}
|
||||
|
||||
toolName = argv[0];
|
||||
|
||||
char const* commandString = nullptr;
|
||||
if (argc == 1)
|
||||
{
|
||||
// No arguments implies help
|
||||
commandString = TOOL_HELP_COMMAND_STRING;
|
||||
}
|
||||
else
|
||||
{
|
||||
commandString = argv[1];
|
||||
}
|
||||
|
||||
// Find command, only happens once, at the beginning.
|
||||
auto commandItr = std::find(commands.begin(), commands.end(), commandString);
|
||||
|
||||
if (commandItr == commands.end())
|
||||
{
|
||||
error = "Unrecognized command: ";
|
||||
error += commandString;
|
||||
return false;
|
||||
}
|
||||
|
||||
command = &*commandItr;
|
||||
|
||||
// Parse all parameters, ensuring that parameter count is met
|
||||
for (int index = 2; index < argc; ++index)
|
||||
{
|
||||
char const* optionString = argv[index];
|
||||
auto option = std::find(command->Options.begin(), command->Options.end(), optionString);
|
||||
|
||||
if (option == command->Options.end())
|
||||
{
|
||||
error = "Unrecognized option: ";
|
||||
error += optionString;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> params;
|
||||
for (size_t i = 0; i < option->ParameterCount; ++i)
|
||||
{
|
||||
if (++index == argc) {
|
||||
error = "Not enough parameters for option: ";
|
||||
error += optionString;
|
||||
return false;
|
||||
}
|
||||
params.emplace_back(argv[index]);
|
||||
}
|
||||
|
||||
options.emplace_back(*option, std::move(params));
|
||||
}
|
||||
|
||||
// Ensure that all required parameters are present, unless help was requested
|
||||
if (!IsOptionPresent(TOOL_HELP_COMMAND_STRING))
|
||||
{
|
||||
for (const Option& option : command->Options)
|
||||
{
|
||||
if (option.Required)
|
||||
{
|
||||
if (!IsOptionPresent(option.Name))
|
||||
{
|
||||
error = "Required option missing: ";
|
||||
error += option.Name;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch (const std::exception& exc)
|
||||
{
|
||||
error = "Exception thrown during Parse: ";
|
||||
error += exc.what();
|
||||
return false;
|
||||
}
|
||||
|
||||
int Run() const try
|
||||
{
|
||||
if (!command)
|
||||
{
|
||||
error = "Unexpected; command should be set if Parse succeeded";
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Check for help option and have command print it
|
||||
if (IsOptionPresent(TOOL_HELP_COMMAND_STRING))
|
||||
{
|
||||
command->PrintHelpText(*this);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return command->Invoke(*this);
|
||||
}
|
||||
catch (const std::exception& exc)
|
||||
{
|
||||
error = "Exception thrown during Run: ";
|
||||
error += exc.what();
|
||||
return -1;
|
||||
}
|
||||
|
||||
const std::string& GetToolName() const { return toolName; }
|
||||
|
||||
const std::string& GetErrorText() const { return error; }
|
||||
|
||||
const Command* GetParsedCommand() const { return command; }
|
||||
|
||||
bool IsOptionPresent(const std::string& name) const
|
||||
{
|
||||
return (GetInvokedOption(name) != nullptr);
|
||||
}
|
||||
|
||||
const std::string& GetOptionValue(const std::string& name) const
|
||||
{
|
||||
const InvokedOption* opt = GetInvokedOption(name);
|
||||
if (!opt)
|
||||
{
|
||||
throw std::runtime_error("Expected option not present");
|
||||
}
|
||||
if (opt->option.ParameterCount != 1)
|
||||
{
|
||||
throw std::runtime_error("Given option does not take exactly one parameter");
|
||||
}
|
||||
return opt->params[0];
|
||||
}
|
||||
|
||||
private:
|
||||
mutable std::string error;
|
||||
std::string toolName;
|
||||
const Command* command = nullptr;
|
||||
|
||||
struct InvokedOption
|
||||
{
|
||||
InvokedOption(const Option& o, std::vector<std::string>&& p) : option(o), params(std::move(p)) {}
|
||||
|
||||
bool operator==(const std::string& rhs) const {
|
||||
return option.Name == rhs;
|
||||
}
|
||||
|
||||
const Option& option;
|
||||
std::vector<std::string> params;
|
||||
};
|
||||
|
||||
std::vector<InvokedOption> options;
|
||||
|
||||
const InvokedOption* GetInvokedOption(const std::string& name) const
|
||||
{
|
||||
auto option = std::find(options.begin(), options.end(), name);
|
||||
return (option == options.end() ? nullptr : &*option);
|
||||
}
|
||||
};
|
||||
|
||||
void Command::PrintHelpText(const Invocation& invocation) const
|
||||
{
|
||||
std::cout << std::endl;
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "------" << std::endl;
|
||||
std::cout << "---------------" << std::endl;
|
||||
|
||||
std::vector<Command>::iterator command;
|
||||
switch (state.specified)
|
||||
std::cout << " " << invocation.GetToolName() << ' ' << Name;
|
||||
|
||||
bool areOptionalOptionsPresent = false;
|
||||
for (const Option& option : Options)
|
||||
{
|
||||
case UserSpecified::Nothing:
|
||||
case UserSpecified::Help:
|
||||
std::cout << " " << toolName << " <command> [options] " << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Valid commands:" << std::endl;
|
||||
std::cout << "---------------" << std::endl;
|
||||
for (const auto& c : commands)
|
||||
if (option.Required)
|
||||
{
|
||||
std::cout << " " << std::left << std::setfill(' ') << std::setw(10) <<
|
||||
c.Name << "-- " << c.Help << std::endl;
|
||||
}
|
||||
std::cout << ' ' << option.Name;
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "For help with a specific command, enter " << toolName << " <command> -?" << std::endl;
|
||||
return 0;
|
||||
case UserSpecified::Unpack:
|
||||
command = std::find(commands.begin(), commands.end(), "unpack");
|
||||
std::cout << " " << toolName << " unpack -p <package> -d <directory> [options] " << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Description:" << std::endl;
|
||||
std::cout << "------------" << std::endl;
|
||||
std::cout << " Extracts all files within an app package at the input <package> name to the" << std::endl;
|
||||
std::cout << " specified output <directory>. The output has the same directory structure " << std::endl;
|
||||
std::cout << " as the package. If <package> is a bundle, it extract its contests with full" << std::endl;
|
||||
std::cout << " applicability validations and its packages will be unpacked in a directory " << std::endl;
|
||||
std::cout << " named as the package full name." << std::endl;
|
||||
break;
|
||||
case UserSpecified::Unbundle:
|
||||
command = std::find(commands.begin(), commands.end(), "unbundle");
|
||||
std::cout << " " << toolName << " unbundle -p <bundle> -d <directory> [options] " << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Description:" << std::endl;
|
||||
std::cout << "------------" << std::endl;
|
||||
std::cout << " Extracts all files and packages within the bundle at the input <bundle> name to the" << std::endl;
|
||||
std::cout << " specified output <directory>. The output has the same directory structure " << std::endl;
|
||||
std::cout << " as the package. its packages will be unpacked in a directory named as the package full name" << std::endl;
|
||||
break;
|
||||
for (size_t i = 0; i < option.ParameterCount; ++i)
|
||||
{
|
||||
std::cout << " <" << option.ParameterName << '>';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
areOptionalOptionsPresent = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (areOptionalOptionsPresent)
|
||||
{
|
||||
std::cout << " [options]";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << "--------" << std::endl;
|
||||
|
||||
for (const auto& option : command->Options) {
|
||||
std::cout << " " << std::left << std::setfill(' ') << std::setw(5) <<
|
||||
option.Name << ": " << option.Help << std::endl;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
// error text if the user provided underspecified input
|
||||
void Error(char* toolName)
|
||||
{
|
||||
std::cout << toolName << ": error : Missing required options. Use '-?' for more details." << std::endl;
|
||||
}
|
||||
|
||||
bool ParseInput(std::vector<Command>& commands, State& state, int argc, char* argv[])
|
||||
{
|
||||
int index = 1;
|
||||
std::vector<Command>::iterator command;
|
||||
std::vector<Option>::iterator option;
|
||||
while (index < argc)
|
||||
if (!Description.empty())
|
||||
{
|
||||
command = std::find(commands.begin(), commands.end(), argv[index]);
|
||||
if (command == commands.end()) {
|
||||
return false;
|
||||
}
|
||||
if (!command->Callback(state)) {
|
||||
return false;
|
||||
}
|
||||
if (++index == argc) {
|
||||
break;
|
||||
}
|
||||
option = std::find(command->Options.begin(), command->Options.end(), argv[index]);
|
||||
while (option != command->Options.end())
|
||||
std::cout << std::endl;
|
||||
std::cout << "Description:" << std::endl;
|
||||
std::cout << "---------------" << std::endl;
|
||||
|
||||
for (const std::string& line : Description)
|
||||
{
|
||||
char const *parameter = "";
|
||||
if (option->TakesParameter) {
|
||||
if (++index == argc) {
|
||||
break;
|
||||
}
|
||||
parameter = argv[index];
|
||||
}
|
||||
if (!option->Callback(state, parameter)) {
|
||||
return false;
|
||||
}
|
||||
if (++index == argc) {
|
||||
break;
|
||||
}
|
||||
option = std::find(command->Options.begin(), command->Options.end(), argv[index]);
|
||||
std::cout << " " << line << std::endl;
|
||||
}
|
||||
}
|
||||
return state.Validate();
|
||||
}
|
||||
|
||||
// Parses argc/argv input via commands into state, and calls into the
|
||||
// appropriate function with the correct parameters if warranted.
|
||||
int ParseAndRun(std::vector<Command>& commands, int argc, char* argv[])
|
||||
{
|
||||
State state;
|
||||
if (!ParseInput(commands, state, argc, argv)) {
|
||||
return Help(argv[0], commands, state);
|
||||
}
|
||||
switch (state.specified)
|
||||
if (!Options.empty())
|
||||
{
|
||||
case UserSpecified::Help:
|
||||
case UserSpecified::Nothing:
|
||||
return Help(argv[0], commands, state);
|
||||
case UserSpecified::Unpack:
|
||||
return UnpackPackage(state.unpackOptions, state.validationOptions,
|
||||
const_cast<char*>(state.packageName.c_str()),
|
||||
const_cast<char*>(state.directoryName.c_str())
|
||||
);
|
||||
case UserSpecified::Unbundle:
|
||||
return UnpackBundle(state.unpackOptions, state.validationOptions,
|
||||
state.applicability,
|
||||
const_cast<char*>(state.packageName.c_str()),
|
||||
const_cast<char*>(state.directoryName.c_str())
|
||||
);
|
||||
std::cout << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << "---------------" << std::endl;
|
||||
|
||||
for (const Option& option : Options)
|
||||
{
|
||||
std::cout << " " << option.Name;
|
||||
|
||||
if (option.ParameterCount)
|
||||
{
|
||||
std::cout << " <" << option.ParameterName << '>';
|
||||
|
||||
if (option.ParameterCount > 1)
|
||||
{
|
||||
std::cout << " [x" << option.ParameterCount << ']';
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
std::cout << " [Flag]";
|
||||
}
|
||||
|
||||
if (option.Required)
|
||||
{
|
||||
std::cout << " {Required}";
|
||||
}
|
||||
std::cout << std::endl;
|
||||
|
||||
std::cout << " " << option.Help << std::endl;
|
||||
}
|
||||
}
|
||||
return -1; // should never end up here.
|
||||
}
|
||||
|
||||
LPVOID STDMETHODCALLTYPE MyAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
|
@ -297,6 +330,231 @@ public:
|
|||
void Cleanup() { if (content) { std::free(content); content = nullptr; } }
|
||||
};
|
||||
|
||||
template <typename EnumType>
|
||||
std::underlying_type_t<EnumType> asut(EnumType e)
|
||||
{
|
||||
return static_cast<std::underlying_type_t<EnumType>>(e);
|
||||
}
|
||||
|
||||
template <typename EnumType>
|
||||
std::enable_if_t<std::is_enum<EnumType>::value, EnumType> operator|=(EnumType& a, EnumType b)
|
||||
{
|
||||
a = static_cast<EnumType>(asut(a) | asut(b));
|
||||
return a;
|
||||
}
|
||||
|
||||
MSIX_PACKUNPACK_OPTION GetPackUnpackOptionBase(const Invocation& invocation)
|
||||
{
|
||||
MSIX_PACKUNPACK_OPTION packUnpack = MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_NONE;
|
||||
|
||||
if (invocation.IsOptionPresent("-pfn"))
|
||||
{
|
||||
packUnpack |= MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_CREATEPACKAGESUBFOLDER;
|
||||
}
|
||||
|
||||
return packUnpack;
|
||||
}
|
||||
|
||||
MSIX_PACKUNPACK_OPTION GetPackUnpackOptionForPackage(const Invocation& invocation)
|
||||
{
|
||||
MSIX_PACKUNPACK_OPTION packUnpack = GetPackUnpackOptionBase(invocation);
|
||||
|
||||
if (invocation.IsOptionPresent("-pfn-flat"))
|
||||
{
|
||||
packUnpack |= MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_CREATEPACKAGESUBFOLDER;
|
||||
}
|
||||
|
||||
return packUnpack;
|
||||
}
|
||||
|
||||
MSIX_PACKUNPACK_OPTION GetPackUnpackOptionForBundle(const Invocation& invocation)
|
||||
{
|
||||
MSIX_PACKUNPACK_OPTION packUnpack = GetPackUnpackOptionBase(invocation);
|
||||
|
||||
if (invocation.IsOptionPresent("-pfn-flat"))
|
||||
{
|
||||
packUnpack |= MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_UNPACKWITHFLATSTRUCTURE;
|
||||
}
|
||||
|
||||
return packUnpack;
|
||||
}
|
||||
|
||||
MSIX_VALIDATION_OPTION GetValidationOption(const Invocation& invocation)
|
||||
{
|
||||
MSIX_VALIDATION_OPTION validation = MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_FULL;
|
||||
|
||||
if (invocation.IsOptionPresent("-ac"))
|
||||
{
|
||||
validation |= MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN;
|
||||
}
|
||||
|
||||
if (invocation.IsOptionPresent("-ss"))
|
||||
{
|
||||
validation |= MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_SKIPSIGNATURE;
|
||||
}
|
||||
|
||||
return validation;
|
||||
}
|
||||
|
||||
MSIX_APPLICABILITY_OPTIONS GetApplicabilityOption(const Invocation& invocation)
|
||||
{
|
||||
MSIX_APPLICABILITY_OPTIONS applicability = MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_FULL;
|
||||
|
||||
if (invocation.IsOptionPresent("-sl"))
|
||||
{
|
||||
applicability |= MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_SKIPLANGUAGE;
|
||||
}
|
||||
|
||||
if (invocation.IsOptionPresent("-sp"))
|
||||
{
|
||||
applicability |= MSIX_APPLICABILITY_OPTIONS::MSIX_APPLICABILITY_OPTION_SKIPPLATFORM;
|
||||
}
|
||||
|
||||
if (invocation.IsOptionPresent("-extract-all"))
|
||||
{
|
||||
applicability = static_cast<MSIX_APPLICABILITY_OPTIONS>(MSIX_APPLICABILITY_NONE);
|
||||
}
|
||||
|
||||
return applicability;
|
||||
}
|
||||
|
||||
#pragma region Commands
|
||||
|
||||
Command CreateHelpCommand(const std::vector<Command>& commands)
|
||||
{
|
||||
Command result{ TOOL_HELP_COMMAND_STRING, "Displays this help text.", {} };
|
||||
|
||||
result.SetInvocationFunc([&commands](const Invocation& invocation)
|
||||
{
|
||||
std::cout << std::endl;
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "---------------" << std::endl;
|
||||
std::cout << " " << invocation.GetToolName() << " <command> [options] " << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Commands:" << std::endl;
|
||||
std::cout << "---------------" << std::endl;
|
||||
|
||||
for (const auto& c : commands)
|
||||
{
|
||||
std::cout << " " << std::left << std::setfill(' ') << std::setw(10) <<
|
||||
c.Name << "-- " << c.Help << std::endl;
|
||||
}
|
||||
|
||||
std::cout << std::endl;
|
||||
std::cout << "For help with a specific command, enter " << invocation.GetToolName() << " <command> -?" << std::endl;
|
||||
|
||||
return 0;
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Command CreateUnpackCommand()
|
||||
{
|
||||
Command result{ "unpack", "Unpack files from a package to disk",
|
||||
{
|
||||
Option{ "-p", "Input package file path.", true, 1, "package" },
|
||||
Option{ "-d", "Output directory path.", true, 1, "directory" },
|
||||
Option{ "-pfn", "Unpacks all files to a subdirectory under the output path, named after the package full name." },
|
||||
Option{ "-ac", "Allows any certificate. By default the signature origin must be known." },
|
||||
Option{ "-ss", "Skips enforcement of signed packages. By default packages must be signed." },
|
||||
// Identical behavior as -pfn. This option was created to create parity with unbundle's -pfn-flat option so that IT pros
|
||||
// creating packages for app attach only need to be aware of a single option.
|
||||
Option{ "-pfn-flat", "Same behavior as -pfn for packages." },
|
||||
Option{ TOOL_HELP_COMMAND_STRING, "Displays this help text." },
|
||||
}
|
||||
};
|
||||
|
||||
result.SetDescription({
|
||||
"Extracts all files within an app package at the input <package> name to the",
|
||||
"specified output <directory>. The output has the same directory structure ",
|
||||
"as the package. If <package> is a bundle, it extract its contests with full",
|
||||
"applicability validations and its packages will be unpacked in a directory ",
|
||||
"named as the package full name.",
|
||||
});
|
||||
|
||||
result.SetInvocationFunc([](const Invocation& invocation)
|
||||
{
|
||||
return UnpackPackage(
|
||||
GetPackUnpackOptionForPackage(invocation),
|
||||
GetValidationOption(invocation),
|
||||
const_cast<char*>(invocation.GetOptionValue("-p").c_str()),
|
||||
const_cast<char*>(invocation.GetOptionValue("-d").c_str()));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Command CreateUnbundleCommand()
|
||||
{
|
||||
Command result{ "unbundle", "Unpack files from a bundle to disk",
|
||||
{
|
||||
Option{ "-p", "Input bundle file path.", true, 1, "bundle" },
|
||||
Option{ "-d", "Output directory path.", true, 1, "directory" },
|
||||
Option{ "-pfn", "Unpacks all files to a subdirectory under the output path, named after the package full name." },
|
||||
Option{ "-ac", "Allows any certificate. By default the signature origin must be known." },
|
||||
Option{ "-ss", "Skips enforcement of signed packages. By default packages must be signed." },
|
||||
Option{ "-sl", "Skips matching packages with the language of the system. By default unpacked resources packages will match the system languages." },
|
||||
Option{ "-sp", "Skips matching packages with of the same system. By default unpacked application packages will only match the platform." },
|
||||
Option{ "-extract-all", "Extracts all packages from the bundle." },
|
||||
Option{ "-pfn-flat", "Unpacks bundle's files to a subdirectory under the specified output path, named after the package full name. Unpacks packages to subdirectories also under the specified output path, named after the package full name. By default unpacked packages will be nested inside the bundle folder." },
|
||||
Option{ TOOL_HELP_COMMAND_STRING, "Displays this help text." },
|
||||
}
|
||||
};
|
||||
|
||||
result.SetDescription({
|
||||
"Extracts files and packages within the bundle at the input <bundle> name to",
|
||||
"the specified output <directory>. By default, the bundle files will be in",
|
||||
"<directory>, while the applicable packages will be placed in subdirectories",
|
||||
"named for their package full name. See the available flags to control which",
|
||||
"packages are unpacked, and where they are output.",
|
||||
});
|
||||
|
||||
result.SetInvocationFunc([](const Invocation& invocation)
|
||||
{
|
||||
return UnpackBundle(
|
||||
GetPackUnpackOptionForBundle(invocation),
|
||||
GetValidationOption(invocation),
|
||||
GetApplicabilityOption(invocation),
|
||||
const_cast<char*>(invocation.GetOptionValue("-p").c_str()),
|
||||
const_cast<char*>(invocation.GetOptionValue("-d").c_str()));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef MSIX_PACK
|
||||
Command CreatePackCommand()
|
||||
{
|
||||
Command result{ "pack", "Pack files from disk to a package",
|
||||
{
|
||||
Option{ "-d", "Input directory path.", true, 1, "directory" },
|
||||
Option{ "-p", "Output package file path.", true, 1, "package" },
|
||||
Option{ TOOL_HELP_COMMAND_STRING, "Displays this help text." },
|
||||
}
|
||||
};
|
||||
|
||||
result.SetDescription({
|
||||
"Creates an app package at <package> by adding all the files from the",
|
||||
"specified input <directory>. You must include a valid package manifest",
|
||||
"file named AppxManifest.xml in the directory provided.",
|
||||
});
|
||||
|
||||
result.SetInvocationFunc([](const Invocation& invocation)
|
||||
{
|
||||
return PackPackage(
|
||||
MSIX_PACKUNPACK_OPTION::MSIX_PACKUNPACK_OPTION_NONE,
|
||||
MSIX_VALIDATION_OPTION::MSIX_VALIDATION_OPTION_FULL,
|
||||
const_cast<char*>(invocation.GetOptionValue("-d").c_str()),
|
||||
const_cast<char*>(invocation.GetOptionValue("-p").c_str()));
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#pragma endregion
|
||||
|
||||
// Defines the grammar of commands and each command's associated options,
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
@ -304,65 +562,46 @@ int main(int argc, char* argv[])
|
|||
std::cout << "Copyright (C) 2017 Microsoft. All rights reserved." << std::endl;
|
||||
|
||||
std::vector<Command> commands = {
|
||||
{ Command("unpack", "Unpack files from a package to disk",
|
||||
[](State& state) { return state.Specify(UserSpecified::Unpack); },
|
||||
{
|
||||
Option("-p", true, "REQUIRED, specify input package name.",
|
||||
[](State& state, const std::string& name) { return state.SetPackageName(name); }),
|
||||
Option("-d", true, "REQUIRED, specify output directory name.",
|
||||
[](State& state, const std::string& name) { return state.SetDirectoryName(name); }),
|
||||
Option("-pfn", false, "Unpacks all files to a subdirectory under the specified output path, named after the package full name.",
|
||||
[](State& state, const std::string&) {return state.CreatePackageSubfolder(); }),
|
||||
Option("-mv", false, "Skips manifest validation. By default manifest validation is enabled.",
|
||||
[](State& state, const std::string&) { return state.SkipManifestValidation(); }),
|
||||
Option("-sv", false, "Skips signature validation. By default signature validation is enabled.",
|
||||
[](State& state, const std::string&) { return state.AllowSignatureOriginUnknown(); }),
|
||||
Option("-ss", false, "Skips enforcement of signed packages. By default packages must be signed.",
|
||||
[](State& state, const std::string&) { return state.SkipSignature(); }),
|
||||
Option("-?", false, "Displays this help text.",
|
||||
[](State& state, const std::string&) { return false; }),
|
||||
// Identical behavior as -pfn. This option was created to create parity with unbundle's -pfn-flat option so that IT pros
|
||||
// creating packages for app attach only need to be aware of a single option.
|
||||
Option("-pfn-flat", false, "Unpacks all files to a subdirectory under the specified output path, named after the package full name. Same behavior as -pfn",
|
||||
[](State& state, const std::string&) {return state.CreatePackageSubfolder(); })
|
||||
})
|
||||
},
|
||||
{ Command("unbundle", "Unpack files from a package to disk",
|
||||
[](State& state) { return state.Specify(UserSpecified::Unbundle); },
|
||||
{
|
||||
Option("-p", true, "REQUIRED, specify input package name.",
|
||||
[](State& state, const std::string& name) { return state.SetPackageName(name); }),
|
||||
Option("-d", true, "REQUIRED, specify output directory name.",
|
||||
[](State& state, const std::string& name) { return state.SetDirectoryName(name); }),
|
||||
Option("-pfn", false, "Unpacks all files to a subdirectory under the specified output path, named after the package full name.",
|
||||
[](State& state, const std::string&) {return state.CreatePackageSubfolder(); }),
|
||||
Option("-mv", false, "Skips manifest validation. By default manifest validation is enabled.",
|
||||
[](State& state, const std::string&) { return state.SkipManifestValidation(); }),
|
||||
Option("-sv", false, "Skips signature validation. By default signature validation is enabled.",
|
||||
[](State& state, const std::string&) { return state.AllowSignatureOriginUnknown(); }),
|
||||
Option("-ss", false, "Skips enforcement of signed packages. By default packages must be signed.",
|
||||
[](State& state, const std::string&) { return state.SkipSignature(); }),
|
||||
Option("-sl", false, "Only for bundles. Skips matching packages with the language of the system. By default unpacked resources packages will match the system languages.",
|
||||
[](State& state, const std::string&) { return state.SkipLanguage(); }),
|
||||
Option("-sp", false, "Only for bundles. Skips matching packages with of the same system. By default unpacked application packages will only match the platform.",
|
||||
[](State& state, const std::string&) { return state.SkipPlatform(); }),
|
||||
Option("-extract-all", false, "Only for bundles. Extracts all packages from the bundle.",
|
||||
[](State& state, const std::string&) { return state.SkipApplicability(); }),
|
||||
Option("-pfn-flat", false, "Unpacks bundle's files to a subdirectory under the specified output path, named after the package full name. Unpacks packages to subdirectories also under the specified output path, named after the package full name. By default unpacked packages will be nested inside the bundle folder",
|
||||
[](State& state, const std::string&) { return state.UnpackWithFlatStructure(); }),
|
||||
Option("-?", false, "Displays this help text.",
|
||||
[](State& state, const std::string&) { return false; })
|
||||
})
|
||||
},
|
||||
{ Command("-?", "Displays this help text.",
|
||||
[](State& state) { return state.Specify(UserSpecified::Help);}, {})
|
||||
},
|
||||
CreateUnpackCommand(),
|
||||
CreateUnbundleCommand(),
|
||||
#ifdef MSIX_PACK
|
||||
CreatePackCommand(),
|
||||
#endif
|
||||
};
|
||||
|
||||
auto result = ParseAndRun(commands, argc, argv);
|
||||
// Help command is always last
|
||||
commands.emplace_back(CreateHelpCommand(commands));
|
||||
const Command& mainHelpCommand = commands.back();
|
||||
|
||||
Invocation invocation;
|
||||
|
||||
if (!invocation.Parse(commands, argc, argv))
|
||||
{
|
||||
std::cout << std::endl;
|
||||
std::cout << "Error: " << invocation.GetErrorText() << std::endl;
|
||||
|
||||
if (invocation.GetParsedCommand())
|
||||
{
|
||||
invocation.GetParsedCommand()->PrintHelpText(invocation);
|
||||
}
|
||||
else
|
||||
{
|
||||
mainHelpCommand.Invoke(invocation);
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
int result = invocation.Run();
|
||||
|
||||
if (result != 0)
|
||||
{
|
||||
std::cout << "Error: " << std::hex << result << std::endl;
|
||||
std::cout << "Error: 0x" << std::hex << result << std::endl;
|
||||
if (!invocation.GetErrorText().empty())
|
||||
{
|
||||
std::cout << "Error: " << invocation.GetErrorText() << std::endl;
|
||||
}
|
||||
|
||||
Text text;
|
||||
auto logResult = GetLogTextUTF8(MyAllocate, &text);
|
||||
if (0 == logResult)
|
||||
|
@ -371,7 +610,7 @@ int main(int argc, char* argv[])
|
|||
}
|
||||
else
|
||||
{
|
||||
std::cout << "UNABLE TO GET LOG WITH HR=" << std::hex << logResult << std::endl;
|
||||
std::cout << "UNABLE TO GET LOG WITH HR=0x" << std::hex << logResult << std::endl;
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
|
|
@ -6,85 +6,36 @@ cmake_minimum_required(VERSION 3.8.0 FATAL_ERROR)
|
|||
|
||||
project(msix)
|
||||
|
||||
if(SKIP_BUNDLES)
|
||||
message(STATUS "Bundle support disabled.")
|
||||
else()
|
||||
add_definitions(-DBUNDLE_SUPPORT=1)
|
||||
endif()
|
||||
# Handle exports and flags we need to set
|
||||
list(APPEND MSIX_UNPACK_EXPORTS
|
||||
"UnpackPackage"
|
||||
"UnpackPackageFromStream"
|
||||
"UnpackPackageFromPackageReader"
|
||||
"UnpackBundle"
|
||||
"UnpackBundleFromStream"
|
||||
"UnpackBundleFromBundleReader"
|
||||
)
|
||||
|
||||
# Define PALs
|
||||
if(XML_PARSER MATCHES xerces)
|
||||
message(STATUS "XML_PARSER defined. Using XERCES-C XML parser." )
|
||||
set(XmlParser PAL/XML/xerces-c/XmlObject.cpp)
|
||||
add_definitions(-DUSING_XERCES=1)
|
||||
endif()
|
||||
|
||||
if(XML_PARSER MATCHES javaxml)
|
||||
message(STATUS "XML_PARSER defined. Using javaxml parser." )
|
||||
set(XmlParser PAL/XML/AOSP/XmlObject.cpp)
|
||||
add_definitions(-DUSING_JAVAXML=1)
|
||||
endif()
|
||||
|
||||
if(XML_PARSER MATCHES applexml)
|
||||
message(STATUS "XML_PARSER defined. Using apple xml parser." )
|
||||
set(XmlParser)
|
||||
list(APPEND XmlParser
|
||||
"PAL/XML/APPLE/XmlObject.cpp"
|
||||
"PAL/XML/APPLE/NSXmlParserDelegateWrapper.mm"
|
||||
"PAL/XML/APPLE/NSXmlParserWrapper.mm"
|
||||
"PAL/XML/APPLE/XmlDocumentReader.cpp"
|
||||
if(MSIX_PACK)
|
||||
list(APPEND MSIX_PACK_EXPORTS
|
||||
"PackPackage"
|
||||
)
|
||||
add_definitions(-DUSING_APPLE_XML=1)
|
||||
endif()
|
||||
|
||||
if(XML_PARSER MATCHES msxml6)
|
||||
message(STATUS "XML_PARSER defined. Using MSXML6 XML parser." )
|
||||
set(XmlParser PAL/XML/msxml6/XmlObject.cpp)
|
||||
add_definitions(-DUSING_MSXML=1)
|
||||
endif()
|
||||
|
||||
if(CRYPTO_LIB MATCHES crypt32)
|
||||
message(STATUS "CRYPTO_LIB defined. Using crypt32 library." )
|
||||
set(SHA256 PAL/SHA256/Win32/SHA256.cpp)
|
||||
set(Signature PAL/Signature/Win32/SignatureValidator.cpp)
|
||||
add_definitions(-DUSING_CRYPT32=1)
|
||||
endif()
|
||||
|
||||
if(CRYPTO_LIB MATCHES openssl)
|
||||
message(STATUS "CRYPTO_LIB defined. Using OpenSSL library." )
|
||||
if(OpenSSL_FOUND)
|
||||
message(STATUS "Using OpenSSL ${OpenSLL_VERSION}")
|
||||
set(SHA256 PAL/SHA256/OpenSSL/SHA256.cpp)
|
||||
set(Signature PAL/Signature/OpenSSL/SignatureValidator.cpp)
|
||||
else()
|
||||
# ... and were done here... :/
|
||||
message(STATUS "OpenSSL NOT FOUND!")
|
||||
return()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Visibility variables for non-win32 platforms
|
||||
set(MSIX_EXPORTS)
|
||||
list(APPEND MSIX_EXPORTS
|
||||
"CoCreateAppxFactory"
|
||||
"CoCreateAppxFactoryWithHeap"
|
||||
"CreateStreamOnFile"
|
||||
"CreateStreamOnFileUTF16"
|
||||
"GetLogTextUTF8"
|
||||
"UnpackPackage"
|
||||
"UnpackPackageFromPackageReader"
|
||||
"UnpackPackageFromStream"
|
||||
"UnpackBundle"
|
||||
"UnpackBundleFromBundleReader"
|
||||
"UnpackBundleFromStream"
|
||||
"CoCreateAppxBundleFactory"
|
||||
"CoCreateAppxBundleFactoryWithHeap"
|
||||
${MSIX_UNPACK_EXPORTS}
|
||||
${MSIX_PACK_EXPORTS}
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
set(DirectoryObject PAL/FileSystem/Win32/DirectoryObject.cpp)
|
||||
set(Applicability PAL/Applicability/Win32/Applicability.cpp)
|
||||
string(REGEX REPLACE ";" "\n " MSIX_EXPORTS "${MSIX_EXPORTS}")
|
||||
string(REGEX REPLACE ";" "\n " MSIX_EXPORTS "${MSIX_EXPORTS}")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/windowsexports.def.cmakein ${CMAKE_CURRENT_BINARY_DIR}/windowsexports.def CRLF)
|
||||
else()
|
||||
if((IOS) OR (MACOS))
|
||||
|
@ -101,25 +52,16 @@ else()
|
|||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/exports.def.cmakein ${CMAKE_CURRENT_BINARY_DIR}/exports.def CRLF)
|
||||
set(DEFINE_EXPORTS "-exported_symbols_list ${CMAKE_CURRENT_BINARY_DIR}/exports.def")
|
||||
# used to get the languages of the device
|
||||
find_library(COREFOUNDATION_LIBRARY CoreFoundation)
|
||||
if(NOT COREFOUNDATION_LIBRARY)
|
||||
message(FATAL_ERROR "CoreFoundation library not found")
|
||||
endif()
|
||||
find_library(FOUNDATION_LIBRARY Foundation)
|
||||
if(NOT FOUNDATION_LIBRARY)
|
||||
message(FATAL_ERROR "Foundation library not found")
|
||||
endif()
|
||||
set(Applicability PAL/Applicability/Apple/Applicability.cpp)
|
||||
if(IOS)
|
||||
add_definitions(-DIOS)
|
||||
else()
|
||||
add_definitions(-DMACOS)
|
||||
endif()
|
||||
find_library(COREFOUNDATION_LIBRARY CoreFoundation REQUIRED)
|
||||
find_library(FOUNDATION_LIBRARY Foundation REQUIRED)
|
||||
else()
|
||||
if(LINUX)
|
||||
add_definitions(-DLINUX)
|
||||
find_package(ICU COMPONENTS uc)
|
||||
set(Applicability PAL/Applicability/Linux/Applicability.cpp)
|
||||
endif()
|
||||
if(AOSP)
|
||||
# add here any exports specific for android
|
||||
|
@ -127,12 +69,12 @@ else()
|
|||
list(APPEND MSIX_EXPORTS
|
||||
"JNI_OnLoad"
|
||||
)
|
||||
set(InteropHpp PAL/Interop/AOSP/JniHelper.hpp)
|
||||
set(InteropCpp PAL/Interop/AOSP/JniHelper.cpp)
|
||||
set(Applicability PAL/Applicability/AOSP/Applicability.cpp)
|
||||
endif()
|
||||
# on Linux and linux-derived platforms, you use a version script to achieve similar ends.
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
|
||||
# Hide visibility and discard unused functions
|
||||
set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden -ffunction-sections -fdata-sections")
|
||||
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fvisibility=hidden -ffunction-sections -fdata-sections")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -Wl,--gc-sections")
|
||||
# On Linux and linux-derived platforms, you use a version script to achieve similar ends.
|
||||
# Make it look readable. The last ; is in the symbol.map.cmakein file
|
||||
string(REGEX REPLACE ";" ";\n\t" MSIX_EXPORTS "${MSIX_EXPORTS}")
|
||||
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/symbol.map.cmakein ${CMAKE_CURRENT_BINARY_DIR}/symbol.map CRLF)
|
||||
|
@ -140,33 +82,9 @@ else()
|
|||
endif()
|
||||
message(STATUS "Using export flag: ${DEFINE_EXPORTS}")
|
||||
set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} ${DEFINE_EXPORTS}")
|
||||
|
||||
set(DirectoryObject PAL/FileSystem/POSIX/DirectoryObject.cpp)
|
||||
endif()
|
||||
|
||||
if(USE_VALIDATION_PARSER)
|
||||
message(STATUS "PAL: VALIDATING parser" )
|
||||
add_definitions(-DVALIDATING=1)
|
||||
else()
|
||||
message(STATUS "PAL: non-validating parser" )
|
||||
add_definitions(-DVALIDATING=0)
|
||||
endif()
|
||||
|
||||
# Compression option
|
||||
if(((IOS) OR (MACOS)) AND (NOT USE_MSIX_SDK_ZLIB))
|
||||
set(CompressionObject PAL/DataCompression/Apple/CompressionObject.cpp)
|
||||
else()
|
||||
set(CompressionObject PAL/DataCompression/Zlib/CompressionObject.cpp)
|
||||
endif()
|
||||
|
||||
message(STATUS "PAL: XML = ${XmlParser}")
|
||||
message(STATUS "PAL: DirectoryObject = ${DirectoryObject}")
|
||||
message(STATUS "PAL: SHA256 = ${SHA256}")
|
||||
message(STATUS "PAL: Signature = ${Signature}")
|
||||
message(STATUS "PAL: Applicability = ${Applicability}")
|
||||
message(STATUS "PAL: Compression = ${CompressionObject}")
|
||||
|
||||
include(msix_resources)
|
||||
include(msix_resources) # Handles all the certificates and schemas we are going to use.
|
||||
|
||||
set(LIB_PUBLIC_HEADERS
|
||||
../inc/AppxPackaging.hpp
|
||||
|
@ -174,59 +92,163 @@ set(LIB_PUBLIC_HEADERS
|
|||
../inc/MsixErrors.hpp
|
||||
)
|
||||
|
||||
# Bundle specific files
|
||||
set(BundleSources)
|
||||
if(NOT SKIP_BUNDLES)
|
||||
list(APPEND BundleSources
|
||||
AppxBundleManifest.cpp
|
||||
ApplicabilityCommon.cpp
|
||||
${Applicability}
|
||||
set(MsixSrc) # list with all the files we are going to use
|
||||
|
||||
# Common for pack and unpack
|
||||
list(APPEND MsixSrc
|
||||
common/AppxFactory.cpp
|
||||
common/MSIXResource.cpp
|
||||
common/Log.cpp
|
||||
common/UnicodeConversion.cpp
|
||||
common/Encoding.cpp
|
||||
common/Exceptions.cpp
|
||||
common/AppxPackageInfo.cpp
|
||||
common/AppxManifestObject.cpp
|
||||
common/ZipObject.cpp
|
||||
common/FileNameValidation.cpp
|
||||
common/AppxManifestValidation.cpp
|
||||
common/IXml.cpp
|
||||
)
|
||||
|
||||
# Unpack. Always add
|
||||
list(APPEND MsixSrc
|
||||
unpack/AppxBlockMapObject.cpp
|
||||
unpack/AppxPackageObject.cpp
|
||||
unpack/AppxSignature.cpp
|
||||
unpack/InflateStream.cpp
|
||||
unpack/ZipObjectReader.cpp
|
||||
)
|
||||
|
||||
# Pack
|
||||
if(MSIX_PACK)
|
||||
add_definitions(-DMSIX_PACK=1)
|
||||
list(APPEND MsixSrc
|
||||
pack/AppxPackageWriter.cpp
|
||||
pack/XmlWriter.cpp
|
||||
pack/AppxBlockMapWriter.cpp
|
||||
pack/ContentTypeWriter.cpp
|
||||
pack/ContentType.cpp
|
||||
pack/DeflateStream.cpp
|
||||
pack/ZipObjectWriter.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
# Bundle specific files
|
||||
if(NOT SKIP_BUNDLES)
|
||||
add_definitions(-DBUNDLE_SUPPORT=1)
|
||||
list(APPEND MsixSrc
|
||||
unpack/AppxBundleManifest.cpp
|
||||
unpack/ApplicabilityCommon.cpp
|
||||
)
|
||||
# Applicability.
|
||||
if (WIN32)
|
||||
list(APPEND MsixSrc PAL/Applicability/Win32/Applicability.cpp)
|
||||
elseif(LINUX)
|
||||
find_package(ICU REQUIRED COMPONENTS uc)
|
||||
list(APPEND MsixSrc PAL/Applicability/Linux/Applicability.cpp)
|
||||
elseif(AOSP)
|
||||
list(APPEND MsixSrc PAL/Applicability/AOSP/Applicability.cpp)
|
||||
else() # iOS and MACOS
|
||||
list(APPEND MsixSrc PAL/Applicability/Apple/Applicability.cpp)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Compression option
|
||||
if(((IOS) OR (MACOS)) AND (NOT USE_MSIX_SDK_ZLIB))
|
||||
list(APPEND MsixSrc PAL/DataCompression/Apple/CompressionObject.cpp)
|
||||
else()
|
||||
list(APPEND MsixSrc PAL/DataCompression/Zlib/CompressionObject.cpp)
|
||||
endif()
|
||||
|
||||
# Directory object
|
||||
if(WIN32)
|
||||
list(APPEND MsixSrc PAL/FileSystem/Win32/DirectoryObject.cpp)
|
||||
else()
|
||||
list(APPEND MsixSrc PAL/FileSystem/POSIX/DirectoryObject.cpp)
|
||||
endif()
|
||||
|
||||
# Xml Parser
|
||||
if(XML_PARSER MATCHES xerces)
|
||||
list(APPEND MsixSrc PAL/XML/xerces-c/XmlObject.cpp)
|
||||
add_definitions(-DUSING_XERCES=1)
|
||||
elseif(XML_PARSER MATCHES javaxml)
|
||||
list(APPEND MsixSrc PAL/XML/AOSP/XmlObject.cpp)
|
||||
add_definitions(-DUSING_JAVAXML=1)
|
||||
elseif(XML_PARSER MATCHES applexml)
|
||||
set(XmlParser)
|
||||
list(APPEND MsixSrc
|
||||
"PAL/XML/APPLE/XmlObject.cpp"
|
||||
"PAL/XML/APPLE/NSXmlParserDelegateWrapper.mm"
|
||||
"PAL/XML/APPLE/NSXmlParserWrapper.mm"
|
||||
"PAL/XML/APPLE/XmlDocumentReader.cpp"
|
||||
)
|
||||
add_definitions(-DUSING_APPLE_XML=1)
|
||||
elseif(XML_PARSER MATCHES msxml6)
|
||||
list(APPEND MsixSrc PAL/XML/msxml6/XmlObject.cpp)
|
||||
add_definitions(-DUSING_MSXML=1)
|
||||
endif()
|
||||
|
||||
# Crypto
|
||||
if(CRYPTO_LIB MATCHES crypt32)
|
||||
list(APPEND MsixSrc
|
||||
PAL/Crypto/Win32/Crypto.cpp
|
||||
PAL/Signature/Win32/SignatureValidator.cpp
|
||||
)
|
||||
elseif(CRYPTO_LIB MATCHES openssl)
|
||||
if(OpenSSL_FOUND)
|
||||
list(APPEND MsixSrc
|
||||
PAL/Crypto/OpenSSL/Crypto.cpp
|
||||
PAL/Signature/OpenSSL/SignatureValidator.cpp
|
||||
)
|
||||
else()
|
||||
# ... and were done here... :/
|
||||
message(FATAL_ERROR "OpenSSL NOT FOUND!")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Interop
|
||||
if(AOSP)
|
||||
list(APPEND MsixSrc
|
||||
PAL/Interop/AOSP/JniHelper.hpp
|
||||
PAL/Interop/AOSP/JniHelper.cpp
|
||||
)
|
||||
endif()
|
||||
|
||||
message(STATUS "Source files:")
|
||||
message(STATUS "\tmsix.cpp")
|
||||
foreach(FILE ${MsixSrc})
|
||||
message(STATUS "\t${FILE}")
|
||||
endforeach()
|
||||
|
||||
# Define the library
|
||||
add_library(${PROJECT_NAME} SHARED
|
||||
AppxBlockMapObject.cpp
|
||||
AppxFactory.cpp
|
||||
AppxManifestObject.cpp
|
||||
AppxManifestValidation.cpp
|
||||
AppxPackageObject.cpp
|
||||
AppxPackageInfo.cpp
|
||||
AppxSignature.cpp
|
||||
Encoding.cpp
|
||||
Exceptions.cpp
|
||||
InflateStream.cpp
|
||||
Log.cpp
|
||||
UnicodeConversion.cpp
|
||||
msix.cpp
|
||||
ZipObject.cpp
|
||||
MSIXResource.cpp
|
||||
IXml.cpp
|
||||
${DirectoryObject}
|
||||
${SHA256}
|
||||
${Signature}
|
||||
${XmlParser}
|
||||
${CompressionObject}
|
||||
${InteropCpp}
|
||||
${BundleSources}
|
||||
${MsixSrc}
|
||||
)
|
||||
|
||||
# Copy out public headers to <binary dir>/src/msix
|
||||
# Adding dependency to the third party libs directory
|
||||
add_dependencies(${PROJECT_NAME} LIBS)
|
||||
|
||||
# Copy out public headers to <binary dir>/src/unpack
|
||||
configure_file(../inc/MSIXWindows.hpp ${CMAKE_CURRENT_BINARY_DIR}/MSIXWindows.hpp )
|
||||
configure_file(../inc/AppxPackaging.hpp ${CMAKE_CURRENT_BINARY_DIR}/AppxPackaging.hpp)
|
||||
configure_file(../inc/MsixErrors.hpp ${CMAKE_CURRENT_BINARY_DIR}/MsixErrors.hpp)
|
||||
|
||||
# Linker and includes
|
||||
# Include MSIX headers
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_PROJECT_ROOT}/src/inc)
|
||||
|
||||
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_PROJECT_ROOT}/src/inc)
|
||||
|
||||
if(WIN32)
|
||||
string(REPLACE "/GR" " " CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS}")
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
COMPILE_FLAGS "/source-charset:utf-8 /Gw /GL /GR-"
|
||||
)
|
||||
if(CMAKE_BUILD_TYPE MATCHES Debug)
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
COMPILE_FLAGS "/source-charset:utf-8 /GR-"
|
||||
)
|
||||
else()
|
||||
set_target_properties(${PROJECT_NAME} PROPERTIES
|
||||
COMPILE_FLAGS "/source-charset:utf-8 /Gw /GL /GR-"
|
||||
)
|
||||
endif()
|
||||
# Using /DELAYLOAD as linker option will use a helper funtion provided by
|
||||
# Visual C++ that will load the dll at runtime by calling LoadLibrary and GetProcAddress.
|
||||
# This dlls are NOT supposed to be loaded ever on Win7. See Exceptions.cpp
|
||||
|
@ -259,19 +281,16 @@ set_target_properties(${PROJECT_NAME} PROPERTIES
|
|||
# Compression
|
||||
if(((IOS) OR (MACOS)) AND (NOT USE_MSIX_SDK_ZLIB))
|
||||
# for macos and ios use the inbox libcompression zlib apis instead of zlib, unless zlib is explicitly requested.
|
||||
message(STATUS "MSIX takes a dependency on inbox libcompression")
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${CMAKE_PROJECT_ROOT}/src/msix/PAL/DataCompression/Apple)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE ${MSIX_PROJECT_ROOT}/src/msix/PAL/DataCompression/Apple)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE libcompression.dylib)
|
||||
elseif((AOSP) AND (NOT USE_MSIX_SDK_ZLIB))
|
||||
# for AOSP, use the libz.so from the android ndk.
|
||||
message(STATUS "MSIX takes a dependency on inbox zlib")
|
||||
find_package(ZLIB REQUIRED)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE -lz)
|
||||
else() # WIN32 or USE_MSIX_SDK_ZLIB
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/zlib
|
||||
${CMAKE_PROJECT_ROOT}/lib/zlib
|
||||
${CMAKE_PROJECT_ROOT}/src/msix/PAL/DataCompression/Zlib
|
||||
${MSIX_PROJECT_ROOT}/lib/zlib
|
||||
)
|
||||
if(USE_SHARED_ZLIB)
|
||||
message(STATUS "MSIX takes a dynamic dependency on zlib")
|
||||
|
@ -286,7 +305,7 @@ endif()
|
|||
if(XML_PARSER MATCHES xerces)
|
||||
target_include_directories(${PROJECT_NAME} PRIVATE
|
||||
${CMAKE_LIBRARY_OUTPUT_DIRECTORY}/xerces/src
|
||||
${CMAKE_PROJECT_ROOT}/lib/xerces/src
|
||||
${MSIX_PROJECT_ROOT}/lib/xerces/src
|
||||
)
|
||||
target_link_libraries(${PROJECT_NAME} PRIVATE xerces-c)
|
||||
endif()
|
||||
|
@ -359,13 +378,3 @@ if(OpenSSL_FOUND)
|
|||
target_link_libraries(${PROJECT_NAME} PRIVATE crypto)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# For windows copy the library to the msix and samples directory
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "bin/${PROJECT_NAME}.dll" "msixtest/${PROJECT_NAME}.dll"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy "bin/${PROJECT_NAME}.dll" "samples/${PROJECT_NAME}.dll"
|
||||
WORKING_DIRECTORY "${CMAKE_BINARY_DIR}")
|
||||
endif()
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#include "Exceptions.hpp"
|
||||
#include "Crypto.hpp"
|
||||
|
||||
#include "openssl/sha.h"
|
||||
#include "openssl/evp.h"
|
||||
|
||||
namespace MSIX {
|
||||
bool SHA256::ComputeHash(std::uint8_t *buffer, std::uint32_t cbBuffer, std::vector<uint8_t>& hash)
|
||||
{
|
||||
hash.resize(SHA256_DIGEST_LENGTH);
|
||||
::SHA256(buffer, cbBuffer, hash.data());
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Base64::ComputeBase64(const std::vector<std::uint8_t>& buffer)
|
||||
{
|
||||
int expectedSize = ((buffer.size() +2)/3)*4; // +2 for a cheap round up if it needs padding
|
||||
std::vector<std::uint8_t> result(expectedSize);
|
||||
int encodeResult = EVP_EncodeBlock(static_cast<unsigned char*>(result.data()), const_cast<unsigned char*>(buffer.data()), buffer.size());
|
||||
ThrowErrorIf(Error::Unexpected, expectedSize != encodeResult, "Error computing base64");
|
||||
return std::string(result.begin(), result.end());
|
||||
}
|
||||
}
|
|
@ -9,7 +9,8 @@
|
|||
#include <winternl.h>
|
||||
#include <winerror.h>
|
||||
#include "Exceptions.hpp"
|
||||
#include "SHA256.hpp"
|
||||
#include "Crypto.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <vector>
|
||||
|
@ -105,4 +106,17 @@ namespace MSIX {
|
|||
|
||||
return true;
|
||||
}
|
||||
|
||||
std::string Base64::ComputeBase64(const std::vector<std::uint8_t>& buffer)
|
||||
{
|
||||
std::wstring result;
|
||||
DWORD encodingFlags = CRYPT_STRING_BASE64 | CRYPT_STRING_NOCRLF;
|
||||
DWORD encodedHashSize = 0;
|
||||
ThrowHrIfFalse(CryptBinaryToStringW(buffer.data(), static_cast<DWORD>(buffer.size()), encodingFlags, nullptr, &encodedHashSize),
|
||||
"CryptBinaryToStringW failed");
|
||||
result.resize(encodedHashSize-1); // CryptBinaryToStringW returned size includes null termination
|
||||
ThrowHrIfFalse(CryptBinaryToStringW(buffer.data(), static_cast<DWORD>(buffer.size()), encodingFlags, const_cast<wchar_t*>(result.data()), &encodedHashSize),
|
||||
"CryptBinaryToStringW failed");
|
||||
return wstring_to_utf8(result);
|
||||
}
|
||||
}
|
|
@ -5,31 +5,60 @@
|
|||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "DirectoryObject.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <errno.h>
|
||||
#include <fts.h>
|
||||
#include <dirent.h>
|
||||
#include <map>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
template<class Lambda>
|
||||
void WalkDirectory(const std::string& root, Lambda& visitor)
|
||||
{
|
||||
static std::string dot(".");
|
||||
static std::string dotdot("..");
|
||||
|
||||
std::unique_ptr<DIR, decltype(&closedir)> dir(opendir(root.c_str()), closedir);
|
||||
ThrowErrorIf(Error::FileNotFound, dir.get() == nullptr, "Invalid directory");
|
||||
struct dirent* dp;
|
||||
// TODO: handle junction loops
|
||||
while((dp = readdir(dir.get())) != nullptr)
|
||||
{
|
||||
std::string fileName = std::string(dp->d_name);
|
||||
std::string child = root + "/" + fileName;
|
||||
if (dp->d_type == DT_DIR)
|
||||
{
|
||||
if ((fileName != dot) && (fileName != dotdot))
|
||||
{
|
||||
WalkDirectory(child, visitor);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: ignore .DS_STORE for mac?
|
||||
struct stat sb;
|
||||
ThrowErrorIf(Error::Unexpected, stat(child.c_str(), &sb) == -1, std::string("stat call failed" + std::to_string(errno)).c_str());
|
||||
if (!visitor(root, std::move(fileName), static_cast<std::uint64_t>(sb.st_mtime)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> DirectoryObject::GetFileNames(FileNameOptions)
|
||||
{
|
||||
// TODO: Implement when standing-up the pack side for test validation purposes
|
||||
NOTIMPLEMENTED;
|
||||
}
|
||||
|
||||
ComPtr<IStream> DirectoryObject::GetFile(const std::string& fileName)
|
||||
{
|
||||
// TODO: Implement when standing-up the pack side for test validation purposes
|
||||
NOTIMPLEMENTED;
|
||||
}
|
||||
|
||||
const char* DirectoryObject::GetPathSeparator() { return "/"; }
|
||||
|
||||
#define DEFAULT_MODE S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH
|
||||
void mkdirp(std::string& path, mode_t mode = DEFAULT_MODE)
|
||||
void mkdirp(std::string& path, size_t startPos = 0, mode_t mode = DEFAULT_MODE)
|
||||
{
|
||||
char* p = const_cast<char*>(path.c_str());
|
||||
char* p = &path[startPos];
|
||||
if (*p == '/') { p++; }
|
||||
while (*p != '\0')
|
||||
{
|
||||
|
@ -43,13 +72,43 @@ namespace MSIX {
|
|||
}
|
||||
}
|
||||
|
||||
const char* DirectoryObject::GetPathSeparator() { return "/"; }
|
||||
|
||||
DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary) : m_root(root)
|
||||
{
|
||||
if (createRootIfNecessary)
|
||||
{
|
||||
mkdirp(m_root);
|
||||
}
|
||||
}
|
||||
|
||||
ComPtr<IStream> DirectoryObject::OpenFile(const std::string& fileName, MSIX::FileStream::Mode mode)
|
||||
{
|
||||
std::string name = m_root + "/" + fileName;
|
||||
auto lastSlash = name.find_last_of("/");
|
||||
std::string name = m_root + GetPathSeparator() + fileName;
|
||||
auto lastSlash = name.find_last_of(GetPathSeparator());
|
||||
std::string path = name.substr(0, lastSlash);
|
||||
mkdirp(path);
|
||||
mkdirp(path, m_root.size());
|
||||
auto result = ComPtr<IStream>::Make<FileStream>(std::move(name), mode);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::multimap<std::uint64_t, std::string> DirectoryObject::GetFilesByLastModDate()
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
std::multimap<std::uint64_t, std::string> files;
|
||||
auto rootSize = m_root.size() + 1; // plus separator
|
||||
auto lamdba = [&](
|
||||
std::string root,
|
||||
std::string&& name,
|
||||
std::uint64_t size)
|
||||
{
|
||||
std::string fileName = root + GetPathSeparator() + name;
|
||||
// root contains the top level directory, which we don't need
|
||||
fileName = fileName.substr(rootSize);
|
||||
files.insert(std::make_pair(size, std::move(fileName)));
|
||||
return true;
|
||||
};
|
||||
WalkDirectory(m_root, lamdba);
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,6 +6,9 @@
|
|||
#include "Exceptions.hpp"
|
||||
#include "DirectoryObject.hpp"
|
||||
#include "FileStream.hpp"
|
||||
#include "MSIXWindows.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <iostream>
|
||||
|
@ -13,8 +16,7 @@
|
|||
#include <sstream>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include "MSIXWindows.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
#include <queue>
|
||||
|
||||
namespace MSIX {
|
||||
enum class WalkOptions : std::uint16_t
|
||||
|
@ -34,13 +36,17 @@ namespace MSIX {
|
|||
return static_cast<WalkOptions>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
|
||||
}
|
||||
|
||||
template <WalkOptions options, class Lambda>
|
||||
void WalkDirectory(const std::string& root, Lambda& visitor)
|
||||
template <class Lambda>
|
||||
void WalkDirectory(const std::string& root, WalkOptions options, Lambda& visitor)
|
||||
{
|
||||
static std::string dot(".");
|
||||
static std::string dotdot("..");
|
||||
|
||||
std::wstring utf16Name = utf8_to_wstring(root);
|
||||
if ((options & WalkOptions::Files) == WalkOptions::Files)
|
||||
{
|
||||
utf16Name += L"\\*";
|
||||
}
|
||||
|
||||
WIN32_FIND_DATA findFileData = {};
|
||||
std::unique_ptr<std::remove_pointer<HANDLE>::type, decltype(&::FindClose)> find(
|
||||
|
@ -57,33 +63,35 @@ namespace MSIX {
|
|||
ThrowWin32ErrorIfNot(lastError, false, "FindFirstFile failed.");
|
||||
}
|
||||
|
||||
// TODO: handle junction loops
|
||||
do
|
||||
{
|
||||
utf16Name = std::wstring(findFileData.cFileName);
|
||||
auto utf8Name = wstring_to_utf8(utf16Name);
|
||||
|
||||
if (((options & WalkOptions::Directories) == WalkOptions::Directories) &&
|
||||
(findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
)
|
||||
if (findFileData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY)
|
||||
{
|
||||
std::string child = (root + "\\" + utf8Name);
|
||||
if (!visitor(root, WalkOptions::Directories, std::move(utf8Name)))
|
||||
if (dot != utf8Name && dotdot != utf8Name)
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ((options & WalkOptions::Recursive) == WalkOptions::Recursive)
|
||||
{
|
||||
WalkDirectory<options>(child, visitor);
|
||||
std::string child = root + "\\" + utf8Name;
|
||||
if ((options & WalkOptions::Directories) == WalkOptions::Directories &&
|
||||
!visitor(root, WalkOptions::Directories, std::move(utf8Name), 0))
|
||||
{
|
||||
break;
|
||||
}
|
||||
if ((options & WalkOptions::Recursive) == WalkOptions::Recursive)
|
||||
{
|
||||
WalkDirectory(child, options, visitor);
|
||||
}
|
||||
}
|
||||
}
|
||||
else if ((options & WalkOptions::Files) == WalkOptions::Files)
|
||||
{
|
||||
if (dot != utf8Name && dotdot != utf8Name)
|
||||
ULARGE_INTEGER fileTime;
|
||||
fileTime.HighPart = findFileData.ftLastWriteTime.dwHighDateTime;
|
||||
fileTime.LowPart = findFileData.ftLastWriteTime.dwLowDateTime;
|
||||
if (!visitor(root, WalkOptions::Files, std::move(utf8Name), static_cast<std::uint64_t>(fileTime.QuadPart)))
|
||||
{
|
||||
if (!visitor(root, WalkOptions::Files, std::move(utf8Name)))
|
||||
{
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -97,74 +105,201 @@ namespace MSIX {
|
|||
"FindNextFile");
|
||||
}
|
||||
|
||||
static std::string GetFullPath(const std::string& path)
|
||||
{
|
||||
const std::wstring longPathPrefix = LR"(\\?\)";
|
||||
|
||||
auto pathWide = utf8_to_wstring(path);
|
||||
|
||||
std::wstring result = longPathPrefix;
|
||||
size_t prefixChars = longPathPrefix.size();
|
||||
if (pathWide.substr(0, longPathPrefix.size()) == longPathPrefix)
|
||||
{
|
||||
// Already begins with long path prefix, so don't add it
|
||||
result = L"";
|
||||
prefixChars = 0;
|
||||
}
|
||||
|
||||
// We aren't going to go out of our way to support crazy incoming paths.
|
||||
// This means that path here is limited to MAX_PATH, but the resulting path won't be.
|
||||
DWORD length = GetFullPathNameW(pathWide.c_str(), 0, nullptr, nullptr);
|
||||
|
||||
// Any errors result in 0
|
||||
ThrowLastErrorIf(length == 0, "Failed to get necessary char count for GetFullPathNameW");
|
||||
|
||||
// When requesting size, length accounts for null char
|
||||
result.resize(prefixChars + length, L' ');
|
||||
|
||||
DWORD newlength = GetFullPathNameW(pathWide.c_str(), length, &result[prefixChars], nullptr);
|
||||
|
||||
// On success, length does not account for null char
|
||||
ThrowLastErrorIf(length == 0, "Failed to get necessary char count for GetFullPathNameW");
|
||||
ThrowErrorIf(Error::Unexpected, (length - 1) != newlength, "Result length was unexpected");
|
||||
result.resize(prefixChars + newlength);
|
||||
|
||||
return wstring_to_utf8(result);
|
||||
}
|
||||
|
||||
struct DirectoryInfo
|
||||
{
|
||||
std::string Name;
|
||||
bool Create;
|
||||
|
||||
DirectoryInfo(std::string&& name, bool create) : Name(std::move(name)), Create(create) {}
|
||||
};
|
||||
|
||||
static void SplitDirectories(const std::string& path, std::queue<DirectoryInfo>& directories, bool forCreate)
|
||||
{
|
||||
static char const* const Delims = "\\/";
|
||||
const std::string longPathPrefix = R"(\\?\)";
|
||||
|
||||
size_t copyPos = 0;
|
||||
size_t searchPos = 0;
|
||||
size_t lastPos = 0;
|
||||
|
||||
if (path.substr(0, longPathPrefix.size()) == longPathPrefix)
|
||||
{
|
||||
// Absolute path, we need to skip it
|
||||
searchPos = longPathPrefix.size();
|
||||
}
|
||||
|
||||
while (lastPos != std::string::npos)
|
||||
{
|
||||
lastPos = path.find_first_of(Delims, searchPos);
|
||||
|
||||
std::string temp = path.substr(copyPos, lastPos - copyPos);
|
||||
if (!temp.empty())
|
||||
{
|
||||
directories.emplace(std::move(temp), forCreate);
|
||||
}
|
||||
|
||||
copyPos = searchPos = lastPos + 1;
|
||||
}
|
||||
}
|
||||
|
||||
// Destroys directories
|
||||
static void EnsureDirectoryStructureExists(const std::string& root, std::queue<DirectoryInfo>& directories, bool lastIsFile, std::string* resultingPath = nullptr)
|
||||
{
|
||||
ThrowErrorIf(Error::Unexpected, directories.empty(), "Some path must be given");
|
||||
|
||||
auto PopFirst = [&directories]()
|
||||
{
|
||||
auto result = directories.front();
|
||||
directories.pop();
|
||||
return result;
|
||||
};
|
||||
|
||||
std::string path = root;
|
||||
bool isFirst = true;
|
||||
|
||||
while (!directories.empty())
|
||||
{
|
||||
auto dirInfo = PopFirst();
|
||||
if (!path.empty())
|
||||
{
|
||||
path += DirectoryObject::GetPathSeparator();
|
||||
}
|
||||
path += dirInfo.Name;
|
||||
|
||||
bool shouldWeCreateDir = dirInfo.Create;
|
||||
|
||||
// When the last entry is a file, and we are on the last entry, never create
|
||||
if (lastIsFile && directories.empty())
|
||||
{
|
||||
shouldWeCreateDir = false;
|
||||
}
|
||||
// If this is a rooted list of directories, the first one will be a device
|
||||
else if (root.empty() && isFirst)
|
||||
{
|
||||
shouldWeCreateDir = false;
|
||||
}
|
||||
|
||||
if (shouldWeCreateDir)
|
||||
{
|
||||
bool found = false;
|
||||
|
||||
std::wstring utf16Name = utf8_to_wstring(path);
|
||||
DWORD attr = GetFileAttributesW(utf16Name.c_str());
|
||||
|
||||
if (attr == INVALID_FILE_ATTRIBUTES)
|
||||
{
|
||||
if (!CreateDirectory(utf16Name.c_str(), nullptr))
|
||||
{
|
||||
auto lastError = GetLastError();
|
||||
ThrowWin32ErrorIfNot(lastError, (lastError == ERROR_ALREADY_EXISTS), std::string("Call to CreateDirectory failed creating: " + path).c_str());
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowWin32ErrorIfNot(ERROR_ALREADY_EXISTS, attr & FILE_ATTRIBUTE_DIRECTORY, ("A file at this path already exists: " + path).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
isFirst = false;
|
||||
}
|
||||
|
||||
if (resultingPath)
|
||||
{
|
||||
*resultingPath = std::move(path);
|
||||
}
|
||||
}
|
||||
|
||||
const char* DirectoryObject::GetPathSeparator() { return "\\"; }
|
||||
|
||||
DirectoryObject::DirectoryObject(const std::string& root, bool createRootIfNecessary)
|
||||
{
|
||||
m_root = GetFullPath(root);
|
||||
|
||||
if (createRootIfNecessary)
|
||||
{
|
||||
std::queue<DirectoryInfo> directories;
|
||||
SplitDirectories(m_root, directories, true);
|
||||
EnsureDirectoryStructureExists({}, directories, false);
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<std::string> DirectoryObject::GetFileNames(FileNameOptions)
|
||||
{
|
||||
// TODO: Implement when standing-up the pack side for test validation purposes.
|
||||
NOTIMPLEMENTED;
|
||||
}
|
||||
|
||||
ComPtr<IStream> DirectoryObject::GetFile(const std::string& fileName)
|
||||
{
|
||||
// TODO: Implement when standing-up the pack side for test validation purposes.
|
||||
NOTIMPLEMENTED;
|
||||
}
|
||||
|
||||
// IDirectoryObject
|
||||
ComPtr<IStream> DirectoryObject::OpenFile(const std::string& fileName, FileStream::Mode mode)
|
||||
{
|
||||
std::vector<std::string> directories;
|
||||
auto PopFirst = [&directories]()
|
||||
{
|
||||
auto result = directories.at(0);
|
||||
std::vector<std::string>(directories.begin() + 1, directories.end()).swap(directories);
|
||||
return result;
|
||||
};
|
||||
std::queue<DirectoryInfo> directories;
|
||||
|
||||
// Add the root directory and build a list of directory names to ensure exist
|
||||
std::istringstream stream(m_root + "/" + fileName);
|
||||
std::string directory;
|
||||
while (getline(stream, directory, '/'))
|
||||
{
|
||||
directories.push_back(std::move(directory));
|
||||
}
|
||||
// Enforce that directory structure exists before creating file at specified location;
|
||||
// but only if we are going to write the file. If reading, the file should already exist.
|
||||
bool modeWillCreateFile = (mode != FileStream::Mode::READ && mode != FileStream::Mode::READ_UPDATE);
|
||||
SplitDirectories(fileName, directories, modeWillCreateFile);
|
||||
|
||||
// Enforce that directory structure exists before creating file at specified location.
|
||||
bool found = false;
|
||||
std::string path = PopFirst();
|
||||
do
|
||||
{
|
||||
WalkDirectory<WalkOptions::Directories>(path, [&](
|
||||
std::string,
|
||||
WalkOptions option,
|
||||
std::string&& name)
|
||||
{
|
||||
found = false;
|
||||
if (directories.front() == name)
|
||||
{
|
||||
found = true;
|
||||
return false;
|
||||
}
|
||||
std::string path;
|
||||
EnsureDirectoryStructureExists(m_root, directories, true, &path);
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if(!found)
|
||||
{
|
||||
std::wstring utf16Name = utf8_to_wstring(path);
|
||||
if (!CreateDirectory(utf16Name.c_str(), nullptr))
|
||||
{
|
||||
auto lastError = GetLastError();
|
||||
ThrowWin32ErrorIfNot(lastError, (lastError == ERROR_ALREADY_EXISTS), "CreateDirectory");
|
||||
}
|
||||
}
|
||||
path = path + GetPathSeparator() + PopFirst();
|
||||
found = false;
|
||||
}
|
||||
while(directories.size() > 0);
|
||||
auto result = ComPtr<IStream>::Make<FileStream>(std::move(utf8_to_wstring(path)), mode);
|
||||
return result;
|
||||
}
|
||||
|
||||
std::multimap<std::uint64_t, std::string> DirectoryObject::GetFilesByLastModDate()
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
std::multimap<std::uint64_t, std::string> files;
|
||||
auto rootSize = m_root.size() + 1; // plus separator
|
||||
WalkDirectory(m_root, WalkOptions::Recursive | WalkOptions::Files, [&](
|
||||
std::string root,
|
||||
WalkOptions option,
|
||||
std::string&& name,
|
||||
std::uint64_t lastWrite)
|
||||
{
|
||||
std::string fileName = root + GetPathSeparator() + name;
|
||||
// root contains the top level directory, which we don't need
|
||||
fileName = fileName.substr(rootSize);
|
||||
files.insert(std::make_pair(lastWrite, std::move(fileName)));
|
||||
return true;
|
||||
});
|
||||
return files;
|
||||
}
|
||||
}
|
||||
|
||||
// Don't pollute other compilation units with any of our #defs...
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#include "Exceptions.hpp"
|
||||
#include "SHA256.hpp"
|
||||
|
||||
#include "openssl/sha.h"
|
||||
|
||||
namespace MSIX {
|
||||
bool SHA256::ComputeHash(std::uint8_t *buffer, std::uint32_t cbBuffer, std::vector<uint8_t>& hash)
|
||||
{
|
||||
hash.resize(SHA256_DIGEST_LENGTH);
|
||||
::SHA256(buffer, cbBuffer, hash.data());
|
||||
return true;
|
||||
}
|
||||
} // namespace MSIX {
|
|
@ -43,6 +43,9 @@ public:
|
|||
getTextContentFunc = m_env->GetMethodID(
|
||||
xmlElementClass.get(), "GetTextContent",
|
||||
"()Ljava/lang/String;");
|
||||
getPrefixFunc = m_env->GetMethodID(
|
||||
xmlElementClass.get(), "GetPrefix",
|
||||
"()Ljava/lang/String;");
|
||||
getElementsByTagNameFunc = m_env->GetMethodID(
|
||||
xmlElementClass.get(), "GetElementsByTagName",
|
||||
"(Ljava/lang/String;)[Lcom/microsoft/msix/XmlElement;");
|
||||
|
@ -70,6 +73,21 @@ public:
|
|||
return GetStringFromJString(jvalue.get());
|
||||
}
|
||||
|
||||
std::string GetPrefix() override
|
||||
{
|
||||
std::unique_ptr<_jstring, JObjectDeleter> jvalue(reinterpret_cast<jstring>(m_env->CallObjectMethod(m_javaXmlElementObject.get(), getPrefixFunc)));
|
||||
if (jvalue.get() != nullptr)
|
||||
{
|
||||
std::string nodeName = GetStringFromJString(jvalue.get());
|
||||
std::size_t semiColon = nodeName.find_first_of(':');
|
||||
if (semiColon != std::string::npos)
|
||||
{
|
||||
return nodeName.substr(0, semiColon);
|
||||
}
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// IJavaXmlElement
|
||||
jobject GetJavaObject() override { return m_javaXmlElementObject.get(); }
|
||||
|
||||
|
@ -130,6 +148,7 @@ private:
|
|||
std::unique_ptr<_jobject, JObjectDeleter> m_javaXmlElementObject;
|
||||
jmethodID getAttributeValueFunc = nullptr;
|
||||
jmethodID getTextContentFunc = nullptr;
|
||||
jmethodID getPrefixFunc = nullptr;
|
||||
jmethodID getElementsByTagNameFunc = nullptr;
|
||||
jmethodID getElementsFunc = nullptr;
|
||||
JNIEnv* m_env = nullptr;
|
||||
|
|
|
@ -23,8 +23,18 @@
|
|||
|
||||
- (void)parser:(NSXMLParser *)parser didStartElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName attributes:(NSDictionary *)attributeDict {
|
||||
std::unique_ptr<MSIX::XmlNode> node(new MSIX::XmlNode());
|
||||
|
||||
node->NodeName = std::string([elementName UTF8String]);
|
||||
|
||||
std::string nodeName = std::string([elementName UTF8String]);
|
||||
std::size_t semiColon = nodeName.find_first_of(':');
|
||||
if (semiColon != std::string::npos)
|
||||
{
|
||||
node->NodeName = nodeName.substr(semiColon + 1);
|
||||
node->Prefix = nodeName.substr(0, semiColon);
|
||||
}
|
||||
else
|
||||
{
|
||||
node->NodeName = nodeName;
|
||||
}
|
||||
if (qName)
|
||||
{
|
||||
node->QualifiedNodeName = std::string([qName UTF8String]);
|
||||
|
@ -41,7 +51,13 @@
|
|||
}
|
||||
|
||||
- (void)parser:(NSXMLParser *)parser didEndElement:(NSString *)elementName namespaceURI:(NSString *)namespaceURI qualifiedName:(NSString *)qName {
|
||||
m_xmlDocumentReader->ProcessNodeEnd(std::string([elementName UTF8String]));
|
||||
std::string name = std::string([elementName UTF8String]);
|
||||
std::size_t semiColon = name.find_first_of(':');
|
||||
if (semiColon != std::string::npos)
|
||||
{
|
||||
name = name.substr(semiColon + 1);
|
||||
}
|
||||
m_xmlDocumentReader->ProcessNodeEnd(name);
|
||||
}
|
||||
|
||||
- (void) parserDidEndDocument:(NSXMLParser *)parser {
|
||||
|
|
|
@ -26,6 +26,7 @@ public:
|
|||
std::string Text;
|
||||
std::string NodeName;
|
||||
std::string QualifiedNodeName;
|
||||
std::string Prefix;
|
||||
|
||||
std::list<XmlNode*> FindElements(std::string xpath);
|
||||
private:
|
||||
|
|
|
@ -54,6 +54,11 @@ public:
|
|||
return m_xmlNode->Text;
|
||||
}
|
||||
|
||||
std::string GetPrefix() override
|
||||
{
|
||||
return m_xmlNode->Prefix;
|
||||
}
|
||||
|
||||
// IAppleXmlElement
|
||||
XmlNode* GetXmlNode() override { return m_xmlNode; }
|
||||
|
||||
|
|
|
@ -163,6 +163,19 @@ public:
|
|||
return {};
|
||||
}
|
||||
|
||||
std::string GetPrefix() override
|
||||
{
|
||||
ComPtr<IXMLDOMNode> node;
|
||||
ThrowHrIfFailed(m_element->QueryInterface(__uuidof(IXMLDOMNode), reinterpret_cast<void**>(&node)));
|
||||
Bstr value;
|
||||
ThrowHrIfFailed(node->get_prefix(value.AddressOf()));
|
||||
if (value.Get() != nullptr)
|
||||
{
|
||||
return wstring_to_utf8(static_cast<WCHAR*>(value.Get()));
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// IMSXMLElement
|
||||
ComPtr<IXMLDOMNodeList> SelectNodes(XmlQueryName query) override
|
||||
{
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
#include <vector>
|
||||
#include <map>
|
||||
#include <queue>
|
||||
#include <list>
|
||||
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
|
@ -204,7 +205,7 @@ public:
|
|||
XMLCh* Get() const { return m_ptr; }
|
||||
protected:
|
||||
inline void Swap(XercesXMLChPtr& right ) { std::swap(m_ptr, right.m_ptr); }
|
||||
XMLCh* m_ptr = nullptr;
|
||||
XMLCh* m_ptr = nullptr;
|
||||
};
|
||||
|
||||
class XercesXMLBytePtr
|
||||
|
@ -235,8 +236,8 @@ public:
|
|||
XMLByte* Get() const { return m_ptr; }
|
||||
protected:
|
||||
inline void Swap(XercesXMLBytePtr& right ) { std::swap(m_ptr, right.m_ptr); }
|
||||
XMLByte* m_ptr = nullptr;
|
||||
};
|
||||
XMLByte* m_ptr = nullptr;
|
||||
};
|
||||
|
||||
class XercesElement final : public ComClass<XercesElement, IXmlElement, IXercesElement, IMsixElement>
|
||||
{
|
||||
|
@ -273,7 +274,22 @@ public:
|
|||
{
|
||||
DOMNode* node = dynamic_cast<DOMNode*>(m_element);
|
||||
XercesCharPtr value(XMLString::transcode(node->getTextContent()));
|
||||
return std::string(value.Get());
|
||||
if (value.Get() != nullptr)
|
||||
{
|
||||
return std::string(value.Get());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
std::string GetPrefix() override
|
||||
{
|
||||
DOMNode* node = dynamic_cast<DOMNode*>(m_element);
|
||||
XercesCharPtr value(XMLString::transcode(node->getPrefix()));
|
||||
if (value.Get() != nullptr)
|
||||
{
|
||||
return std::string(value.Get());
|
||||
}
|
||||
return {};
|
||||
}
|
||||
|
||||
// IXercesElement
|
||||
|
@ -398,6 +414,7 @@ public:
|
|||
auto entityResolver = std::make_unique<MsixEntityResolver>(m_factory, s_xmlNamespaces[static_cast<std::uint8_t>(footPrintType)]);
|
||||
m_parser->setErrorHandler(errorHandler.get());
|
||||
m_parser->setXMLEntityResolver(entityResolver.get());
|
||||
m_parser->setDoNamespaces(true);
|
||||
|
||||
if (!schemas.empty())
|
||||
{
|
||||
|
@ -409,7 +426,6 @@ public:
|
|||
m_parser->setValidationScheme(XERCES_CPP_NAMESPACE::AbstractDOMParser::ValSchemes::Val_Always);
|
||||
m_parser->cacheGrammarFromParse(true);
|
||||
m_parser->setDoSchema(true);
|
||||
m_parser->setDoNamespaces(true);
|
||||
m_parser->setValidationSchemaFullChecking(true);
|
||||
// Disable DTD and prevent XXE attacks. See https://www.owasp.org/index.php/XML_External_Entity_(XXE)_Prevention_Cheat_Sheet#libxerces-c for additional details.
|
||||
m_parser->setIgnoreCachedDTD(true);
|
||||
|
@ -426,7 +442,6 @@ public:
|
|||
}
|
||||
|
||||
m_parser->parse(*source);
|
||||
m_resolver = XercesPtr<DOMXPathNSResolver>(m_parser->getDocument()->createNSResolver(m_parser->getDocument()));
|
||||
|
||||
// TODO: Do semantic check for all the elements we modified to maxOcurrs=unbounded and xs:patterns
|
||||
}
|
||||
|
@ -439,21 +454,33 @@ public:
|
|||
|
||||
bool ForEachElementIn(const ComPtr<IXmlElement>& root, XmlQueryName query, XmlVisitor& visitor) override
|
||||
{
|
||||
ComPtr<IXercesElement> element = root.As<IXercesElement>();
|
||||
DOMElement* element = root.As<IXercesElement>()->GetElement();
|
||||
|
||||
XercesXMLChPtr xPath(XMLString::transcode(GetQueryString(query)));
|
||||
XercesPtr<DOMXPathResult> result(m_parser->getDocument()->evaluate(
|
||||
xPath.Get(),
|
||||
element->GetElement(),
|
||||
m_resolver.Get(),
|
||||
DOMXPathResult::ORDERED_NODE_SNAPSHOT_TYPE,
|
||||
nullptr));
|
||||
|
||||
for (XMLSize_t i = 0; i < result->getSnapshotLength(); i++)
|
||||
std::list<DOMElement*> list;
|
||||
std::string xpath(GetQueryString(query));
|
||||
|
||||
if (xpath.size() >= 2 && xpath[0] == '.' && xpath[1] == '/')
|
||||
{
|
||||
result->snapshotItem(i);
|
||||
auto node = static_cast<DOMElement*>(result->getNodeValue());
|
||||
auto item = ComPtr<IXmlElement>::Make<XercesElement>(m_factory, node, m_parser.get());
|
||||
FindChildElements(xpath.substr(2), element, list);
|
||||
}
|
||||
else if (xpath.size() > 1 && xpath[0] == '/')
|
||||
{
|
||||
// Get name of root element
|
||||
std::size_t secondSlash = xpath.find_first_of('/', 1);
|
||||
XercesXMLChPtr firstElement(XMLString::transcode(xpath.substr(1, secondSlash - 1).c_str()));
|
||||
if (XMLString::compareString(firstElement.Get(), static_cast<DOMNode*>(element)->getLocalName()) == 0)
|
||||
{
|
||||
FindChildElements(xpath.substr(secondSlash + 1), element, list);
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowErrorAndLog(Error::XmlFatal, "Invalid root element");
|
||||
}
|
||||
}
|
||||
|
||||
for(const auto& element : list)
|
||||
{
|
||||
auto item = ComPtr<IXmlElement>::Make<XercesElement>(m_factory, element, m_parser.get());
|
||||
if (!visitor(item))
|
||||
{
|
||||
return false;
|
||||
|
@ -580,9 +607,37 @@ protected:
|
|||
}
|
||||
}
|
||||
|
||||
void FindChildElements(std::string xpath, DOMElement* root, std::list<DOMElement*>& list)
|
||||
{
|
||||
// The special value "*" matches all namespaces
|
||||
XercesXMLChPtr allNS(XMLString::transcode("*"));
|
||||
|
||||
// Find next element to search
|
||||
std::size_t nextSeparator = xpath.find_first_of('/');
|
||||
XercesXMLChPtr nextElement(XMLString::transcode(xpath.substr(0, nextSeparator).c_str()));
|
||||
|
||||
DOMNodeList* childs = root->getElementsByTagNameNS(allNS.Get(), nextElement.Get());
|
||||
XMLSize_t childsSize = childs->getLength();
|
||||
for(XMLSize_t i = 0; i < childsSize; i++)
|
||||
{
|
||||
DOMNode* node = childs->item(i);
|
||||
if (XMLString::compareString(nextElement.Get(), node->getLocalName()) == 0)
|
||||
{
|
||||
if (nextSeparator == std::string::npos)
|
||||
{
|
||||
// This is the node we are looking for.
|
||||
list.emplace_back(static_cast<DOMElement*>(node));
|
||||
}
|
||||
else
|
||||
{
|
||||
FindChildElements(xpath.substr(nextSeparator + 1), static_cast<DOMElement*>(node), list);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
IMsixFactory* m_factory;
|
||||
std::unique_ptr<XERCES_CPP_NAMESPACE::XercesDOMParser> m_parser;
|
||||
XercesPtr<DOMXPathNSResolver> m_resolver;
|
||||
ComPtr<IStream> m_stream;
|
||||
};
|
||||
|
||||
|
|
|
@ -36,6 +36,10 @@ public class XmlElement {
|
|||
return m_element.getTextContent();
|
||||
}
|
||||
|
||||
public String GetPrefix() {
|
||||
return m_element.getNodeName(); // getPrefix keep returning null
|
||||
}
|
||||
|
||||
public XmlElement[] GetElementsByTagName(String name) {
|
||||
List<XmlElement> elements = new ArrayList<>();
|
||||
try {
|
||||
|
|
|
@ -1,868 +0,0 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "ObjectBase.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "ZipObject.hpp"
|
||||
#include "ZipFileStream.hpp"
|
||||
#include "InflateStream.hpp"
|
||||
#include "VectorStream.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
namespace MSIX {
|
||||
/* Zip File Structure
|
||||
[LocalFileHeader 1]
|
||||
[encryption header 1]
|
||||
[file data 1]
|
||||
[data descriptor 1]
|
||||
.
|
||||
.
|
||||
[LocalFileHeader n]
|
||||
[encryption header n]
|
||||
[file data n]
|
||||
[data descriptor n]
|
||||
[archive decryption header]
|
||||
[archive extra data record]
|
||||
[CentralFileHeader 1]
|
||||
.
|
||||
.
|
||||
[CentralFileHeader n]
|
||||
[Zip64EndOfCentralDirectoryRecord]
|
||||
[Zip64EndOfCentralDirectoryLocator]
|
||||
[EndCentralDirectoryRecord]
|
||||
*/
|
||||
enum class ZipVersions : std::uint16_t
|
||||
{
|
||||
Zip32DefaultVersion = 20,
|
||||
Zip64FormatExtension = 45,
|
||||
};
|
||||
|
||||
// from AppNote.txt, section 4.5.2:
|
||||
enum class HeaderIDs : std::uint16_t
|
||||
{
|
||||
Zip64ExtendedInfo = 0x0001, // Zip64 extended information extra field
|
||||
AV = 0x0007, // AV Info
|
||||
RESERVED_1 = 0x0008, // Reserved for extended language encoding data (PFS) (see APPENDIX D)
|
||||
OS2 = 0x0009, // OS/2
|
||||
NTFS = 0x000a, // NTFS
|
||||
OpenVMS = 0x000c, // OpenVMS
|
||||
UNIX = 0x000d, // UNIX
|
||||
RESERVED_2 = 0x000e, // Reserved for file stream and fork descriptors
|
||||
PatchDescriptor = 0x000f, // Patch Descriptor
|
||||
UNSUPPORTED_1 = 0x0014, // PKCS#7 Store for X.509 Certificates
|
||||
UNSUPPORTED_2 = 0x0015, // X.509 Certificate ID and Signature for individual file
|
||||
UNSUPPORTED_3 = 0x0016, // X.509 Certificate ID for Central Directory
|
||||
UNSUPPORTED_4 = 0x0017, // Strong Encryption Header
|
||||
RecordManagement = 0x0018, // Record Management Controls
|
||||
UNSUPPORTED_5 = 0x0019, // PKCS#7 Encryption Recipient Certificate List
|
||||
IBMS390 = 0x0065, // IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed
|
||||
IBM_Reserved = 0x0066, // Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed
|
||||
RESERVED_3 = 0x4690, // POSZIP 4690 (reserved)
|
||||
};
|
||||
|
||||
// from ZIP file format specification detailed in AppNote.txt
|
||||
enum class Signatures : std::uint32_t
|
||||
{
|
||||
LocalFileHeader = 0x04034b50,
|
||||
DataDescriptor = 0x08074b50,
|
||||
CentralFileHeader = 0x02014b50,
|
||||
Zip64EndOfCD = 0x06064b50,
|
||||
Zip64EndOfCDLocator = 0x07064b50,
|
||||
EndOfCentralDirectory = 0x06054b50,
|
||||
};
|
||||
|
||||
enum class CompressionType : std::uint16_t
|
||||
{
|
||||
Store = 0,
|
||||
Deflate = 8,
|
||||
};
|
||||
|
||||
// Hat tip to the people at Facebook. Timestamp for files in ZIP archive
|
||||
// format held constant to make pack/unpack deterministic
|
||||
enum class MagicNumbers : std::uint16_t
|
||||
{
|
||||
FileTime = 0x6B60, // kudos to those know this
|
||||
FileDate = 0xA2B1, // :)
|
||||
};
|
||||
|
||||
enum class GeneralPurposeBitFlags : std::uint16_t
|
||||
{
|
||||
UNSUPPORTED_0 = 0x0001, // Bit 0: If set, indicates that the file is encrypted.
|
||||
|
||||
Deflate_MaxCompress = 0x0002, // Maximum compression (-exx/-ex), otherwise, normal compression (-en)
|
||||
Deflate_FastCompress = 0x0004, // Fast (-ef), if Max+Fast then SuperFast (-es) compression
|
||||
|
||||
GeneralPurposeBit = 0x0008, // the field's crc-32 compressed and uncompressed sizes = 0 in the local header
|
||||
// the correct values are put in the data descriptor immediately following the
|
||||
// compressed data.
|
||||
EnhancedDeflate = 0x0010,
|
||||
CompressedPatchedData = 0x0020,
|
||||
UNSUPPORTED_6 = 0x0040, // Strong encryption.
|
||||
UnUsed_7 = 0x0080, // currently unused
|
||||
UnUsed_8 = 0x0100, // currently unused
|
||||
UnUsed_9 = 0x0200, // currently unused
|
||||
UnUsed_10 = 0x0400, // currently unused
|
||||
|
||||
EncodingMustUseUTF8 = 0x0800, // Language encoding flag (EFS). File name and comments fields MUST be encoded UTF-8
|
||||
|
||||
UNSUPPORTED_12 = 0x1000, // Reserved by PKWARE for enhanced compression
|
||||
UNSUPPORTED_13 = 0x2000, // Set when encrypting the Central Directory
|
||||
UNSUPPORTED_14 = 0x4000, // Reserved by PKWARE
|
||||
UNSUPPORTED_15 = 0x8000, // Reserved by PKWARE
|
||||
};
|
||||
|
||||
constexpr GeneralPurposeBitFlags operator &(GeneralPurposeBitFlags a, GeneralPurposeBitFlags b)
|
||||
{ return static_cast<GeneralPurposeBitFlags>(static_cast<uint16_t>(a) & static_cast<uint16_t>(b));
|
||||
}
|
||||
|
||||
constexpr GeneralPurposeBitFlags operator |(GeneralPurposeBitFlags a, GeneralPurposeBitFlags b)
|
||||
{ return static_cast<GeneralPurposeBitFlags>(static_cast<uint16_t>(a) | static_cast<uint16_t>(b));
|
||||
}
|
||||
|
||||
// if any of these are set, then fail.
|
||||
constexpr static const GeneralPurposeBitFlags UnsupportedFlagsMask =
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_0 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_6 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_12 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_13 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_14 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_15;
|
||||
|
||||
/* FROM APPNOTE.TXT section 4.5.3:
|
||||
If one of the size or offset fields in the Local or Central directory
|
||||
record is too small to hold the required data, a Zip64 extended information
|
||||
record is created. The order of the fields in the zip64 extended
|
||||
information record is fixed, but the fields MUST only appear if the
|
||||
corresponding Local or Central directory record field is set to 0xFFFF
|
||||
or 0xFFFFFFFF.
|
||||
|
||||
Note: all fields stored in Intel low-byte/high-byte order.
|
||||
*/
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Zip64ExtendedInformation //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class Zip64ExtendedInformation final : public Meta::StructuredObject<
|
||||
Meta::Field2Bytes, // 0 - tag for the "extra" block type 2 bytes(0x0001)
|
||||
Meta::Field2Bytes, // 1 - size of this "extra" block 2 bytes
|
||||
Meta::Field8Bytes, // 2 - Original uncompressed file size 8 bytes
|
||||
// No point in validating these as it is actually
|
||||
// possible to have a 0-byte file... Who knew.
|
||||
Meta::Field8Bytes, // 3 - Compressed file size 8 bytes
|
||||
// No point in validating these as it is actually
|
||||
// possible to have a 0-byte file... Who knew.
|
||||
Meta::Field8Bytes // 4 - Offset of local header record 8 bytes
|
||||
//Meta::Field4Bytes // 5 - number of the disk on which the file starts 4 bytes -- ITS A FAAKEE!
|
||||
>
|
||||
{
|
||||
public:
|
||||
void Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>().value, static_cast<std::uint32_t>(HeaderIDs::Zip64ExtendedInfo));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>().value);
|
||||
Meta::OnlyEitherValueValidation<std::uint32_t>(Field<1>().value, 24, 28);
|
||||
|
||||
StreamBase::Read(stream, &Field<2>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<3>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<4>().value);
|
||||
ThrowErrorIfNot(Error::ZipBadExtendedData, Field<4>().value < m_start.QuadPart, "invalid relative header offset");
|
||||
}
|
||||
|
||||
Zip64ExtendedInformation(ULARGE_INTEGER start) : m_start(start) {}
|
||||
|
||||
std::uint64_t GetUncompressedSize() noexcept { return Field<2>().value; }
|
||||
void SetUncompressedSize(std::uint64_t v) noexcept { Field<2>().value = v; }
|
||||
std::uint64_t GetCompressedSize() noexcept { return Field<3>().value; }
|
||||
void SetCompressedSize(std::uint64_t v) noexcept { Field<3>().value = v; }
|
||||
std::uint64_t GetRelativeOffset() noexcept { return Field<4>().value; }
|
||||
void SetRelativeOffset(std::uint64_t v) noexcept { Field<4>().value = v; }
|
||||
|
||||
private:
|
||||
ULARGE_INTEGER m_start;
|
||||
};
|
||||
|
||||
/* TODO: Implement large file support.
|
||||
This type currently represents a "zip32" Central Directory Header. We need to create a new field type (offset)
|
||||
to replace the type for fields 8 & 9 whose implementation is determined by the version needed to extract (field 2)'s
|
||||
value.
|
||||
|
||||
This type would replace its existing compressed & uncompressed sizes properties (encapsulated in fields 8 & 9)
|
||||
with a 64-bit value version of those methods:
|
||||
std::uint64_t GetCompressedSize() { ...
|
||||
std::uint64_t GetUncompressedSize() { ...
|
||||
void SetCompressedSize (std::uint64_t...
|
||||
void SetUncompressedSize(std::uint64_t...
|
||||
|
||||
The underlying implementation of these methods would validate the resulting in/out values and pass the correct
|
||||
static_casted value to the new meta object type (offset) which would then hold the correct value, as well
|
||||
as handle the correct sizes w.r.t. (de)serialization.
|
||||
|
||||
As-is I don't believe that we "need" this for now, so keeping the implementation "simpler" is probably the correct
|
||||
answer for now.
|
||||
*/
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// CentralDirectoryFileHeader //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class CentralDirectoryFileHeader final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - central file header signature 4 bytes(0x02014b50)
|
||||
Meta::Field2Bytes, // 1 - version made by 2 bytes
|
||||
Meta::Field2Bytes, // 2 - version needed to extract 2 bytes
|
||||
Meta::Field2Bytes, // 3 - general purpose bit flag 2 bytes
|
||||
Meta::Field2Bytes, // 4 - compression method 2 bytes
|
||||
Meta::Field2Bytes, // 5 - last mod file time 2 bytes
|
||||
Meta::Field2Bytes, // 6 - last mod file date 2 bytes
|
||||
Meta::Field4Bytes, // 7 - crc - 32 4 bytes
|
||||
Meta::Field4Bytes, // 8 - compressed size 4 bytes
|
||||
Meta::Field4Bytes, // 9 - uncompressed size 4 bytes
|
||||
Meta::Field2Bytes, //10 - file name length 2 bytes
|
||||
Meta::Field2Bytes, //11 - extra field length 2 bytes
|
||||
Meta::Field2Bytes, //12 - file comment length 2 bytes
|
||||
Meta::Field2Bytes, //13 - disk number start 2 bytes
|
||||
Meta::Field2Bytes, //14 - internal file attributes 2 bytes
|
||||
Meta::Field4Bytes, //15 - external file attributes 4 bytes
|
||||
Meta::Field4Bytes, //16 - relative offset of local header 4 bytes
|
||||
Meta::FieldNBytes, //17 - file name(variable size)
|
||||
Meta::FieldNBytes, //18 - extra field(variable size)
|
||||
Meta::FieldNBytes //19 - file comment(variable size)
|
||||
>
|
||||
{
|
||||
public:
|
||||
void Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>().value, static_cast<std::uint32_t>(Signatures::CentralFileHeader));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<2>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<3>().value);
|
||||
ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
0 == (Field<3>().value & static_cast<std::uint16_t>(UnsupportedFlagsMask)),
|
||||
"unsupported flag(s) specified");
|
||||
|
||||
StreamBase::Read(stream, &Field<4>().value);
|
||||
Meta::OnlyEitherValueValidation<std::uint16_t>(Field<4>().value, static_cast<std::uint16_t>(CompressionType::Deflate),
|
||||
static_cast<std::uint16_t>(CompressionType::Store));
|
||||
|
||||
StreamBase::Read(stream, &Field<5>().value);
|
||||
StreamBase::Read(stream, &Field<6>().value);
|
||||
StreamBase::Read(stream, &Field<7>().value);
|
||||
StreamBase::Read(stream, &Field<8>().value);
|
||||
StreamBase::Read(stream, &Field<9>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<10>().value);
|
||||
ThrowErrorIfNot(Error::ZipCentralDirectoryHeader, (Field<10>().value != 0), "unsupported file name size");
|
||||
if (Field<10>().value !=0) {Field<17>().value.resize(Field<10>().value, 0); }
|
||||
|
||||
StreamBase::Read(stream, &Field<11>().value);
|
||||
if (Field<11>().value != 0) { Field<18>().value.resize(Field<11>().value, 0); }
|
||||
|
||||
StreamBase::Read(stream, &Field<12>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<12>().value, 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<13>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<13>().value, 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<14>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<15>().value);
|
||||
StreamBase::Read(stream, &Field<16>().value);
|
||||
ULARGE_INTEGER pos = {0};
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
if (!GetIsZip64())
|
||||
{
|
||||
ThrowErrorIf(Error::ZipCentralDirectoryHeader, (Field<16>().value >= pos.QuadPart), "invalid relative header offset");
|
||||
}
|
||||
else
|
||||
{
|
||||
ThrowErrorIf(Error::ZipCentralDirectoryHeader, (Field<16>().value != 0xFFFFFFFF), "invalid zip64 local header offset");
|
||||
}
|
||||
|
||||
if (Field<17>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<17>().value.data()), static_cast<ULONG>(Field<17>().Size()), nullptr));
|
||||
}
|
||||
|
||||
if (Field<18>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<18>().value.data()), static_cast<ULONG>(Field<18>().Size()), nullptr));
|
||||
}
|
||||
// Only process for Zip64ExtendedInformation
|
||||
if (Field<18>().Size() > 2 && Field<18>().value[0] == 0x01 && Field<18>().value[1] == 0x00)
|
||||
{
|
||||
LARGE_INTEGER zero = {0};
|
||||
ThrowHrIfFailed(stream->Seek(zero, StreamBase::Reference::CURRENT, &pos));
|
||||
auto vectorStream = ComPtr<IStream>::Make<VectorStream>(&Field<18>().value);
|
||||
m_extendedInfo = std::make_unique<Zip64ExtendedInformation>(pos);
|
||||
ThrowErrorIfNot(Error::ZipCentralDirectoryHeader, (Field<18>().Size() >= m_extendedInfo->Size()), "Unexpected extended info size");
|
||||
m_extendedInfo->Read(vectorStream.Get());
|
||||
}
|
||||
|
||||
if (Field<19>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<19>().value.data()), static_cast<ULONG>(Field<19>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
CentralDirectoryFileHeader(bool isZip64) : m_isZip64(isZip64)
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::CentralFileHeader));
|
||||
SetVersionMadeBy(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion)); // only set to Zip64FormatExtension iff required!
|
||||
SetLastModFileDate(static_cast<std::uint16_t>(MagicNumbers::FileDate));
|
||||
SetLastModFileTime(static_cast<std::uint16_t>(MagicNumbers::FileTime));
|
||||
SetExtraFieldLength(0);
|
||||
SetFileCommentLength(0);
|
||||
SetDiskNumberStart(0);
|
||||
SetInternalFileAttributes(0);
|
||||
SetExternalFileAttributes(0);
|
||||
}
|
||||
|
||||
bool IsGeneralPurposeBitSet() noexcept
|
||||
{ return ((GetGeneralPurposeBitFlags() & GeneralPurposeBitFlags::GeneralPurposeBit) == GeneralPurposeBitFlags::GeneralPurposeBit);
|
||||
}
|
||||
|
||||
std::uint16_t GetVersionNeededToExtract() noexcept { return Field<2>().value; }
|
||||
GeneralPurposeBitFlags GetGeneralPurposeBitFlags() noexcept { return static_cast<GeneralPurposeBitFlags>(Field<3>().value); }
|
||||
void SetGeneralPurposeBitFlags(std::uint16_t value) noexcept { Field<3>().value = value; }
|
||||
std::uint16_t GetCompressionMethod() noexcept { return Field<4>().value; }
|
||||
void SetCompressionMethod(std::uint16_t value) noexcept { Field<4>().value= value; }
|
||||
std::uint32_t GetCrc32() noexcept { return Field<7>().value; }
|
||||
void SetCrc(std::uint32_t value) noexcept { Field<7>().value = value; }
|
||||
|
||||
std::uint64_t GetCompressedSize() noexcept
|
||||
{
|
||||
if (!m_extendedInfo.get()) {
|
||||
return static_cast<std::uint64_t>(Field<8>().value);
|
||||
}
|
||||
return m_extendedInfo->GetCompressedSize();
|
||||
}
|
||||
|
||||
// TODO: on-demand create m_extendedInfo?
|
||||
void SetCompressedSize(std::uint32_t value) noexcept { Field<8>().value = value; }
|
||||
|
||||
std::uint64_t GetUncompressedSize() noexcept
|
||||
{
|
||||
if (!m_extendedInfo.get()) {
|
||||
return static_cast<std::uint64_t>(Field<9>().value);
|
||||
}
|
||||
return m_extendedInfo->GetUncompressedSize();
|
||||
}
|
||||
|
||||
// TODO: on-demand create m_extendedInfo?
|
||||
void SetUncompressedSize(std::uint32_t value) noexcept { Field<9>().value = value; }
|
||||
|
||||
std::uint64_t GetRelativeOffsetOfLocalHeader() noexcept
|
||||
{
|
||||
if (!m_extendedInfo.get()) {
|
||||
return static_cast<std::uint64_t>(Field<16>().value);
|
||||
}
|
||||
return m_extendedInfo->GetRelativeOffset();
|
||||
}
|
||||
|
||||
// TODO: on-demand create m_extendedInfo?
|
||||
void SetRelativeOffsetOfLocalHeader(std::uint32_t value) noexcept { Field<16>().value = value; }
|
||||
|
||||
std::string GetFileName()
|
||||
{
|
||||
auto data = Field<17>().value;
|
||||
return std::string(data.begin(), data.end());
|
||||
}
|
||||
|
||||
void SetFileName(std::string name)
|
||||
{
|
||||
auto data = Field<17>().value;
|
||||
data.resize(name.size());
|
||||
data.assign(name.begin(), name.end());
|
||||
SetFileNameLength(static_cast<std::uint16_t>(name.size()));
|
||||
}
|
||||
|
||||
inline bool GetIsZip64() noexcept { return m_isZip64; }
|
||||
|
||||
private:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>().value = value; }
|
||||
void SetVersionMadeBy(std::uint16_t value) noexcept { Field<1>().value = value; }
|
||||
void SetVersionNeededToExtract(std::uint16_t value) noexcept { Field<2>().value = value; }
|
||||
void SetLastModFileTime(std::uint16_t value) noexcept { Field<5>().value = value; }
|
||||
void SetLastModFileDate(std::uint16_t value) noexcept { Field<6>().value = value; }
|
||||
void SetFileNameLength(std::uint16_t value) noexcept { Field<10>().value = value; }
|
||||
void SetExtraFieldLength(std::uint16_t value) noexcept { Field<11>().value = value; }
|
||||
std::uint16_t GetExtraFieldLength() noexcept { return Field<11>().value; }
|
||||
void SetFileCommentLength(std::uint16_t value) noexcept { Field<12>().value = value; }
|
||||
void SetDiskNumberStart(std::uint16_t value) noexcept { Field<13>().value = value; }
|
||||
void SetInternalFileAttributes(std::uint16_t value) noexcept { Field<14>().value = value; }
|
||||
void SetExternalFileAttributes(std::uint16_t value) noexcept { Field<15>().value = value; }
|
||||
|
||||
std::unique_ptr<Zip64ExtendedInformation> m_extendedInfo;
|
||||
bool m_isZip64 = false;
|
||||
};//class CentralDirectoryFileHeader
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// LocalFileHeader //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class LocalFileHeader final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - local file header signature 4 bytes(0x04034b50)
|
||||
Meta::Field2Bytes, // 1 - version needed to extract 2 bytes
|
||||
Meta::Field2Bytes, // 2 - general purpose bit flag 2 bytes
|
||||
Meta::Field2Bytes, // 3 - compression method 2 bytes
|
||||
Meta::Field2Bytes, // 4 - last mod file time 2 bytes
|
||||
Meta::Field2Bytes, // 5 - last mod file date 2 bytes
|
||||
Meta::Field4Bytes, // 6 - crc - 32 4 bytes
|
||||
Meta::Field4Bytes, // 7 - compressed size 4 bytes
|
||||
Meta::Field4Bytes, // 8 - uncompressed size 4 bytes
|
||||
Meta::Field2Bytes, // 9 - file name length 2 bytes
|
||||
Meta::Field2Bytes, // 10- extra field length 2 bytes
|
||||
Meta::FieldNBytes, // 11- file name (variable size)
|
||||
Meta::FieldNBytes // 12- extra field (variable size)
|
||||
>
|
||||
{
|
||||
public:
|
||||
void Read(const ComPtr<IStream> &stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>( Field<0>().value, static_cast<std::uint32_t>(Signatures::LocalFileHeader));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>().value);
|
||||
Meta::OnlyEitherValueValidation<std::uint16_t>(Field<1>().value, static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion),
|
||||
static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
|
||||
StreamBase::Read(stream, &Field<2>().value);
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, ((Field<2>().value & static_cast<std::uint16_t>(UnsupportedFlagsMask)) == 0), "unsupported flag(s) specified");
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (IsGeneralPurposeBitSet() == m_directoryEntry->IsGeneralPurposeBitSet()), "inconsistent general purpose bits specified");
|
||||
|
||||
StreamBase::Read(stream, &Field<3>().value);
|
||||
Meta::OnlyEitherValueValidation<std::uint16_t>(Field<3>().value, static_cast<std::uint16_t>(CompressionType::Deflate),
|
||||
static_cast<std::uint16_t>(CompressionType::Store));
|
||||
|
||||
StreamBase::Read(stream, &Field<4>().value);
|
||||
StreamBase::Read(stream, &Field<5>().value);
|
||||
StreamBase::Read(stream, &Field<6>().value);
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (!IsGeneralPurposeBitSet() || (Field<6>().value == 0)), "Invalid Zip CRC");
|
||||
|
||||
StreamBase::Read(stream, &Field<7>().value);
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (!IsGeneralPurposeBitSet() || (Field<7>().value == 0)), "Invalid Zip compressed size");
|
||||
|
||||
StreamBase::Read(stream, &Field<8>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<9>().value);
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (Field<9>().value != 0), "unsupported file name size");
|
||||
Field<11>().value.resize(GetFileNameLength(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<10>().value);
|
||||
// Even if we don't validate them, we need to read the extra field
|
||||
if (Field<10>().value != 0) {Field<12>().value.resize(Field<10>().value, 0); }
|
||||
|
||||
if (Field<11>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<11>().value.data()), static_cast<ULONG>(Field<11>().Size()), nullptr));
|
||||
}
|
||||
|
||||
if (Field<12>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<12>().value.data()), static_cast<ULONG>(Field<12>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
LocalFileHeader(std::shared_ptr<CentralDirectoryFileHeader> directoryEntry) : m_isZip64(directoryEntry->GetIsZip64()), m_directoryEntry(directoryEntry)
|
||||
{
|
||||
}
|
||||
|
||||
bool IsGeneralPurposeBitSet() noexcept
|
||||
{
|
||||
return ((GetGeneralPurposeBitFlags() & GeneralPurposeBitFlags::GeneralPurposeBit) == GeneralPurposeBitFlags::GeneralPurposeBit);
|
||||
}
|
||||
|
||||
GeneralPurposeBitFlags GetGeneralPurposeBitFlags() noexcept { return static_cast<GeneralPurposeBitFlags>(Field<2>().value); }
|
||||
CompressionType GetCompressionType() noexcept { return static_cast<CompressionType>(Field<3>().value); }
|
||||
|
||||
std::uint64_t GetCompressedSize() noexcept
|
||||
{
|
||||
return IsGeneralPurposeBitSet() ? m_directoryEntry->GetCompressedSize() : static_cast<std::uint64_t>(Field<7>().value);
|
||||
}
|
||||
|
||||
std::uint64_t GetUncompressedSize() noexcept
|
||||
{ return IsGeneralPurposeBitSet() ? m_directoryEntry->GetUncompressedSize() : static_cast<std::uint64_t>(Field<8>().value);
|
||||
}
|
||||
|
||||
std::uint16_t GetFileNameLength() noexcept { return Field<9>().value; }
|
||||
std::uint16_t GetExtraFieldLength() noexcept { return Field<10>().value; }
|
||||
void SetGeneralPurposeBitFlag(std::uint16_t value) noexcept { Field<2>().value = value; }
|
||||
void SetCompressedSize(std::uint32_t value) noexcept { Field<7>().value = value; }
|
||||
void SetUncompressedSize(std::uint32_t value) noexcept { Field<8>().value = value; }
|
||||
void SetFileNameLength(std::uint16_t value) noexcept { Field<9>().value = value; }
|
||||
void SetExtraFieldLength(std::uint16_t value) noexcept { Field<10>().value = value; }
|
||||
|
||||
std::string GetFileName()
|
||||
{
|
||||
auto data = Field<11>().value;
|
||||
return std::string(data.begin(), data.end());
|
||||
}
|
||||
|
||||
void SetFileName(std::string name)
|
||||
{
|
||||
auto data = Field<11>().value;
|
||||
data.resize(name.size());
|
||||
data.assign(name.begin(), name.end());
|
||||
SetFileNameLength(static_cast<std::uint16_t>(name.size()));
|
||||
}
|
||||
protected:
|
||||
bool m_isZip64 = false;
|
||||
std::shared_ptr<CentralDirectoryFileHeader> m_directoryEntry = nullptr;
|
||||
}; //class LocalFileHeader
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Zip64EndOfCentralDirectoryRecord //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class Zip64EndOfCentralDirectoryRecord final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - zip64 end of central dir signature 4 bytes(0x06064b50)
|
||||
Meta::Field8Bytes, // 1 - size of zip64 end of central directory record 8 bytes
|
||||
Meta::Field2Bytes, // 2 - version made by 2 bytes
|
||||
Meta::Field2Bytes, // 3 - version needed to extract 2 bytes
|
||||
Meta::Field4Bytes, // 4 - number of this disk 4 bytes
|
||||
Meta::Field4Bytes, // 5 - number of the disk with the start of the central directory 4 bytes
|
||||
Meta::Field8Bytes, // 6 - total number of entries in the central directory on this disk 8 bytes
|
||||
Meta::Field8Bytes, // 7 - total number of entries in the central directory 8 bytes
|
||||
Meta::Field8Bytes, // 8 - size of the central directory 8 bytes
|
||||
Meta::Field8Bytes, // 9 - offset of start of central directory with respect to the
|
||||
// starting disk number 8 bytes
|
||||
Meta::FieldNBytes //10 - zip64 extensible data sector (variable size)
|
||||
>
|
||||
{
|
||||
public:
|
||||
void Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>().value, static_cast<std::uint32_t>(Signatures::Zip64EndOfCD));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>().value);
|
||||
//4.3.14.1 The value stored into the "size of zip64 end of central
|
||||
// directory record" should be the size of the remaining
|
||||
// record and should not include the leading 12 bytes.
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, (Field<1>().value == (this->Size() - 12)), "invalid size of zip64 EOCD");
|
||||
|
||||
StreamBase::Read(stream, &Field<2>().value);
|
||||
Meta::ExactValueValidation<std::uint16_t>(Field<2>().value, static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
|
||||
StreamBase::Read(stream, &Field<3>().value);
|
||||
Meta::ExactValueValidation<std::uint16_t>(Field<3>().value, static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
|
||||
StreamBase::Read(stream, &Field<4>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<4>().value, 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<5>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<5>().value, 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<6>().value);
|
||||
Meta::NotValueValidation<std::uint64_t>(Field<6>().value, 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<7>().value);
|
||||
Meta::NotValueValidation<std::uint64_t>(Field<7>().value, 0);
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, (Field<7>().value == this->GetTotalNumberOfEntries()), "invalid total number of entries");
|
||||
|
||||
ULARGE_INTEGER pos = {0};
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
StreamBase::Read(stream, &Field<8>().value);
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, ((Field<8>().value != 0) && (Field<8>().value < pos.QuadPart)), "invalid size of central directory");
|
||||
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
StreamBase::Read(stream, &Field<9>().value);
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, ((Field<9>().value != 0) && (Field<9>().value < pos.QuadPart)), "invalid size of central directory");
|
||||
|
||||
if (Field<10>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<10>().value.data()), static_cast<ULONG>(Field<10>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
Zip64EndOfCentralDirectoryRecord()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::Zip64EndOfCD));
|
||||
SetGetSizeOfZip64CDRecord(this->Size() - 12);
|
||||
SetVersionMadeBy(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetNumberOfThisDisk(0);
|
||||
SetTotalNumberOfEntries(0);
|
||||
}
|
||||
|
||||
std::uint64_t GetTotalNumberOfEntries() noexcept { return Field<6>().value; }
|
||||
|
||||
void SetTotalNumberOfEntries(std::uint64_t value) noexcept
|
||||
{
|
||||
Field<6>().value = value;
|
||||
Field<7>().value = value;
|
||||
}
|
||||
|
||||
std::uint64_t GetSizeOfCD() noexcept { return Field<8>().value; }
|
||||
void SetSizeOfCD(std::uint64_t value) noexcept { Field<8>().value = value; }
|
||||
std::uint64_t GetOffsetStartOfCD() noexcept { return Field<9>().value; }
|
||||
void SetOffsetfStartOfCD(std::uint64_t value) noexcept { Field<9>().value = value; }
|
||||
|
||||
private:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>().value = value; }
|
||||
void SetGetSizeOfZip64CDRecord(std::uint64_t value) noexcept { Field<1>().value = value; }
|
||||
void SetVersionMadeBy(std::uint16_t value) noexcept { Field<2>().value = value; }
|
||||
void SetVersionNeededToExtract(std::uint16_t value) noexcept { Field<3>().value = value; }
|
||||
void SetNumberOfThisDisk(std::uint32_t value) noexcept { Field<4>().value = value; }
|
||||
|
||||
}; //class Zip64EndOfCentralDirectoryRecord
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Zip64EndOfCentralDirectoryLocator //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class Zip64EndOfCentralDirectoryLocator final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - zip64 end of central dir locator signature 4 bytes(0x07064b50)
|
||||
Meta::Field4Bytes, // 1 - number of the disk with the start of the zip64
|
||||
// end of central directory 4 bytes
|
||||
Meta::Field8Bytes, // 2 - relative offset of the zip64 end of central
|
||||
// directory record 8 bytes
|
||||
Meta::Field4Bytes // 3 - total number of disks 4 bytes
|
||||
>
|
||||
{
|
||||
public:
|
||||
void Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>().value, static_cast<std::uint32_t>(Signatures::Zip64EndOfCDLocator));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<1>().value, 0);
|
||||
|
||||
ULARGE_INTEGER pos = {0};
|
||||
StreamBase::Read(stream, &Field<2>().value);
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
ThrowErrorIfNot(Error::Zip64EOCDLocator, ((Field<2>().value != 0) && (Field<2>().value < pos.QuadPart)), "Invalid relative offset");
|
||||
|
||||
StreamBase::Read(stream, &Field<3>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<3>().value, 1);
|
||||
}
|
||||
|
||||
Zip64EndOfCentralDirectoryLocator()
|
||||
{
|
||||
// set smart defaults.
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::Zip64EndOfCDLocator));
|
||||
SetNumberOfDisk(0);
|
||||
SetTotalNumberOfDisks(1);
|
||||
}
|
||||
|
||||
std::uint64_t GetRelativeOffset() noexcept { return Field<2>().value; }
|
||||
void SetRelativeOffset(std::uint64_t value) noexcept { Field<2>().value = value; }
|
||||
|
||||
private:
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>().value = value; }
|
||||
void SetNumberOfDisk(std::uint32_t value) noexcept { Field<1>().value = value; }
|
||||
void SetTotalNumberOfDisks(std::uint32_t value) noexcept { Field<3>().value = value; }
|
||||
}; //class Zip64EndOfCentralDirectoryLocator
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// EndOfCentralDirectoryRecord //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
class EndCentralDirectoryRecord final : public Meta::StructuredObject<
|
||||
Meta::Field4Bytes, // 0 - end of central dir signature 4 bytes (0x06054b50)
|
||||
Meta::Field2Bytes, // 1 - number of this disk 2 bytes
|
||||
Meta::Field2Bytes, // 2 - number of the disk with the start of the
|
||||
// central directory 2 bytes
|
||||
Meta::Field2Bytes, // 3 - total number of entries in the central
|
||||
// directory on this disk 2 bytes
|
||||
Meta::Field2Bytes, // 4 - total number of entries in the central
|
||||
// directory 2 bytes
|
||||
Meta::Field4Bytes, // 5 - size of the central directory 4 bytes
|
||||
Meta::Field4Bytes, // 6 - offset of start of central directory with
|
||||
// respect to the starting disk number 4 bytes
|
||||
Meta::Field2Bytes, // 7 - .ZIP file comment length 2 bytes
|
||||
Meta::FieldNBytes // 8 - .ZIP file comment (variable size)
|
||||
>
|
||||
{
|
||||
public:
|
||||
void Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>().value, static_cast<std::uint32_t>(Signatures::EndOfCentralDirectory));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>().value);
|
||||
Meta::OnlyEitherValueValidation<std::uint32_t>(Field<1>().value, 0, 0xFFFF);
|
||||
|
||||
StreamBase::Read(stream, &Field<2>().value);
|
||||
Meta::OnlyEitherValueValidation<std::uint32_t>(Field<2>().value, 0, 0xFFFF);
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, (Field<1>().value != Field<2>().value), "field missmatch");
|
||||
m_isZip64 = (0xFFFF == Field<2>().value);
|
||||
|
||||
StreamBase::Read(stream, &Field<3>().value);
|
||||
if (Field<3>().value != 0 && Field<3>().value != 0xFFFF)
|
||||
{ m_archiveHasZip64Locator = false;
|
||||
}
|
||||
|
||||
StreamBase::Read(stream, &Field<4>().value);
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, (Field<3>().value != Field<4>().value), "field missmatch");
|
||||
|
||||
StreamBase::Read(stream, &Field<5>().value);
|
||||
StreamBase::Read(stream, &Field<6>().value);
|
||||
|
||||
if(m_archiveHasZip64Locator)
|
||||
{
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, ((Field<5>().value != 0) && (Field<5>().value != 0xFFFFFFFF)),
|
||||
"unsupported size of central directory");
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, ((Field<6>().value != 0) && (Field<6>().value != 0xFFFFFFFF)),
|
||||
"unsupported offset of start of central directory");
|
||||
}
|
||||
|
||||
StreamBase::Read(stream, &Field<7>().value);
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<7>().value, 0);
|
||||
|
||||
if (Field<8>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<8>().value.data()), static_cast<ULONG>(Field<8>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
EndCentralDirectoryRecord()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::EndOfCentralDirectory));
|
||||
SetNumberOfDisk(0);
|
||||
SetDiskStart(0);
|
||||
// by default, the next 12 bytes need to be: FFFF FFFF FFFF FFFF FFFF FFFF
|
||||
SetTotalNumberOfEntries (std::numeric_limits<std::uint16_t>::max());
|
||||
SetTotalEntriesInCentralDirectory(std::numeric_limits<std::uint16_t>::max());
|
||||
SetSizeOfCentralDirectory (std::numeric_limits<std::uint32_t>::max());
|
||||
SetOffsetOfCentralDirectory (std::numeric_limits<std::uint32_t>::max());
|
||||
// last 2 bytes need to be : 00
|
||||
SetCommentLength(0);
|
||||
}
|
||||
|
||||
bool GetArchiveHasZip64Locator() noexcept { return m_archiveHasZip64Locator; }
|
||||
bool GetIsZip64() noexcept { return m_isZip64; }
|
||||
std::uint64_t GetNumberOfCentralDirectoryEntries() noexcept { return static_cast<std::uint64_t>(Field<3>().value); }
|
||||
std::uint64_t GetStartOfCentralDirectory() noexcept { return static_cast<std::uint64_t>(Field<6>().value); }
|
||||
|
||||
private:
|
||||
bool m_isZip64 = false;
|
||||
bool m_archiveHasZip64Locator = true;
|
||||
|
||||
void SetSignature(std::uint32_t value) noexcept { Field<0>().value = value; }
|
||||
void SetNumberOfDisk(std::uint16_t value) noexcept { Field<1>().value = value; }
|
||||
void SetDiskStart(std::uint16_t value) noexcept { Field<2>().value = value; }
|
||||
void SetTotalNumberOfEntries(std::uint16_t value) noexcept { Field<3>().value = value; }
|
||||
void SetTotalEntriesInCentralDirectory(std::uint16_t value) noexcept { Field<4>().value = value; }
|
||||
void SetSizeOfCentralDirectory(std::uint32_t value) noexcept { Field<5>().value = value; }
|
||||
void SetOffsetOfCentralDirectory(std::uint32_t value) noexcept { Field<6>().value = value; }
|
||||
std::uint32_t GetOffsetOfCentralDirectory() noexcept { return Field<6>().value; }
|
||||
void SetCommentLength(std::uint16_t value) noexcept { Field<7>().value = value; }
|
||||
};//class EndCentralDirectoryRecord
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// ZipObject member implementation //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
std::vector<std::string> ZipObject::GetFileNames(FileNameOptions)
|
||||
{
|
||||
std::vector<std::string> result;
|
||||
std::for_each(m_streams.begin(), m_streams.end(), [&result](auto it)
|
||||
{
|
||||
result.push_back(it.first);
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
ComPtr<IStream> ZipObject::GetFile(const std::string& fileName)
|
||||
{ // TODO: Make this on-demand populate m_streams and then pull from there.
|
||||
auto result = m_streams.find(fileName);
|
||||
if (result == m_streams.end())
|
||||
{
|
||||
return ComPtr<IStream>();
|
||||
}
|
||||
return result->second;
|
||||
}
|
||||
|
||||
std::string ZipObject::GetFileName()
|
||||
{
|
||||
return m_stream.As<IStreamInternal>()->GetName();
|
||||
}
|
||||
|
||||
ZipObject::ZipObject(IMsixFactory* appxFactory, const ComPtr<IStream>& stream) : m_factory(appxFactory), m_stream(stream)
|
||||
{ // Confirm that the file IS the correct format
|
||||
EndCentralDirectoryRecord endCentralDirectoryRecord;
|
||||
LARGE_INTEGER pos = {0};
|
||||
pos.QuadPart = -1 * endCentralDirectoryRecord.Size();
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::END, nullptr));
|
||||
endCentralDirectoryRecord.Read(m_stream.Get());
|
||||
|
||||
// find where the zip central directory exists.
|
||||
std::uint64_t offsetStartOfCD = 0;
|
||||
std::uint64_t totalNumberOfEntries = 0;
|
||||
Zip64EndOfCentralDirectoryLocator zip64Locator;
|
||||
if (!endCentralDirectoryRecord.GetArchiveHasZip64Locator())
|
||||
{
|
||||
offsetStartOfCD = endCentralDirectoryRecord.GetStartOfCentralDirectory();
|
||||
totalNumberOfEntries = endCentralDirectoryRecord.GetNumberOfCentralDirectoryEntries();
|
||||
}
|
||||
else
|
||||
{ // Make sure that we have a zip64 end of central directory locator
|
||||
pos.QuadPart = -1*(endCentralDirectoryRecord.Size() + zip64Locator.Size());
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::END, nullptr));
|
||||
zip64Locator.Read(m_stream.Get());
|
||||
|
||||
// now read the end of zip central directory record
|
||||
Zip64EndOfCentralDirectoryRecord zip64EndOfCentralDirectory;
|
||||
pos.QuadPart = zip64Locator.GetRelativeOffset();
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr));
|
||||
zip64EndOfCentralDirectory.Read(m_stream.Get());
|
||||
offsetStartOfCD = zip64EndOfCentralDirectory.GetOffsetStartOfCD();
|
||||
totalNumberOfEntries = zip64EndOfCentralDirectory.GetTotalNumberOfEntries();
|
||||
}
|
||||
|
||||
// read the zip central directory
|
||||
std::map<std::string, std::shared_ptr<CentralDirectoryFileHeader>> centralDirectory;
|
||||
pos.QuadPart = offsetStartOfCD;
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, StreamBase::Reference::START, nullptr));
|
||||
for (std::uint32_t index = 0; index < totalNumberOfEntries; index++)
|
||||
{
|
||||
auto centralFileHeader = std::make_shared<CentralDirectoryFileHeader>(endCentralDirectoryRecord.GetIsZip64());
|
||||
centralFileHeader->Read(m_stream.Get());
|
||||
// TODO: ensure that there are no collisions on name!
|
||||
centralDirectory.insert(std::make_pair(centralFileHeader->GetFileName(), centralFileHeader));
|
||||
}
|
||||
|
||||
if (endCentralDirectoryRecord.GetArchiveHasZip64Locator())
|
||||
{ // We should have no data between the end of the last central directory header and the start of the EoCD
|
||||
ULARGE_INTEGER uPos = {0};
|
||||
ThrowHrIfFailed(m_stream->Seek({0}, StreamBase::Reference::CURRENT, &uPos));
|
||||
ThrowErrorIfNot(Error::ZipHiddenData, (uPos.QuadPart == zip64Locator.GetRelativeOffset()), "hidden data unsupported");
|
||||
}
|
||||
|
||||
// TODO: change population of m_streams into cache semantics and move into ZipObject::GetFile
|
||||
// Read the file repository
|
||||
for (const auto& centralFileHeader : centralDirectory)
|
||||
{
|
||||
pos.QuadPart = centralFileHeader.second->GetRelativeOffsetOfLocalHeader();
|
||||
ThrowHrIfFailed(m_stream->Seek(pos, MSIX::StreamBase::Reference::START, nullptr));
|
||||
auto localFileHeader = std::make_shared<LocalFileHeader>(centralFileHeader.second);
|
||||
localFileHeader->Read(m_stream.Get());
|
||||
|
||||
auto fileStream = ComPtr<IStream>::Make<ZipFileStream>(
|
||||
centralFileHeader.second->GetFileName(),
|
||||
"TODO: Implement", // TODO: put value from content type
|
||||
m_factory,
|
||||
localFileHeader->GetCompressionType() == CompressionType::Deflate,
|
||||
centralFileHeader.second->GetRelativeOffsetOfLocalHeader() + localFileHeader->Size(),
|
||||
localFileHeader->GetCompressedSize(),
|
||||
m_stream
|
||||
);
|
||||
|
||||
if (localFileHeader->GetCompressionType() == CompressionType::Deflate)
|
||||
{
|
||||
fileStream = ComPtr<IStream>::Make<InflateStream>(std::move(fileStream), localFileHeader->GetUncompressedSize());
|
||||
}
|
||||
|
||||
m_streams.insert(std::make_pair(centralFileHeader.second->GetFileName(), std::move(fileStream)));
|
||||
}
|
||||
} // ZipObject::ZipObject
|
||||
} // namespace MSIX
|
|
@ -5,21 +5,38 @@
|
|||
#include "AppxFactory.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
#include "Exceptions.hpp"
|
||||
#include "ZipObject.hpp"
|
||||
#include "ZipObjectReader.hpp"
|
||||
#include "AppxPackageObject.hpp"
|
||||
#include "MSIXResource.hpp"
|
||||
#include "VectorStream.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
#include "AppxPackageWriter.hpp"
|
||||
#include "ZipObjectWriter.hpp"
|
||||
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
#include "AppxBundleManifest.hpp"
|
||||
#endif
|
||||
|
||||
namespace MSIX {
|
||||
// IAppxFactory
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreatePackageWriter (
|
||||
IStream* outputStream,
|
||||
APPX_PACKAGE_SETTINGS* ,//settings, TODO: plumb this through
|
||||
IAppxPackageWriter** packageWriter) noexcept
|
||||
IAppxPackageWriter** packageWriter) noexcept try
|
||||
{
|
||||
return static_cast<HRESULT>(Error::NotImplemented);
|
||||
}
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
ThrowErrorIf(Error::InvalidParameter, (outputStream == nullptr || packageWriter == nullptr || *packageWriter != nullptr), "Invalid parameter");
|
||||
// We should never be here is packing if disabled, but the compiler
|
||||
// is not smart enough to remove it and the linker will fail.
|
||||
#ifdef MSIX_PACK
|
||||
ComPtr<IMsixFactory> self;
|
||||
ThrowHrIfFailed(QueryInterface(UuidOfImpl<IMsixFactory>::iid, reinterpret_cast<void**>(&self)));
|
||||
auto zip = ComPtr<IZipWriter>::Make<ZipObjectWriter>(outputStream);
|
||||
auto result = ComPtr<IAppxPackageWriter>::Make<AppxPackageWriter>(self.Get(), zip);
|
||||
*packageWriter = result.Detach();
|
||||
#endif
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreatePackageReader (
|
||||
IStream* inputStream,
|
||||
|
@ -27,7 +44,7 @@ namespace MSIX {
|
|||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (packageReader == nullptr || *packageReader != nullptr), "Invalid parameter");
|
||||
ComPtr<IStream> input(inputStream);
|
||||
auto zip = ComPtr<IStorageObject>::Make<ZipObject>(this, input);
|
||||
auto zip = ComPtr<IStorageObject>::Make<ZipObjectReader>(input);
|
||||
auto result = ComPtr<IAppxPackageReader>::Make<AppxPackageObject>(this, m_validationOptions, m_applicabilityFlags, zip);
|
||||
*packageReader = result.Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
|
@ -68,39 +85,32 @@ namespace MSIX {
|
|||
} CATCH_RETURN();
|
||||
|
||||
// IAppxBundleFactory
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleWriter(IStream *outputStream, UINT64 bundleVersion, IAppxBundleWriter **bundleWriter) noexcept
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleWriter(IStream *outputStream, UINT64 bundleVersion, IAppxBundleWriter **bundleWriter) noexcept try
|
||||
{
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
return static_cast<HRESULT>(Error::NotImplemented);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
}
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
NOTIMPLEMENTED;
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleReader(IStream *inputStream, IAppxBundleReader **bundleReader) noexcept try
|
||||
{
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
ComPtr<IAppxPackageReader> reader;
|
||||
ThrowHrIfFailed(CreatePackageReader(inputStream, &reader));
|
||||
auto result = reader.As<IAppxBundleReader>();
|
||||
*bundleReader = result.Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
ComPtr<IAppxPackageReader> reader;
|
||||
ThrowHrIfFailed(CreatePackageReader(inputStream, &reader));
|
||||
auto result = reader.As<IAppxBundleReader>();
|
||||
*bundleReader = result.Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxFactory::CreateBundleManifestReader(IStream *inputStream, IAppxBundleManifestReader **manifestReader) noexcept try
|
||||
{
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
ThrowErrorIf(Error::InvalidParameter, (manifestReader == nullptr || *manifestReader != nullptr), "Invalid parameter");
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
ThrowErrorIf(Error::InvalidParameter, (manifestReader == nullptr || *manifestReader != nullptr), "Invalid parameter");
|
||||
ComPtr<IStream> input(inputStream);
|
||||
auto result = ComPtr<IAppxBundleManifestReader>::Make<AppxBundleManifestObject>(this, input);
|
||||
*manifestReader = result.Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
ComPtr<IStream> input(inputStream);
|
||||
auto result = ComPtr<IAppxBundleManifestReader>::Make<AppxBundleManifestObject>(this, input);
|
||||
*manifestReader = result.Detach();
|
||||
#endif
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
// IMsixFactory
|
||||
|
@ -159,7 +169,7 @@ namespace MSIX {
|
|||
// Get stream of the resource zip file generated at CMake processing.
|
||||
m_resourcesVector = std::vector<std::uint8_t>(Resource::resourceByte, Resource::resourceByte + Resource::resourceLength);
|
||||
auto resourceStream = ComPtr<IStream>::Make<VectorStream>(&m_resourcesVector);
|
||||
m_resourcezip = ComPtr<IStorageObject>::Make<ZipObject>(this, resourceStream.Get());
|
||||
m_resourcezip = ComPtr<IStorageObject>::Make<ZipObjectReader>(resourceStream.Get());
|
||||
}
|
||||
auto file = m_resourcezip->GetFile(resource);
|
||||
ThrowErrorIfNot(Error::FileNotFound, file, resource.c_str());
|
|
@ -9,6 +9,8 @@
|
|||
#include "Encoding.hpp"
|
||||
#include "Enumerators.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
template<typename T>
|
||||
|
@ -208,14 +210,14 @@ namespace MSIX {
|
|||
contextProperties->wasFound = true;
|
||||
return true;
|
||||
});
|
||||
context->self->m_dom->ForEachElementIn(propertiesNode, XmlQueryName::Package_Properties_Framework, visitorBool);
|
||||
context->self->m_dom->ForEachElementIn(propertiesNode, XmlQueryName::Child_Framework, visitorBool);
|
||||
if (!contextPropertiesBool.wasFound)
|
||||
{ // False is default value for Framework
|
||||
context->boolValues->insert(std::pair<std::string, bool>(contextPropertiesBool.value, false));
|
||||
}
|
||||
contextPropertiesBool.value = "ResourcePackage";
|
||||
contextPropertiesBool.wasFound = false;
|
||||
context->self->m_dom->ForEachElementIn(propertiesNode, XmlQueryName::Package_Properties_ResourcePackage, visitorBool);
|
||||
context->self->m_dom->ForEachElementIn(propertiesNode, XmlQueryName::Child_ResourcePackage, visitorBool);
|
||||
if (!contextPropertiesBool.wasFound)
|
||||
{ // False is default value for ResourcePackage
|
||||
context->boolValues->insert(std::pair<std::string, bool>(contextPropertiesBool.value, false));
|
||||
|
@ -238,7 +240,7 @@ namespace MSIX {
|
|||
|
||||
HRESULT STDMETHODCALLTYPE AppxManifestObject::GetPackageDependencies(IAppxManifestPackageDependenciesEnumerator **dependencies) noexcept try
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (dependencies == nullptr || *dependencies != nullptr), "bad pointer.");
|
||||
ThrowErrorIf(Error::InvalidParameter, (dependencies == nullptr || *dependencies != nullptr), "bad pointer");
|
||||
std::vector<ComPtr<IAppxManifestPackageDependency>> packageDependencies;
|
||||
struct _context
|
||||
{
|
||||
|
@ -267,29 +269,26 @@ namespace MSIX {
|
|||
|
||||
HRESULT STDMETHODCALLTYPE AppxManifestObject::GetCapabilities(APPX_CAPABILITIES *capabilities) noexcept try
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (capabilities == nullptr), "bad pointer.");
|
||||
ThrowErrorIf(Error::InvalidParameter, (capabilities == nullptr), "bad pointer");
|
||||
|
||||
// Parse Capability elements.
|
||||
APPX_CAPABILITIES appxCapabilities = static_cast<APPX_CAPABILITIES>(0);
|
||||
XmlVisitor visitorCapabilities(static_cast<void*>(&appxCapabilities), [](void* c, const ComPtr<IXmlElement>& capabilitiesNode)->bool
|
||||
auto capabilitiesNames = GetCapabilities(APPX_CAPABILITY_CLASS_GENERAL);
|
||||
for (const auto& capability : capabilitiesNames)
|
||||
{
|
||||
APPX_CAPABILITIES* capabilities = reinterpret_cast<APPX_CAPABILITIES*>(c);
|
||||
auto name = capabilitiesNode->GetAttributeValue(XmlAttributeName::Name);
|
||||
const auto& capabilityEntry = std::find(std::begin(capabilitiesList), std::end(capabilitiesList), name.c_str());
|
||||
const auto& capabilityEntry = std::find(std::begin(capabilitiesList), std::end(capabilitiesList), capability.c_str());
|
||||
// Don't fail if not found as it can be custom capability or from a different namespace.
|
||||
if (capabilityEntry != std::end(capabilitiesList))
|
||||
{ // Don't fail if not found as it can be custom capabilities.
|
||||
*capabilities = static_cast<APPX_CAPABILITIES>((*capabilities) | (*capabilityEntry).value);
|
||||
{
|
||||
appxCapabilities = static_cast<APPX_CAPABILITIES>((appxCapabilities) | (*capabilityEntry).value);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
m_dom->ForEachElementIn(m_dom->GetDocument(), XmlQueryName::Package_Capabilities_Capability, visitorCapabilities);
|
||||
}
|
||||
*capabilities = appxCapabilities;
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxManifestObject::GetResources(IAppxManifestResourcesEnumerator **resources) noexcept try
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (resources == nullptr || *resources != nullptr), "bad pointer.");
|
||||
ThrowErrorIf(Error::InvalidParameter, (resources == nullptr || *resources != nullptr), "bad pointer");
|
||||
// Parse Resource elements.
|
||||
std::vector<std::string> appxResources;
|
||||
XmlVisitor visitorResource(static_cast<void*>(&appxResources), [](void* r, const ComPtr<IXmlElement>& resourceNode)->bool
|
||||
|
@ -317,7 +316,7 @@ namespace MSIX {
|
|||
|
||||
HRESULT STDMETHODCALLTYPE AppxManifestObject::GetApplications(IAppxManifestApplicationsEnumerator **applications) noexcept try
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (applications == nullptr || *applications != nullptr), "bad pointer.");
|
||||
ThrowErrorIf(Error::InvalidParameter, (applications == nullptr || *applications != nullptr), "bad pointer");
|
||||
|
||||
std::vector<ComPtr<IAppxManifestApplication>> apps;
|
||||
struct _context
|
||||
|
@ -365,7 +364,12 @@ namespace MSIX {
|
|||
APPX_CAPABILITY_CLASS_TYPE capabilityClass,
|
||||
IAppxManifestCapabilitiesEnumerator **capabilities) noexcept
|
||||
{
|
||||
return static_cast<HRESULT>(Error::NotImplemented);
|
||||
ThrowErrorIf(Error::InvalidParameter, (capabilities == nullptr), "bad pointer");
|
||||
|
||||
*capabilities = nullptr;
|
||||
auto capabilitiesNames = GetCapabilities(capabilityClass);
|
||||
*capabilities = ComPtr<IAppxManifestCapabilitiesEnumerator>::Make<EnumeratorString<IAppxManifestCapabilitiesEnumerator, IAppxManifestCapabilitiesEnumeratorUtf8>>(m_factory.Get(), capabilitiesNames).Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
}
|
||||
|
||||
// IAppxManifestReader4
|
||||
|
@ -394,7 +398,7 @@ namespace MSIX {
|
|||
|
||||
HRESULT STDMETHODCALLTYPE AppxManifestObject::GetTargetDeviceFamilies(IAppxManifestTargetDeviceFamiliesEnumerator **targetDeviceFamilies) noexcept try
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, (targetDeviceFamilies == nullptr || *targetDeviceFamilies != nullptr), "bad pointer.");
|
||||
ThrowErrorIf(Error::InvalidParameter, (targetDeviceFamilies == nullptr || *targetDeviceFamilies != nullptr), "bad pointer");
|
||||
*targetDeviceFamilies = ComPtr<IAppxManifestTargetDeviceFamiliesEnumerator>::
|
||||
Make<EnumeratorCom<IAppxManifestTargetDeviceFamiliesEnumerator,IAppxManifestTargetDeviceFamily>>(m_tdf).Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
|
@ -407,4 +411,108 @@ namespace MSIX {
|
|||
*documentElement = m_dom->GetDocument().As<IMsixElement>().Detach();
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
// Helper to get capabilities from the manifest
|
||||
std::vector<std::string> AppxManifestObject::GetCapabilities(APPX_CAPABILITY_CLASS_TYPE capabilityClass)
|
||||
{
|
||||
ThrowErrorIf(Error::InvalidParameter, capabilityClass > APPX_CAPABILITY_CLASS_CUSTOM || capabilityClass < APPX_CAPABILITY_CLASS_DEFAULT,
|
||||
"Invalid capability class.");
|
||||
|
||||
std::vector<std::string> capabilitiesNames;
|
||||
struct _context
|
||||
{
|
||||
APPX_CAPABILITY_CLASS_TYPE capabilityClass;
|
||||
std::vector<std::string>& capabilitiesNames;
|
||||
};
|
||||
_context context = { capabilityClass, capabilitiesNames};
|
||||
|
||||
// Parse Capability elements.
|
||||
if (capabilityClass != APPX_CAPABILITY_CLASS_CUSTOM)
|
||||
{
|
||||
XmlVisitor visitorCapabilities(static_cast<void*>(&context), [](void* c, const ComPtr<IXmlElement>& capabilitiesNode)->bool
|
||||
{
|
||||
_context* context = reinterpret_cast<_context*>(c);
|
||||
std::string prefix = capabilitiesNode->GetPrefix();
|
||||
auto name = capabilitiesNode->GetAttributeValue(XmlAttributeName::Name);
|
||||
|
||||
static std::array<std::string, 11> generalCapabilities =
|
||||
{
|
||||
"foundation",
|
||||
"uap",
|
||||
"win10foundation",
|
||||
"win10uap",
|
||||
"uap2",
|
||||
"uap3",
|
||||
"uap4",
|
||||
"uap6",
|
||||
"uap7",
|
||||
"win10mobile",
|
||||
"", // If no prefix then default namespace for AppxManifest is win10foundation.
|
||||
};
|
||||
|
||||
static std::array<std::string, 2> restrictedCapabilities =
|
||||
{
|
||||
"rescap",
|
||||
"win10rescap",
|
||||
};
|
||||
|
||||
static std::array<std::string, 2> windowsCapabilities =
|
||||
{
|
||||
"wincap",
|
||||
"win10wincap",
|
||||
};
|
||||
|
||||
if (context->capabilityClass == APPX_CAPABILITY_CLASS_GENERAL ||
|
||||
context->capabilityClass == APPX_CAPABILITY_CLASS_DEFAULT ||
|
||||
context->capabilityClass == APPX_CAPABILITY_CLASS_ALL)
|
||||
{
|
||||
auto found = std::find(generalCapabilities.begin(), generalCapabilities.end(), prefix);
|
||||
if (found != generalCapabilities.end())
|
||||
{
|
||||
context->capabilitiesNames.push_back(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->capabilityClass == APPX_CAPABILITY_CLASS_RESTRICTED ||
|
||||
context->capabilityClass == APPX_CAPABILITY_CLASS_ALL)
|
||||
{
|
||||
auto found = std::find(restrictedCapabilities.begin(), restrictedCapabilities.end(), prefix);
|
||||
if (found != restrictedCapabilities.end())
|
||||
{
|
||||
context->capabilitiesNames.push_back(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (context->capabilityClass == APPX_CAPABILITY_CLASS_WINDOWS ||
|
||||
context->capabilityClass == APPX_CAPABILITY_CLASS_ALL)
|
||||
{
|
||||
auto found = std::find(windowsCapabilities.begin(), windowsCapabilities.end(), prefix);
|
||||
if (found != windowsCapabilities.end())
|
||||
{
|
||||
context->capabilitiesNames.push_back(name);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
m_dom->ForEachElementIn(m_dom->GetDocument(), XmlQueryName::Package_Capabilities_Capability, visitorCapabilities);
|
||||
}
|
||||
|
||||
if (capabilityClass == APPX_CAPABILITY_CLASS_CUSTOM || capabilityClass == APPX_CAPABILITY_CLASS_ALL)
|
||||
{
|
||||
XmlVisitor visitorCustomCapabilities(static_cast<void*>(&context), [](void* c, const ComPtr<IXmlElement>& capabilitiesNode)->bool
|
||||
{
|
||||
_context* context = reinterpret_cast<_context*>(c);
|
||||
auto name = capabilitiesNode->GetAttributeValue(XmlAttributeName::Name);
|
||||
context->capabilitiesNames.push_back(name);
|
||||
return true;
|
||||
});
|
||||
m_dom->ForEachElementIn(m_dom->GetDocument(), XmlQueryName::Package_Capabilities_CustomCapability, visitorCustomCapabilities);
|
||||
}
|
||||
|
||||
return capabilitiesNames;
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
#include "Exceptions.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include "IXml.hpp"
|
||||
#include "FileNameValidation.hpp"
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
|
@ -85,102 +86,6 @@ namespace MSIX {
|
|||
|
||||
#pragma region ValidateIdentifiers
|
||||
|
||||
const char* ProhibitedFileNames[] = {
|
||||
".",
|
||||
"..",
|
||||
"con",
|
||||
"prn",
|
||||
"aux",
|
||||
"nul",
|
||||
"com1",
|
||||
"com2",
|
||||
"com3",
|
||||
"com4",
|
||||
"com5",
|
||||
"com6",
|
||||
"com7",
|
||||
"com8",
|
||||
"com9",
|
||||
"lpt1",
|
||||
"lpt2",
|
||||
"lpt3",
|
||||
"lpt4",
|
||||
"lpt5",
|
||||
"lpt6",
|
||||
"lpt7",
|
||||
"lpt8",
|
||||
"lpt9",
|
||||
};
|
||||
|
||||
// Caller must pass in a lowercase string!
|
||||
bool IsProhibitedFileName(const std::string& identifier)
|
||||
{
|
||||
for (const auto& name : ProhibitedFileNames)
|
||||
{
|
||||
if (identifier == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string ProhibitedPrefixes[] = {
|
||||
"con.",
|
||||
"prn.",
|
||||
"aux.",
|
||||
"nul.",
|
||||
"com1.",
|
||||
"com2.",
|
||||
"com3.",
|
||||
"com4.",
|
||||
"com5.",
|
||||
"com6.",
|
||||
"com7.",
|
||||
"com8.",
|
||||
"com9.",
|
||||
"lpt1.",
|
||||
"lpt2.",
|
||||
"lpt3.",
|
||||
"lpt4.",
|
||||
"lpt5.",
|
||||
"lpt6.",
|
||||
"lpt7.",
|
||||
"lpt8.",
|
||||
"lpt9.",
|
||||
"xn--",
|
||||
};
|
||||
|
||||
bool HasProhibitedPrefix(const std::string& identifier)
|
||||
{
|
||||
for (const auto& prefix : ProhibitedPrefixes)
|
||||
{
|
||||
if (identifier.size() >= prefix.size() &&
|
||||
identifier.substr(0, prefix.size()) == prefix)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string ProhibitedSuffixes[] = {
|
||||
".",
|
||||
};
|
||||
|
||||
bool HasProhibitedSuffix(const std::string& identifier)
|
||||
{
|
||||
for (const auto& suffix : ProhibitedSuffixes)
|
||||
{
|
||||
if (identifier.size() >= suffix.size() &&
|
||||
identifier.substr(identifier.size() - suffix.size()) == suffix)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void ValidateIdentifier(const TargetAttribute& target, const MSIX::ComPtr<IXmlElement>& element)
|
||||
{
|
||||
std::string attributeValue = element->GetAttributeValue(target.AttributeName);
|
||||
|
@ -365,9 +270,7 @@ namespace MSIX {
|
|||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string lowIdent = Helper::tolower(identifier);
|
||||
return !IsProhibitedFileName(lowIdent) && !HasProhibitedPrefix(lowIdent) && !HasProhibitedSuffix(lowIdent);
|
||||
return FileNameValidation::IsIdentifierValid(identifier);
|
||||
}
|
||||
|
||||
void AppxManifestValidation::ValidateManifest(IXmlDom* manifest)
|
|
@ -7,7 +7,7 @@
|
|||
#include "AppxManifestValidation.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
#include "Encoding.hpp"
|
||||
#include "SHA256.hpp"
|
||||
#include "Crypto.hpp"
|
||||
#include "Enumerators.hpp"
|
||||
#include "IXml.hpp"
|
||||
|
|
@ -27,8 +27,8 @@ namespace MSIX { namespace Encoding {
|
|||
L"%40", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // @
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, L"%5B", nullptr, L"%5D", nullptr, nullptr, // [ ]
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, L"%5B", nullptr, L"%5D", L"%5E", nullptr, // [ ] ^
|
||||
L"%60", nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, // `
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
|
||||
nullptr, nullptr, nullptr, L"%7B", nullptr, L"%7D", nullptr, // { }
|
|
@ -0,0 +1,306 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#include "FileNameValidation.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
namespace
|
||||
{
|
||||
static const std::array<bool, 0x7D> IsReservedFileNameChar = {
|
||||
true, true, true, true, true, true, true, true,
|
||||
true, true, true, true, true, true, true, true,
|
||||
true, true, true, true, true, true, true, true,
|
||||
true, true, true, true, true, true, true, true, // characters in the range 0 - 31
|
||||
false, false, true, false, false, false, false, false, // character "
|
||||
false, false, true, false, false, false, false, true, // characters * /
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, true, false, true, false, true, true, // characters : < > ?
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, false, false, false, false,
|
||||
false, false, false, false, true // character |
|
||||
};
|
||||
|
||||
const std::string ProhibitedFileNames[] = {
|
||||
".",
|
||||
"..",
|
||||
"con",
|
||||
"prn",
|
||||
"aux",
|
||||
"nul",
|
||||
"com1",
|
||||
"com2",
|
||||
"com3",
|
||||
"com4",
|
||||
"com5",
|
||||
"com6",
|
||||
"com7",
|
||||
"com8",
|
||||
"com9",
|
||||
"lpt1",
|
||||
"lpt2",
|
||||
"lpt3",
|
||||
"lpt4",
|
||||
"lpt5",
|
||||
"lpt6",
|
||||
"lpt7",
|
||||
"lpt8",
|
||||
"lpt9",
|
||||
};
|
||||
|
||||
// Caller must pass in a lowercase string!
|
||||
bool IsProhibitedFileName(const std::string& identifier)
|
||||
{
|
||||
for (const auto& name : ProhibitedFileNames)
|
||||
{
|
||||
if (identifier == name)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string ProhibitedPrefixes[] = {
|
||||
"con.",
|
||||
"prn.",
|
||||
"aux.",
|
||||
"nul.",
|
||||
"com1.",
|
||||
"com2.",
|
||||
"com3.",
|
||||
"com4.",
|
||||
"com5.",
|
||||
"com6.",
|
||||
"com7.",
|
||||
"com8.",
|
||||
"com9.",
|
||||
"lpt1.",
|
||||
"lpt2.",
|
||||
"lpt3.",
|
||||
"lpt4.",
|
||||
"lpt5.",
|
||||
"lpt6.",
|
||||
"lpt7.",
|
||||
"lpt8.",
|
||||
"lpt9.",
|
||||
"xn--",
|
||||
};
|
||||
|
||||
bool HasProhibitedPrefix(const std::string& identifier)
|
||||
{
|
||||
for (const auto& prefix : ProhibitedPrefixes)
|
||||
{
|
||||
if (identifier.size() >= prefix.size() &&
|
||||
identifier.substr(0, prefix.size()) == prefix)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string ProhibitedSuffixes[] = {
|
||||
".",
|
||||
};
|
||||
|
||||
bool HasProhibitedSuffix(const std::string& identifier)
|
||||
{
|
||||
for (const auto& suffix : ProhibitedSuffixes)
|
||||
{
|
||||
if (identifier.size() >= suffix.size() &&
|
||||
identifier.substr(identifier.size() - suffix.size()) == suffix)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr wchar_t highSurrogateStart = 0xd800;
|
||||
constexpr wchar_t highSurrogateEnd = 0xdbff;
|
||||
constexpr wchar_t lowSurrogateStart = 0xdc00;
|
||||
constexpr wchar_t lowSurrogateEnd = 0xdfff;
|
||||
|
||||
bool IsLowSurrogate(wchar_t ch)
|
||||
{
|
||||
return (((ch) >= lowSurrogateStart) && ((ch) <= lowSurrogateEnd));
|
||||
}
|
||||
|
||||
bool IsHighSurrogate(wchar_t ch)
|
||||
{
|
||||
return (((ch) >= highSurrogateStart) && ((ch) <= highSurrogateEnd));
|
||||
}
|
||||
|
||||
|
||||
bool IsValidSegment(wchar_t previousChar, const std::wstring& segmentUtf16)
|
||||
{
|
||||
// Segments cannot end with multiple segment delimiters ('\'), space, dot or orphaned high surrogate char.
|
||||
if ((previousChar == L'\\') || (previousChar == L' ') || (previousChar == L'.' ) || IsHighSurrogate(previousChar))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Segments cannot be longer than 255 characters
|
||||
if (segmentUtf16.size() > 255)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
std::string segment = wstring_to_utf8(segmentUtf16);
|
||||
return FileNameValidation::IsIdentifierValid(segment);
|
||||
}
|
||||
}
|
||||
|
||||
// Checks for disallowed character sequences in a file name and check if the file name is valid against reserved folders.
|
||||
// This method will catch the following types of invalid file names:
|
||||
// - Empty file name
|
||||
// - File names longer than 32767
|
||||
// - Containing reserved characters (< > : " / | ? *)
|
||||
// - Multiple slashes in a row (including UNC paths)
|
||||
// - Relative paths ("./", "..\", etc.)
|
||||
// - Absolute paths (beginning with a slash)
|
||||
// - File names that end with a dot or a slash
|
||||
// - Names that end with a dot or a space
|
||||
// - Names that conflict with device names (e.g. CON, AUX, NUL, etc.)
|
||||
// - Segments that are longer than 255 characters
|
||||
// - File name same as System Reserved Folder name or Package Reserved Folder name
|
||||
// - File name is a file under the System Reserved Folder
|
||||
bool FileNameValidation::IsFileNameValid(const std::string& name)
|
||||
{
|
||||
constexpr std::size_t UnicodeStringMaxChars = 32767;
|
||||
if (name.empty() || name.size() > UnicodeStringMaxChars)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Windows files cannot have '/'.
|
||||
#ifdef WIN32
|
||||
auto findSlash = name.find("/");
|
||||
if (findSlash != std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#else
|
||||
// Unix-based systems can have '\' in their file names, but restrict
|
||||
// it to allow real "cross plat" packages
|
||||
auto findBackSlash = name.find("\\");
|
||||
if (findBackSlash != std::string::npos)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::string backSlashName = Helper::toBackSlash(name);
|
||||
if (backSlashName[0] == '\\')
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
wchar_t previousChar = L'\0';
|
||||
std::wstring currentSegment;
|
||||
bool hasRelsSubFolder = false; // the file name is under a _rels subfolder
|
||||
|
||||
std::wstring nameUtf16 = utf8_to_wstring(backSlashName);
|
||||
for(size_t i = 0; i < nameUtf16.size(); i++)
|
||||
{
|
||||
wchar_t currentChar = nameUtf16[i];
|
||||
|
||||
// Reserved characters in the file name
|
||||
if ((currentChar < IsReservedFileNameChar.size()) && IsReservedFileNameChar[currentChar])
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Character is a non-character Unicode codepoint
|
||||
if (currentChar == 0xFFFE || currentChar == 0xFFFF)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Found low surrogate value without preceding high surrogate value
|
||||
if (IsLowSurrogate(currentChar) && !IsHighSurrogate(previousChar))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Previous high surrogate value was not completed
|
||||
if (IsHighSurrogate(previousChar) && !IsLowSurrogate(currentChar))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (currentChar == L'\\')
|
||||
{
|
||||
if(!IsValidSegment(previousChar, currentSegment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
std::wstring lowSegment = Helper::towlower(currentSegment);
|
||||
if (lowSegment == L"_rels")
|
||||
{
|
||||
hasRelsSubFolder = true;
|
||||
}
|
||||
|
||||
currentSegment.clear();
|
||||
}
|
||||
else
|
||||
{
|
||||
currentSegment += currentChar;
|
||||
}
|
||||
|
||||
previousChar = currentChar;
|
||||
}
|
||||
|
||||
// Validate final segment
|
||||
if (!IsValidSegment(previousChar, currentSegment))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// Relationship Part URI must have a .rels extension and be under a _rels subfolder
|
||||
if (hasRelsSubFolder)
|
||||
{
|
||||
std::string ext = Helper::tolower(name.substr(name.find_last_of(".") + 1));
|
||||
if (ext == "rels")
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileNameValidation::IsIdentifierValid(const std::string& identifier)
|
||||
{
|
||||
std::string lowSegment = Helper::tolower(identifier);
|
||||
return !IsProhibitedFileName(lowSegment) && !HasProhibitedPrefix(lowSegment) && !HasProhibitedSuffix(lowSegment);
|
||||
}
|
||||
|
||||
bool FileNameValidation::IsFootPrintFile(const std::string& fileName)
|
||||
{
|
||||
std::string lowIdent = Helper::tolower(fileName);
|
||||
return ((lowIdent == "appxmanifest.xml") ||
|
||||
(lowIdent == "appxsignature.p7x") ||
|
||||
(lowIdent == "appxblockmap.xml") ||
|
||||
(lowIdent == "[content_types].xml"));
|
||||
}
|
||||
|
||||
bool FileNameValidation::IsReservedFolder(const std::string& fileName)
|
||||
{
|
||||
std::string lowIdent = Helper::tolower(fileName);
|
||||
return ((lowIdent.rfind("appxmetadata", 0) != std::string::npos) ||
|
||||
(lowIdent.rfind("microsoft.system.package.metadata", 0) != std::string::npos));
|
||||
}
|
||||
}
|
|
@ -61,6 +61,7 @@ static const MSIX::XmlQueryNameCharType* xPaths[] = {
|
|||
/* Package_Properties_Framework */L"/*[local-name()='Package']/*[local-name()='Properties']/*[local-name()='Framework']",
|
||||
/* Package_Properties_ResourcePackage */L"/*[local-name()='Package']/*[local-name()='Properties']/*[local-name()='ResourcePackage']",
|
||||
/* Package_Properties_SupportedUsers */L"/*[local-name()='Package']/*[local-name()='Properties']/*[local-name()='SupportedUsers']",
|
||||
/* Package_Capabilities_CustomCapability */L"/*[local-name()='Package']/*[local-name()='Capabilities']/*[local-name()='CustomCapability']",
|
||||
};
|
||||
#else
|
||||
|
||||
|
@ -93,6 +94,7 @@ static const MSIX::XmlQueryNameCharType* xPaths[] = {
|
|||
/* Package_Properties_Framework */"/Package/Properties/Framework",
|
||||
/* Package_Properties_ResourcePackage */"/Package/Properties/ResourcePackage",
|
||||
/* Package_Properties_SupportedUsers */"/Package/Properties/SupportedUsers",
|
||||
/* Package_Capabilities_CustomCapability */"/Package/Capabilities/CustomCapability",
|
||||
};
|
||||
#endif
|
||||
|
|
@ -0,0 +1,495 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "ObjectBase.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "ZipObject.hpp"
|
||||
#include "VectorStream.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <limits>
|
||||
#include <functional>
|
||||
#include <algorithm>
|
||||
namespace MSIX {
|
||||
/* Zip File Structure
|
||||
[LocalFileHeader 1]
|
||||
[encryption header 1]
|
||||
[file data 1]
|
||||
[data descriptor 1]
|
||||
.
|
||||
.
|
||||
[LocalFileHeader n]
|
||||
[encryption header n]
|
||||
[file data n]
|
||||
[data descriptor n]
|
||||
[archive decryption header]
|
||||
[archive extra data record]
|
||||
[CentralFileHeader 1]
|
||||
.
|
||||
.
|
||||
[CentralFileHeader n]
|
||||
[Zip64EndOfCentralDirectoryRecord]
|
||||
[Zip64EndOfCentralDirectoryLocator]
|
||||
[EndCentralDirectoryRecord]
|
||||
*/
|
||||
|
||||
// from AppNote.txt, section 4.5.2:
|
||||
enum class HeaderIDs : std::uint16_t
|
||||
{
|
||||
Zip64ExtendedInfo = 0x0001, // Zip64 extended information extra field
|
||||
AV = 0x0007, // AV Info
|
||||
RESERVED_1 = 0x0008, // Reserved for extended language encoding data (PFS) (see APPENDIX D)
|
||||
OS2 = 0x0009, // OS/2
|
||||
NTFS = 0x000a, // NTFS
|
||||
OpenVMS = 0x000c, // OpenVMS
|
||||
UNIX = 0x000d, // UNIX
|
||||
RESERVED_2 = 0x000e, // Reserved for file stream and fork descriptors
|
||||
PatchDescriptor = 0x000f, // Patch Descriptor
|
||||
UNSUPPORTED_1 = 0x0014, // PKCS#7 Store for X.509 Certificates
|
||||
UNSUPPORTED_2 = 0x0015, // X.509 Certificate ID and Signature for individual file
|
||||
UNSUPPORTED_3 = 0x0016, // X.509 Certificate ID for Central Directory
|
||||
UNSUPPORTED_4 = 0x0017, // Strong Encryption Header
|
||||
RecordManagement = 0x0018, // Record Management Controls
|
||||
UNSUPPORTED_5 = 0x0019, // PKCS#7 Encryption Recipient Certificate List
|
||||
IBMS390 = 0x0065, // IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed
|
||||
IBM_Reserved = 0x0066, // Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed
|
||||
RESERVED_3 = 0x4690, // POSZIP 4690 (reserved)
|
||||
};
|
||||
|
||||
// Hat tip to the people at Facebook. Timestamp for files in ZIP archive
|
||||
// format held constant to make pack/unpack deterministic
|
||||
enum class MagicNumbers : std::uint16_t
|
||||
{
|
||||
FileTime = 0x6B60, // kudos to those know this
|
||||
FileDate = 0xA2B1, // :)
|
||||
};
|
||||
|
||||
// if any of these are set, then fail.
|
||||
constexpr static const GeneralPurposeBitFlags UnsupportedFlagsMask =
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_0 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_6 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_12 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_13 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_14 |
|
||||
GeneralPurposeBitFlags::UNSUPPORTED_15;
|
||||
|
||||
// Note: all fields stored in Intel low-byte/high-byte order.
|
||||
|
||||
// FROM APPNOTE.TXT section 4.5.3:
|
||||
// If one of the size or offset fields in the Local or Central directory
|
||||
// record is too small to hold the required data, a Zip64 extended information
|
||||
// record is created. The order of the fields in the zip64 extended
|
||||
// information record is fixed, but the fields MUST only appear if the
|
||||
// corresponding Local or Central directory record field is set to 0xFFFF
|
||||
// or 0xFFFFFFFF.
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Zip64ExtendedInformation //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Zip64ExtendedInformation::Zip64ExtendedInformation()
|
||||
{
|
||||
SetSignature(static_cast<std::uint16_t>(HeaderIDs::Zip64ExtendedInfo));
|
||||
SetSize(NonOptionalSize);
|
||||
}
|
||||
|
||||
void Zip64ExtendedInformation::Read(const ComPtr<IStream>& stream, ULARGE_INTEGER start, uint32_t uncompressedSize, uint32_t compressedSize, uint32_t offset, uint16_t disk)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>(), static_cast<std::uint32_t>(HeaderIDs::Zip64ExtendedInfo));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>());
|
||||
// The incoming stream will be just the extended info, and our size should match it minus the fixed bytes
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<1>(), static_cast<uint32_t>(stream.As<IStreamInternal>()->GetSize() - NonOptionalSize));
|
||||
|
||||
if (IsValueInExtendedInfo(uncompressedSize))
|
||||
{
|
||||
StreamBase::Read(stream, &Field<2>());
|
||||
}
|
||||
|
||||
if (IsValueInExtendedInfo(compressedSize))
|
||||
{
|
||||
StreamBase::Read(stream, &Field<3>());
|
||||
}
|
||||
|
||||
if (IsValueInExtendedInfo(offset))
|
||||
{
|
||||
StreamBase::Read(stream, &Field<4>());
|
||||
ThrowErrorIfNot(Error::ZipBadExtendedData, Field<4>().get() < start.QuadPart, "invalid relative header offset");
|
||||
}
|
||||
|
||||
if (IsValueInExtendedInfo(disk))
|
||||
{
|
||||
StreamBase::Read(stream, &Field<5>());
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// CentralDirectoryFileHeader //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
CentralDirectoryFileHeader::CentralDirectoryFileHeader()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::CentralFileHeader));
|
||||
SetVersionMadeBy(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion));
|
||||
SetGeneralPurposeBitFlags(0);
|
||||
SetLastModFileDate(static_cast<std::uint16_t>(MagicNumbers::FileDate)); // TODO: figure out how to convert to msdos time
|
||||
SetLastModFileTime(static_cast<std::uint16_t>(MagicNumbers::FileTime));
|
||||
SetCompressedSize(0);
|
||||
SetUncompressedSize(0);
|
||||
SetExtraFieldLength(0);
|
||||
SetFileCommentLength(0);
|
||||
SetDiskNumberStart(0);
|
||||
SetInternalFileAttributes(0);
|
||||
SetExternalFileAttributes(0);
|
||||
SetRelativeOffsetOfLocalHeader(0);
|
||||
}
|
||||
|
||||
void CentralDirectoryFileHeader::SetData(const std::string& name, std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize,
|
||||
std::uint64_t relativeOffset, std::uint16_t compressionMethod, bool forceDataDescriptor)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
SetCompressionMethod(compressionMethod);
|
||||
SetCrc(crc);
|
||||
SetFileName(name);
|
||||
|
||||
// Set these values that may end up in the extra field info; then make sure to update it
|
||||
SetCompressedSize(compressedSize);
|
||||
SetUncompressedSize(uncompressedSize);
|
||||
SetRelativeOffsetOfLocalHeader(relativeOffset);
|
||||
UpdateExtraField();
|
||||
|
||||
if (forceDataDescriptor ||
|
||||
compressedSize > MaxSizeToNotUseDataDescriptor ||
|
||||
uncompressedSize > MaxSizeToNotUseDataDescriptor)
|
||||
{
|
||||
SetGeneralPurposeBitFlags(static_cast<std::uint16_t>(GeneralPurposeBitFlags::DataDescriptor));
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
}
|
||||
}
|
||||
|
||||
void CentralDirectoryFileHeader::Read(const ComPtr<IStream>& stream, bool isZip64)
|
||||
{
|
||||
m_isZip64 = isZip64;
|
||||
StreamBase::Read(stream, &Field<0>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>(), static_cast<std::uint32_t>(Signatures::CentralFileHeader));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>());
|
||||
StreamBase::Read(stream, &Field<2>());
|
||||
|
||||
StreamBase::Read(stream, &Field<3>());
|
||||
ThrowErrorIfNot(Error::ZipCentralDirectoryHeader,
|
||||
0 == (Field<3>().get() & static_cast<std::uint16_t>(UnsupportedFlagsMask)),
|
||||
"unsupported flag(s) specified");
|
||||
|
||||
StreamBase::Read(stream, &Field<4>());
|
||||
Meta::OnlyEitherValueValidation<std::uint16_t>(Field<4>(), static_cast<std::uint16_t>(CompressionType::Deflate),
|
||||
static_cast<std::uint16_t>(CompressionType::Store));
|
||||
|
||||
StreamBase::Read(stream, &Field<5>());
|
||||
StreamBase::Read(stream, &Field<6>());
|
||||
StreamBase::Read(stream, &Field<7>());
|
||||
StreamBase::Read(stream, &Field<8>());
|
||||
StreamBase::Read(stream, &Field<9>());
|
||||
|
||||
StreamBase::Read(stream, &Field<10>());
|
||||
ThrowErrorIfNot(Error::ZipCentralDirectoryHeader, (Field<10>().get() != 0), "unsupported file name size");
|
||||
if (Field<10>().get() != 0) {Field<17>().get().resize(Field<10>().get(), 0); }
|
||||
|
||||
StreamBase::Read(stream, &Field<11>());
|
||||
if (Field<11>().get() != 0) { Field<18>().get().resize(Field<11>().get(), 0); }
|
||||
|
||||
StreamBase::Read(stream, &Field<12>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<12>(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<13>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<13>(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<14>());
|
||||
|
||||
StreamBase::Read(stream, &Field<15>());
|
||||
StreamBase::Read(stream, &Field<16>());
|
||||
ULARGE_INTEGER pos = {0};
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
if (!m_isZip64 || !IsValueInExtendedInfo(Field<16>()))
|
||||
{
|
||||
ThrowErrorIf(Error::ZipCentralDirectoryHeader, (Field<16>().get() >= pos.QuadPart), "invalid relative header offset");
|
||||
}
|
||||
|
||||
if (Field<17>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<17>().get().data()), static_cast<ULONG>(Field<17>().Size()), nullptr));
|
||||
}
|
||||
|
||||
if (Field<18>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<18>().get().data()), static_cast<ULONG>(Field<18>().Size()), nullptr));
|
||||
}
|
||||
// Only process for Zip64ExtendedInformation
|
||||
if (Field<18>().Size() > 2 && Field<18>().get()[0] == 0x01 && Field<18>().get()[1] == 0x00)
|
||||
{
|
||||
LARGE_INTEGER zero = {0};
|
||||
ThrowHrIfFailed(stream->Seek(zero, StreamBase::Reference::CURRENT, &pos));
|
||||
auto vectorStream = ComPtr<IStream>::Make<VectorStream>(&Field<18>());
|
||||
m_extendedInfo.Read(vectorStream.Get(), pos, Field<9>(), Field<8>(), Field<16>(), Field<13>());
|
||||
}
|
||||
|
||||
if (Field<19>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<19>().get().data()), static_cast<ULONG>(Field<19>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// LocalFileHeader //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
LocalFileHeader::LocalFileHeader()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::LocalFileHeader));
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension)); // deafult to zip64
|
||||
SetGeneralPurposeBitFlags(static_cast<std::uint16_t>(GeneralPurposeBitFlags::DataDescriptor));
|
||||
SetLastModFileTime(static_cast<std::uint16_t>(MagicNumbers::FileTime)); // TODO: figure out how to convert to msdos time
|
||||
SetLastModFileDate(static_cast<std::uint16_t>(MagicNumbers::FileDate));
|
||||
SetCrc(0);
|
||||
SetCompressedSize(0);
|
||||
SetUncompressedSize(0);
|
||||
SetExtraFieldLength(0);
|
||||
}
|
||||
|
||||
void LocalFileHeader::SetData(const std::string& name, bool isCompressed)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
auto compressMethod = (isCompressed) ? CompressionType::Deflate : CompressionType::Store;
|
||||
SetCompressionMethod(static_cast<std::uint16_t>(compressMethod));
|
||||
SetFileName(name);
|
||||
}
|
||||
|
||||
void LocalFileHeader::SetData(std::uint32_t crc, std::uint64_t compressedSize, std::uint64_t uncompressedSize)
|
||||
{
|
||||
THROW_IF_PACK_NOT_ENABLED
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion));
|
||||
SetGeneralPurposeBitFlags(static_cast<std::uint16_t>(GetGeneralPurposeBitFlags() & ~GeneralPurposeBitFlags::DataDescriptor));
|
||||
SetCrc(crc);
|
||||
SetCompressedSize(static_cast<uint32_t>(compressedSize));
|
||||
SetUncompressedSize(static_cast<uint32_t>(uncompressedSize));
|
||||
}
|
||||
|
||||
void LocalFileHeader::Read(const ComPtr<IStream> &stream, CentralDirectoryFileHeader& directoryEntry)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>(), static_cast<std::uint32_t>(Signatures::LocalFileHeader));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>());
|
||||
Meta::OnlyEitherValueValidation<std::uint16_t>(Field<1>(), static_cast<std::uint16_t>(ZipVersions::Zip32DefaultVersion),
|
||||
static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
|
||||
StreamBase::Read(stream, &Field<2>());
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, ((Field<2>().get() & static_cast<std::uint16_t>(UnsupportedFlagsMask)) == 0), "unsupported flag(s) specified");
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (IsGeneralPurposeBitSet() == directoryEntry.IsGeneralPurposeBitSet()), "inconsistent general purpose bits specified");
|
||||
|
||||
StreamBase::Read(stream, &Field<3>());
|
||||
Meta::OnlyEitherValueValidation<std::uint16_t>(Field<3>(), static_cast<std::uint16_t>(CompressionType::Deflate),
|
||||
static_cast<std::uint16_t>(CompressionType::Store));
|
||||
|
||||
StreamBase::Read(stream, &Field<4>());
|
||||
StreamBase::Read(stream, &Field<5>());
|
||||
StreamBase::Read(stream, &Field<6>());
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (!IsGeneralPurposeBitSet() || (Field<6>().get() == 0)), "Invalid Zip CRC");
|
||||
|
||||
StreamBase::Read(stream, &Field<7>());
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (!IsGeneralPurposeBitSet() || (Field<7>().get() == 0)), "Invalid Zip compressed size");
|
||||
|
||||
StreamBase::Read(stream, &Field<8>());
|
||||
|
||||
StreamBase::Read(stream, &Field<9>());
|
||||
ThrowErrorIfNot(Error::ZipLocalFileHeader, (Field<9>().get() != 0), "unsupported file name size");
|
||||
Field<11>().get().resize(GetFileNameLength(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<10>());
|
||||
// Even if we don't validate them, we need to read the extra field
|
||||
if (Field<10>().get() != 0) {Field<12>().get().resize(Field<10>().get(), 0); }
|
||||
|
||||
if (Field<11>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<11>().get().data()), static_cast<ULONG>(Field<11>().Size()), nullptr));
|
||||
}
|
||||
|
||||
if (Field<12>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<12>().get().data()), static_cast<ULONG>(Field<12>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Zip64EndOfCentralDirectoryRecord //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Zip64EndOfCentralDirectoryRecord::Zip64EndOfCentralDirectoryRecord()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::Zip64EndOfCD));
|
||||
// We not use Field<10> so there's no variable data
|
||||
SetSizeOfZip64CDRecord(static_cast<std::uint64_t>(Size() - Field<0>().Size() - Field<1>().Size()));
|
||||
SetVersionMadeBy(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetVersionNeededToExtract(static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
SetNumberOfThisDisk(0);
|
||||
SetNumberOfTheDiskWithStartOfCD(0);
|
||||
}
|
||||
|
||||
void Zip64EndOfCentralDirectoryRecord::SetData(std::uint64_t numCentralDirs, std::uint64_t sizeCentralDir, std::uint64_t offsetStartCentralDirectory)
|
||||
{
|
||||
SetTotalNumberOfEntriesDisk(numCentralDirs);
|
||||
SetSizeOfCD(sizeCentralDir);
|
||||
SetOffsetfStartOfCD(offsetStartCentralDirectory);
|
||||
}
|
||||
|
||||
void Zip64EndOfCentralDirectoryRecord::Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>(), static_cast<std::uint32_t>(Signatures::Zip64EndOfCD));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>());
|
||||
//4.3.14.1 The value stored into the "size of zip64 end of central
|
||||
// directory record" should be the size of the remaining
|
||||
// record and should not include the leading 12 bytes.
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, (Field<1>().get() == (this->Size() - 12)), "invalid size of zip64 EOCD");
|
||||
|
||||
StreamBase::Read(stream, &Field<2>());
|
||||
Meta::ExactValueValidation<std::uint16_t>(Field<2>(), static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
|
||||
StreamBase::Read(stream, &Field<3>());
|
||||
Meta::ExactValueValidation<std::uint16_t>(Field<3>(), static_cast<std::uint16_t>(ZipVersions::Zip64FormatExtension));
|
||||
|
||||
StreamBase::Read(stream, &Field<4>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<4>(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<5>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<5>(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<6>());
|
||||
Meta::NotValueValidation<std::uint64_t>(Field<6>(), 0);
|
||||
|
||||
StreamBase::Read(stream, &Field<7>());
|
||||
Meta::NotValueValidation<std::uint64_t>(Field<7>(), 0);
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, (Field<7>().get() == GetTotalNumberOfEntries()), "invalid total number of entries");
|
||||
|
||||
ULARGE_INTEGER pos = {0};
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
StreamBase::Read(stream, &Field<8>());
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, ((Field<8>().get() != 0) && (Field<8>().get() < pos.QuadPart)), "invalid size of central directory");
|
||||
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
StreamBase::Read(stream, &Field<9>());
|
||||
ThrowErrorIfNot(Error::Zip64EOCDRecord, ((Field<9>().get() != 0) && (Field<9>().get() < pos.QuadPart)), "invalid size of central directory");
|
||||
|
||||
if (Field<10>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<10>().get().data()), static_cast<ULONG>(Field<10>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// Zip64EndOfCentralDirectoryLocator //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
Zip64EndOfCentralDirectoryLocator::Zip64EndOfCentralDirectoryLocator()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::Zip64EndOfCDLocator));
|
||||
SetNumberOfDisk(0);
|
||||
SetTotalNumberOfDisks(1);
|
||||
}
|
||||
|
||||
void Zip64EndOfCentralDirectoryLocator::SetData(std::uint64_t zip64EndCdrOffset)
|
||||
{
|
||||
SetRelativeOffset(zip64EndCdrOffset);
|
||||
}
|
||||
|
||||
void Zip64EndOfCentralDirectoryLocator::Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>(), static_cast<std::uint32_t>(Signatures::Zip64EndOfCDLocator));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<1>(), 0);
|
||||
|
||||
ULARGE_INTEGER pos = {0};
|
||||
StreamBase::Read(stream, &Field<2>());
|
||||
ThrowHrIfFailed(stream->Seek({0}, StreamBase::Reference::CURRENT, &pos));
|
||||
ThrowErrorIfNot(Error::Zip64EOCDLocator, ((Field<2>().get() != 0) && (Field<2>().get() < pos.QuadPart)), "Invalid relative offset");
|
||||
|
||||
StreamBase::Read(stream, &Field<3>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<3>(), 1);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// EndOfCentralDirectoryRecord //
|
||||
//////////////////////////////////////////////////////////////////////////////////////////////
|
||||
EndCentralDirectoryRecord::EndCentralDirectoryRecord()
|
||||
{
|
||||
SetSignature(static_cast<std::uint32_t>(Signatures::EndOfCentralDirectory));
|
||||
// Always use zip64
|
||||
SetNumberOfDisk(0);
|
||||
SetDiskStart(0);
|
||||
SetTotalNumberOfEntries(std::numeric_limits<std::uint16_t>::max());
|
||||
SetTotalEntriesInCentralDirectory(std::numeric_limits<std::uint16_t>::max());
|
||||
SetSizeOfCentralDirectory(std::numeric_limits<std::uint32_t>::max());
|
||||
SetOffsetOfCentralDirectory(std::numeric_limits<std::uint32_t>::max());
|
||||
SetCommentLength(0);
|
||||
}
|
||||
|
||||
void EndCentralDirectoryRecord::Read(const ComPtr<IStream>& stream)
|
||||
{
|
||||
StreamBase::Read(stream, &Field<0>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<0>(), static_cast<std::uint32_t>(Signatures::EndOfCentralDirectory));
|
||||
|
||||
StreamBase::Read(stream, &Field<1>());
|
||||
Meta::OnlyEitherValueValidation<std::uint32_t>(Field<1>(), 0, 0xFFFF);
|
||||
|
||||
StreamBase::Read(stream, &Field<2>());
|
||||
Meta::OnlyEitherValueValidation<std::uint32_t>(Field<2>(), 0, 0xFFFF);
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, (Field<1>().get() != Field<2>().get()), "field missmatch");
|
||||
|
||||
StreamBase::Read(stream, &Field<3>());
|
||||
StreamBase::Read(stream, &Field<4>());
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, (Field<3>().get() != Field<4>().get()), "field missmatch");
|
||||
|
||||
StreamBase::Read(stream, &Field<5>());
|
||||
StreamBase::Read(stream, &Field<6>());
|
||||
|
||||
m_isZip64 = (
|
||||
IsValueInExtendedInfo(Field<1>()) ||
|
||||
IsValueInExtendedInfo(Field<2>()) ||
|
||||
IsValueInExtendedInfo(Field<3>()) ||
|
||||
IsValueInExtendedInfo(Field<4>()) ||
|
||||
IsValueInExtendedInfo(Field<5>()) ||
|
||||
IsValueInExtendedInfo(Field<6>()));
|
||||
|
||||
if (m_isZip64)
|
||||
{
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, ((Field<5>().get() != 0) && (Field<5>().get() != 0xFFFFFFFF)),
|
||||
"unsupported size of central directory");
|
||||
ThrowErrorIf(Error::ZipEOCDRecord, ((Field<6>().get() != 0) && (Field<6>().get() != 0xFFFFFFFF)),
|
||||
"unsupported offset of start of central directory");
|
||||
}
|
||||
|
||||
StreamBase::Read(stream, &Field<7>());
|
||||
Meta::ExactValueValidation<std::uint32_t>(Field<7>(), 0);
|
||||
|
||||
if (Field<8>().Size())
|
||||
{
|
||||
ThrowHrIfFailed(stream->Read(reinterpret_cast<void*>(Field<8>().get().data()), static_cast<ULONG>(Field<8>().Size()), nullptr));
|
||||
}
|
||||
}
|
||||
|
||||
// Use for editing a package
|
||||
ZipObject::ZipObject(const ComPtr<IStorageObject>& storageObject)
|
||||
{
|
||||
auto other = reinterpret_cast<ZipObject*>(storageObject.Get());
|
||||
m_endCentralDirectoryRecord = other->m_endCentralDirectoryRecord;
|
||||
m_zip64Locator = other->m_zip64Locator;
|
||||
m_zip64EndOfCentralDirectory = other->m_zip64EndOfCentralDirectory;
|
||||
m_centralDirectories = std::move(other->m_centralDirectories);
|
||||
m_stream = std::move(m_stream);
|
||||
}
|
||||
|
||||
} // namespace MSIX
|
|
@ -1,25 +1,24 @@
|
|||
//
|
||||
// Copyright (C) 2017 Microsoft. All rights reserved.
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#include "Exceptions.hpp"
|
||||
#include "StreamBase.hpp"
|
||||
#include "FileStream.hpp"
|
||||
#include "RangeStream.hpp"
|
||||
#include "ZipObject.hpp"
|
||||
#include "DirectoryObject.hpp"
|
||||
#include "UnicodeConversion.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "AppxPackageObject.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
#include "Log.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <memory>
|
||||
#include <cstdlib>
|
||||
#include <functional>
|
||||
|
||||
#include "Exceptions.hpp"
|
||||
#include "FileStream.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
#include "Log.hpp"
|
||||
#include "DirectoryObject.hpp"
|
||||
#include "AppxPackageObject.hpp"
|
||||
#include "MsixFeatureSelector.hpp"
|
||||
#include "AppxPackageWriter.hpp"
|
||||
#include "ScopeExit.hpp"
|
||||
|
||||
#ifndef WIN32
|
||||
// on non-win32 platforms, compile with -fvisibility=hidden
|
||||
#undef MSIX_API
|
||||
|
@ -42,160 +41,6 @@ static void finalizer(void) {
|
|||
LPVOID STDMETHODCALLTYPE InternalAllocate(SIZE_T cb) { return std::malloc(cb); }
|
||||
void STDMETHODCALLTYPE InternalFree(LPVOID pv) { std::free(pv); }
|
||||
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackage(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
char* utf8SourcePackage,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(utf8SourcePackage != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IAppxFactory> factory;
|
||||
// We don't need to use the caller's heap here because we're not marshalling any strings
|
||||
// out to the caller. So default to new / delete[] and be done with it!
|
||||
ThrowHrIfFailed(CoCreateAppxFactoryWithHeap(InternalAllocate, InternalFree, validationOption, &factory));
|
||||
|
||||
MSIX::ComPtr<IStream> stream;
|
||||
ThrowHrIfFailed(CreateStreamOnFile(utf8SourcePackage, true, &stream));
|
||||
|
||||
MSIX::ComPtr<IAppxPackageReader> reader;
|
||||
ThrowHrIfFailed(factory->CreatePackageReader(stream.Get(), &reader));
|
||||
|
||||
ThrowHrIfFailed(UnpackPackageFromPackageReader(packUnpackOptions, reader.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackageFromPackageReader(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
IAppxPackageReader* packageReader,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(packageReader != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
auto to = MSIX::ComPtr<IStorageObject>::Make<MSIX::DirectoryObject>(utf8Destination);
|
||||
|
||||
MSIX::ComPtr<IPackage> package;
|
||||
ThrowHrIfFailed(packageReader->QueryInterface(UuidOfImpl<IPackage>::iid, reinterpret_cast<void**>(&package)));
|
||||
|
||||
package->Unpack(packUnpackOptions, to.Get());
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackageFromStream(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
IStream* stream,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(stream != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IAppxFactory> factory;
|
||||
// We don't need to use the caller's heap here because we're not marshalling any strings
|
||||
// out to the caller. So default to new / delete[] and be done with it!
|
||||
ThrowHrIfFailed(CoCreateAppxFactoryWithHeap(InternalAllocate, InternalFree, validationOption, &factory));
|
||||
|
||||
MSIX::ComPtr<IAppxPackageReader> reader;
|
||||
ThrowHrIfFailed(factory->CreatePackageReader(stream, &reader));
|
||||
|
||||
ThrowHrIfFailed(UnpackPackageFromPackageReader(packUnpackOptions, reader.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundle(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
MSIX_APPLICABILITY_OPTIONS applicabilityOptions,
|
||||
char* utf8SourcePackage,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(utf8SourcePackage != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IAppxBundleFactory> factory;
|
||||
// We don't need to use the caller's heap here because we're not marshalling any strings
|
||||
// out to the caller. So default to new / delete[] and be done with it!
|
||||
ThrowHrIfFailed(CoCreateAppxBundleFactoryWithHeap(InternalAllocate, InternalFree, validationOption, applicabilityOptions, &factory));
|
||||
|
||||
MSIX::ComPtr<IStream> stream;
|
||||
ThrowHrIfFailed(CreateStreamOnFile(utf8SourcePackage, true, &stream));
|
||||
|
||||
MSIX::ComPtr<IAppxBundleReader> reader;
|
||||
ThrowHrIfFailed(factory->CreateBundleReader(stream.Get(), &reader));
|
||||
|
||||
ThrowHrIfFailed(UnpackBundleFromBundleReader(packUnpackOptions, reader.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundleFromBundleReader(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
IAppxBundleReader* bundleReader,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(bundleReader != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IPackage> package;
|
||||
ThrowHrIfFailed(bundleReader->QueryInterface(UuidOfImpl<IPackage>::iid, reinterpret_cast<void**>(&package)));
|
||||
|
||||
auto to = MSIX::ComPtr<IStorageObject>::Make<MSIX::DirectoryObject>(utf8Destination);
|
||||
package->Unpack(packUnpackOptions, to.Get());
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundleFromStream(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
MSIX_APPLICABILITY_OPTIONS applicabilityOptions,
|
||||
IStream* stream,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(stream != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IAppxBundleFactory> factory;
|
||||
// We don't need to use the caller's heap here because we're not marshalling any strings
|
||||
// out to the caller. So default to new / delete[] and be done with it!
|
||||
ThrowHrIfFailed(CoCreateAppxBundleFactoryWithHeap(InternalAllocate, InternalFree, validationOption, applicabilityOptions, &factory));
|
||||
|
||||
MSIX::ComPtr<IAppxBundleReader> reader;
|
||||
ThrowHrIfFailed(factory->CreateBundleReader(stream, &reader));
|
||||
|
||||
ThrowHrIfFailed(UnpackBundleFromBundleReader(packUnpackOptions, reader.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE GetLogTextUTF8(COTASKMEMALLOC* memalloc, char** logText) noexcept try
|
||||
{
|
||||
ThrowErrorIf(MSIX::Error::InvalidParameter, (logText == nullptr || *logText != nullptr), "bad pointer" );
|
||||
|
@ -264,12 +109,9 @@ MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxBundleFactoryWithHeap(
|
|||
MSIX_APPLICABILITY_OPTIONS applicabilityOptions,
|
||||
IAppxBundleFactory** appxBundleFactory) noexcept try
|
||||
{
|
||||
#ifdef BUNDLE_SUPPORT
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
*appxBundleFactory = MSIX::ComPtr<IAppxBundleFactory>::Make<MSIX::AppxFactory>(validationOption, applicabilityOptions, memalloc, memfree).Detach();
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
#else
|
||||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
} CATCH_RETURN();
|
||||
|
||||
// Call specific for Windows. Default to call CoTaskMemAlloc and CoTaskMemFree
|
||||
|
@ -284,3 +126,166 @@ MSIX_API HRESULT STDMETHODCALLTYPE CoCreateAppxBundleFactory(
|
|||
return static_cast<HRESULT>(MSIX::Error::NotSupported);
|
||||
#endif
|
||||
}
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackage(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
char* utf8SourcePackage,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(utf8SourcePackage != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IStream> stream;
|
||||
ThrowHrIfFailed(CreateStreamOnFile(utf8SourcePackage, true, &stream));
|
||||
ThrowHrIfFailed(UnpackPackageFromStream(packUnpackOptions, validationOption, stream.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackageFromPackageReader(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
IAppxPackageReader* packageReader,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(packageReader != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
auto to = MSIX::ComPtr<IDirectoryObject>::Make<MSIX::DirectoryObject>(utf8Destination, true);
|
||||
|
||||
MSIX::ComPtr<IPackage> package;
|
||||
ThrowHrIfFailed(packageReader->QueryInterface(UuidOfImpl<IPackage>::iid, reinterpret_cast<void**>(&package)));
|
||||
|
||||
package->Unpack(packUnpackOptions, to.Get());
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackPackageFromStream(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
IStream* stream,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(stream != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IAppxFactory> factory;
|
||||
// We don't need to use the caller's heap here because we're not marshalling any strings
|
||||
// out to the caller. So default to new / delete[] and be done with it!
|
||||
ThrowHrIfFailed(CoCreateAppxFactoryWithHeap(InternalAllocate, InternalFree, validationOption, &factory));
|
||||
|
||||
MSIX::ComPtr<IAppxPackageReader> reader;
|
||||
ThrowHrIfFailed(factory->CreatePackageReader(stream, &reader));
|
||||
|
||||
ThrowHrIfFailed(UnpackPackageFromPackageReader(packUnpackOptions, reader.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundle(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
MSIX_APPLICABILITY_OPTIONS applicabilityOptions,
|
||||
char* utf8SourcePackage,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(utf8SourcePackage != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IStream> stream;
|
||||
ThrowHrIfFailed(CreateStreamOnFile(utf8SourcePackage, true, &stream));
|
||||
ThrowHrIfFailed(UnpackBundleFromStream(packUnpackOptions, validationOption, applicabilityOptions, stream.Get(), utf8Destination));
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundleFromBundleReader(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
IAppxBundleReader* bundleReader,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(bundleReader != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IPackage> package;
|
||||
ThrowHrIfFailed(bundleReader->QueryInterface(UuidOfImpl<IPackage>::iid, reinterpret_cast<void**>(&package)));
|
||||
|
||||
auto to = MSIX::ComPtr<IDirectoryObject>::Make<MSIX::DirectoryObject>(utf8Destination, true);
|
||||
package->Unpack(packUnpackOptions, to.Get());
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE UnpackBundleFromStream(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
MSIX_APPLICABILITY_OPTIONS applicabilityOptions,
|
||||
IStream* stream,
|
||||
char* utf8Destination) noexcept try
|
||||
{
|
||||
THROW_IF_BUNDLE_NOT_ENABLED
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(stream != nullptr && utf8Destination != nullptr),
|
||||
"Invalid parameters"
|
||||
);
|
||||
|
||||
MSIX::ComPtr<IAppxBundleFactory> factory;
|
||||
// We don't need to use the caller's heap here because we're not marshalling any strings
|
||||
// out to the caller. So default to new / delete[] and be done with it!
|
||||
ThrowHrIfFailed(CoCreateAppxBundleFactoryWithHeap(InternalAllocate, InternalFree, validationOption, applicabilityOptions, &factory));
|
||||
|
||||
MSIX::ComPtr<IAppxBundleReader> reader;
|
||||
ThrowHrIfFailed(factory->CreateBundleReader(stream, &reader));
|
||||
|
||||
ThrowHrIfFailed(UnpackBundleFromBundleReader(packUnpackOptions, reader.Get(), utf8Destination));
|
||||
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
#ifdef MSIX_PACK
|
||||
|
||||
MSIX_API HRESULT STDMETHODCALLTYPE PackPackage(
|
||||
MSIX_PACKUNPACK_OPTION packUnpackOptions,
|
||||
MSIX_VALIDATION_OPTION validationOption,
|
||||
char* directoryPath,
|
||||
char* outputPackage
|
||||
) noexcept try
|
||||
{
|
||||
ThrowErrorIfNot(MSIX::Error::InvalidParameter,
|
||||
(directoryPath != nullptr && outputPackage != nullptr),
|
||||
"Invalid parameters");
|
||||
|
||||
auto from = MSIX::ComPtr<IDirectoryObject>::Make<MSIX::DirectoryObject>(directoryPath);
|
||||
// PackPackage assumes AppxManifest.xml to be in the directory provided.
|
||||
auto manifest = from.As<IStorageObject>()->GetFile(MSIX::footprintFiles[APPX_FOOTPRINT_FILE_TYPE_MANIFEST]);
|
||||
|
||||
auto deleteFile = MSIX::scope_exit([&outputPackage]
|
||||
{
|
||||
remove(outputPackage);
|
||||
});
|
||||
|
||||
MSIX::ComPtr<IStream> stream;
|
||||
ThrowHrIfFailed(CreateStreamOnFile(outputPackage, false, &stream));
|
||||
|
||||
MSIX::ComPtr<IAppxFactory> factory;
|
||||
ThrowHrIfFailed(CoCreateAppxFactoryWithHeap(InternalAllocate, InternalFree, validationOption, &factory));
|
||||
|
||||
MSIX::ComPtr<IAppxPackageWriter> writer;
|
||||
ThrowHrIfFailed(factory->CreatePackageWriter(stream.Get(), nullptr, &writer));
|
||||
writer.As<IPackageWriter>()->PackPayloadFiles(from);
|
||||
ThrowHrIfFailed(writer->Close(manifest.Get()));
|
||||
deleteFile.release();
|
||||
return static_cast<HRESULT>(MSIX::Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
#endif // MSIX_PACK
|
||||
|
|
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#include "XmlWriter.hpp"
|
||||
#include "AppxBlockMapWriter.hpp"
|
||||
#include "Crypto.hpp"
|
||||
#include "StringHelper.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
/*
|
||||
<BlockMap HashMethod="http://www.w3.org/2001/04/xmlenc#sha256" xmlns="http://schemas.microsoft.com/appx/2010/blockmap">
|
||||
<File Size="18944" Name="App1.exe" LfhSize="38">
|
||||
<Block Size="2948" Hash="ORIk+3QF9mSpuOq51oT3Xqn0Gy0vcGbnBRn5lBg5irM="/>
|
||||
</File>
|
||||
<File Size="1430" Name="Assets\LockScreenLogo.scale-200.png" LfhSize="65">
|
||||
<Block Hash="pBoFOz/DsMEJcgzNQ3oZclrpFj6nWZAiKhK1lrnHynY="/>
|
||||
</File>
|
||||
...
|
||||
*/
|
||||
static const char* blockMapElement = "BlockMap";
|
||||
static const char* hashMethodAttribute = "HashMethod";
|
||||
static const char* hashMethodAttributeValue = "http://www.w3.org/2001/04/xmlenc#sha256";
|
||||
static const char* blockMapNamespace = "http://schemas.microsoft.com/appx/2010/blockmap";
|
||||
static const char* fileElement = "File";
|
||||
static const char* sizeAttribute = "Size";
|
||||
static const char* nameAttribute = "Name";
|
||||
static const char* lfhSizeAttribute = "LfhSize";
|
||||
static const char* blockElement = "Block";
|
||||
static const char* hashAttribute = "Hash";
|
||||
|
||||
// <BlockMap HashMethod="http://www.w3.org/2001/04/xmlenc#sha256" xmlns="http://schemas.microsoft.com/appx/2010/blockmap">
|
||||
BlockMapWriter::BlockMapWriter() : m_xmlWriter(XmlWriter(blockMapElement))
|
||||
{
|
||||
// For now, we always use SHA256.
|
||||
m_xmlWriter.AddAttribute(xmlnsAttribute, blockMapNamespace);
|
||||
m_xmlWriter.AddAttribute(hashMethodAttribute, hashMethodAttributeValue);
|
||||
}
|
||||
|
||||
// <File Size="18944" Name="App1.exe" LfhSize="38">
|
||||
void BlockMapWriter::AddFile(const std::string& name, std::uint64_t uncompressedSize, std::uint32_t lfh)
|
||||
{
|
||||
// For the blockmap we always use the windows separator.
|
||||
std::string winName = Helper::toBackSlash(name);
|
||||
m_xmlWriter.StartElement(fileElement);
|
||||
m_xmlWriter.AddAttribute(nameAttribute, winName);
|
||||
m_xmlWriter.AddAttribute(sizeAttribute, std::to_string(uncompressedSize));
|
||||
m_xmlWriter.AddAttribute(lfhSizeAttribute, std::to_string(lfh));
|
||||
}
|
||||
|
||||
// <Block Size="2948" Hash="ORIk+3QF9mSpuOq51oT3Xqn0Gy0vcGbnBRn5lBg5irM="/>
|
||||
void BlockMapWriter::AddBlock(const std::vector<std::uint8_t>& block, ULONG size, bool isCompressed)
|
||||
{
|
||||
// hash block
|
||||
std::vector<std::uint8_t> hash;
|
||||
ThrowErrorIfNot(MSIX::Error::BlockMapInvalidData,
|
||||
MSIX::SHA256::ComputeHash(const_cast<std::uint8_t*>(block.data()), static_cast<uint32_t>(block.size()), hash),
|
||||
"Failed computing hash");
|
||||
|
||||
m_xmlWriter.StartElement(blockElement);
|
||||
m_xmlWriter.AddAttribute(hashAttribute, Base64::ComputeBase64(hash));
|
||||
// We only add the size attribute for compressed files, we cannot just check for the
|
||||
// size of the block because the last block is going to be smaller than the default.
|
||||
if(isCompressed)
|
||||
{
|
||||
m_xmlWriter.AddAttribute(sizeAttribute, std::to_string(size));
|
||||
}
|
||||
m_xmlWriter.CloseElement();
|
||||
}
|
||||
|
||||
void BlockMapWriter::CloseFile()
|
||||
{
|
||||
m_xmlWriter.CloseElement();
|
||||
}
|
||||
|
||||
void BlockMapWriter::Close()
|
||||
{
|
||||
m_xmlWriter.CloseElement();
|
||||
ThrowErrorIf(Error::Unexpected, m_xmlWriter.GetState() != XmlWriter::Finish, "The blockmap didn't close correctly");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,257 @@
|
|||
//
|
||||
// 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 <string>
|
||||
#include <memory>
|
||||
#include <future>
|
||||
#include <algorithm>
|
||||
#include <functional>
|
||||
|
||||
#include <zlib.h>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
AppxPackageWriter::AppxPackageWriter(IMsixFactory* factory, const ComPtr<IZipWriter>& zip) : m_factory(factory), m_zipWriter(zip)
|
||||
{
|
||||
m_state = WriterState::Open;
|
||||
}
|
||||
|
||||
// IPackageWriter
|
||||
void AppxPackageWriter::PackPayloadFiles(const ComPtr<IDirectoryObject>& 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) || 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<IStorageObject>()->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<IStream> manifestStream(manifest);
|
||||
|
||||
// Process AppxManifest.xml
|
||||
// If the creating the AppxManifestObject succeeds, then the stream is valid.
|
||||
auto manifestObj = ComPtr<IAppxManifestReader>::Make<AppxManifestObject>(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<HRESULT>(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<IStream> stream(inputStream);
|
||||
ValidateAndAddPayloadFile(fileName, stream.Get(), compressionOption, contentType);
|
||||
failState.release();
|
||||
return static_cast<HRESULT>(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<IStream> 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<HRESULT>(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<IStream> stream(payloadFiles[i].inputStream);
|
||||
ValidateAndAddPayloadFile(payloadFiles[i].fileName, stream.Get(), payloadFiles[i].compressionOption, payloadFiles[i].contentType);
|
||||
}
|
||||
failState.release();
|
||||
return static_cast<HRESULT>(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), "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)
|
||||
{
|
||||
// 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<std::uint64_t>(end.u.LowPart);
|
||||
|
||||
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 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<std::uint32_t>(bytesToRead);
|
||||
bytesToRead -= blockSize;
|
||||
|
||||
// read block from stream
|
||||
std::vector<std::uint8_t> block;
|
||||
block.resize(blockSize);
|
||||
ULONG bytesRead;
|
||||
ThrowHrIfFailed(stream->Read(static_cast<void*>(block.data()), static_cast<ULONG>(blockSize), &bytesRead));
|
||||
ThrowErrorIfNot(Error::FileRead, (static_cast<ULONG>(blockSize) == bytesRead), "Read stream file failed");
|
||||
crc = crc32(crc, block.data(), static_cast<uInt>(block.size()));
|
||||
|
||||
// Write block and compress if needed
|
||||
ULONG bytesWritten = 0;
|
||||
ThrowHrIfFailed(zipFileStream->Write(block.data(), static_cast<ULONG>(block.size()), &bytesWritten));
|
||||
|
||||
// Add block to blockmap
|
||||
if (addToBlockMap)
|
||||
{
|
||||
m_blockMapWriter.AddBlock(block, bytesWritten, toCompress);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (toCompress)
|
||||
{
|
||||
// Put the stream termination on
|
||||
std::vector<std::uint8_t> buffer;
|
||||
ULONG bytesWritten = 0;
|
||||
ThrowHrIfFailed(zipFileStream->Write(buffer.data(), static_cast<ULONG>(buffer.size()), &bytesWritten));
|
||||
}
|
||||
|
||||
// Close File element
|
||||
if (addToBlockMap)
|
||||
{
|
||||
m_blockMapWriter.CloseFile();
|
||||
}
|
||||
|
||||
// This could be the compressed or uncompressed size
|
||||
auto streamSize = zipFileStream.As<IStreamInternal>()->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.");
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
#include "AppxPackaging.hpp"
|
||||
#include "ContentType.hpp"
|
||||
#include "Exceptions.hpp"
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
// Well-known file types to automatically select a MIME content type and compression option to use based on the file extension
|
||||
// If the extension is not in this map the default is application/octet-stream and APPX_COMPRESSION_OPTION_NORMAL
|
||||
const ContentType& ContentType::GetContentTypeByExtension(std::string& ext)
|
||||
{
|
||||
static const std::map<std::string, ContentType> extToContentType =
|
||||
{
|
||||
{ "atom", ContentType("application/atom+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "appx", ContentType("application/vnd.ms-appx", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "b64", ContentType("application/base64", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "cab", ContentType("application/vnd.ms-cab-compressed", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "doc", ContentType("application/msword", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "dot", ContentType("application/msword", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "docm", ContentType("application/vnd.ms-word.document.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "docx", ContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "dotm", ContentType("application/vnd.ms-word.document.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "dotx", ContentType("application/vnd.openxmlformats-officedocument.wordprocessingml.document", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "dll", ContentType("application/x-msdownload", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "dtd", ContentType("application/xml-dtd", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "exe", ContentType("application/x-msdownload", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "gz", ContentType("application/x-gzip-compressed", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "java", ContentType("application/java", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "json", ContentType("application/json", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "p7s", ContentType("application/x-pkcs7-signature", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "pdf", ContentType("application/pdf", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "ps", ContentType("application/postscript", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "potm", ContentType("application/vnd.ms-powerpoint.template.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "potx", ContentType("application/vnd.openxmlformats-officedocument.presentationml.template", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "ppam", ContentType("application/vnd.ms-powerpoint.addin.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "ppsm", ContentType("application/vnd.ms-powerpoint.slideshow.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "ppsx", ContentType("application/vnd.openxmlformats-officedocument.presentationml.slideshow", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "ppt", ContentType("application/vnd.ms-powerpoint", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "pot", ContentType("application/vnd.ms-powerpoint", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "pps", ContentType("application/vnd.ms-powerpoint", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "ppa", ContentType("application/vnd.ms-powerpoint", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "pptm", ContentType("application/vnd.ms-powerpoint.presentation.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "pptx", ContentType("application/vnd.openxmlformats-officedocument.presentationml.presentation", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "rar", ContentType("application/x-rar-compressed", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "rss", ContentType("application/rss+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "soap", ContentType("application/soap+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "tar", ContentType("application/x-tar", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xaml", ContentType("application/xaml+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xap", ContentType("application/x-silverlight-app", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xbap", ContentType("application/x-ms-xbap", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xhtml", ContentType("application/xhtml+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xlam", ContentType("application/vnd.ms-excel.addin.macroenabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xls", ContentType("application/vnd.ms-excel", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xlt", ContentType("application/vnd.ms-excel", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xla", ContentType("application/vnd.ms-excel", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xlsb", ContentType("application/vnd.ms-excel.sheet.binary.macroEnabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xlsm", ContentType("application/vnd.ms-excel.sheet.macroEnabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xlsx", ContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xltm", ContentType("application/vnd.ms-excel.template.macroEnabled.12", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xltx", ContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.template", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "xsl", ContentType("application/xslt+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xslt", ContentType("application/xslt+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "zip", ContentType("application/x-zip-compressed", APPX_COMPRESSION_OPTION_NONE) },
|
||||
// Text types
|
||||
{ "c", ContentType("text/plain", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "cpp", ContentType("text/plain", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "cs", ContentType("text/plain", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "css", ContentType("text/css", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "csv", ContentType("text/csv", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "h", ContentType("text/plain", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "htm", ContentType("text/html", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "html", ContentType("text/html", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "js", ContentType("application/x-javascript", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "rtf", ContentType("text/richtext", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "sct", ContentType("text/scriptlet", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "txt", ContentType("text/plain", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xml", ContentType("text/xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "xsd", ContentType("text/xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
// Audio types
|
||||
{ "aiff", ContentType("audio/x-aiff", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "au", ContentType("audio/basic", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "m4a", ContentType("audio/mp4", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "mid", ContentType("audio/mid", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "mp3", ContentType("audio/mpeg", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "smf", ContentType("audio/mid", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "wav", ContentType("audio/wav", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "wma", ContentType("audio/x-ms-wma", APPX_COMPRESSION_OPTION_NONE) },
|
||||
// Image types
|
||||
{ "bmp", ContentType("image/bmp", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "emf", ContentType("image/x-emf", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "gif", ContentType("image/gif", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "ico", ContentType("image/vnd.microsoft.icon", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "jpg", ContentType("image/jpeg", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "jpeg", ContentType("image/jpeg", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "png", ContentType("image/png", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "svg", ContentType("image/svg+xml", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "tif", ContentType("image/tiff", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "tiff", ContentType("image/tiff", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
{ "wmf", ContentType("image/x-wmf", APPX_COMPRESSION_OPTION_NORMAL) },
|
||||
// Video types
|
||||
{ "avi", ContentType("video/avi", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "mpeg", ContentType("video/mpeg", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "mpg", ContentType("video/mpeg", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "mov", ContentType("video/quicktime", APPX_COMPRESSION_OPTION_NONE) },
|
||||
{ "wmv", ContentType("video/x-ms-wmv", APPX_COMPRESSION_OPTION_NONE) }
|
||||
};
|
||||
// if the extension is not in the map these are the defaults
|
||||
static const ContentType defaultContentType = ContentType("application/octet-stream", APPX_COMPRESSION_OPTION_NORMAL);
|
||||
|
||||
auto findExt = extToContentType.find(ext);
|
||||
if (findExt == extToContentType.end())
|
||||
{
|
||||
return defaultContentType;
|
||||
}
|
||||
return findExt->second;
|
||||
}
|
||||
|
||||
const std::string ContentType::GetPayloadFileContentType(APPX_FOOTPRINT_FILE_TYPE footprintFile)
|
||||
{
|
||||
if (footprintFile == APPX_FOOTPRINT_FILE_TYPE_MANIFEST)
|
||||
{
|
||||
return "application/vnd.ms-appx.manifest+xml";
|
||||
}
|
||||
if (footprintFile == APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP)
|
||||
{
|
||||
return "application/vnd.ms-appx.blockmap+xml";
|
||||
}
|
||||
if (footprintFile == APPX_FOOTPRINT_FILE_TYPE_SIGNATURE)
|
||||
{
|
||||
return "application/vnd.ms-appx.signature";
|
||||
}
|
||||
// TODO: add other ones if needed, otherwise throw
|
||||
ThrowErrorAndLog(Error::NotSupported, "Payload file content type not found");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,105 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#include "XmlWriter.hpp"
|
||||
#include "ContentTypeWriter.hpp"
|
||||
#include "Encoding.hpp"
|
||||
|
||||
#include <map>
|
||||
#include <algorithm>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
/*
|
||||
<Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
<Default ContentType="image/png" Extension="png"/>
|
||||
<Default ContentType="application/x-msdownload" Extension="dll"/>
|
||||
<Default ContentType="application/vnd.ms-appx.manifest+xml" Extension="xml"/>
|
||||
<Override ContentType="application/vnd.ms-appx.blockmap+xml" PartName="/AppxBlockMap.xml"/>
|
||||
<Override ContentType="application/vnd.ms-appx.signature" PartName="/AppxSignature.p7x"/>
|
||||
<Override ContentType="application/vnd.ms-pkiseccat" PartName="/AppxMetadata/CodeIntegrity.cat"/>
|
||||
</Types>
|
||||
*/
|
||||
|
||||
static const char* typesElement = "Types";
|
||||
static const char* typesNamespace = "http://schemas.openxmlformats.org/package/2006/content-types";
|
||||
static const char* defaultElement = "Default";
|
||||
static const char* contentTypeAttribute = "ContentType";
|
||||
static const char* extensionAttribute = "Extension";
|
||||
static const char* overrideElement = "Override";
|
||||
static const char* partNameAttribute = "PartName";
|
||||
|
||||
// <Types xmlns="http://schemas.openxmlformats.org/package/2006/content-types">
|
||||
ContentTypeWriter::ContentTypeWriter() : m_xmlWriter(XmlWriter(typesElement, true))
|
||||
{
|
||||
m_xmlWriter.AddAttribute(xmlnsAttribute, typesNamespace);
|
||||
}
|
||||
|
||||
// File extension to MIME value map that are added as default elements
|
||||
// If the extension is already in the map and its content type is different or
|
||||
// if the file doesn't have an extensions AddOverride is called.
|
||||
void ContentTypeWriter::AddContentType(const std::string& name, const std::string& contentType, bool forceOverride)
|
||||
{
|
||||
auto percentageEncodedName = Encoding::EncodeFileName(name);
|
||||
|
||||
if (forceOverride)
|
||||
{
|
||||
AddOverride(percentageEncodedName, contentType);
|
||||
return;
|
||||
}
|
||||
|
||||
auto findLastPeriod = percentageEncodedName.find_last_of(".");
|
||||
if (findLastPeriod != std::string::npos)
|
||||
{
|
||||
// See if already exist
|
||||
std::string ext = percentageEncodedName.substr(percentageEncodedName.find_last_of(".") + 1);
|
||||
std::string normalizedExt = ext;
|
||||
std::transform(normalizedExt.begin(), normalizedExt.end(), normalizedExt.begin(), ::tolower);
|
||||
auto find = m_defaultExtensions.find(normalizedExt);
|
||||
if (find != m_defaultExtensions.end())
|
||||
{
|
||||
if (find->second != contentType)
|
||||
{
|
||||
// The extension is in the table but with a different content type
|
||||
AddOverride(percentageEncodedName, contentType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
auto result = m_defaultExtensions.emplace(normalizedExt, contentType);
|
||||
AddDefault(ext, contentType);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AddOverride(percentageEncodedName, contentType);
|
||||
}
|
||||
}
|
||||
|
||||
void ContentTypeWriter::Close()
|
||||
{
|
||||
m_xmlWriter.CloseElement();
|
||||
ThrowErrorIf(Error::Unexpected, m_xmlWriter.GetState() != XmlWriter::Finish, "Content Type xml didn't close correctly");
|
||||
}
|
||||
|
||||
// <Default ContentType="application/vnd.ms-appx.manifest+xml" Extension="xml"/>
|
||||
void ContentTypeWriter::AddDefault(const std::string& ext, const std::string& contentType)
|
||||
{
|
||||
m_xmlWriter.StartElement(defaultElement);
|
||||
m_xmlWriter.AddAttribute(contentTypeAttribute, contentType);
|
||||
m_xmlWriter.AddAttribute(extensionAttribute, ext);
|
||||
m_xmlWriter.CloseElement();
|
||||
}
|
||||
|
||||
// <Override ContentType="application/vnd.ms-appx.signature" PartName="/AppxSignature.p7x"/>
|
||||
void ContentTypeWriter::AddOverride(const std::string& file, const std::string& contentType)
|
||||
{
|
||||
std::string partName = "/" + file;
|
||||
m_xmlWriter.StartElement(overrideElement);
|
||||
m_xmlWriter.AddAttribute(contentTypeAttribute, contentType);
|
||||
m_xmlWriter.AddAttribute(partNameAttribute, partName);
|
||||
m_xmlWriter.CloseElement();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
//
|
||||
// Copyright (C) 2019 Microsoft. All rights reserved.
|
||||
// See LICENSE file in the project root for full license information.
|
||||
//
|
||||
|
||||
#include "DeflateStream.hpp"
|
||||
#include "Exceptions.hpp"
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace MSIX {
|
||||
|
||||
DeflateStream::DeflateStream(const ComPtr<IStream>& stream) : m_stream(stream)
|
||||
{
|
||||
m_zstrm.zalloc = Z_NULL;
|
||||
m_zstrm.zfree = Z_NULL;
|
||||
m_zstrm.opaque = Z_NULL;
|
||||
auto result = deflateInit2(&m_zstrm, Z_BEST_COMPRESSION, Z_DEFLATED, -MAX_WBITS, MAX_MEM_LEVEL, Z_DEFAULT_STRATEGY);
|
||||
ThrowErrorIf(Error::DeflateInitialize, result != Z_OK, "Error calling deflateinit2");
|
||||
}
|
||||
|
||||
DeflateStream::~DeflateStream()
|
||||
{
|
||||
deflateEnd(&m_zstrm);
|
||||
}
|
||||
|
||||
// IStream
|
||||
HRESULT STDMETHODCALLTYPE DeflateStream::Seek(LARGE_INTEGER move, DWORD origin, ULARGE_INTEGER *newPosition) noexcept try
|
||||
{
|
||||
// just forward to the VectorStream
|
||||
ThrowErrorIf(Error::DeflateRead, m_state != State::Closed, "DeflateStream needs to be closed before being consume");
|
||||
ThrowHrIfFailed(m_stream->Seek(move, origin, newPosition));
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
HRESULT STDMETHODCALLTYPE DeflateStream::Read(void* buffer, ULONG countBytes, ULONG* bytesRead) noexcept try
|
||||
{
|
||||
// just forward to the VectorStream
|
||||
ThrowErrorIf(Error::DeflateRead, m_state != State::Closed, "DeflateStream needs to be closed before being consume");
|
||||
ThrowHrIfFailed(m_stream->Read(buffer, countBytes, bytesRead));
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
// Caller should NOT assume that bytesWritten returned is going to be equal to countBytes
|
||||
HRESULT STDMETHODCALLTYPE DeflateStream::Write(void const *buffer, ULONG countBytes, ULONG *bytesWritten) noexcept try
|
||||
{
|
||||
ThrowErrorIf(Error::DeflateWrite, m_state == State::Closed, "DeflateStream is already closed");
|
||||
// Important! If this stream is asked to write with 0 bytes, then it means that we are done.
|
||||
// We need to terminate the stream and call deflate with Z_FINISH.
|
||||
int disposition = Z_FULL_FLUSH;
|
||||
if (countBytes == 0)
|
||||
{
|
||||
disposition = Z_FINISH;
|
||||
m_state = State::Closed;
|
||||
}
|
||||
m_zstrm.next_in = reinterpret_cast<Bytef *>(const_cast<void*>(buffer));
|
||||
m_zstrm.avail_in = static_cast<std::uint32_t>(countBytes);
|
||||
auto toWrite = Deflate(disposition);
|
||||
ThrowHrIfFailed(m_stream->Write(toWrite.data(), static_cast<ULONG>(toWrite.size()), bytesWritten));
|
||||
return static_cast<HRESULT>(Error::OK);
|
||||
} CATCH_RETURN();
|
||||
|
||||
std::vector<std::uint8_t> DeflateStream::Deflate(int disposition)
|
||||
{
|
||||
std::vector<std::uint8_t> compressedBuffer;
|
||||
std::vector<std::uint8_t> deflateBuffer(1024);
|
||||
do
|
||||
{
|
||||
m_zstrm.next_out = deflateBuffer.data();
|
||||
m_zstrm.avail_out = static_cast<std::uint32_t>(deflateBuffer.size());
|
||||
auto result = deflate(&m_zstrm, disposition);
|
||||
if (disposition == Z_FINISH && result == Z_STREAM_END)
|
||||
{
|
||||
result = Z_OK;
|
||||
}
|
||||
ThrowErrorIf(Error::DeflateWrite, result != Z_OK, "Error deflating stream");
|
||||
auto have = deflateBuffer.size() - m_zstrm.avail_out;
|
||||
compressedBuffer.insert(compressedBuffer.end(), deflateBuffer.data(), deflateBuffer.data() + have);
|
||||
} while (m_zstrm.avail_out == 0);
|
||||
return compressedBuffer;
|
||||
}
|
||||
|
||||
}
|
Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше
Загрузка…
Ссылка в новой задаче