Merged PR 2607225: [MSIX SDK] Verify that extracting a package where the files inside it have intl character works

When a package contains files with international characters, the opc container will have them as percentage encoded, but the block map not, this causes the extraction to fail because the files in the opc container and the block map don't match as our decoding/encoding was only for ascii only. This changes expands DecodeFileName and EncodeFileName for non-ascii characters.

Related work items: #19276513
This commit is contained in:
Ruben Guerrero Samaniego 2018-11-27 22:12:53 +00:00 коммит произвёл Rubén Guerrero Samaniego
Родитель 9775952287
Коммит 802e41741c
15 изменённых файлов: 491 добавлений и 203 удалений

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

@ -4,192 +4,14 @@
//
#pragma once
#include <array>
#include <sstream>
#include <algorithm>
#include <string>
#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<const char*, PercentangeEncodingTableSize> 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<uint8_t>& bytes);
std::vector<std::uint8_t> 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<std::uint8_t>(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<uint8_t>& 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<std::uint8_t> GetBase64DecodedValue(const std::string& value)
{
std::vector<std::uint8_t> 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<std::uint8_t>(((v1 << 2) | ((v2 >> 4) & 0x03))));
if (byteCount >1)
{
result.push_back(static_cast<std::uint8_t>(((v2 << 4) | ((v3 >> 2) & 0x0F)) & 0xFF));
if (byteCount >2)
{
result.push_back(static_cast<std::uint8_t>(((v3 << 6) | ((v4 >> 0) & 0x3F)) & 0xFF));
}
}
}
return result;
}
}
} /*Encoding */ } /* MSIX */

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

@ -170,6 +170,6 @@ namespace MSIX {
std::vector<std::uint8_t> hash;
ThrowErrorIfNot(Error::Unexpected, SHA256::ComputeHash(buffer.data(), static_cast<uint32_t>(buffer.size()), hash), "Failed computing publisherId");
return Base32Encoding(hash);
return Encoding::Base32Encoding(hash);
}
}

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

@ -235,7 +235,7 @@ namespace MSIX {
{
auto bundleInfoInternal = package.As<IAppxBundleManifestPackageInfoInternal>();
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<IAppxFile>::Make<MSIX::AppxFile>(m_factory.Get(), fileName, std::move(blockMapStream));
filesToProcess.erase(std::remove(filesToProcess.begin(), filesToProcess.end(), containerFileName), filesToProcess.end());
m_files[opcFileName] = MSIX::ComPtr<IAppxFile>::Make<MSIX::AppxFile>(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<IAppxManifestPackageIdInternal>()->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<HRESULT>(Error::PackageIsBundle); }
ThrowErrorIf(Error::InvalidParameter, (file == nullptr || *file != nullptr), "bad pointer");
ThrowErrorIf(Error::FileNotFound, (static_cast<size_t>(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<IStream> stream;
@ -523,8 +522,7 @@ namespace MSIX {
{
if (m_isBundle) { return static_cast<HRESULT>(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<IStream> stream;

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

@ -212,6 +212,7 @@ add_library(${PROJECT_NAME} SHARED
AppxPackageInfo.cpp
AppxPackaging_i.cpp
AppxSignature.cpp
Encoding.cpp
Exceptions.cpp
InflateStream.cpp
Log.cpp

451
src/msix/Encoding.cpp Normal file
Просмотреть файл

@ -0,0 +1,451 @@
//
// Copyright (C) 2017 Microsoft. All rights reserved.
// See LICENSE file in the project root for full license information.
//
#include <string>
#include <algorithm>
#include <vector>
#include <array>
#include "Encoding.hpp"
#include "Exceptions.hpp"
#include "UnicodeConversion.hpp"
namespace MSIX { namespace Encoding {
const std::size_t PercentageEncodingTableSize = 0x7F;
const std::array<const wchar_t*, PercentageEncodingTableSize> 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<std::uint32_t>(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:
// <http://www.unicode.org/versions/Unicode4.0.0/ch03.pdf#G7404>
//
// 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<uint8_t>& 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<std::uint8_t> GetBase64DecodedValue(const std::string& value)
{
std::vector<std::uint8_t> 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<std::uint8_t>(((v1 << 2) | ((v2 >> 4) & 0x03))));
if (byteCount >1)
{
result.push_back(static_cast<std::uint8_t>(((v2 << 4) | ((v3 >> 2) & 0x0F)) & 0xFF));
if (byteCount >2)
{
result.push_back(static_cast<std::uint8_t>(((v3 << 6) | ((v4 >> 0) & 0x3F)) & 0xFF));
}
}
}
return result;
}
} /*Encoding */ } /* MSIX */

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

@ -163,7 +163,7 @@ namespace MSIX {
found = false;
}
while(directories.size() > 0);
auto result = ComPtr<IStream>::Make<FileStream>(std::move(path), mode);
auto result = ComPtr<IStream>::Make<FileStream>(std::move(utf8_to_wstring(path)), mode);
return result;
}
}

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

@ -62,7 +62,7 @@ public:
std::vector<std::uint8_t> GetBase64DecodedAttributeValue(XmlAttributeName attribute) override
{
auto intermediate = GetAttributeValue(attribute);
return GetBase64DecodedValue(intermediate);
return Encoding::GetBase64DecodedValue(intermediate);
}
std::string GetText() override

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

@ -49,7 +49,7 @@ public:
std::vector<std::uint8_t> GetBase64DecodedAttributeValue(XmlAttributeName attribute) override
{
auto intermediate = GetAttributeValue(attribute);
return GetBase64DecodedValue(intermediate);
return Encoding::GetBase64DecodedValue(intermediate);
}
std::string GetText() override

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

@ -225,7 +225,7 @@ public:
std::vector<std::uint8_t> GetBase64DecodedAttributeValue(XmlAttributeName attribute) override
{
auto intermediate = GetAttributeValue(attribute);
return GetBase64DecodedValue(intermediate);
return Encoding::GetBase64DecodedValue(intermediate);
}
std::string GetText() override

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

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

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

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

Двоичные данные
test/appx/IntlPackage.appx Normal file

Двоичный файл не отображается.

Двоичные данные
test/appx/bundles/BundleWithIntlPackage.appxbundle Normal file

Двоичный файл не отображается.

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

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

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

@ -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 = "<group>"; };
4CAC1C2C2135DCC0005C3B2C /* scale-180.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = "scale-180.appx"; sourceTree = "<group>"; };
4CAC1C2D2135DCC0005C3B2C /* scale-240.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = "scale-240.appx"; sourceTree = "<group>"; };
4CDE14A121A493960085A796 /* IntlPackage.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = IntlPackage.appx; sourceTree = "<group>"; };
4CDE14A421ACC9CB0085A796 /* BundleWithIntlPackage.appxbundle */ = {isa = PBXFileReference; lastKnownFileType = file; path = BundleWithIntlPackage.appxbundle; sourceTree = "<group>"; };
6A0FC3D62179413E003B606A /* NotepadPlusPlus.appx */ = {isa = PBXFileReference; lastKnownFileType = file; path = NotepadPlusPlus.appx; sourceTree = "<group>"; };
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 = "<group>"; };
@ -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;