Files
raven/testdata/native_loader/appx_client.c
T
Raymond Bennett aadce22347 feat: read .appx and .msix packages through the packaging API
raven-appx.dll serves the AppxFactory and AppxBundleFactory classes
that package managers open packages with (winget reads its source
package this way). A package reader checks that the package is a ZIP
archive (ZIP64 records included) with a manifest, a block map and
content types, and that the block map lists every payload file with its
size; a file's content is then inflated and checked against the block
map's hash one 64 KiB block at a time as it is read, failing with
APPX_E_CORRUPT_CONTENT where it differs. The manifest reader gives the
identity with the package's full and family names, properties,
dependencies, target device families, capabilities, resources,
prerequisites and applications; the block map reader its files and
blocks. A bundle opened as a package, or a package as a bundle, answers
APPX_E_MISSING_REQUIRED_FILE, as programs tell the two apart.

The component is built by provision-installer.py beside cabinet.dll, on
the same zlib, and reads XML with xmllite; the factories are registered
in both registry views. Writing packages, reading bundles, block maps
validated against a signature, IAppxDigestProvider and the later
manifest reader interfaces answer E_NOTIMPL.

The test writes its packages with appx_fixtures.py, as package writers
make them, and reads them back in 64-bit and 32-bit processes.

Assisted-by: Claude Code:claude-opus-5-5
AI-Scope: the component, its provisioning, registry and packaging wiring, the fixture generator and the appx_client test
2026-10-31 13:57:58 -07:00

247 lines
13 KiB
C

