diff --git a/src/inc/Encoding.hpp b/src/inc/Encoding.hpp index a7515066..c647567e 100644 --- a/src/inc/Encoding.hpp +++ b/src/inc/Encoding.hpp @@ -4,192 +4,14 @@ // #pragma once -#include -#include -#include +#include -#include "Exceptions.hpp" +namespace MSIX { namespace Encoding { -namespace MSIX { + std::string DecodeFileName(const std::string& fileName); + std::string EncodeFileName(const std::string& fileName); - static const std::size_t PercentangeEncodingTableSize = 0x7E; - static const std::array PercentangeEncoding = - { 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, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - "%20", "%21", nullptr, "%23", "%24", "%25", "%26", "%27", // [space] ! # $ % & ' - "%28", "%29", nullptr, "%2B", "%2C", nullptr, nullptr, nullptr, // ( ) + , - nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, - nullptr, nullptr, nullptr, "%3B", nullptr, "%3D", nullptr, nullptr, // ; = - "%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, "%5B", nullptr, "%5D", 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, - nullptr, nullptr, nullptr, "%7B", nullptr, "%7D", // { } - }; + std::string Base32Encoding(const std::vector& bytes); + std::vector GetBase64DecodedValue(const std::string& value); - // Douglas Crockford's base 32 alphabet variant is 0-9, A-Z except for i, l, o, and u. - static const char base32DigitList[] = "0123456789abcdefghjkmnpqrstvwxyz"; - - struct EncodingChar - { - const char* encode; - char decode; - - bool operator==(const std::string& rhs) const { - return rhs == encode; - } - EncodingChar(const char* e, char d) : encode(e), decode(d) {} - }; - - static const EncodingChar EncodingToChar[] = - { EncodingChar("20", ' '), EncodingChar("21", '!'), EncodingChar("23", '#'), EncodingChar("24", '$'), - EncodingChar("25", '%'), EncodingChar("26", '&'), EncodingChar("27", '\''), EncodingChar("28", '('), - EncodingChar("29", ')'), EncodingChar("25", '+'), EncodingChar("2B", '%'), EncodingChar("2C", ','), - EncodingChar("3B", ';'), EncodingChar("3D", '='), EncodingChar("40", '@'), EncodingChar("5B", '['), - EncodingChar("5D", ']'), EncodingChar("7B", '{'), EncodingChar("7D", '}') - }; - - static std::string EncodeFileName(std::string fileName) - { - std::ostringstream result; - for (std::uint32_t position = 0; position < fileName.length(); ++position) - { std::uint8_t index = static_cast(fileName[position]); - if(fileName[position] < PercentangeEncodingTableSize && index < PercentangeEncoding.size() && PercentangeEncoding[index] != nullptr) - { result << PercentangeEncoding[index]; - } - else if (fileName[position] == '\\') // Remove Windows file separator. - { result << '/'; - } - else - { result << fileName[position]; - } - } - return result.str(); - } - - static std::string DecodeFileName(const std::string& fileName) - { - std::string result; - for (std::uint32_t i = 0; i < fileName.length(); ++i) - { if(fileName[i] == '%') - { const auto& found = std::find(std::begin(EncodingToChar), std::end(EncodingToChar), fileName.substr(i+1, 2)); - ThrowErrorIf(Error::UnknownFileNameEncoding, (found == std::end(EncodingToChar)), fileName.c_str()) - result += found->decode; - i += 2; - } - else - { result += fileName[i]; - } - } - return result; - } - - static std::string Base32Encoding(const std::vector& bytes) - { - static const size_t publisherIdSize = 13; - static const size_t byteCount = 8; - - // Consider groups of five bytes. This is the smallest number of bytes that has a number of bits - // that's evenly divisible by five. - // Every five bits starting with the most significant of the first byte are made into a base32 value. - // Each value is used to index into the alphabet array to produce a base32 digit. - // When out of bytes but the corresponding base32 value doesn't yet have five bits, 0 is used. - // Normally in these cases a particular number of '=' characters are appended to the resulting base32 - // string to indicate how many bits didn't come from the actual byte value. For our purposes no - // such padding characters are necessary. - // - // Bytes: aaaaaaaa bbbbbbbb cccccccc dddddddd eeeeeeee - // Base32 Values: 000aaaaa 000aaabb 000bbbbb 000bcccc 000ccccd 000ddddd 000ddeee 000eeeee - // - // Combo of byte a & F8 a & 07 b & 3E b & 01 c & 0F d & 7C d & 03 e & 1F - // values except b & C0 c & F0 d & 80 e & E0 - // for shifting - - // Make sure the following math doesn't overflow. - char output[publisherIdSize+1] = ""; - size_t outputIndex = 0; - for(size_t byteIndex = 0; byteIndex < byteCount; byteIndex +=5) - { - uint8_t firstByte = bytes[byteIndex]; - uint8_t secondByte = (byteIndex + 1) < byteCount ? bytes[byteIndex + 1] : 0; - output[outputIndex++] = base32DigitList[(firstByte & 0xF8) >> 3]; - output[outputIndex++] = base32DigitList[((firstByte & 0x07) << 2) | ((secondByte & 0xC0) >> 6)]; - - if(byteIndex + 1 < byteCount) - { - uint8_t thirdByte = (byteIndex + 2) < byteCount ? bytes[byteIndex + 2] : 0; - output[outputIndex++] = base32DigitList[(secondByte & 0x3E) >> 1]; - output[outputIndex++] = base32DigitList[((secondByte & 0x01) << 4) | ((thirdByte & 0xF0) >> 4)]; - - if(byteIndex + 2 < byteCount) - { - uint8_t fourthByte = (byteIndex + 3) < byteCount ? bytes[byteIndex + 3] : 0; - output[outputIndex++] = base32DigitList[((thirdByte & 0x0F) << 1) | ((fourthByte & 0x80) >> 7)]; - - if (byteIndex + 3 < byteCount) - { - uint8_t fifthByte = (byteIndex + 4) < byteCount ? bytes[byteIndex + 4] : 0; - output[outputIndex++] = base32DigitList[(fourthByte & 0x7C) >> 2]; - output[outputIndex++] = base32DigitList[((fourthByte & 0x03) << 3) | ((fifthByte & 0xE0) >> 5)]; - - if (byteIndex + 4 < byteCount) - { - output[outputIndex++] = base32DigitList[fifthByte & 0x1F]; - } - } - } - } - } - output[publisherIdSize] = '\0'; - return std::string(output); - } - - static const std::uint8_t base64DecoderRing[128] = - { - /* 0-15 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 16-31 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 32-47 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 62, 0xFF, 0xFF, 0xFF, 63, - /* 48-63 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0xFF, 0xFF, 0xFF, 64, 0xFF, 0xFF, - /* 64-79 */ 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, - /* 80-95 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - /* 96-111 */ 0xFF, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, - /* 112-127 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, - }; - - static std::vector GetBase64DecodedValue(const std::string& value) - { - std::vector result; - - ThrowErrorIfNot(Error::InvalidParameter, (0 == (value.length() % 4)), "invalid base64 encoding"); - for(std::size_t index=0; index < value.length(); index += 4) - { - ThrowErrorIf(Error::InvalidParameter,( - (value[index+0] | value[index+1] | value[index+2] | value[index+3]) >= 128 - ), "invalid base64 encoding"); - - ULONG v1 = base64DecoderRing[value[index+0]]; - ULONG v2 = base64DecoderRing[value[index+1]]; - ULONG v3 = base64DecoderRing[value[index+2]]; - ULONG v4 = base64DecoderRing[value[index+3]]; - - ThrowErrorIf(Error::InvalidParameter,(((v1 | v2) >= 64) || ((v3 | v4) == 0xFF)), "first two chars of a four char base64 sequence can't be ==, and must be valid"); - ThrowErrorIf(Error::InvalidParameter,(v3 == 64 && v4 != 64), "if the third char is = then the fourth char must be ="); - std::size_t byteCount = (v4 != 64 ? 3 : (v3 != 64 ? 2 : 1)); - result.push_back(static_cast(((v1 << 2) | ((v2 >> 4) & 0x03)))); - if (byteCount >1) - { - result.push_back(static_cast(((v2 << 4) | ((v3 >> 2) & 0x0F)) & 0xFF)); - if (byteCount >2) - { - result.push_back(static_cast(((v3 << 6) | ((v4 >> 0) & 0x3F)) & 0xFF)); - } - } - } - return result; - } -} \ No newline at end of file +} /*Encoding */ } /* MSIX */ diff --git a/src/msix/AppxPackageInfo.cpp b/src/msix/AppxPackageInfo.cpp index 07ad331f..f76c9e6b 100644 --- a/src/msix/AppxPackageInfo.cpp +++ b/src/msix/AppxPackageInfo.cpp @@ -170,6 +170,6 @@ namespace MSIX { std::vector hash; ThrowErrorIfNot(Error::Unexpected, SHA256::ComputeHash(buffer.data(), static_cast(buffer.size()), hash), "Failed computing publisherId"); - return Base32Encoding(hash); + return Encoding::Base32Encoding(hash); } } diff --git a/src/msix/AppxPackageObject.cpp b/src/msix/AppxPackageObject.cpp index d199c06b..c206050c 100644 --- a/src/msix/AppxPackageObject.cpp +++ b/src/msix/AppxPackageObject.cpp @@ -235,7 +235,7 @@ namespace MSIX { { auto bundleInfoInternal = package.As(); auto packageName = bundleInfoInternal->GetFileName(); - auto packageStream = m_container->GetFile(packageName); + auto packageStream = m_container->GetFile(Encoding::EncodeFileName(packageName)); if (packageStream) { // The package is in the bundle. Verify is not compressed. @@ -337,14 +337,14 @@ namespace MSIX { { auto footPrintFile = std::find(std::begin(footPrintFileNames), std::end(footPrintFileNames), fileName); if (footPrintFile == std::end(footPrintFileNames)) { - auto containerFileName = EncodeFileName(fileName); - m_payloadFiles.push_back(containerFileName); - auto fileStream = m_container->GetFile(containerFileName); + auto opcFileName = Encoding::EncodeFileName(fileName); + m_payloadFiles.push_back(opcFileName); + auto fileStream = m_container->GetFile(opcFileName); ThrowErrorIfNot(Error::FileNotFound, fileStream, "File described in blockmap not contained in OPC container"); VerifyFile(fileStream, fileName, blockMapInternal); auto blockMapStream = m_appxBlockMap->GetValidationStream(fileName, fileStream); - m_files[containerFileName] = MSIX::ComPtr::Make(m_factory.Get(), fileName, std::move(blockMapStream)); - filesToProcess.erase(std::remove(filesToProcess.begin(), filesToProcess.end(), containerFileName), filesToProcess.end()); + m_files[opcFileName] = MSIX::ComPtr::Make(m_factory.Get(), fileName, std::move(blockMapStream)); + filesToProcess.erase(std::remove(filesToProcess.begin(), filesToProcess.end(), opcFileName), filesToProcess.end()); } } @@ -424,7 +424,7 @@ namespace MSIX { targetName = packageId.As()->GetPackageFullName() + "/" + fileName; } else - { targetName = DecodeFileName(fileName); + { targetName = Encoding::DecodeFileName(fileName); } auto targetFile = to->OpenFile(targetName, MSIX::FileStream::Mode::WRITE_UPDATE); @@ -508,8 +508,7 @@ namespace MSIX { if (m_isBundle) { return static_cast(Error::PackageIsBundle); } ThrowErrorIf(Error::InvalidParameter, (file == nullptr || *file != nullptr), "bad pointer"); ThrowErrorIf(Error::FileNotFound, (static_cast(type) > footprintFiles.size()), "unknown footprint file type"); - std::string footprint (footprintFiles[type]); - auto result = GetAppxFile(footprint); + auto result = GetAppxFile(footprintFiles[type]); ThrowErrorIfNot(Error::FileNotFound, result, "requested footprint file not in package") // Clients expect the stream's pointer to be at the start of the file! ComPtr stream; @@ -523,8 +522,7 @@ namespace MSIX { { if (m_isBundle) { return static_cast(Error::PackageIsBundle); } ThrowErrorIf(Error::InvalidParameter, (fileName == nullptr || file == nullptr || *file != nullptr), "bad pointer"); - std::string name = utf16_to_utf8(fileName); - auto result = GetAppxFile(EncodeFileName(name)); + auto result = GetAppxFile(Encoding::EncodeFileName(utf16_to_utf8(fileName))); ThrowErrorIfNot(Error::FileNotFound, result, "requested file not in package") // Clients expect the stream's pointer to be at the start of the file! ComPtr stream; diff --git a/src/msix/CMakeLists.txt b/src/msix/CMakeLists.txt index 6f67f80a..b2da4796 100644 --- a/src/msix/CMakeLists.txt +++ b/src/msix/CMakeLists.txt @@ -212,6 +212,7 @@ add_library(${PROJECT_NAME} SHARED AppxPackageInfo.cpp AppxPackaging_i.cpp AppxSignature.cpp + Encoding.cpp Exceptions.cpp InflateStream.cpp Log.cpp diff --git a/src/msix/Encoding.cpp b/src/msix/Encoding.cpp new file mode 100644 index 00000000..f70aaf9c --- /dev/null +++ b/src/msix/Encoding.cpp @@ -0,0 +1,451 @@ +// +// Copyright (C) 2017 Microsoft. All rights reserved. +// See LICENSE file in the project root for full license information. +// + +#include +#include +#include +#include + +#include "Encoding.hpp" +#include "Exceptions.hpp" +#include "UnicodeConversion.hpp" + +namespace MSIX { namespace Encoding { + + const std::size_t PercentageEncodingTableSize = 0x7F; + const std::array PercentageEncoding = + { 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, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + L"%20", L"%21", nullptr, L"%23", L"%24", L"%25", L"%26", L"%27", // [space] ! # $ % & ' + L"%28", L"%29", nullptr, L"%2B", L"%2C", nullptr, nullptr, nullptr, // ( ) + , + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, L"%3B", nullptr, L"%3D", nullptr, nullptr, // ; = + 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, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, + nullptr, nullptr, nullptr, L"%7B", nullptr, L"%7D", nullptr, // { } + }; + + struct EncodingChar + { + const wchar_t* encode; + wchar_t decode; + + bool operator==(const std::wstring& rhs) const { + return rhs == encode; + } + EncodingChar(const wchar_t* e, wchar_t d) : encode(e), decode(d) {} + }; + + // Returns the file name percentage encoded. + std::string EncodeFileName(const std::string& fileName) + { + ThrowErrorIf(Error::InvalidParameter, fileName.empty(), "Empty value tries to be encoded"); + std::wstring fileNameW = utf8_to_wstring(fileName); + std::wstring result = L""; + + for (std::uint32_t index = 0; index < fileNameW.length(); index++) + { + std::uint32_t codepoint = static_cast(fileNameW[index]); + + // Start of double wchar UTF-16 sequence + if ((codepoint & 0xFC00) == 0xD800) + { + if ((fileNameW[index] & 0xFC00) == 0xD800 && + (fileNameW[index+1] & 0xFC00) == 0xDC00) + { + codepoint = (((fileNameW[index] & 0x03C0) + 0x0040) | (fileNameW[index] & 0x003F)) << 10; + codepoint |= (fileNameW[index+1] & 0x03FF); + } + else + { + ThrowError(Error::InvalidParameter); + } + index++; + } + else if ((codepoint & 0xFC00) == 0xDC00) + { + ThrowErrorAndLog(Error::InvalidParameter, "The second surrogate pair may not exist alone"); + } + + // See if it's one of the special cases we encode + if (codepoint < PercentageEncodingTableSize && PercentageEncoding[codepoint] != nullptr) + { result += PercentageEncoding[codepoint]; + } + else if (fileNameW[index] == '\\') // replace backslash + { result.push_back('/'); + } + else if (codepoint > PercentageEncodingTableSize) + { // Returns the length of the UTF-8 byte sequence associated with the given codepoint + // We already know is > 0x7F, so it can't be 1 byte + std::uint8_t totalBytes = 0; + if (codepoint <= 0x07FF) { totalBytes = 2; } + else if (codepoint <= 0xFFFF) { totalBytes = 3; } + else { totalBytes = 4; } + + const std::wstring hexadecimal = L"0123456789ABCDEF"; + for (size_t byteIndex = 0; byteIndex < totalBytes; byteIndex++) + { + std::uint32_t mychar; + switch (totalBytes - byteIndex) + { + case 1: + if (totalBytes == 1) { mychar = codepoint; } + else { mychar = 0x80 | (codepoint & 0x003F); } + break; + case 2: + if (totalBytes == 2) { mychar = 0xC0 | ((codepoint & 0x07C0) >> 6); } + else { mychar = 0x80 | ((codepoint & 0x0FC0) >> 6); } + break; + case 3: + if (totalBytes == 3) { mychar = 0xE0 | ((codepoint & 0xF000) >> 12); } + else { mychar = 0x80 | ((codepoint & 0x03F000) >> 12); } + break; + case 4: + mychar = 0xF0 | ((codepoint & 0x1C0000) >> 18); + break; + default: + ThrowError(Error::Unexpected); // This should never happen. + break; + } + + auto highDigit = mychar / hexadecimal.size(); + auto lowDigit = mychar % hexadecimal.size(); + + ThrowErrorIf(Error::InvalidParameter, (highDigit > hexadecimal.size() || lowDigit > hexadecimal.size()), "Invalid"); + result.push_back('%'); // we are percentage encoding + result.push_back(hexadecimal[highDigit]); + result.push_back(hexadecimal[lowDigit]); + } + } + else + { result.push_back(fileNameW[index]); + } + } + return utf16_to_utf8(result); + } + + const EncodingChar EncodingToChar[] = + { EncodingChar(L"20", ' '), EncodingChar(L"21", '!'), EncodingChar(L"23", '#'), EncodingChar(L"24", '$'), + EncodingChar(L"25", '%'), EncodingChar(L"26", '&'), EncodingChar(L"27", '\''), EncodingChar(L"28", '('), + EncodingChar(L"29", ')'), EncodingChar(L"25", '+'), EncodingChar(L"2B", '%'), EncodingChar(L"2C", ','), + EncodingChar(L"3B", ';'), EncodingChar(L"3D", '='), EncodingChar(L"40", '@'), EncodingChar(L"5B", '['), + EncodingChar(L"5D", ']'), EncodingChar(L"7B", '{'), EncodingChar(L"7D", '}') + }; + + // Convert a single hex digit to its corresponding value + inline std::uint32_t ConvertHex(wchar_t ch) + { + if (ch >= '0' && ch <= '9') { return ch - '0'; } + else if (ch >= 'A' && ch <= 'F') { return ch - 'A' + 10; } + else if (ch >= 'a' && ch <= 'f') { return ch - 'a' + 10; } + ThrowErrorAndLog(Error::Unexpected, "Invalid hexadecimal"); + } + + //+---------------------------------------------------------------------------- + // STRING UTF-8 PERCENT-ENCODING UTILITIES + // + // Two write the UTF-8, UTF-16, and UTF-32 conversion code see the following tables from: + // + // + // UTF-8 Bit Distribution + // | Unicode Codepoint | 1st Byte | 2nd Byte | 3rd Byte | 4th Byte | + // |----------------------------|----------|----------|----------|----------| + // | 00000000 0xxxxxxx | 0xxxxxxx | | | | + // | 00000yyy yyxxxxxx | 110yyyyy | 10xxxxxx | | | + // | zzzzyyyy yyxxxxxx | 1110zzzz | 10yyyyyy | 10xxxxxx | | + // | 000uuuuu zzzzyyyy yyxxxxxx | 11110uuu | 10uuzzzz | 10yyyyyy | 10xxxxxx | + // + // Well-Formed UTF-8 Byte Sequences + // | Codepoint Range | 1st Byte | 2nd Byte | 3rd Byte | 4th Byte | + // |----------------------------|----------|----------|----------|----------| + // | U+0000 ..U+007F | 00..7F | | | | + // | U+0080 ..U+07FF | C2..DF | 80..BF | | | + // | U+0800 ..U+0FFF | E0 | A0..BF | 80..BF | | + // | U+1000 ..U+CFFF | E1..EC | 80..BF | 80..BF | | + // | U+D000 ..U+D7FF | ED | 80..9F | 80..BF | | + // | U+E000 ..U+FFFF | EE..EF | 80..BF | 80..BF | | + // | U+10000 ..U+3FFFF | F0 | 90..BF | 80..BF | 80..BF | + // | U+40000 ..U+FFFFF | F1..F3 | 80..BF | 80..BF | 80..BF | + // | U+100000..U+10FFFF | F4 | 80..8F | 80..BF | 80..BF | + // + // UTF-16 Bit Distribution + // | Unicode Codepoint | UTF-16 | + // |----------------------------|-------------------------------------| + // | xxxxxxxx,xxxxxxxx | xxxxxxxx,xxxxxxxx | + // | 000uuuuu xxxxxxxx,xxxxxxxx | 110110ww,wwxxxxxx 110111xx,xxxxxxxx | + // where wwww = uuuuu - 1 + // + //----------------------------------------------------------------------------- + void ValidateCodepoint(std::uint32_t codepoint, std::uint32_t sequenceSize) + { + // The valid range of Unicode code points is [U+0000, U+10FFFF]. DecodeFileName cannot generate a value larger + // than 0x10FFFF: The "4 Byte sequence" section of code is responsible for the most significant change to the + // code point. The bottom 3 bits of the first byte of the four byte sequence are shifted 18 to make up the most + // significant 3 bits of the 21 bit code point. Since the first byte is less than 0xF4 and since its most + // significant bits must be 1111,0xxx, the only possible values are [0xF0, 0xF4]. Assuming the largest value, + // when shifted left 18bits this gives a code point in binary of 1,00yy,yyyy,yyyy,yyyy,yyyy (where y are binary + // digits yet to be determined by the subsequent trail bytes) so we must ensure that the next trail byte in the + // 4 byte sequence cannot place 1's in the most significant two y's. By the above, in the case that the first + // byte is less than 0xF4, the most significant bit is 0 and so the codepoint is at most 0x0FFFFF, so we'll + // only consider the case where the first byte is 0xF4. In that case maxNextSequenceValue is set to 0x8F and + // the min value is 0x80. Since the bottom six bits of the trail bytes are used (see 'decoded & 0x3F') + // and the range only allows the bottom four of those bits to be set, the most significant two y's from above + // will always be zero. Accordingly, the largest value one could produce is 0x10FFFF. + ThrowErrorIf(Error::UnknownFileNameEncoding, codepoint >= 0x110000, "Codepoint is invalid because is too big!"); + + // The range [U+D800,U+DBFF] is for high surrogates and [U+DC00,U+DFFF] is for low surrogates, neither of which + // a UTF-8 sequence is allowed to decode to. This function cannot generate values in that range: The range of + // surrogate codepoints requires 16 bits to represent and so would require a 3 byte UTF8 sequence (which + // represents codepoints between 0x800 and 0xFFFF). The 3 byte UTF8 sequence is of the form + // 1110,yyyy 10yy,yyyy 10yy,yyyy where the 'y's are the bits from the codepoint. Since in the + // surrogate range the most significant 5 bits of the 16bit codepoint are always set, the lead byte is always + // 1110,1101 (ED). This is a valid lead byte but the maximum for the next trail byte, specifically for this case + // (see 'decoded == 0xED') is set to 0x9F. Since the top 5 bits are set the trail byte will be 101y,yyyy or at + // least 0xA0 which is greater than the maximum. + ThrowErrorIf(Error::UnknownFileNameEncoding, codepoint >= 0xD800 && codepoint <= 0xDFFF, "Invalid codepoint"); + } + + // Decodes a percentage encoded string + std::string DecodeFileName(const std::string& fileName) + { + std::wstring fileNameW = utf8_to_wstring(fileName); + std::wstring result = L""; + for (std::uint32_t index = 0; index < fileNameW.length(); index++) + { + if(fileName[index] == '%') + { + ThrowErrorIf(Error::UnknownFileNameEncoding, index+2 >= fileNameW.length(), "Invalid encoding.") + auto encoding = fileNameW.substr(index+1, 2); + const auto& found = std::find(std::begin(EncodingToChar), std::end(EncodingToChar), fileNameW.substr(index+1, 2)); + if (found != std::end(EncodingToChar)) + { // Use special cheat table + result.push_back(found->decode); + index += 2; + } + else + { + std::uint32_t codepoint = 0; + std::uint32_t sequenceSize = 1; + std::uint32_t sequenceIndex = 0; + std::uint8_t minNextSequenceValue = 0; + std::uint8_t maxNextSequenceValue = 0; + bool done = false; + + // Ok, here we go... + while (index < fileNameW.length() && !done) + { + if (fileNameW[index] == '%') + { + ThrowErrorIf(Error::UnknownFileNameEncoding, index+2 >= fileNameW.length(), "Invalid encoding"); + + auto decoded = ConvertHex(fileNameW[++index]) * 16; // hi nibble + decoded += ConvertHex(fileNameW[++index]); // low nibble + + if (sequenceIndex == 0) + { + if (decoded <= 0x7F) + { // Actually, because of EncodingToChar, 0x7F is the only case here. <= just in case... + codepoint = decoded; + done = true; + } + else if (decoded >= 0xC2 && decoded <= 0xF4) + { // decode and reset values for UTF-8 sequence + if ((decoded & 0xE0) == 0xC0) // 2 Byte sequence starts with 110y,yyyy + { + sequenceSize = 2; + codepoint = (decoded & 0x1F) << 6; + minNextSequenceValue = 0x80; + maxNextSequenceValue = 0xBF; + } + else if ((decoded & 0xF0) == 0xE0) // 3 Byte sequence starts with 1110,zzzz + { + sequenceSize = 3; + codepoint = (decoded & 0x0F) << 12; + minNextSequenceValue = (decoded == 0xE0 ? 0xA0 : 0x80); + maxNextSequenceValue = (decoded == 0xED ? 0x9F : 0xBF); + } + else if ((decoded & 0xF8) == 0xF0) // 4 Byte sequence starts with 1111,0uuu + { + sequenceSize = 4; + codepoint = ((decoded & 0x07) << 18); + minNextSequenceValue = (decoded == 0xF0 ? 0x90 : 0x80); + maxNextSequenceValue = (decoded == 0xF4 ? 0x8F : 0xBF); + } + else { ThrowError(Error::UnknownFileNameEncoding); } + } + else { ThrowError(Error::UnknownFileNameEncoding); } + } + else + { // continue UTF-8 sequence + if (decoded >= minNextSequenceValue && decoded <= maxNextSequenceValue) + { // Adjust codepoint with new bits. Trailing bytes can only contain 6 bits of information + std::uint32_t shiftDistance = ((sequenceSize - sequenceIndex) - 1) * 6; + codepoint |= (decoded & 0x3F) << shiftDistance; + + // Set values for next byte in sequence + minNextSequenceValue = 0x80; + maxNextSequenceValue = 0xBF; + + // If full sequence then we're done! + if (sequenceSize == sequenceIndex + 1) { done = true; } + } + else { ThrowErrorAndLog(Error::UnknownFileNameEncoding, "Unexpected next sequence value"); } + } + + if (!done) + { // We are not done! Point to the next % encoded UTF-8 seq + sequenceIndex++; + index++; + } + } + else + { // We are looking for a % and we are not done. Abort! + ThrowError(Error::UnknownFileNameEncoding); + } + } + ValidateCodepoint(codepoint, sequenceSize); + + if (codepoint <= 0xFFFF) { result.push_back(codepoint); } + else + { // Because of the expected range of codepoints [0x010000, 0x10FFFF], the + // subtraction never underflows. What you end up with is the 11 bits after + // the first 10 of the codepoint minus 0x1000 OR'ed with 0xD800. Since the + // max for the codepoint is 0x10FFFF, the max for the 11 bits minus 0x1000 + // is 0x3FF (and the minimum is 0). OR'ed with D800 gives the range + // [0xD800, 0xDBFF] which is the range of the high surrogate. + wchar_t ch1 = ((codepoint & 0x00FC00) >> 10) | + (((codepoint & 0x1F0000) - 0x010000) >> 10) | + 0x00D800; + result.push_back(ch1); + // Since the codepoint is AND'ed with 0x3FF (the bottom 10 bits of the + // codepoint) and OR'ed with 0xDC00, the possible range is 0xDC00 through + // 0xDFFF. This is exactly the range of the low surrogates. + wchar_t ch2 = (codepoint & 0x0003FF) | 0x00DC00; + result.push_back(ch2); + } + } + } + else + { result += fileNameW[index]; + } + } + return utf16_to_utf8(result); + } + + // Douglas Crockford's base 32 alphabet variant is 0-9, A-Z except for i, l, o, and u. + const char base32DigitList[] = "0123456789abcdefghjkmnpqrstvwxyz"; + + std::string Base32Encoding(const std::vector& bytes) + { + const size_t publisherIdSize = 13; + const size_t byteCount = 8; + + // Consider groups of five bytes. This is the smallest number of bytes that has a number of bits + // that's evenly divisible by five. + // Every five bits starting with the most significant of the first byte are made into a base32 value. + // Each value is used to index into the alphabet array to produce a base32 digit. + // When out of bytes but the corresponding base32 value doesn't yet have five bits, 0 is used. + // Normally in these cases a particular number of '=' characters are appended to the resulting base32 + // string to indicate how many bits didn't come from the actual byte value. For our purposes no + // such padding characters are necessary. + // + // Bytes: aaaaaaaa bbbbbbbb cccccccc dddddddd eeeeeeee + // Base32 Values: 000aaaaa 000aaabb 000bbbbb 000bcccc 000ccccd 000ddddd 000ddeee 000eeeee + // + // Combo of byte a & F8 a & 07 b & 3E b & 01 c & 0F d & 7C d & 03 e & 1F + // values except b & C0 c & F0 d & 80 e & E0 + // for shifting + + // Make sure the following math doesn't overflow. + char output[publisherIdSize+1] = ""; + size_t outputIndex = 0; + for(size_t byteIndex = 0; byteIndex < byteCount; byteIndex +=5) + { + uint8_t firstByte = bytes[byteIndex]; + uint8_t secondByte = (byteIndex + 1) < byteCount ? bytes[byteIndex + 1] : 0; + output[outputIndex++] = base32DigitList[(firstByte & 0xF8) >> 3]; + output[outputIndex++] = base32DigitList[((firstByte & 0x07) << 2) | ((secondByte & 0xC0) >> 6)]; + + if(byteIndex + 1 < byteCount) + { + uint8_t thirdByte = (byteIndex + 2) < byteCount ? bytes[byteIndex + 2] : 0; + output[outputIndex++] = base32DigitList[(secondByte & 0x3E) >> 1]; + output[outputIndex++] = base32DigitList[((secondByte & 0x01) << 4) | ((thirdByte & 0xF0) >> 4)]; + + if(byteIndex + 2 < byteCount) + { + uint8_t fourthByte = (byteIndex + 3) < byteCount ? bytes[byteIndex + 3] : 0; + output[outputIndex++] = base32DigitList[((thirdByte & 0x0F) << 1) | ((fourthByte & 0x80) >> 7)]; + + if (byteIndex + 3 < byteCount) + { + uint8_t fifthByte = (byteIndex + 4) < byteCount ? bytes[byteIndex + 4] : 0; + output[outputIndex++] = base32DigitList[(fourthByte & 0x7C) >> 2]; + output[outputIndex++] = base32DigitList[((fourthByte & 0x03) << 3) | ((fifthByte & 0xE0) >> 5)]; + + if (byteIndex + 4 < byteCount) + { + output[outputIndex++] = base32DigitList[fifthByte & 0x1F]; + } + } + } + } + } + output[publisherIdSize] = '\0'; + return std::string(output); + } + + const std::uint8_t base64DecoderRing[128] = + { + /* 0-15 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* 16-31 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* 32-47 */ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 62, 0xFF, 0xFF, 0xFF, 63, + /* 48-63 */ 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 0xFF, 0xFF, 0xFF, 64, 0xFF, 0xFF, + /* 64-79 */ 0xFF, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, + /* 80-95 */ 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + /* 96-111 */ 0xFF, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, + /* 112-127 */ 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + }; + + std::vector GetBase64DecodedValue(const std::string& value) + { + std::vector result; + + ThrowErrorIfNot(Error::InvalidParameter, (0 == (value.length() % 4)), "invalid base64 encoding"); + for(std::size_t index=0; index < value.length(); index += 4) + { + ThrowErrorIf(Error::InvalidParameter,( + (value[index+0] | value[index+1] | value[index+2] | value[index+3]) >= 128 + ), "invalid base64 encoding"); + + auto v1 = base64DecoderRing[value[index+0]]; + auto v2 = base64DecoderRing[value[index+1]]; + auto v3 = base64DecoderRing[value[index+2]]; + auto v4 = base64DecoderRing[value[index+3]]; + + ThrowErrorIf(Error::InvalidParameter,(((v1 | v2) >= 64) || ((v3 | v4) == 0xFF)), "first two chars of a four char base64 sequence can't be ==, and must be valid"); + ThrowErrorIf(Error::InvalidParameter,(v3 == 64 && v4 != 64), "if the third char is = then the fourth char must be ="); + std::size_t byteCount = (v4 != 64 ? 3 : (v3 != 64 ? 2 : 1)); + result.push_back(static_cast(((v1 << 2) | ((v2 >> 4) & 0x03)))); + if (byteCount >1) + { + result.push_back(static_cast(((v2 << 4) | ((v3 >> 2) & 0x0F)) & 0xFF)); + if (byteCount >2) + { + result.push_back(static_cast(((v3 << 6) | ((v4 >> 0) & 0x3F)) & 0xFF)); + } + } + } + return result; + } + +} /*Encoding */ } /* MSIX */ diff --git a/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp b/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp index f0f6cf88..44a683b6 100644 --- a/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp +++ b/src/msix/PAL/FileSystem/Win32/DirectoryObject.cpp @@ -163,7 +163,7 @@ namespace MSIX { found = false; } while(directories.size() > 0); - auto result = ComPtr::Make(std::move(path), mode); + auto result = ComPtr::Make(std::move(utf8_to_wstring(path)), mode); return result; } } diff --git a/src/msix/PAL/XML/AOSP/XmlObject.cpp b/src/msix/PAL/XML/AOSP/XmlObject.cpp index 467f2e65..5e7397f9 100644 --- a/src/msix/PAL/XML/AOSP/XmlObject.cpp +++ b/src/msix/PAL/XML/AOSP/XmlObject.cpp @@ -62,7 +62,7 @@ public: std::vector GetBase64DecodedAttributeValue(XmlAttributeName attribute) override { auto intermediate = GetAttributeValue(attribute); - return GetBase64DecodedValue(intermediate); + return Encoding::GetBase64DecodedValue(intermediate); } std::string GetText() override diff --git a/src/msix/PAL/XML/Apple/XmlObject.cpp b/src/msix/PAL/XML/Apple/XmlObject.cpp index deeb39d4..a28070f3 100644 --- a/src/msix/PAL/XML/Apple/XmlObject.cpp +++ b/src/msix/PAL/XML/Apple/XmlObject.cpp @@ -49,7 +49,7 @@ public: std::vector GetBase64DecodedAttributeValue(XmlAttributeName attribute) override { auto intermediate = GetAttributeValue(attribute); - return GetBase64DecodedValue(intermediate); + return Encoding::GetBase64DecodedValue(intermediate); } std::string GetText() override diff --git a/src/msix/PAL/XML/msxml6/XmlObject.cpp b/src/msix/PAL/XML/msxml6/XmlObject.cpp index 9ad39f4a..004aa066 100644 --- a/src/msix/PAL/XML/msxml6/XmlObject.cpp +++ b/src/msix/PAL/XML/msxml6/XmlObject.cpp @@ -225,7 +225,7 @@ public: std::vector GetBase64DecodedAttributeValue(XmlAttributeName attribute) override { auto intermediate = GetAttributeValue(attribute); - return GetBase64DecodedValue(intermediate); + return Encoding::GetBase64DecodedValue(intermediate); } std::string GetText() override diff --git a/test/MacOS-Linux/MacOS-Linux-Etc.sh b/test/MacOS-Linux/MacOS-Linux-Etc.sh index 5d312b78..dbce07ca 100755 --- a/test/MacOS-Linux/MacOS-Linux-Etc.sh +++ b/test/MacOS-Linux/MacOS-Linux-Etc.sh @@ -73,6 +73,7 @@ FindBinFolder RunTest 2 ./../appx/Empty.appx -sv RunTest 0 ./../appx/HelloWorld.appx -ss RunTest 0 ./../appx/NotepadPlusPlus.appx -ss +RunTest 0 ./../appx/IntlPackage.appx -ss RunTest 66 ./../appx/SignatureNotLastPart-ERROR_BAD_FORMAT.appx RunTest 66 ./../appx/SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx RunTest 65 ./../appx/SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx -sv @@ -172,7 +173,7 @@ RunTest 3 ./../appx/bundles/PayloadPackageIsEmpty.appxbundle -ss RunTest 87 ./../appx/bundles/PayloadPackageIsNotAppxPackage.appxbundle -ss # RunTest 0 ./../appx/bundles/PayloadPackageNotListedInManifest.appxbundle RunTest 66 ./../appx/bundles/SignedUntrustedCert-CERT_E_CHAINING.appxbundle - +RunTest 0 ./../appx/bundles/BundleWithIntlPackage.appxbundle -ss RunTest 0 ./../appx/bundles/StoreSigned_Desktop_x86_x64_MoviesTV.appxbundle ValidateResult ExpectedResult/$directory/StoreSigned_Desktop_x86_x64_MoviesTV.txt diff --git a/test/Win32/Win32.ps1 b/test/Win32/Win32.ps1 index ae0cc452..a40af4c1 100644 --- a/test/Win32/Win32.ps1 +++ b/test/Win32/Win32.ps1 @@ -89,6 +89,7 @@ FindBinFolder RunTest 0x8bad0002 .\..\appx\Empty.appx "-sv" RunTest 0x00000000 .\..\appx\HelloWorld.appx "-ss" RunTest 0x00000000 .\..\appx\NotepadPlusPlus.appx "-ss" +RunTest 0x00000000 .\..\appx\IntlPackage.appx "-ss" RunTest 0x8bad0042 .\..\appx\SignatureNotLastPart-ERROR_BAD_FORMAT.appx # RunTest 0x134 .\appx\SignedMismatchedPublisherName-ERROR_BAD_FORMAT.appx RunTest 0x8bad0042 .\..\appx\SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx @@ -143,7 +144,7 @@ RunTest 0x8bad0003 .\..\appx\bundles\PayloadPackageIsEmpty.appxbundle "-ss" RunTest 0x80070057 .\..\appx\bundles\PayloadPackageIsNotAppxPackage.appxbundle "-ss" # RunTest 0x00000000 .\..\appx\bundles\PayloadPackageNotListedInManifest.appxbundle RunTest 0x8bad0042 .\..\appx\bundles\SignedUntrustedCert-CERT_E_CHAINING.appxbundle - +RunTest 0x00000000 .\..\appx\bundles\BundleWithIntlPackage.appxbundle "-ss" RunTest 0x00000000 .\..\appx\bundles\StoreSigned_Desktop_x86_x64_MoviesTV.appxbundle ValidateResult ExpectedResults\StoreSigned_Desktop_x86_x64_MoviesTV.txt diff --git a/test/appx/IntlPackage.appx b/test/appx/IntlPackage.appx new file mode 100644 index 00000000..d495f431 Binary files /dev/null and b/test/appx/IntlPackage.appx differ diff --git a/test/appx/bundles/BundleWithIntlPackage.appxbundle b/test/appx/bundles/BundleWithIntlPackage.appxbundle new file mode 100644 index 00000000..664ed8f9 Binary files /dev/null and b/test/appx/bundles/BundleWithIntlPackage.appxbundle differ diff --git a/test/mobile/common/MobileTests.cpp b/test/mobile/common/MobileTests.cpp index 1b2b7281..a471a466 100644 --- a/test/mobile/common/MobileTests.cpp +++ b/test/mobile/common/MobileTests.cpp @@ -118,6 +118,7 @@ static HRESULT RunTestsInternal(std::string source, std::string target) hr = RunTest(source + "Empty.appx", unpackFolder, sv, 2); hr = RunTest(source + "HelloWorld.appx", unpackFolder, ss, 0); hr = RunTest(source + "NotepadPlusPlus.appx", unpackFolder, ss, 0); + hr = RunTest(source + "IntlPackage.appx", unpackFolder, ss, 0); hr = RunTest(source + "SignatureNotLastPart-ERROR_BAD_FORMAT.appx", unpackFolder, full, 66); hr = RunTest(source + "SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx", unpackFolder, full, 66); hr = RunTest(source + "SignedTamperedBlockMap-TRUST_E_BAD_DIGEST.appx", unpackFolder, sv, 65); @@ -169,6 +170,7 @@ static HRESULT RunTestsInternal(std::string source, std::string target) hr = RunTest(source + "bundles/PayloadPackageIsNotAppxPackage.appxbundle", unpackFolder, ss, 87); //hr = RunTest(source + "bundles/PayloadPackageNotListedInManifest.appxbundle", unpackFolder, full, 0); hr = RunTest(source + "bundles/SignedUntrustedCert-CERT_E_CHAINING.appxbundle", unpackFolder, full, 66); + hr = RunTest(source + "bundles/BundleWithIntlPackage.appxbundle", unpackFolder, ss, 0); hr = RunTest(source + "bundles/StoreSigned_Desktop_x86_x64_MoviesTV.appxbundle", unpackFolder, full, 0); // Flat diff --git a/test/mobile/iOSBVT/iOSBVT.xcodeproj/project.pbxproj b/test/mobile/iOSBVT/iOSBVT.xcodeproj/project.pbxproj index f97eef48..95129596 100644 --- a/test/mobile/iOSBVT/iOSBVT.xcodeproj/project.pbxproj +++ b/test/mobile/iOSBVT/iOSBVT.xcodeproj/project.pbxproj @@ -80,6 +80,10 @@ 4CAC1C532135DCDA005C3B2C /* scale-140.appx in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4CAC1C2B2135DCC0005C3B2C /* scale-140.appx */; }; 4CAC1C542135DCDA005C3B2C /* scale-180.appx in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4CAC1C2C2135DCC0005C3B2C /* scale-180.appx */; }; 4CAC1C552135DCDA005C3B2C /* scale-240.appx in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4CAC1C2D2135DCC0005C3B2C /* scale-240.appx */; }; + 4CDE14A221A493960085A796 /* IntlPackage.appx in Resources */ = {isa = PBXBuildFile; fileRef = 4CDE14A121A493960085A796 /* IntlPackage.appx */; }; + 4CDE14A321A493B00085A796 /* IntlPackage.appx in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4CDE14A121A493960085A796 /* IntlPackage.appx */; }; + 4CDE14A521ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle in Resources */ = {isa = PBXBuildFile; fileRef = 4CDE14A421ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle */; }; + 4CDE14A621ACC9EA0085A796 /* BundleWithIntlPackage.appxbundle in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4CDE14A421ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle */; }; 6A0FC3D72179413E003B606A /* NotepadPlusPlus.appx in Resources */ = {isa = PBXBuildFile; fileRef = 6A0FC3D62179413E003B606A /* NotepadPlusPlus.appx */; }; EEE4055020225CDF007B25CE /* AppDelegate.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE4054F20225CDF007B25CE /* AppDelegate.m */; }; EEE4055320225CDF007B25CE /* ViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = EEE4055220225CDF007B25CE /* ViewController.m */; }; @@ -162,6 +166,7 @@ dstPath = bundles; dstSubfolderSpec = 7; files = ( + 4CDE14A621ACC9EA0085A796 /* BundleWithIntlPackage.appxbundle in CopyFiles */, 4C7F376D20783FD2002942F4 /* BlockMapContainsPayloadPackage.appxbundle in CopyFiles */, 4C7F376E20783FD2002942F4 /* BlockMapIsMissing.appxbundle in CopyFiles */, 4C7F376F20783FD2002942F4 /* BlockMapViolatesSchema.appxbundle in CopyFiles */, @@ -248,6 +253,7 @@ dstPath = ""; dstSubfolderSpec = 7; files = ( + 4CDE14A321A493B00085A796 /* IntlPackage.appx in CopyFiles */, EEE405A420227EC1007B25CE /* CentennialCoffee.appx in CopyFiles */, EEE405A520227EC1007B25CE /* Empty.appx in CopyFiles */, EEE405A620227EC1007B25CE /* HelloWorld.appx in CopyFiles */, @@ -341,6 +347,8 @@ 4CAC1C2B2135DCC0005C3B2C /* scale-140.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = "scale-140.appx"; sourceTree = ""; }; 4CAC1C2C2135DCC0005C3B2C /* scale-180.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = "scale-180.appx"; sourceTree = ""; }; 4CAC1C2D2135DCC0005C3B2C /* scale-240.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = "scale-240.appx"; sourceTree = ""; }; + 4CDE14A121A493960085A796 /* IntlPackage.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = IntlPackage.appx; sourceTree = ""; }; + 4CDE14A421ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle */ = {isa = PBXFileReference; lastKnownFileType = file; path = BundleWithIntlPackage.appxbundle; sourceTree = ""; }; 6A0FC3D62179413E003B606A /* NotepadPlusPlus.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = NotepadPlusPlus.appx; sourceTree = ""; }; EEE4054B20225CDF007B25CE /* iOSBVT.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = iOSBVT.app; sourceTree = BUILT_PRODUCTS_DIR; }; EEE4054E20225CDF007B25CE /* AppDelegate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AppDelegate.h; sourceTree = ""; }; @@ -407,6 +415,7 @@ 4C7F375120783FC2002942F4 /* bundles */ = { isa = PBXGroup; children = ( + 4CDE14A421ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle */, 4C7F375220783FC2002942F4 /* BlockMapContainsPayloadPackage.appxbundle */, 4C7F375320783FC2002942F4 /* BlockMapIsMissing.appxbundle */, 4C7F375420783FC2002942F4 /* BlockMapViolatesSchema.appxbundle */, @@ -517,6 +526,7 @@ EEE4056B20225EF5007B25CE /* appx */ = { isa = PBXGroup; children = ( + 4CDE14A121A493960085A796 /* IntlPackage.appx */, 4C80CD9C216EE2B7008E3292 /* महसुस */, EEE4056C20225EF5007B25CE /* BlockMap */, EEE4057720225EF5007B25CE /* CentennialCoffee.appx */, @@ -662,6 +672,7 @@ EEE4055820225CDF007B25CE /* Assets.xcassets in Resources */, 4CAC1C322135DCC0005C3B2C /* language-es.appx in Resources */, 4CAC1C402135DCC0005C3B2C /* scale-180.appx in Resources */, + 4CDE14A521ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle in Resources */, EEE4059520225EF5007B25CE /* InvalidSignatureBadCodeIntegrity.appx in Resources */, 4CAC1C372135DCC0005C3B2C /* language-pt.appx in Resources */, EEE4059F20225EF5007B25CE /* StoreSigned_Desktop_x64_MoviesTV.appx in Resources */, @@ -676,6 +687,7 @@ 4CAC1C412135DCC0005C3B2C /* scale-240.appx in Resources */, 4C11C9D12048C7C700F6BE01 /* en-us_win32.appx in Resources */, 4CAC1C3E2135DCC0005C3B2C /* main_x86.appx in Resources */, + 4CDE14A221A493960085A796 /* IntlPackage.appx in Resources */, EEE4055620225CDF007B25CE /* Main.storyboard in Resources */, ); runOnlyForDeploymentPostprocessing = 0;