Merge pull request #191 from microsoft/packaging

This commit is contained in:
Ruben Guerrero 2019-08-06 14:16:12 -07:00 коммит произвёл GitHub
Родитель bab1fbaf8a 99dcc10b03
Коммит bfaf56ac40
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
148 изменённых файлов: 7861 добавлений и 3112 удалений

9
.gitattributes поставляемый
Просмотреть файл

@ -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

1
.gitignore поставляемый
Просмотреть файл

@ -399,6 +399,7 @@ lib/zlib
# Files generated by CMake
src/inc/MSIXResource.hpp
src/msix/MSIXResource.cpp
src/msix/common/MSIXResource.cpp
.vscode/

Просмотреть файл

@ -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
Просмотреть файл

@ -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:

117
cmake/msix_options.cmake Normal file
Просмотреть файл

@ -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)

Просмотреть файл

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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

Просмотреть файл

@ -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;
}

216
sample/inc/Helpers.hpp Normal file
Просмотреть файл

@ -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

31
src/inc/ContentType.hpp Normal file
Просмотреть файл

@ -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);
};
}

45
src/inc/DeflateStream.hpp Normal file
Просмотреть файл

@ -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:

71
src/inc/ScopeExit.hpp Normal file
Просмотреть файл

@ -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;
}
}
}

70
src/inc/StringStream.hpp Normal file
Просмотреть файл

@ -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;

59
src/inc/XmlWriter.hpp Normal file
Просмотреть файл

@ -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;
}
}

Некоторые файлы не были показаны из-за слишком большого количества измененных файлов Показать больше