#define WIN32_LEAN_AND_MEAN
#define INITGUID
#define CINTERFACE
#define COBJMACROS
#include <windows.h>
#include <objidl.h>
#include <shlwapi.h>
#include "appxpackaging.h"
/* The generated header has no method macros: a call goes through the vtable. */
#define CALL(object, method, ...) ((object)->lpVtbl->method((object), ##__VA_ARGS__))
static WCHAR directory[MAX_PATH];
/* A fixture beside the client, which appx_fixtures.py wrote. */
static IStream *open_fixture(const WCHAR *name)
{
WCHAR path[MAX_PATH];
lstrcpyW(path, directory);
lstrcatW(path, name);
IStream *stream = NULL;
return SUCCEEDED(SHCreateStreamOnFileEx(path, STGM_READ | STGM_SHARE_DENY_WRITE, 0, FALSE, NULL, &stream)) ? stream : NULL;
}
static HRESULT open_package(IAppxFactory *factory, const WCHAR *name, IAppxPackageReader **reader)
{
*reader = NULL;
IStream *stream = open_fixture(name);
if (!stream)
return E_FAIL;
const HRESULT result = CALL(factory, CreatePackageReader, stream, reader);
CALL(stream, Release);
return result;
}
/* Whether a string the API returned is the expected one; it is freed either way. */
static BOOL is(LPWSTR value, const WCHAR *expected)
{
const BOOL same = value && expected ? !lstrcmpW(value, expected) : value == expected;
CoTaskMemFree(value);
return same;
}
/* The bytes of Public\index.db, as appx_fixtures.py makes them. */
static BYTE pattern(UINT32 index) { return (BYTE)((index % 251) ^ ((index >> 9) & 0xff)); }
#define INDEX_SIZE (2 * 65536 + 12345)
/* The packaging API through COM: a package's manifest, files and block map; ZIP64 records; packages and bundles told apart; and content
* that differs from the block map refused where it is read. */
int raven_entry(void)
{
GetModuleFileNameW(NULL, directory, MAX_PATH);
for (WCHAR *at = directory + lstrlenW(directory); at > directory && at[-1] != '\\'; --at)
at[-1] = 0;
if (FAILED(CoInitializeEx(NULL, COINIT_MULTITHREADED))) return 1;
IAppxFactory *factory = NULL;
IAppxBundleFactory *bundles = NULL;
if (CoCreateInstance(&CLSID_AppxFactory, NULL, CLSCTX_INPROC_SERVER, &IID_IAppxFactory, (void **)&factory) != S_OK) return 2;
if (CoCreateInstance(&CLSID_AppxBundleFactory, NULL, CLSCTX_INPROC_SERVER, &IID_IAppxBundleFactory, (void **)&bundles) != S_OK) return 3;
/* A package is not a bundle, and a bundle is not a package: what tells a program which of the two readers to use. */
IStream *stream = open_fixture(L"appx-good.msix");
IAppxBundleReader *bundle = NULL;
if (!stream || CALL(bundles, CreateBundleReader, stream, &bundle) != APPX_E_MISSING_REQUIRED_FILE || bundle) return 4;
CALL(stream, Release);
IAppxPackageReader *reader = NULL;
if (open_package(factory, L"appx-bundle.msixbundle", &reader) != APPX_E_MISSING_REQUIRED_FILE || reader) return 5;
/* Every part other than the footprint files is in the block map. */
if (open_package(factory, L"appx-unlisted.msix", &reader) != APPX_E_INVALID_BLOCKMAP || reader) return 6;
if (open_package(factory, L"appx-good.msix", &reader) != S_OK) return 7;
/* The identity, and the names that follow from it: the publisher ID of this publisher is the one Microsoft's packages carry. */
IAppxManifestReader *manifest = NULL;
IAppxManifestPackageId *id = NULL;
LPWSTR text = NULL;
UINT64 version = 0;
APPX_PACKAGE_ARCHITECTURE architecture = 99;
BOOL same = FALSE;
if (CALL(reader, GetManifest, &manifest) != S_OK || CALL(manifest, GetPackageId, &id) != S_OK) return 8;
if (CALL(id, GetName, &text) != S_OK || !is(text, L"Raven.Test")) return 9;
if (CALL(id, GetVersion, &version) != S_OK || version != 0x0001000200030004ull) return 10;
if (CALL(id, GetArchitecture, &architecture) != S_OK || architecture != APPX_PACKAGE_ARCHITECTURE_X64) return 11;
if (CALL(id, GetPackageFullName, &text) != S_OK || !is(text, L"Raven.Test_1.2.3.4_x64__8wekyb3d8bbwe")) return 12;
if (CALL(id, GetPackageFamilyName, &text) != S_OK || !is(text, L"Raven.Test_8wekyb3d8bbwe")) return 13;
if (CALL(id, GetResourceId, &text) != S_OK || !is(text, NULL)) return 14;
if (CALL(id, ComparePublisher, L"CN=Microsoft Corporation, O=Microsoft Corporation, L=Redmond, S=Washington, C=US", &same) != S_OK || !same) return 15;
if (CALL(id, ComparePublisher, L"CN=Someone Else", &same) != S_OK || same) return 16;
CALL(id, Release);
/* Properties, dependencies, the target device family, capabilities and applications. */
IAppxManifestProperties *properties = NULL;
BOOL flag = TRUE;
if (CALL(manifest, GetProperties, &properties) != S_OK) return 17;
if (CALL(properties, GetStringValue, L"DisplayName", &text) != S_OK || !is(text, L"Raven test package")) return 18;
if (CALL(properties, GetBoolValue, L"Framework", &flag) != S_OK || flag) return 19;
if (CALL(properties, GetStringValue, L"Nonsense", &text) != E_INVALIDARG) return 20;
CALL(properties, Release);
IAppxManifestPackageDependenciesEnumerator *dependencies = NULL;
IAppxManifestPackageDependency *dependency = NULL;
BOOL has = FALSE;
if (CALL(manifest, GetPackageDependencies, &dependencies) != S_OK || CALL(dependencies, GetHasCurrent, &has) != S_OK || !has) return 21;
if (CALL(dependencies, GetCurrent, &dependency) != S_OK || CALL(dependency, GetName, &text) != S_OK || !is(text, L"Microsoft.VCLibs.140.00.UWPDesktop"))
return 22;
if (CALL(dependency, GetMinVersion, &version) != S_OK || version != 0x000e00005e990000ull) return 23;
CALL(dependency, Release);
if (CALL(dependencies, MoveNext, &has) != S_OK || has || CALL(dependencies, GetCurrent, &dependency) == S_OK) return 24;
CALL(dependencies, Release);
IAppxManifestReader3 *manifest3 = NULL;
IAppxManifestTargetDeviceFamiliesEnumerator *families = NULL;
IAppxManifestTargetDeviceFamily *family = NULL;
if (CALL(manifest, QueryInterface, &IID_IAppxManifestReader3, (void **)&manifest3) != S_OK) return 25;
if (CALL(manifest3, GetTargetDeviceFamilies, &families) != S_OK || CALL(families, GetCurrent, &family) != S_OK) return 26;
if (CALL(family, GetName, &text) != S_OK || !is(text, L"Windows.Desktop")) return 27;
if (CALL(family, GetMinVersion, &version) != S_OK || version != 0x000a000045630000ull) return 28;
CALL(family, Release);
CALL(families, Release);
CALL(manifest3, Release);
APPX_CAPABILITIES capabilities = 0;
if (CALL(manifest, GetCapabilities, &capabilities) != S_OK || capabilities != APPX_CAPABILITY_INTERNET_CLIENT) return 29;
IAppxManifestResourcesEnumerator *resources = NULL;
if (CALL(manifest, GetResources, &resources) != S_OK || CALL(resources, GetCurrent, &text) != S_OK || !is(text, L"en-us")) return 30;
CALL(resources, Release);
IAppxManifestApplicationsEnumerator *applications = NULL;
IAppxManifestApplication *application = NULL;
if (CALL(manifest, GetApplications, &applications) != S_OK || CALL(applications, GetCurrent, &application) != S_OK) return 31;
if (CALL(application, GetAppUserModelId, &text) != S_OK || !is(text, L"Raven.Test_8wekyb3d8bbwe!App")) return 32;
if (CALL(application, GetStringValue, L"Executable", &text) != S_OK || !is(text, L"raven.exe")) return 33;
if (CALL(application, GetStringValue, L"DisplayName", &text) != S_OK || !is(text, L"Raven")) return 34;
CALL(application, Release);
CALL(applications, Release);
CALL(manifest, Release);
/* A deflated payload file of three blocks, read whole, from within its second block, and through a clone. */
IAppxFile *file = NULL;
UINT64 size = 0;
APPX_COMPRESSION_OPTION compression = 99;
if (CALL(reader, GetPayloadFile, L"Public\\index.db", &file) != S_OK) return 35;
if (CALL(file, GetName, &text) != S_OK || !is(text, L"Public\\index.db")) return 36;
if (CALL(file, GetSize, &size) != S_OK || size != INDEX_SIZE) return 37;
if (CALL(file, GetCompressionOption, &compression) != S_OK || compression != APPX_COMPRESSION_OPTION_NORMAL) return 38;
if (CALL(file, GetContentType, &text) != S_OK || !is(text, L"application/octet-stream")) return 39;
IStream *content = NULL;
static BYTE buffer[INDEX_SIZE + 16];
ULONG done = 0;
if (CALL(file, GetStream, &content) != S_OK) return 40;
if (CALL(content, Read, buffer, sizeof buffer, &done) != S_FALSE || done != INDEX_SIZE) return 41;
for (UINT32 index = 0; index < INDEX_SIZE; ++index)
if (buffer[index] != pattern(index)) return 42;
LARGE_INTEGER move;
move.QuadPart = 65536 + 4000;
if (CALL(content, Seek, move, STREAM_SEEK_SET, NULL) != S_OK || CALL(content, Read, buffer, 100, &done) != S_OK || done != 100) return 43;
for (UINT32 index = 0; index < 100; ++index)
if (buffer[index] != pattern(65536 + 4000 + index)) return 44;
IStream *clone = NULL;
if (CALL(content, Clone, &clone) != S_OK || CALL(clone, Read, buffer, 10, &done) != S_OK || buffer[0] != pattern(65536 + 4100)) return 45;
CALL(clone, Release);
STATSTG stat;
if (CALL(content, Stat, &stat, STATFLAG_NONAME) != S_OK || stat.cbSize.QuadPart != INDEX_SIZE) return 46;
CALL(file, Release);
/* The same file named with '/'; a stored file whose part name is percent-encoded; names that are not payload files. */
if (CALL(reader, GetPayloadFile, L"public/INDEX.db", &file) != S_OK) return 47;
CALL(file, Release);
if (CALL(reader, GetPayloadFile, L"Docs\\read me.txt", &file) != S_OK || CALL(file, GetStream, &clone) != S_OK) return 48;
if (CALL(clone, Read, buffer, sizeof buffer, &done) != S_FALSE || done != 5 || buffer[0] != 'h' || buffer[4] != 'o') return 49;
if (CALL(file, GetCompressionOption, &compression) != S_OK || compression != APPX_COMPRESSION_OPTION_NONE) return 50;
CALL(clone, Release);
CALL(file, Release);
if (SUCCEEDED(CALL(reader, GetPayloadFile, L"AppxManifest.xml", &file)) || SUCCEEDED(CALL(reader, GetPayloadFile, L"missing.txt", &file))) return 51;
IAppxFilesEnumerator *files = NULL;
UINT32 count = 0;
if (CALL(reader, GetPayloadFiles, &files) != S_OK) return 52;
for (CALL(files, GetHasCurrent, &has); has; CALL(files, MoveNext, &has))
++count;
if (count != 3) return 53;
CALL(files, Release);
/* Footprint files: the manifest is there with its content type, an unsigned package has no signature. */
if (CALL(reader, GetFootprintFile, APPX_FOOTPRINT_FILE_TYPE_MANIFEST, &file) != S_OK) return 54;
if (CALL(file, GetContentType, &text) != S_OK || !is(text, L"application/vnd.ms-appx.manifest+xml")) return 55;
CALL(file, Release);
if (CALL(reader, GetFootprintFile, APPX_FOOTPRINT_FILE_TYPE_BLOCKMAP, &file) != S_OK || CALL(file, GetContentType, &text) != S_OK ||
!is(text, L"application/vnd.ms-appx.blockmap+xml"))
return 56;
CALL(file, Release);
if (SUCCEEDED(CALL(reader, GetFootprintFile, APPX_FOOTPRINT_FILE_TYPE_SIGNATURE, &file))) return 57;
/* The block map: a file's blocks, its local header, and a stream checked against it. */
IAppxBlockMapReader *map = NULL;
IAppxBlockMapFile *mapped = NULL;
IAppxBlockMapBlocksEnumerator *blocks = NULL;
UINT32 header = 0;
if (CALL(reader, GetBlockMap, &map) != S_OK || CALL(map, GetFile, L"Public\\index.db", &mapped) != S_OK) return 58;
if (CALL(mapped, GetLocalFileHeaderSize, &header) != S_OK || header != 30 + 15) return 59;
if (CALL(mapped, GetBlocks, &blocks) != S_OK) return 60;
count = 0;
for (CALL(blocks, GetHasCurrent, &has); has; CALL(blocks, MoveNext, &has)) {
IAppxBlockMapBlock *block = NULL;
UINT32 hash_size = 0, stored = 0;
BYTE *hash = NULL;
if (CALL(blocks, GetCurrent, &block) != S_OK || CALL(block, GetHash, &hash_size, &hash) != S_OK || hash_size != 32) return 61;
if (CALL(block, GetCompressedSize, &stored) != S_OK || !stored) return 62;
CoTaskMemFree(hash);
CALL(block, Release);
++count;
}
if (count != 3) return 63;
CALL(blocks, Release);
if (CALL(mapped, ValidateFileHash, content, &same) != S_OK || !same) return 65;
CALL(content, Release);
CALL(mapped, Release);
CALL(map, Release);
if (CALL(reader, Release)) return 66;
/* A manifest read on its own. */
stream = open_fixture(L"appx-manifest.xml");
if (!stream || CALL(factory, CreateManifestReader, stream, &manifest) != S_OK || CALL(manifest, GetPackageId, &id) != S_OK) return 67;
if (CALL(id, GetPackageFamilyName, &text) != S_OK || !is(text, L"Raven.Test_8wekyb3d8bbwe")) return 68;
CALL(id, Release);
CALL(manifest, Release);
CALL(stream, Release);
/* The same package with ZIP64 records. */
if (open_package(factory, L"appx-zip64.msix", &reader) != S_OK || CALL(reader, GetPayloadFile, L"Public\\index.db", &file) != S_OK) return 69;
if (CALL(file, GetStream, &content) != S_OK || CALL(content, Read, buffer, sizeof buffer, &done) != S_FALSE || done != INDEX_SIZE) return 70;
if (buffer[INDEX_SIZE - 1] != pattern(INDEX_SIZE - 1)) return 71;
CALL(content, Release);
CALL(file, Release);
CALL(reader, Release);
/* Content that differs from the block map opens, and fails where it is read: the deflated file's first block reads, its second does
* not; the stored file fails at once. */
if (open_package(factory, L"appx-tampered.msix", &reader) != S_OK) return 72;
if (CALL(reader, GetPayloadFile, L"Public\\index.db", &file) != S_OK || CALL(file, GetStream, &content) != S_OK) return 73;
if (CALL(content, Read, buffer, 65536, &done) != S_OK || done != 65536) return 74;
if (CALL(content, Read, buffer, 100, &done) != APPX_E_CORRUPT_CONTENT) return 75;
CALL(content, Release);
CALL(file, Release);
if (CALL(reader, GetPayloadFile, L"Assets\\Logo.png", &file) != S_OK || CALL(file, GetStream, &content) != S_OK) return 76;
if (CALL(content, Read, buffer, 4, &done) != APPX_E_CORRUPT_CONTENT) return 77;
CALL(content, Release);
CALL(file, Release);
CALL(reader, Release);
CALL(bundles, Release);
CALL(factory, Release);
CoUninitialize();
return 0;
}