Merged PR 1168654: Unpack save files with their encoding name, instead of the name on the BlockMap
Fix saving the files using the percentage encoding name that OPC uses. We now save files as the name defined in the AppxBlockMap.xml file. Unpacking test\appx\BlockMap\HelloWorld.appx used to produce a file with name Shortcut%20File\HelloWorld.appxref-ms and now is Shortcut File\HelloWorld.appxref-ms Unpacking test\appx\HelloWorld.appx used to produce a files with name icon-32%255B2%255D.png and icon-32%25255B2%25255D.png and now is icon-32%5B2%5D.png and icon-32%255B2%255D.png respectively. Related work items: #14874780
This commit is contained in:
Родитель
0a2745c16c
Коммит
a4c27c4162
|
@ -3,6 +3,10 @@
|
|||
#include <string>
|
||||
#include <locale>
|
||||
#include <codecvt>
|
||||
#include <iostream>
|
||||
#include <iomanip>
|
||||
#include <functional>
|
||||
#include <map>
|
||||
|
||||
#ifdef WIN32
|
||||
#define UNICODE
|
||||
|
@ -68,6 +72,98 @@ std::wstring utf8_to_utf16(const std::string& utf8string)
|
|||
return result;
|
||||
}
|
||||
|
||||
// describes an option to a command that the user may specify
|
||||
struct Option
|
||||
{
|
||||
using CBF = std::function<bool(const std::string& value)>;
|
||||
|
||||
Option(bool param, const std::string& help, CBF callback): Help(help), Callback(callback), TakesParameter(param)
|
||||
{}
|
||||
|
||||
bool TakesParameter;
|
||||
std::string Name;
|
||||
std::string Help;
|
||||
CBF Callback;
|
||||
};
|
||||
|
||||
// Tracks the state of the current parse operation as well as implements input validation
|
||||
struct State
|
||||
{
|
||||
bool CreatePackageSubfolder()
|
||||
{
|
||||
unpackOptions = static_cast<APPX_PACKUNPACK_OPTION>(unpackOptions | APPX_PACKUNPACK_OPTION::APPX_PACKUNPACK_OPTION_CREATEPACKAGESUBFOLDER);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipManifestValidation()
|
||||
{
|
||||
validationOptions = static_cast<APPX_VALIDATION_OPTION>(validationOptions | APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_SKIPAPPXMANIFEST);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SkipSignature()
|
||||
{
|
||||
validationOptions = static_cast<APPX_VALIDATION_OPTION>(validationOptions | APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_SKIPSIGNATURE);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AllowSignatureOriginUnknown()
|
||||
{
|
||||
validationOptions = static_cast<APPX_VALIDATION_OPTION>(validationOptions | APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_ALLOWSIGNATUREORIGINUNKNOWN);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetPackageName(const std::string& name)
|
||||
{
|
||||
if (!packageName.empty() || name.empty()) { return false; }
|
||||
packageName = utf8_to_utf16(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool SetDirectoryName(const std::string& name)
|
||||
{
|
||||
if (!directoryName.empty() || name.empty()) { return false; }
|
||||
directoryName = utf8_to_utf16(name);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::wstring packageName;
|
||||
std::wstring directoryName;
|
||||
APPX_VALIDATION_OPTION validationOptions = APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_FULL;
|
||||
APPX_PACKUNPACK_OPTION unpackOptions = APPX_PACKUNPACK_OPTION::APPX_PACKUNPACK_OPTION_NONE;
|
||||
};
|
||||
|
||||
// Displays contextual formatted help to the user.
|
||||
int Help(char* toolName, std::map<std::string, Option>& options)
|
||||
{
|
||||
std::cout << std::endl;
|
||||
std::cout << "Usage:" << std::endl;
|
||||
std::cout << "------" << std::endl;
|
||||
std::cout << "\t" << toolName << " -p <package> -d <directory> [options] " << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Description:" << std::endl;
|
||||
std::cout << "------------" << std::endl;
|
||||
std::cout << "\tExtracts all files within an app package at the input <package> name to the" << std::endl;
|
||||
std::cout << "\tspecified output <directory>. The output has the same directory structure " << std::endl;
|
||||
std::cout << "\tas the package." << std::endl;
|
||||
std::cout << std::endl;
|
||||
std::cout << "Options:" << std::endl;
|
||||
std::cout << "--------" << std::endl;
|
||||
|
||||
for (const auto& option : options)
|
||||
{
|
||||
std::cout << "\t" << std::left << std::setfill(' ') << std::setw(5) <<
|
||||
option.first << ": " << option.second.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;
|
||||
}
|
||||
|
||||
#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)
|
||||
|
@ -147,7 +243,7 @@ struct FootprintFilesType
|
|||
|
||||
// Types of footprint files in an app package
|
||||
const int FootprintFilesCount = 4;
|
||||
const FootprintFilesType footprintFilesType[FootprintFilesCount] = {
|
||||
FootprintFilesType footprintFilesType[FootprintFilesCount] = {
|
||||
{APPX_FOOTPRINT_FILE_TYPE_MANIFEST, "manifest", true },
|
||||
{APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP, "block map", true },
|
||||
{APPX_FOOTPRINT_FILE_TYPE_SIGNATURE, "digital signature", true },
|
||||
|
@ -198,13 +294,13 @@ void STDMETHODCALLTYPE MyFree(LPVOID pv) { std::free(pv); }
|
|||
// reader
|
||||
// On success, receives the created instance of IAppxPackageReader.
|
||||
//
|
||||
HRESULT GetPackageReader(LPCWSTR inputFileName, IAppxPackageReader** package)
|
||||
HRESULT GetPackageReader(State& state, IAppxPackageReader** package)
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
ComPtr<IAppxFactory> appxFactory;
|
||||
ComPtr<IStream> inputStream;
|
||||
|
||||
hr = CreateStreamOnFileUTF16(inputFileName, true, &inputStream);
|
||||
hr = CreateStreamOnFileUTF16(state.packageName.c_str(), true, &inputStream);
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
// On Win32 platforms CoCreateAppxFactory defaults to CoTaskMemAlloc/CoTaskMemFree
|
||||
|
@ -213,7 +309,7 @@ HRESULT GetPackageReader(LPCWSTR inputFileName, IAppxPackageReader** package)
|
|||
hr = CoCreateAppxFactoryWithHeap(
|
||||
MyAllocate,
|
||||
MyFree,
|
||||
APPX_VALIDATION_OPTION::APPX_VALIDATION_OPTION_SKIPAPPXMANIFEST,
|
||||
state.validationOptions,
|
||||
&appxFactory);
|
||||
|
||||
// Create a new package reader using the factory. For
|
||||
|
@ -361,33 +457,88 @@ HRESULT ExtractPayloadFiles(IAppxPackageReader* package, LPCWSTR outputPath)
|
|||
return hr;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
// Parses argc/argv input via commands into state, and extract the package.
|
||||
int ParseAndRun(std::map<std::string, Option>& options, State& state, int argc, char* argv[])
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
auto ParseInput = [&]()->bool {
|
||||
int index = 1;
|
||||
while (index < argc)
|
||||
{
|
||||
auto option = options.find(argv[index]);
|
||||
if (option == options.end()) { return false; }
|
||||
char const *parameter = "";
|
||||
if (option->second.TakesParameter)
|
||||
{
|
||||
if (++index == argc) { break; }
|
||||
parameter = argv[index];
|
||||
}
|
||||
if (!option->second.Callback(parameter)) { return false; }
|
||||
++index;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
std::wstring fileName = utf8_to_utf16(argv[1]);
|
||||
std::wstring pathName = utf8_to_utf16(argv[2]);
|
||||
if (!ParseInput()) { return Help(argv[0], options); }
|
||||
if (state.packageName.empty() || state.directoryName.empty())
|
||||
{ Error(argv[0]);
|
||||
return -1;
|
||||
}
|
||||
|
||||
HRESULT hr = S_OK;
|
||||
// Create a package using the file name in argv[1]
|
||||
ComPtr<IAppxPackageReader> package;
|
||||
hr = GetPackageReader(fileName.c_str(), &package);
|
||||
hr = GetPackageReader(state, &package);
|
||||
|
||||
// Print information about all footprint files, and extract them to disk
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ExtractFootprintFiles(package.Get(), pathName.c_str());
|
||||
hr = ExtractFootprintFiles(package.Get(), state.directoryName.c_str());
|
||||
}
|
||||
|
||||
// Print information about all payload files, and extract them to disk
|
||||
if (SUCCEEDED(hr))
|
||||
{
|
||||
hr = ExtractPayloadFiles(package.Get(), pathName.c_str());
|
||||
hr = ExtractPayloadFiles(package.Get(), state.directoryName.c_str());
|
||||
}
|
||||
|
||||
if (FAILED(hr))
|
||||
{
|
||||
// TODO: Tell a more specific reason why the faiulre occurred.
|
||||
std::printf("\nError %X occurred while extracting the appx package\n", static_cast<int>(hr));
|
||||
}
|
||||
|
||||
return static_cast<int>(hr);
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
HRESULT hr = S_OK;
|
||||
|
||||
State state;
|
||||
std::map<std::string, Option> options = {
|
||||
{ "-p", Option(true, "REQUIRED, specify input package name.",
|
||||
[&](const std::string& name) { return state.SetPackageName(name); })
|
||||
},
|
||||
{ "-d", Option(true, "REQUIRED, specify output directory name.",
|
||||
[&](const std::string& name) { return state.SetDirectoryName(name); })
|
||||
},
|
||||
{ "-pfn", Option(false, "Unpacks all files to a subdirectory under the specified output path, named after the package full name.",
|
||||
[&](const std::string&) { return state.CreatePackageSubfolder(); })
|
||||
},
|
||||
{ "-mv", Option(false, "Skips manifest validation. By default manifest validation is enabled.",
|
||||
[&](const std::string&) { return state.SkipManifestValidation(); })
|
||||
},
|
||||
{ "-sv", Option(false, "Skips signature validation. By default signature validation is enabled.",
|
||||
[&](const std::string&) { return state.AllowSignatureOriginUnknown(); })
|
||||
},
|
||||
{ "-ss", Option(false, "Skips enforcement of signed packages. By default packages must be signed.",
|
||||
[&](const std::string&)
|
||||
{ footprintFilesType[2].isRequired = false;
|
||||
return state.SkipSignature();
|
||||
})
|
||||
},
|
||||
{ "-?", Option(false, "Displays this help text.",
|
||||
[&](const std::string&) { return false; })
|
||||
}
|
||||
};
|
||||
|
||||
auto result = ParseAndRun(options, state, argc, argv);
|
||||
if (result != 0)
|
||||
{
|
||||
std::cout << "Error: " << std::hex << result << " while extracting the appx package" <<std::endl;
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -7,6 +7,7 @@
|
|||
#include "HashStream.hpp"
|
||||
#include "ComHelper.hpp"
|
||||
#include "SHA256.hpp"
|
||||
#include "AppxFactory.hpp"
|
||||
|
||||
#include <string>
|
||||
#include <map>
|
||||
|
@ -35,7 +36,8 @@ namespace xPlat {
|
|||
class BlockMapStream : public StreamBase
|
||||
{
|
||||
public:
|
||||
BlockMapStream(IStream* stream, std::vector<Block>& blocks) : m_stream(stream)
|
||||
BlockMapStream(IxPlatFactory* factory, std::string decodedName, IStream* stream, std::vector<Block>& blocks)
|
||||
: m_factory(factory), m_decodedName(decodedName), m_stream(stream)
|
||||
{
|
||||
// Determine overall stream size
|
||||
ULARGE_INTEGER uli;
|
||||
|
@ -141,7 +143,7 @@ namespace xPlat {
|
|||
|
||||
HRESULT STDMETHODCALLTYPE GetName(LPWSTR* fileName) override
|
||||
{
|
||||
return ResultOf([&]{ return m_stream.As<IAppxFile>()->GetName(fileName); });
|
||||
return m_factory->MarshalOutString(m_decodedName, fileName);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE GetContentType(LPWSTR* contentType) override
|
||||
|
@ -159,6 +161,8 @@ namespace xPlat {
|
|||
std::vector<BlockPlusStream> m_blockStreams;
|
||||
std::uint64_t m_relativePosition;
|
||||
std::uint64_t m_streamSize;
|
||||
std::string m_decodedName;
|
||||
ComPtr<IStream> m_stream;
|
||||
IxPlatFactory* m_factory;
|
||||
};
|
||||
}
|
|
@ -40,7 +40,7 @@ namespace xPlat {
|
|||
bool hasValue = !attributeValue.empty();
|
||||
std::uint32_t value = 0;
|
||||
if (hasValue) { value = static_cast<std::uint32_t>(std::stoul(attributeValue)); }
|
||||
return value;
|
||||
return value;
|
||||
}
|
||||
|
||||
static std::string GetName(XERCES_CPP_NAMESPACE::DOMElement* element)
|
||||
|
@ -67,7 +67,7 @@ namespace xPlat {
|
|||
XercesXMLChPtr nameAttr(XMLString::transcode("Hash"));
|
||||
XMLSize_t len = 0;
|
||||
XercesXMLBytePtr decodedData(XERCES_CPP_NAMESPACE::Base64::decodeToXMLByte(
|
||||
element->getAttribute(nameAttr.Get()),
|
||||
element->getAttribute(nameAttr.Get()),
|
||||
&len));
|
||||
std::vector<std::uint8_t> result(len);
|
||||
for(XMLSize_t index=0; index < len; index++)
|
||||
|
@ -83,7 +83,7 @@ namespace xPlat {
|
|||
result.hash = GetDigestData(element);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
AppxBlockMapObject::AppxBlockMapObject(IxPlatFactory* factory, ComPtr<IStream>& stream) : m_factory(factory), m_stream(stream)
|
||||
{
|
||||
auto dom = ComPtr<IXmlObject>::Make<XmlObject>(stream, &blockMapSchema);
|
||||
|
@ -109,7 +109,7 @@ namespace xPlat {
|
|||
ThrowErrorIf(Error::BlockMapSemanticError, (existing != m_blockMap.end()), "duplicate file name specified.");
|
||||
|
||||
// Get blocks elements
|
||||
XercesXMLChPtr blockXPath(XMLString::transcode("./Block"));
|
||||
XercesXMLChPtr blockXPath(XMLString::transcode("./Block"));
|
||||
XercesPtr<DOMXPathResult> blockResult = dom->Document()->evaluate(
|
||||
blockXPath.Get(),
|
||||
fileNode,
|
||||
|
@ -118,13 +118,13 @@ namespace xPlat {
|
|||
nullptr);
|
||||
|
||||
// get all the blocks for the file.
|
||||
std::vector<Block> blocks(blockResult->getSnapshotLength());
|
||||
std::vector<Block> blocks(blockResult->getSnapshotLength());
|
||||
for (XMLSize_t j = 0; j < blockResult->getSnapshotLength(); j++)
|
||||
{
|
||||
blockResult->snapshotItem(j);
|
||||
auto blockNode = static_cast<DOMElement*>(blockResult->getNodeValue());
|
||||
blocks[j] = GetBlock(blockNode);
|
||||
}
|
||||
}
|
||||
|
||||
m_blockMap.insert(std::make_pair(name, std::move(blocks)));
|
||||
m_blockMapfiles.insert(std::make_pair(name,
|
||||
|
@ -134,7 +134,7 @@ namespace xPlat {
|
|||
GetLocalFileHeaderSize(fileNode),
|
||||
name,
|
||||
GetSize(fileNode))));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
xPlat::ComPtr<IStream> AppxBlockMapObject::GetValidationStream(const std::string& part, IStream* stream)
|
||||
|
@ -142,7 +142,7 @@ namespace xPlat {
|
|||
ThrowErrorIf(Error::InvalidParameter, (part.empty() || stream == nullptr), "bad input");
|
||||
auto item = m_blockMap.find(part);
|
||||
ThrowErrorIf(Error::BlockMapSemanticError, item == m_blockMap.end(), "file not tracked by blockmap");
|
||||
return ComPtr<IStream>::Make<BlockMapStream>(stream, item->second);
|
||||
return ComPtr<IStream>::Make<BlockMapStream>(m_factory, part, stream, item->second);
|
||||
}
|
||||
|
||||
HRESULT STDMETHODCALLTYPE AppxBlockMapObject::GetFile(LPCWSTR filename, IAppxBlockMapFile **file)
|
||||
|
@ -163,7 +163,7 @@ namespace xPlat {
|
|||
ComPtr<IAppxBlockMapReader> self;
|
||||
ThrowHrIfFailed(QueryInterface(UuidOfImpl<IAppxBlockMapReader>::iid, reinterpret_cast<void**>(&self)));
|
||||
*enumerator = ComPtr<IAppxBlockMapFilesEnumerator>::Make<AppxBlockMapFilesEnumerator>(
|
||||
self.Get(),
|
||||
self.Get(),
|
||||
std::move(GetFileNames(FileNameOptions::All))).Detach();
|
||||
});
|
||||
}
|
||||
|
|
|
@ -31,8 +31,8 @@ namespace xPlat {
|
|||
};
|
||||
|
||||
static const std::uint8_t PercentangeEncodingTableSize = 0x5E;
|
||||
static const std::vector<std::string> PercentangeEncoding = {
|
||||
"", "", "", "", "", "", "", "",
|
||||
static const std::vector<std::string> PercentangeEncoding =
|
||||
{ "", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
"", "", "", "", "", "", "", "",
|
||||
|
@ -47,8 +47,7 @@ namespace xPlat {
|
|||
};
|
||||
|
||||
static const std::map<std::string, char> EncodingToChar =
|
||||
{
|
||||
{"20", ' '}, {"21", '!'}, {"23", '#'}, {"24", '$'},
|
||||
{ {"20", ' '}, {"21", '!'}, {"23", '#'}, {"24", '$'},
|
||||
{"25", '%'}, {"26", '&'}, {"27", '\''}, {"28", '('},
|
||||
{"29", ')'}, {"25", '+'}, {"2B", '%'}, {"2C", ','},
|
||||
{"3B", ';'}, {"3D", '='}, {"40", '@'}, {"5B", '['},
|
||||
|
@ -59,19 +58,15 @@ namespace xPlat {
|
|||
{
|
||||
std::string result;
|
||||
for (std::uint32_t position = 0; position < fileName.length(); ++position)
|
||||
{
|
||||
std::uint8_t index = static_cast<std::uint8_t>(fileName[position]);
|
||||
{ std::uint8_t index = static_cast<std::uint8_t>(fileName[position]);
|
||||
if(fileName[position] < PercentangeEncodingTableSize && index < PercentangeEncoding.size() && !PercentangeEncoding[index].empty())
|
||||
{
|
||||
result += PercentangeEncoding[index];
|
||||
{ result += PercentangeEncoding[index];
|
||||
}
|
||||
else if (fileName[position] == '\\') // Remove Windows file name
|
||||
{
|
||||
result += '/';
|
||||
else if (fileName[position] == '\\') // Remove Windows file separator.
|
||||
{ result += '/';
|
||||
}
|
||||
else
|
||||
{
|
||||
result += fileName[position];
|
||||
{ result += fileName[position];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -81,27 +76,18 @@ namespace xPlat {
|
|||
{
|
||||
std::string result;
|
||||
for (std::uint32_t i = 0; i < fileName.length(); ++i)
|
||||
{
|
||||
if(fileName[i] == '%')
|
||||
{
|
||||
auto found = EncodingToChar.find(fileName.substr(i+1, 2));
|
||||
{ if(fileName[i] == '%')
|
||||
{ auto found = EncodingToChar.find(fileName.substr(i+1, 2));
|
||||
if (found != EncodingToChar.end())
|
||||
{
|
||||
result += found->second;
|
||||
{ result += found->second;
|
||||
}
|
||||
else
|
||||
{
|
||||
throw Exception(Error::AppxUnknownFileNameEncoding, fileName);
|
||||
{ throw Exception(Error::AppxUnknownFileNameEncoding, fileName);
|
||||
}
|
||||
i += 2;
|
||||
}
|
||||
else if (fileName[i] == '/') // Windows file name
|
||||
{
|
||||
result += '\\';
|
||||
}
|
||||
else
|
||||
{
|
||||
result += fileName[i];
|
||||
{ result += fileName[i];
|
||||
}
|
||||
}
|
||||
return result;
|
||||
|
@ -210,13 +196,11 @@ namespace xPlat {
|
|||
{
|
||||
std::string targetName;
|
||||
if (options & APPX_PACKUNPACK_OPTION_CREATEPACKAGESUBFOLDER)
|
||||
{
|
||||
throw Exception(Error::NotImplemented);
|
||||
{ throw Exception(Error::NotImplemented);
|
||||
//targetName = GetAppxManifest()->GetPackageFullName() + to->GetPathSeparator() + fileName;
|
||||
}
|
||||
else
|
||||
{
|
||||
targetName = fileName;
|
||||
{ targetName = DecodeFileName(fileName);
|
||||
}
|
||||
|
||||
auto targetFile = to->OpenFile(targetName, xPlat::FileStream::Mode::WRITE_UPDATE);
|
||||
|
|
Двоичные данные
test/appx/BlockMap/TODAVIANO/Signature_in_BlockMap.appx
Двоичные данные
test/appx/BlockMap/TODAVIANO/Signature_in_BlockMap.appx
Двоичный файл не отображается.
Двоичный файл не отображается.
Загрузка…
Ссылка в новой задаче