This MR introduces an initial implementation for `ReadPackageFromStream` in order to be able to decompress/read OPC packages, and enumerate their parts.
This is required for supporting AppX packages, which themselves are OPC packages as well. As such, the code does not implement support for reading relationships or interleaved archives, as AppX does not support either (`APPX_E_RELATIONSHIPS_NOT_ALLOWED`, `APPX_E_INTERLEAVING_NOT_ALLOWED`).
-- v2: opcservices: Implement IOpcPartSet::GetEnumerator for opened packages. opcservices: Implement IOpcPart::GetContentType for opened packages. opcservices: Implement IOpcPart::GetContentStream for opened packages.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/tests/opcservices.c | 178 +++++++++++++++++++++++++++ include/opcbase.idl | 1 + 2 files changed, 179 insertions(+)
diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 3fa9c732e42..34ef87ad80b 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1242,6 +1242,183 @@ static void test_write_package(void) IOpcPackage_Release(package); }
+static void test_read_package(void) +{ + static struct { + const WCHAR *type; + const WCHAR *uri; + OPC_COMPRESSION_OPTIONS opt; + BOOL found; + } parts[] = { + {L"type1/subtype1", L"/entry1.11", OPC_COMPRESSION_NONE}, + {L"type1/subtype2", L"/entry2.12", OPC_COMPRESSION_NORMAL}, + {L"type2/subtype1", L"/entry3.21", OPC_COMPRESSION_MAXIMUM}, + {L"type2/subtype2", L"/entry4.22", OPC_COMPRESSION_FAST}, + {L"type1/subtype1", L"/entry5.11", OPC_COMPRESSION_SUPERFAST}, + {L"type1/subtype2", L"/entry6.12", OPC_COMPRESSION_NONE}, + {L"type2/subtype1", L"/entry7.21", OPC_COMPRESSION_NORMAL}, + {L"type2/subtype2", L"/entry8.22", OPC_COMPRESSION_MAXIMUM}, + {L"type1/subtype1", L"/dir1/entry1.11", OPC_COMPRESSION_FAST} + }; + IOpcPartEnumerator *partenum; + LARGE_INTEGER start = {0}; + IOpcFactory *factory; + IOpcPackage *package; + IOpcPartSet *partset; + IOpcPartUri *uri; + IStream *stream; + IOpcPart *part; + BYTE buf[256]; + HRESULT hr; + BOOL next; + int i; + + factory = create_factory(); + hr = IOpcFactory_CreatePackage(factory, &package); + ok(hr == S_OK || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#lx.\n", hr); + if (FAILED(hr)) goto done; + + hr = IOpcPackage_GetPartSet(package, &partset); + ok(hr == S_OK, "Failed to create a part set, hr %#lx.\n", hr); + + for (i = 0; i < ARRAY_SIZE(parts); i++) + { + ULONG read = 0; + + memset(buf, i, sizeof(buf)); + winetest_push_context("parts[%d]", i); + + hr = IOpcFactory_CreatePartUri(factory, parts[i].uri, &uri); + ok(hr == S_OK, "Failed to create part uri, hr %#lx.\n", hr); + hr = IOpcPartSet_CreatePart(partset, uri, parts[i].type, parts[i].opt, &part); + IOpcPartUri_Release(uri); + ok(hr == S_OK, "Failed to create a part, hr %#lx.\n", hr); + hr = IOpcPart_GetContentStream(part, &stream); + IOpcPart_Release(part); + ok(hr == S_OK, "Failed to get content stream, hr %#lx.\n", hr); + hr = IStream_Write(stream, buf, sizeof(buf), &read); + ok(hr == S_OK, "Failed to write to stream, hr %#lx.\n", hr); + ok(read == sizeof(buf), "Got read %lu != %Iu\n", read, sizeof(buf)); + IStream_Release(stream); + + winetest_pop_context(); + } + + hr = CreateStreamOnHGlobal(NULL, TRUE, &stream); + ok(hr == S_OK, "Failed to create a stream, hr %#lx.\n", hr); + hr = IOpcFactory_WritePackageToStream(factory, package, OPC_WRITE_FORCE_ZIP32, stream); + ok(hr == S_OK, "Failed to write package, hr %#lx.\n", hr); + IOpcPackage_Release(package); + hr = IStream_Seek(stream, start, STREAM_SEEK_SET, NULL); + ok(hr == S_OK, "Failed to seek stream, hr %#lx.\n", hr); + + hr = IOpcFactory_ReadPackageFromStream(factory, stream, OPC_READ_DEFAULT, &package); + todo_wine ok(hr == S_OK, "Failed to read package, hr %#lx.\n", hr); + IStream_Release(stream); + if (FAILED(hr)) + { + IOpcPackage_Release(package); + goto done; + } + hr = IOpcPackage_GetPartSet(package, &partset); + ok(hr == S_OK, "Failed to get part set, hr %#lx.\n", hr); + + for (i = 0; i < ARRAY_SIZE(parts); i++) + { + OPC_COMPRESSION_OPTIONS opt = 0xdeadbeef; + WCHAR *type = NULL; + ULONG read = 0; + + winetest_push_context("parts[%d]", i); + + hr = IOpcFactory_CreatePartUri(factory, parts[i].uri, &uri); + ok(hr == S_OK, "Failed to create part uri, hr %#lx.\n", hr); + hr = IOpcPartSet_GetPart(partset, uri, &part); + IOpcPartUri_Release(uri); + todo_wine ok(hr == S_OK, "Failed to get part, hr %#lx.\n", hr); + if (FAILED(hr)) continue; + hr = IOpcPart_GetContentType(part, &type); + todo_wine ok(hr == S_OK, "Failed to get content type, hr %#lx.\n", hr); + if (SUCCEEDED(hr)) + { + ok(!wcscmp(type, parts[i].type), "got type %s != %s.\n", debugstr_w(type), debugstr_w(parts[i].type)); + CoTaskMemFree(type); + } + hr = IOpcPart_GetCompressionOptions(part, &opt); + todo_wine ok(hr == S_OK, "Failed to get compression options, hr %#lx.\n", hr); + todo_wine ok(opt == parts[i].opt, "Got opt %d != %d.\n", opt, parts[i].opt); + hr = IOpcPart_GetContentStream(part, &stream); + IOpcPart_Release(part); + todo_wine ok(hr == S_OK, "Failed to get content stream, hr %#lx.\n", hr); + if (SUCCEEDED(hr)) + { + BYTE exp_buf[256]; + + memset(buf, 0, sizeof(buf)); + memset(exp_buf, i, sizeof(exp_buf)); + hr = IStream_Read(stream, buf, sizeof(buf), &read); + ok(hr == S_OK, "Failed to read from stream, hr %#lx.\n", hr); + ok(read == sizeof(buf), "Got read %lu != %Iu.\n", read, sizeof(buf)); + ok(!memcmp(buf, exp_buf, read), "Got mismatching data.\n"); + IStream_Release(stream); + } + + winetest_pop_context(); + } + + hr = IOpcPartSet_GetEnumerator(partset, &partenum); + todo_wine ok(hr == S_OK, "Failed to get part enumerator, hr %#lx.\n", hr); + if (FAILED(hr)) + { + IOpcPartSet_Release(partset); + IOpcPackage_Release(package); + goto done; + } + + hr = IOpcPartEnumerator_GetCurrent(partenum, &part); + ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#lx.\n", hr); + for(;;) + { + BSTR str; + + hr = IOpcPartEnumerator_MoveNext(partenum, &next); + ok(hr == S_OK, "MoveNext failed, hr %#lx.\n", hr); + if (!next) break; + hr = IOpcPartEnumerator_GetCurrent(partenum, &part); + ok(hr == S_OK, "GetCurrent failed, hr %#lx.\n", hr); + hr = IOpcPart_GetName(part, &uri); + IOpcPart_Release(part); + ok(hr == S_OK, "GetName failed, hr %#lx.\n", hr); + hr = IOpcPartUri_GetAbsoluteUri(uri, &str); + IOpcPartUri_Release(uri); + ok(hr == S_OK, "GetAbsoluteUri failed, hr %#lx.\n", hr); + for (i = 0; i < ARRAY_SIZE(parts); i++) + { + if (!wcscmp(parts[i].uri, str)) + { + ok(!parts[i].found, "Unexpected duplicate part %s.\n", debugstr_w(str)); + parts[i].found = TRUE; + break; + } + } + SysFreeString(str); + } + + hr = IOpcPartEnumerator_GetCurrent(partenum, &part); + ok(hr == OPC_E_ENUM_INVALID_POSITION, "Unexpected hr %#lx.\n", hr); + hr = IOpcPartEnumerator_MoveNext(partenum, &next); + ok(hr == OPC_E_ENUM_CANNOT_MOVE_NEXT, "Unexpected hr %#lx.\n", hr); + + for (i = 0; i < ARRAY_SIZE(parts); i++) + ok(parts[i].found, "Package does not contain part %s.\n", debugstr_w(parts[i].uri)); + + IOpcPartEnumerator_Release(partenum); + IOpcPartSet_Release(partset); + IOpcPackage_Release(package); +done: + IOpcFactory_Release(factory); +} + START_TEST(opcservices) { IOpcFactory *factory; @@ -1266,6 +1443,7 @@ START_TEST(opcservices) test_combine_uri(); test_create_part_uri(); test_write_package(); + test_read_package();
IOpcFactory_Release(factory);
diff --git a/include/opcbase.idl b/include/opcbase.idl index 3df2df80635..fcf641481e2 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -49,4 +49,5 @@ cpp_quote("#define OPC_E_DUPLICATE_RELATIONSHIP MAKE_HRESULT(SEVERITY_ERROR, FAC cpp_quote("#define OPC_E_NO_SUCH_PART MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x18)") cpp_quote("#define OPC_E_NO_SUCH_RELATIONSHIP MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x48)") cpp_quote("#define OPC_E_ENUM_COLLECTION_CHANGED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x50)") +cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_NEXT MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x51)") cpp_quote("#define OPC_E_ENUM_INVALID_POSITION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x53)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 126 +++++++++++++++++++++++++++ dlls/opcservices/factory.c | 12 ++- dlls/opcservices/opc_private.h | 2 + dlls/opcservices/package.c | 6 ++ dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 6 files changed, 147 insertions(+), 3 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 748293d7acd..fab203a1664 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -85,6 +85,7 @@ struct central_directory_end DWORD directory_offset; WORD comment_length; }; + #pragma pack(pop)
#define CENTRAL_DIR_SIGNATURE 0x02014b50 @@ -346,3 +347,128 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path,
return S_OK; } + +struct zip_part_set +{ + IOpcPartSet IOpcPartSet_iface; + LONG ref; +}; + +static inline struct zip_part_set *impl_from_IOpcPartSet(IOpcPartSet *iface) +{ + return CONTAINING_RECORD(iface, struct zip_part_set, IOpcPartSet_iface); +} + +static HRESULT WINAPI zip_part_set_QueryInterface(IOpcPartSet *iface, REFIID iid, void **out) +{ + struct zip_part_set *part_set = impl_from_IOpcPartSet(iface); + + TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IOpcPartSet)) + { + *out = &part_set->IOpcPartSet_iface; + IOpcPartSet_AddRef(iface); + return S_OK; + } + + *out = NULL; + WARN("Unsupported interface %s.\n", debugstr_guid(iid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI zip_part_set_AddRef(IOpcPartSet *iface) +{ + struct zip_part_set *part_set = impl_from_IOpcPartSet(iface); + + TRACE("(%p)\n", iface); + + return InterlockedIncrement(&part_set->ref); +} + +static ULONG WINAPI zip_part_set_Release(IOpcPartSet *iface) +{ + struct zip_part_set *part_set = impl_from_IOpcPartSet(iface); + ULONG ref = InterlockedDecrement(&part_set->ref); + + TRACE("(%p)\n", iface); + + if (!ref) + free(part_set); + return ref; +} + +static HRESULT WINAPI zip_part_set_GetPart(IOpcPartSet *iface, IOpcPartUri *name, IOpcPart **part) +{ + FIXME("(%p, %p, %p) stub\n", iface, name, part); + + *part = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_set_CreatePart(IOpcPartSet *iface, IOpcPartUri *name, const WCHAR *type, + OPC_COMPRESSION_OPTIONS opts, IOpcPart **out) +{ + FIXME("(%p, %p, %p, %d, %p)\n", iface, name, debugstr_w(type), opts, out); + + *out = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_set_DeletePart(IOpcPartSet *iface, IOpcPartUri *name) +{ + FIXME("(%p, %p)", iface, name); + + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_set_PartExists(IOpcPartSet *iface, IOpcPartUri *name, BOOL *exists) +{ + FIXME("(%p, %p, %p) stub\n", iface, name, exists); + + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_set_GetEnumerator(IOpcPartSet *iface, IOpcPartEnumerator **out) +{ + FIXME("(%p, %p) stub\n", iface, out); + + *out = NULL; + return E_NOTIMPL; +} + +static const IOpcPartSetVtbl zip_part_set_vtbl = +{ + /* IUnknown */ + zip_part_set_QueryInterface, + zip_part_set_AddRef, + zip_part_set_Release, + /* IPartSet */ + zip_part_set_GetPart, + zip_part_set_CreatePart, + zip_part_set_DeletePart, + zip_part_set_PartExists, + zip_part_set_GetEnumerator, +}; + +static HRESULT zip_part_set_create(IOpcPartSet **out) +{ + struct zip_part_set *part_set; + + if (!(part_set = calloc(1, sizeof(*part_set)))) + return E_OUTOFMEMORY; + + *out = &part_set->IOpcPartSet_iface; + part_set->IOpcPartSet_iface.lpVtbl = &zip_part_set_vtbl; + part_set->ref = 1; + *out = &part_set->IOpcPartSet_iface; + + return S_OK; +} + +HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, IOpcPartSet **part_set) +{ + FIXME("(%p, %p, %p) stub\n", factory, stream, part_set); + + return zip_part_set_create(part_set); +} diff --git a/dlls/opcservices/factory.c b/dlls/opcservices/factory.c index fcebb977c1d..5e9cab65471 100644 --- a/dlls/opcservices/factory.c +++ b/dlls/opcservices/factory.c @@ -375,9 +375,17 @@ static HRESULT WINAPI opc_factory_CreatePackage(IOpcFactory *iface, IOpcPackage static HRESULT WINAPI opc_factory_ReadPackageFromStream(IOpcFactory *iface, IStream *stream, OPC_READ_FLAGS flags, IOpcPackage **package) { - FIXME("iface %p, stream %p, flags %#x, package %p stub!\n", iface, stream, flags, package); + IOpcPartSet *part_set; + HRESULT hr;
- return E_NOTIMPL; + TRACE("iface %p, stream %p, flags %#x, package %p\n", iface, stream, flags, package); + + if (flags) + FIXME("Unsupported flags: %#x\n", flags); + + if (FAILED(hr = compress_open_archive(iface, stream, &part_set))) + return hr; + return opc_package_create_with_partset(iface, part_set, package); }
static HRESULT WINAPI opc_factory_WritePackageToStream(IOpcFactory *iface, IOpcPackage *package, OPC_WRITE_FLAGS flags, diff --git a/dlls/opcservices/opc_private.h b/dlls/opcservices/opc_private.h index cfd8ee864b2..045716cf3e6 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -56,6 +56,7 @@ struct opc_uri };
extern HRESULT opc_package_create(IOpcFactory *factory, IOpcPackage **package); +extern HRESULT opc_package_create_with_partset(IOpcFactory *factory, IOpcPartSet *part_set, IOpcPackage **package); extern HRESULT opc_part_uri_create(IUri *uri, struct opc_uri *source_uri, IOpcPartUri **part_uri); extern HRESULT opc_root_uri_create(IOpcUri **opc_uri);
@@ -66,3 +67,4 @@ extern HRESULT compress_create_archive(IStream *output, struct zip_archive **arc extern HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, IStream *content, OPC_COMPRESSION_OPTIONS options); extern void compress_finalize_archive(struct zip_archive *archive); +extern HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, IOpcPartSet **part_set); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index e080b386c61..29fd766ccb6 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -1511,6 +1511,11 @@ static const IOpcPackageVtbl opc_package_vtbl = };
HRESULT opc_package_create(IOpcFactory *factory, IOpcPackage **out) +{ + return opc_package_create_with_partset(factory, NULL, out); +} + +HRESULT opc_package_create_with_partset(IOpcFactory *factory, IOpcPartSet *part_set, IOpcPackage **out) { struct opc_package *package; HRESULT hr; @@ -1526,6 +1531,7 @@ HRESULT opc_package_create(IOpcFactory *factory, IOpcPackage **out) free(package); return hr; } + if (part_set) IOpcPartSet_AddRef((package->part_set = part_set));
*out = &package->IOpcPackage_iface; TRACE("Created package %p.\n", *out); diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 34ef87ad80b..8e878dff669 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1313,7 +1313,7 @@ static void test_read_package(void) ok(hr == S_OK, "Failed to seek stream, hr %#lx.\n", hr);
hr = IOpcFactory_ReadPackageFromStream(factory, stream, OPC_READ_DEFAULT, &package); - todo_wine ok(hr == S_OK, "Failed to read package, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to read package, hr %#lx.\n", hr); IStream_Release(stream); if (FAILED(hr)) { diff --git a/include/opcbase.idl b/include/opcbase.idl index fcf641481e2..6f86045561e 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -51,3 +51,5 @@ cpp_quote("#define OPC_E_NO_SUCH_RELATIONSHIP MAKE_HRESULT(SEVERITY_ERROR, FACIL cpp_quote("#define OPC_E_ENUM_COLLECTION_CHANGED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x50)") cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_NEXT MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x51)") cpp_quote("#define OPC_E_ENUM_INVALID_POSITION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x53)") +cpp_quote("#define OPC_E_ZIP_CORRUPTED_ARCHIVE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1002)") +cpp_quote("#define OPC_E_ZIP_DUPLICATE_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x100b)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 35 +++++++++++++++++++++++++++++++++-- 1 file changed, 33 insertions(+), 2 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index fab203a1664..d9251a20893 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -96,6 +96,11 @@ struct central_directory_end
enum entry_flags { + DEFLATE_NORMAL = 0x0, + DEFLATE_MAX = 0x2, + DEFLATE_FAST = 0x4, + DEFLATE_SUPERFAST = 0x6, + DEFLATE_LEVEL_MASK = 0x6, USE_DATA_DESCRIPTOR = 0x8, };
@@ -291,8 +296,34 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, /* Local header */ local_header.signature = LOCAL_HEADER_SIGNATURE; local_header.version = VERSION; - local_header.flags = USE_DATA_DESCRIPTOR; - local_header.method = 8; /* Z_DEFLATED */ + if (options == OPC_COMPRESSION_NONE) + { + local_header.method = 0; + local_header.flags = 0; + } + else + { + local_header.method = 8; /* Z_DEFLATED */ + switch (options) + { + case OPC_COMPRESSION_MAXIMUM: + local_header.flags = DEFLATE_MAX; + break; + case OPC_COMPRESSION_FAST: + local_header.flags = DEFLATE_FAST; + break; + case OPC_COMPRESSION_SUPERFAST: + local_header.flags = DEFLATE_SUPERFAST; + break; + default: + WARN("Unsupported compression options %d.\n", options); + case OPC_COMPRESSION_NORMAL: + local_header.flags = DEFLATE_NORMAL; + break; + } + } + + local_header.flags |= USE_DATA_DESCRIPTOR; local_header.mtime = archive->mtime; local_header.crc32 = 0; local_header.compressed_size = 0;
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 473 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 6 +- 2 files changed, 468 insertions(+), 11 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index d9251a20893..7098734ab3a 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -18,6 +18,7 @@
#define COBJMACROS
+#include <stdint.h> #include <stdarg.h> #include <zlib.h>
@@ -74,6 +75,16 @@ struct central_directory_header DWORD local_file_offset; };
+struct zip64_extended_field +{ + WORD signature; + WORD size; + ULONG64 uncompressed_size; + ULONG64 compressed_size; + ULONG64 local_file_offset; + DWORD diskid; +}; + struct central_directory_end { DWORD signature; @@ -86,14 +97,45 @@ struct central_directory_end WORD comment_length; };
+struct central_directory_end_locator +{ + DWORD signature; + DWORD diskid; + ULONG64 end_directory_offset; + DWORD num_disks; +}; + +struct central_directory_end64 +{ + DWORD signature; + ULONG64 size; + WORD version; + WORD version_needed; + DWORD diskid; + DWORD firstdisk; + ULONG64 records_num; + ULONG64 records_total; + ULONG64 directory_size; + ULONG64 directory_offset; +}; #pragma pack(pop)
#define CENTRAL_DIR_SIGNATURE 0x02014b50 #define LOCAL_HEADER_SIGNATURE 0x04034b50 #define DIRECTORY_END_SIGNATURE 0x06054b50 +#define DIRECTORY_END_LOCATOR_SIGNATURE 0x07064b50 +#define DIRECTORY_END64_SIGNATURE 0x06064b50 #define DATA_DESCRIPTOR_SIGNATURE 0x08074b50 +#define ZIP64_EXTENDED_INFO_SIGNATURE 0x00000001 #define VERSION 20
+static const OPC_COMPRESSION_OPTIONS deflate_opts[] = { + OPC_COMPRESSION_NORMAL, + OPC_COMPRESSION_MAXIMUM, + OPC_COMPRESSION_FAST, + OPC_COMPRESSION_SUPERFAST +}; + enum entry_flags { DEFLATE_NORMAL = 0x0, @@ -379,9 +421,36 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, return S_OK; }
+struct zip_entry +{ + OPC_COMPRESSION_OPTIONS opt; + ULARGE_INTEGER local_file_offset; +}; + +static const char *debugstr_zip_entry(const struct zip_entry *entry) +{ + static const char *opts[] = { + "OPC_COMPRESSION_NONE", + "OPC_COMPRESSION_NORMAL", + "OPC_COMPRESSION_MAXIMUM", + "OPC_COMPRESSION_FAST", + "OPC_COMPRESSION_SUPERFAST" + }; + const char *opt_str; + + if (entry->opt + 1 < ARRAY_SIZE(opts)) + opt_str = opts[entry->opt + 1]; + else + opt_str = wine_dbg_sprintf("%d", entry->opt); + + return wine_dbg_sprintf("{%s, %#I64x}", opt_str, entry->local_file_offset.QuadPart); +} + struct zip_part_set { IOpcPartSet IOpcPartSet_iface; + IOpcPart **parts; + size_t count; LONG ref; };
@@ -425,16 +494,45 @@ static ULONG WINAPI zip_part_set_Release(IOpcPartSet *iface) TRACE("(%p)\n", iface);
if (!ref) + { + size_t i; + + for (i = 0; i < part_set->count; i++) + IOpcPart_Release(part_set->parts[i]); + free(part_set->parts); free(part_set); + } return ref; }
static HRESULT WINAPI zip_part_set_GetPart(IOpcPartSet *iface, IOpcPartUri *name, IOpcPart **part) { - FIXME("(%p, %p, %p) stub\n", iface, name, part); + struct zip_part_set *part_set= impl_from_IOpcPartSet(iface); + size_t i; + + TRACE("(%p, %p, %p)\n", iface, name, part); + + for (i = 0; i < part_set->count; i++) + { + BOOL exists = FALSE; + IOpcPartUri *name2; + HRESULT hr; + + if (FAILED(hr = IOpcPart_GetName(part_set->parts[i], &name2))) + return hr; + hr = IOpcPartUri_IsEqual(name, (IUri *)name2, &exists); + IOpcPartUri_Release(name2); + if (FAILED(hr)) + return hr; + if (exists) + { + IOpcPart_AddRef((*part = part_set->parts[i])); + return S_OK; + } + }
*part = NULL; - return E_NOTIMPL; + return OPC_E_NO_SUCH_PART; }
static HRESULT WINAPI zip_part_set_CreatePart(IOpcPartSet *iface, IOpcPartUri *name, const WCHAR *type, @@ -453,17 +551,43 @@ static HRESULT WINAPI zip_part_set_DeletePart(IOpcPartSet *iface, IOpcPartUri *n return E_NOTIMPL; }
+static HRESULT part_name_exists(IOpcPartUri *name, IOpcPart **parts, size_t parts_len, BOOL *exists) +{ + size_t i; + + *exists = FALSE; + for (i = 0; i < parts_len; i++) + { + IOpcPartUri *name2; + HRESULT hr; + + if (FAILED(hr = IOpcPart_GetName(parts[i], &name2))) + return hr; + hr = IOpcPartUri_IsEqual(name, (IUri *)name2, exists); + IOpcPartUri_Release(name2); + if (FAILED(hr)) + return hr; + if (*exists) + { + *exists = TRUE; + return S_OK; + } + } + return S_OK; +} + static HRESULT WINAPI zip_part_set_PartExists(IOpcPartSet *iface, IOpcPartUri *name, BOOL *exists) { - FIXME("(%p, %p, %p) stub\n", iface, name, exists); + struct zip_part_set *part_set = impl_from_IOpcPartSet(iface);
- return E_NOTIMPL; + TRACE("(%p, %p, %p)\n", iface, name, exists); + + return part_name_exists(name, part_set->parts, part_set->count, exists); }
static HRESULT WINAPI zip_part_set_GetEnumerator(IOpcPartSet *iface, IOpcPartEnumerator **out) { FIXME("(%p, %p) stub\n", iface, out); - *out = NULL; return E_NOTIMPL; } @@ -482,7 +606,7 @@ static const IOpcPartSetVtbl zip_part_set_vtbl = zip_part_set_GetEnumerator, };
-static HRESULT zip_part_set_create(IOpcPartSet **out) +static HRESULT zip_part_set_create(IOpcPart **parts, size_t len, IOpcPartSet **out) { struct zip_part_set *part_set;
@@ -492,14 +616,347 @@ static HRESULT zip_part_set_create(IOpcPartSet **out) *out = &part_set->IOpcPartSet_iface; part_set->IOpcPartSet_iface.lpVtbl = &zip_part_set_vtbl; part_set->ref = 1; + part_set->count = len; + part_set->parts = parts; *out = &part_set->IOpcPartSet_iface;
return S_OK; }
+struct zip_part +{ + IOpcPart IOpcPart_iface; + struct zip_entry entry; + IOpcPartUri *name; + LONG ref; +}; + +static inline struct zip_part *impl_from_IOpcPart(IOpcPart *iface) +{ + return CONTAINING_RECORD(iface, struct zip_part, IOpcPart_iface); +} + +static HRESULT WINAPI zip_part_QueryInterface(IOpcPart *iface, REFIID iid, void **out) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IOpcPart)) + { + IOpcPart_AddRef((*out = &part->IOpcPart_iface)); + return S_OK; + } + + *out = NULL; + ERR("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI zip_part_AddRef(IOpcPart *iface) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + TRACE("(%p)\n", part); + return InterlockedIncrement(&part->ref); +} + +static ULONG WINAPI zip_part_Release(IOpcPart *iface) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + ULONG ref = InterlockedDecrement(&part->ref); + + TRACE("(%p)\n", part); + + if (!ref) + { + IOpcPartUri_Release(part->name); + free(part); + } + return ref; +} + +static HRESULT WINAPI zip_part_GetRelationshipSet(IOpcPart *iface, IOpcRelationshipSet **rels_set) +{ + FIXME("(%p, %p) stub!\n", iface, rels_set); + *rels_set = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_GetContentStream(IOpcPart *iface, IStream **stream) +{ + FIXME("(%p, %p) stub!\n", iface, stream); + *stream = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_GetName(IOpcPart *iface, IOpcPartUri **name) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %p)\n", iface, name); + + IOpcPartUri_AddRef((*name = part->name)); + return S_OK; +} + +static HRESULT WINAPI zip_part_GetContentType(IOpcPart *iface, WCHAR **type) +{ + FIXME("(%p, %p) stub!\n", iface, type); + *type = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_GetCompressionOptions(IOpcPart *iface, OPC_COMPRESSION_OPTIONS *options) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %p)\n", iface, options); + + *options = part->entry.opt; + return S_OK; +} + +static const IOpcPartVtbl zip_part_vtbl = +{ + zip_part_QueryInterface, + zip_part_AddRef, + zip_part_Release, + zip_part_GetRelationshipSet, + zip_part_GetContentStream, + zip_part_GetName, + zip_part_GetContentType, + zip_part_GetCompressionOptions, +}; + +static HRESULT zip_part_create(IOpcPartUri *name, struct zip_entry entry, IOpcPart **part) +{ + struct zip_part *zip_part; + + if (!(zip_part = calloc(1, sizeof(*zip_part)))) + return E_OUTOFMEMORY; + + zip_part->IOpcPart_iface.lpVtbl = &zip_part_vtbl; + IOpcPartUri_AddRef((zip_part->name = name)); + zip_part->entry = entry; + zip_part->ref = 1; + *part = &zip_part->IOpcPart_iface; + return S_OK; +} + +/* The stream should be at the start of the central directory. */ +static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, IOpcPart ***parts, + size_t *parts_len) +{ + static const char *content_types_name = "[Content_Types].xml"; + BOOL found_content_types = FALSE; + HRESULT hr; + ULONG i; + + *parts = NULL; + *parts_len = 0; + for (i = 0; i < dir_records; i++) + { + struct central_directory_header dir = {0}; + struct zip64_extended_field ext = {0}; + struct zip_entry entry = {0}; + ULONG read, ext_size = 0; + LARGE_INTEGER off; + char *name; + + hr = IStream_Read(stream, &dir, sizeof(dir), &read); + if (hr != S_OK) + goto failed; + if (!(name = calloc(1, dir.name_length + 1))) + { + hr = E_OUTOFMEMORY; + goto failed; + } + + hr = IStream_Read(stream, name, dir.name_length, &read); + if (hr != S_OK) + { + free(name); + goto failed; + } + /* If any of the fields are UINT32_MAX, there is an extra ZIP64 extended information field */ + if (dir.uncompressed_size == UINT32_MAX) + ext_size = 8; + if (dir.compressed_size == UINT32_MAX) + ext_size = 16; + if (dir.local_file_offset == UINT32_MAX) + ext_size = 24; + if (ext_size) + ext_size += 4; /* For the signature and size fields. */ + /* Make sure that the central directory record actually indicates there is a ZIP64 extended field. */ + if (dir.extra_length < ext_size) + { + hr = OPC_E_ZIP_CORRUPTED_ARCHIVE; + free(name); + goto failed; + } + if (ext_size && ((hr = IStream_Read(stream, &ext, ext_size, &read) != S_OK))) + { + free(name); + goto failed; + } + + off.QuadPart = dir.extra_length - ext_size; + /* Skip any extra data that we haven't read. */ + if (off.QuadPart && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) + { + free(name); + goto failed; + } + entry.opt = dir.method ? deflate_opts[(dir.flags & DEFLATE_LEVEL_MASK) >> 1] : OPC_COMPRESSION_NONE; + entry.local_file_offset.QuadPart = (dir.local_file_offset == UINT32_MAX) ? ext.local_file_offset : dir.local_file_offset; + off.QuadPart = dir.comment_length; + if (dir.comment_length && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) + { + free(name); + goto failed; + } + if (!strcmp(name, content_types_name)) + { + free(name); + if (found_content_types) + { + hr = OPC_E_ZIP_DUPLICATE_NAME; + goto failed; + } + found_content_types = TRUE; + } + else + { + IOpcPart *new_part, **tmp; + IOpcPartUri *name_uri; + BOOL dup = FALSE; + WCHAR *nameW; + INT len; + + TRACE("Adding new OPC part %s: %s\n", debugstr_a(name), debugstr_zip_entry(&entry)); + + len = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0); + if (!(nameW = calloc(sizeof(WCHAR), len))) + { + hr = E_OUTOFMEMORY; + free(name); + goto failed; + } + MultiByteToWideChar(CP_ACP, 0, name, -1, nameW, len); + free(name); + hr = IOpcFactory_CreatePartUri(factory, nameW, &name_uri); + free(nameW); + if (FAILED(hr)) + goto failed; + if (FAILED(hr = part_name_exists(name_uri, *parts, *parts_len, &dup)) || dup) + { + IOpcPartUri_Release(name_uri); + if (dup) hr = OPC_E_ZIP_DUPLICATE_NAME; + goto failed; + } + hr = zip_part_create(name_uri, entry, &new_part); + IOpcPartUri_Release(name_uri); + if (FAILED(hr)) + goto failed; + if (!(tmp = realloc(*parts, sizeof(**parts) * (*parts_len + 1)))) + { + hr = E_OUTOFMEMORY; + IOpcPart_Release(new_part); + goto failed; + } + *parts = tmp; + (*parts)[*parts_len] = new_part; + *parts_len += 1; + } + } + return S_OK; + +failed: + for (i = 0; i < *parts_len; i++) + IOpcPart_Release((*parts)[i]); + free(*parts); + *parts = NULL; + *parts_len = 0; + return hr == S_FALSE ? OPC_E_ZIP_CORRUPTED_ARCHIVE : hr; +} + HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, IOpcPartSet **part_set) { - FIXME("(%p, %p, %p) stub\n", factory, stream, part_set); + struct central_directory_end end = {0}; + ULARGE_INTEGER end_dir_start; + IOpcPart **parts = NULL; + size_t parts_count = 0; + LARGE_INTEGER off; + HRESULT hr; + + off.QuadPart = -sizeof(end); + *part_set = NULL; + hr = IStream_Seek(stream, off, STREAM_SEEK_END, &end_dir_start); + /* Start from the end of the archive to find a end of central directory header. */ + while (SUCCEEDED(hr)) + { + struct central_directory_end_locator locator = {0}; + struct central_directory_end64 end64 = {0}; + LARGE_INTEGER central_dir_start; + ULONG read, dir_records; + + if (FAILED(hr = IStream_Read(stream, &end, sizeof(end), &read))) + return hr; + if (hr == S_FALSE || end.signature != DIRECTORY_END_SIGNATURE) + goto next; + /* Check any of the fields in the header are UINT16_MAX, and there are enough bytes for a EOCD locator and EOCD64 record. + * In that case, this might be a ZIP64 archive. */ + if ((end.records_total == UINT16_MAX || end.directory_size == UINT16_MAX || end.directory_offset == UINT16_MAX) && + end_dir_start.QuadPart >= (sizeof(locator) + sizeof(end64))) + { + ULARGE_INTEGER loc_start, end64_start; + + /* ZIP64 uses an additional End of Central Directory Locator record. */ + off.QuadPart = end_dir_start.QuadPart - sizeof(locator); + if (FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_SET, &loc_start))) + goto next; + if (FAILED(hr = IStream_Read(stream, &locator, sizeof(locator), &read))) + return hr; + if (hr == S_FALSE || locator.signature != DIRECTORY_END_LOCATOR_SIGNATURE || + locator.end_directory_offset >= loc_start.QuadPart || + loc_start.QuadPart - locator.end_directory_offset < sizeof(end64)) + goto next; + off.QuadPart = locator.end_directory_offset; + if (FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_SET, &end64_start))) + goto next; + if (FAILED(hr = IStream_Read(stream, &end64, sizeof(end64), &read))) + return hr; + if (hr == S_FALSE || end64.signature != DIRECTORY_END64_SIGNATURE || end64.size < (sizeof(end64) - 12)) + goto next; + central_dir_start.QuadPart = end64.directory_offset; + dir_records = end64.records_total; + } + else /* Otherwise, treat it as ZIP32. */ + { + /* Check if end.directory_offset actually points to the start of central directory. */ + if (end.directory_offset >= end_dir_start.QuadPart || + end_dir_start.QuadPart - end.directory_offset < sizeof(end)) + goto next; + central_dir_start.QuadPart = end.directory_offset; + dir_records = end.records_total; + } + + /* The central directory can't be located after the EOCD header. */ + if (central_dir_start.QuadPart >= end_dir_start.QuadPart) + goto next; + if (FAILED(hr = IStream_Seek(stream, central_dir_start, STREAM_SEEK_SET, NULL))) + goto next; + if (FAILED(hr = compress_read_entries(factory, stream, dir_records, &parts, &parts_count))) + return hr; + return zip_part_set_create(parts, parts_count, part_set); + + /* Seek back sizeof(end) bytes from where we initially were, and try again. */ + next: + if (FAILED(hr = IStream_Seek(stream, *(LARGE_INTEGER *)&end_dir_start, STREAM_SEEK_SET, NULL))) + return hr; + off.QuadPart = -sizeof(end); + hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, &end_dir_start); + }
- return zip_part_set_create(part_set); + return hr; } diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 8e878dff669..ef087e0d2ea 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1335,7 +1335,7 @@ static void test_read_package(void) ok(hr == S_OK, "Failed to create part uri, hr %#lx.\n", hr); hr = IOpcPartSet_GetPart(partset, uri, &part); IOpcPartUri_Release(uri); - todo_wine ok(hr == S_OK, "Failed to get part, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get part, hr %#lx.\n", hr); if (FAILED(hr)) continue; hr = IOpcPart_GetContentType(part, &type); todo_wine ok(hr == S_OK, "Failed to get content type, hr %#lx.\n", hr); @@ -1345,8 +1345,8 @@ static void test_read_package(void) CoTaskMemFree(type); } hr = IOpcPart_GetCompressionOptions(part, &opt); - todo_wine ok(hr == S_OK, "Failed to get compression options, hr %#lx.\n", hr); - todo_wine ok(opt == parts[i].opt, "Got opt %d != %d.\n", opt, parts[i].opt); + ok(hr == S_OK, "Failed to get compression options, hr %#lx.\n", hr); + ok(opt == parts[i].opt, "Got opt %d != %d.\n", opt, parts[i].opt); hr = IOpcPart_GetContentStream(part, &stream); IOpcPart_Release(part); todo_wine ok(hr == S_OK, "Failed to get content stream, hr %#lx.\n", hr);
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 189 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 3 files changed, 188 insertions(+), 5 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 7098734ab3a..dbd928d8aef 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -55,6 +55,14 @@ struct data_descriptor DWORD uncompressed_size; };
+struct data_descriptor64 +{ + DWORD signature; + DWORD crc32; + ULONG64 compressed_size; + ULONG64 uncompressed_size; +}; + struct central_directory_header { DWORD signature; @@ -424,6 +432,9 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, struct zip_entry { OPC_COMPRESSION_OPTIONS opt; + ULONG64 compressed_size; + ULONG64 uncompressed_size; + DWORD crc32; ULARGE_INTEGER local_file_offset; };
@@ -626,6 +637,8 @@ static HRESULT zip_part_set_create(IOpcPart **parts, size_t len, IOpcPartSet **o struct zip_part { IOpcPart IOpcPart_iface; + IStream *archive; + IStream *content; struct zip_entry entry; IOpcPartUri *name; LONG ref; @@ -669,6 +682,8 @@ static ULONG WINAPI zip_part_Release(IOpcPart *iface)
if (!ref) { + IStream_Release(part->archive); + if (part->content) IStream_Release(part->content); IOpcPartUri_Release(part->name); free(part); } @@ -682,11 +697,172 @@ static HRESULT WINAPI zip_part_GetRelationshipSet(IOpcPart *iface, IOpcRelations return E_NOTIMPL; }
+static BOOL compress_validate_part_size(const struct zip_entry *entry, const struct local_file_header *file, + const struct zip64_extended_field *ext) +{ + if (file->flags & USE_DATA_DESCRIPTOR) + return !(file->compressed_size || file->uncompressed_size || file->crc32 || ext->compressed_size || + ext->uncompressed_size); + else + { + ULONG64 compressed_size = file->compressed_size == UINT32_MAX ? ext->compressed_size : file->compressed_size; + ULONG64 uncompressed_size = file->uncompressed_size == UINT32_MAX ? ext->uncompressed_size : file->uncompressed_size; + + return compressed_size == entry->compressed_size && uncompressed_size == entry->uncompressed_size && + file->crc32 == entry->crc32; + } +} + +static HRESULT part_create_content_stream(IStream *archive, const struct zip_entry *entry, IStream **out) +{ + ULONG64 compressed_data_read = 0, data_uncompressed = 0; + ULONG ext_size = 0, read, crc32 = 0, exp_crc32 = 0; + static const ULONG input_buffer_size = 0x8000; + struct zip64_extended_field ext = {0}; + struct local_file_header file = {0}; + BYTE *input_buf, *output_buf; + z_stream z_str = {0}; + LARGE_INTEGER off; + HGLOBAL global; + HRESULT hr; + int ret; + + *out = NULL; + off.QuadPart = entry->local_file_offset.QuadPart; + if (FAILED(hr = IStream_Seek(archive, off, STREAM_SEEK_SET, NULL))) + return hr; + hr = IStream_Read(archive, &file, sizeof(file), &read); + if (hr != S_OK) + return hr == S_FALSE ? OPC_E_ZIP_DECOMPRESSION_FAILED : hr; + if (file.method && file.method != Z_DEFLATED) + return OPC_E_ZIP_UNSUPPORTEDARCHIVE; + if (file.uncompressed_size == UINT32_MAX) + ext_size = 8; + if (file.compressed_size == UINT32_MAX) + ext_size = 16; + if (ext_size) + ext_size += 4; /* For the signature and size fields. */ + if (file.extra_length < ext_size) + return OPC_E_ZIP_CORRUPTED_ARCHIVE; + + off.QuadPart = file.name_length; + if (FAILED(hr = IStream_Seek(archive, off, STREAM_SEEK_CUR, NULL))) + return hr; + if (ext_size && ((hr = IStream_Read(archive, &ext, ext_size, &read) != S_OK))) + return hr == S_FALSE ? OPC_E_ZIP_DECOMPRESSION_FAILED : hr; + if (!compress_validate_part_size(entry, &file, &ext)) + return OPC_E_ZIP_CORRUPTED_ARCHIVE; + if (!(file.flags & USE_DATA_DESCRIPTOR)) + exp_crc32 = file.crc32; + if (!(global = GlobalAlloc(GMEM_MOVEABLE, entry->uncompressed_size))) + return E_OUTOFMEMORY; + if (!(input_buf = malloc(input_buffer_size))) + { + GlobalFree(global); + return E_OUTOFMEMORY; + } + + z_str.zalloc = zalloc; + z_str.zfree = zfree; + z_str.next_in = input_buf; + z_str.next_out = output_buf = GlobalLock(global); + z_str.avail_out = min(entry->uncompressed_size, UINT32_MAX); + ret = inflateInit2(&z_str, -MAX_WBITS); + if (ret) + { + GlobalUnlock(global); + GlobalFree(global); + free(input_buf); + return OPC_E_ZIP_DECOMPRESSION_FAILED; + } + hr = OPC_E_ZIP_DECOMPRESSION_FAILED; + while (compressed_data_read < entry->compressed_size && data_uncompressed < entry->uncompressed_size) + { + ULONG to_read = min(entry->compressed_size - compressed_data_read, input_buffer_size); + + z_str.next_out = &z_str.next_out[z_str.total_out]; + z_str.avail_out = min(entry->uncompressed_size - data_uncompressed, UINT32_MAX); + z_str.total_out = 0; + hr = IStream_Read(archive, input_buf, to_read, &read); + if (hr != S_OK) + { + if (hr == S_FALSE) hr = OPC_E_ZIP_DECOMPRESSION_FAILED; + goto fail; + } + z_str.avail_in = read; + ret = inflate(&z_str, Z_SYNC_FLUSH); + if (ret && ret != Z_STREAM_END) + goto fail; + compressed_data_read += read; + data_uncompressed += z_str.total_out; + } + if (compressed_data_read != entry->compressed_size || data_uncompressed != entry->uncompressed_size) + goto fail; + inflateEnd(&z_str); + free(input_buf); + crc32 = RtlComputeCrc32(0, output_buf, data_uncompressed); + GlobalUnlock(global); + if (file.flags & USE_DATA_DESCRIPTOR) + { + ULONG64 exp_compressed, exp_uncompressed; + union { + struct data_descriptor desc32; + struct data_descriptor64 desc64; + } desc = {0}; + + hr = IStream_Read(archive, &desc, ext_size ? sizeof(desc.desc64) : sizeof(desc.desc32), &read); + if (hr != S_OK) + { + GlobalFree(global); + return hr == S_FALSE ? OPC_E_ZIP_DECOMPRESSION_FAILED : hr; + } + if (ext_size) + { + exp_compressed = desc.desc64.compressed_size; + exp_uncompressed = desc.desc64.uncompressed_size; + } + else + { + exp_compressed = desc.desc32.compressed_size; + exp_uncompressed = desc.desc32.uncompressed_size; + } + if (exp_compressed != compressed_data_read || exp_uncompressed != data_uncompressed) + { + GlobalFree(global); + return OPC_E_ZIP_CORRUPTED_ARCHIVE; + } + exp_crc32 = desc.desc64.crc32; + } + if (exp_crc32 != crc32) + { + GlobalFree(global); + return OPC_E_ZIP_CORRUPTED_ARCHIVE; + } + if (FAILED(hr = CreateStreamOnHGlobal(global, TRUE, out))) + GlobalFree(global); + return hr; + +fail: + inflateEnd(&z_str); + GlobalUnlock(global); + GlobalFree(global); + free(input_buf); + return hr; +} + static HRESULT WINAPI zip_part_GetContentStream(IOpcPart *iface, IStream **stream) { - FIXME("(%p, %p) stub!\n", iface, stream); + struct zip_part *part = impl_from_IOpcPart(iface); + HRESULT hr; + + TRACE("(%p, %p)\n", iface, stream); + *stream = NULL; - return E_NOTIMPL; + if (!part->content && FAILED(hr = part_create_content_stream(part->archive, &part->entry, &part->content))) + return hr; + + IStream_AddRef((*stream = part->content)); + return S_OK; }
static HRESULT WINAPI zip_part_GetName(IOpcPart *iface, IOpcPartUri **name) @@ -728,7 +904,7 @@ static const IOpcPartVtbl zip_part_vtbl = zip_part_GetCompressionOptions, };
-static HRESULT zip_part_create(IOpcPartUri *name, struct zip_entry entry, IOpcPart **part) +static HRESULT zip_part_create(IOpcPartUri *name, IStream *stream, struct zip_entry entry, IOpcPart **part) { struct zip_part *zip_part;
@@ -737,6 +913,7 @@ static HRESULT zip_part_create(IOpcPartUri *name, struct zip_entry entry, IOpcPa
zip_part->IOpcPart_iface.lpVtbl = &zip_part_vtbl; IOpcPartUri_AddRef((zip_part->name = name)); + IStream_AddRef((zip_part->archive = stream)); zip_part->entry = entry; zip_part->ref = 1; *part = &zip_part->IOpcPart_iface; @@ -808,7 +985,11 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON goto failed; } entry.opt = dir.method ? deflate_opts[(dir.flags & DEFLATE_LEVEL_MASK) >> 1] : OPC_COMPRESSION_NONE; + entry.uncompressed_size = (dir.uncompressed_size == UINT32_MAX) ? ext.uncompressed_size : dir.uncompressed_size; + entry.compressed_size = (dir.compressed_size == UINT32_MAX) ? ext.compressed_size : dir.compressed_size; + entry.crc32 = dir.crc32; entry.local_file_offset.QuadPart = (dir.local_file_offset == UINT32_MAX) ? ext.local_file_offset : dir.local_file_offset; + off.QuadPart = dir.comment_length; if (dir.comment_length && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) { @@ -854,7 +1035,7 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON if (dup) hr = OPC_E_ZIP_DUPLICATE_NAME; goto failed; } - hr = zip_part_create(name_uri, entry, &new_part); + hr = zip_part_create(name_uri, stream, entry, &new_part); IOpcPartUri_Release(name_uri); if (FAILED(hr)) goto failed; diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index ef087e0d2ea..f671b5022e3 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1349,7 +1349,7 @@ static void test_read_package(void) ok(opt == parts[i].opt, "Got opt %d != %d.\n", opt, parts[i].opt); hr = IOpcPart_GetContentStream(part, &stream); IOpcPart_Release(part); - todo_wine ok(hr == S_OK, "Failed to get content stream, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get content stream, hr %#lx.\n", hr); if (SUCCEEDED(hr)) { BYTE exp_buf[256]; diff --git a/include/opcbase.idl b/include/opcbase.idl index 6f86045561e..8418f8475ae 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -52,4 +52,6 @@ cpp_quote("#define OPC_E_ENUM_COLLECTION_CHANGED MAKE_HRESULT(SEVERITY_ERROR, FA cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_NEXT MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x51)") cpp_quote("#define OPC_E_ENUM_INVALID_POSITION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x53)") cpp_quote("#define OPC_E_ZIP_CORRUPTED_ARCHIVE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1002)") +cpp_quote("#define OPC_E_ZIP_DECOMPRESSION_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1004)") +cpp_quote("#define OPC_E_ZIP_UNSUPPORTEDARCHIVE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1008)") cpp_quote("#define OPC_E_ZIP_DUPLICATE_NAME MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x100b)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 287 +++++++++++++++++++++++---- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 3 + 3 files changed, 249 insertions(+), 43 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index dbd928d8aef..e32396d0d00 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -25,9 +25,11 @@ #include "windef.h" #include "winternl.h" #include "msopc.h" +#include "xmllite.h"
#include "opc_private.h"
+#include "wine/rbtree.h" #include "wine/debug.h"
WINE_DEFAULT_DEBUG_CHANNEL(msopc); @@ -457,10 +459,26 @@ static const char *debugstr_zip_entry(const struct zip_entry *entry) return wine_dbg_sprintf("{%s, %#I64x}", opt_str, entry->local_file_offset.QuadPart); }
+struct zip_part +{ + IOpcPart IOpcPart_iface; + IStream *archive; + IStream *content; + struct zip_entry entry; + IOpcPartUri *name; + WCHAR *content_type; + LONG ref; +}; + +static inline struct zip_part *impl_from_IOpcPart(IOpcPart *iface) +{ + return CONTAINING_RECORD(iface, struct zip_part, IOpcPart_iface); +} + struct zip_part_set { IOpcPartSet IOpcPartSet_iface; - IOpcPart **parts; + struct zip_part **parts; size_t count; LONG ref; }; @@ -509,7 +527,7 @@ static ULONG WINAPI zip_part_set_Release(IOpcPartSet *iface) size_t i;
for (i = 0; i < part_set->count; i++) - IOpcPart_Release(part_set->parts[i]); + IOpcPart_Release(&part_set->parts[i]->IOpcPart_iface); free(part_set->parts); free(part_set); } @@ -526,18 +544,14 @@ static HRESULT WINAPI zip_part_set_GetPart(IOpcPartSet *iface, IOpcPartUri *name for (i = 0; i < part_set->count; i++) { BOOL exists = FALSE; - IOpcPartUri *name2; HRESULT hr;
- if (FAILED(hr = IOpcPart_GetName(part_set->parts[i], &name2))) - return hr; - hr = IOpcPartUri_IsEqual(name, (IUri *)name2, &exists); - IOpcPartUri_Release(name2); + hr = IOpcPartUri_IsEqual(name, (IUri *)part_set->parts[i]->name, &exists); if (FAILED(hr)) return hr; if (exists) { - IOpcPart_AddRef((*part = part_set->parts[i])); + IOpcPart_AddRef((*part = &part_set->parts[i]->IOpcPart_iface)); return S_OK; } } @@ -562,20 +576,16 @@ static HRESULT WINAPI zip_part_set_DeletePart(IOpcPartSet *iface, IOpcPartUri *n return E_NOTIMPL; }
-static HRESULT part_name_exists(IOpcPartUri *name, IOpcPart **parts, size_t parts_len, BOOL *exists) +static HRESULT part_name_exists(IOpcPartUri *name, struct zip_part **parts, size_t parts_len, BOOL *exists) { size_t i;
*exists = FALSE; for (i = 0; i < parts_len; i++) { - IOpcPartUri *name2; HRESULT hr;
- if (FAILED(hr = IOpcPart_GetName(parts[i], &name2))) - return hr; - hr = IOpcPartUri_IsEqual(name, (IUri *)name2, exists); - IOpcPartUri_Release(name2); + hr = IOpcPartUri_IsEqual(name, (IUri *)parts[i]->name, exists); if (FAILED(hr)) return hr; if (*exists) @@ -617,7 +627,7 @@ static const IOpcPartSetVtbl zip_part_set_vtbl = zip_part_set_GetEnumerator, };
-static HRESULT zip_part_set_create(IOpcPart **parts, size_t len, IOpcPartSet **out) +static HRESULT zip_part_set_create(struct zip_part **parts, size_t len, IOpcPartSet **out) { struct zip_part_set *part_set;
@@ -634,21 +644,6 @@ static HRESULT zip_part_set_create(IOpcPart **parts, size_t len, IOpcPartSet **o return S_OK; }
-struct zip_part -{ - IOpcPart IOpcPart_iface; - IStream *archive; - IStream *content; - struct zip_entry entry; - IOpcPartUri *name; - LONG ref; -}; - -static inline struct zip_part *impl_from_IOpcPart(IOpcPart *iface) -{ - return CONTAINING_RECORD(iface, struct zip_part, IOpcPart_iface); -} - static HRESULT WINAPI zip_part_QueryInterface(IOpcPart *iface, REFIID iid, void **out) { struct zip_part *part = impl_from_IOpcPart(iface); @@ -685,6 +680,7 @@ static ULONG WINAPI zip_part_Release(IOpcPart *iface) IStream_Release(part->archive); if (part->content) IStream_Release(part->content); IOpcPartUri_Release(part->name); + if (part->content_type) free(part->content_type); free(part); } return ref; @@ -877,9 +873,12 @@ static HRESULT WINAPI zip_part_GetName(IOpcPart *iface, IOpcPartUri **name)
static HRESULT WINAPI zip_part_GetContentType(IOpcPart *iface, WCHAR **type) { - FIXME("(%p, %p) stub!\n", iface, type); - *type = NULL; - return E_NOTIMPL; + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %p)\n", iface, type); + + *type = wcsdup(part->content_type); + return *type ? S_OK : E_OUTOFMEMORY; }
static HRESULT WINAPI zip_part_GetCompressionOptions(IOpcPart *iface, OPC_COMPRESSION_OPTIONS *options) @@ -904,7 +903,7 @@ static const IOpcPartVtbl zip_part_vtbl = zip_part_GetCompressionOptions, };
-static HRESULT zip_part_create(IOpcPartUri *name, IStream *stream, struct zip_entry entry, IOpcPart **part) +static HRESULT zip_part_create(IOpcPartUri *name, IStream *stream, struct zip_entry entry, struct zip_part **out) { struct zip_part *zip_part;
@@ -916,19 +915,212 @@ static HRESULT zip_part_create(IOpcPartUri *name, IStream *stream, struct zip_en IStream_AddRef((zip_part->archive = stream)); zip_part->entry = entry; zip_part->ref = 1; - *part = &zip_part->IOpcPart_iface; + *out = zip_part; + return S_OK; +} + +struct content_type_entry +{ + struct rb_entry entry; + WCHAR *extension_or_part_name; + WCHAR *type; +}; + +static int content_type_entry_compare(const void *key, const struct rb_entry *entry) +{ + const WCHAR *key2 = RB_ENTRY_VALUE(entry, struct content_type_entry, entry)->extension_or_part_name; + return wcsicmp(key, key2); +} + +static void content_type_entry_destroy(struct rb_entry *entry, void *data) +{ + free(RB_ENTRY_VALUE(entry, struct content_type_entry, entry)); +} + +static HRESULT xml_get_attribute(IXmlReader *reader, const WCHAR *name, WCHAR **val_ret) +{ + const WCHAR *val; + HRESULT hr; + + hr = IXmlReader_MoveToAttributeByName(reader, name, NULL); + if (hr != S_OK) + return FAILED(hr) ? hr : OPC_E_INVALID_CONTENT_TYPE_XML; + if (FAILED(hr = IXmlReader_GetValue(reader, &val, NULL))) + return hr; + *val_ret = wcsdup(val); + return *val_ret ? S_OK : E_OUTOFMEMORY; +} + +static HRESULT xml_get_next_node(IXmlReader *reader, XmlNodeType exp_type, const WCHAR *exp_name) +{ + XmlNodeType type; + HRESULT hr; + + do { + hr = IXmlReader_Read(reader, &type); + if (hr != S_OK) + return hr; + } while (type == XmlNodeType_Whitespace); + if (exp_type != XmlNodeType_None && type != exp_type) + return OPC_E_INVALID_CONTENT_TYPE_XML; + if (exp_name) + { + const WCHAR *name; + + if (FAILED(hr = IXmlReader_GetLocalName(reader, &name, NULL))) + return hr; + if (wcscmp(name, exp_name)) + return OPC_E_INVALID_CONTENT_TYPE_XML; + } + return hr; +} + +static HRESULT content_types_add_type(IXmlReader *reader, const WCHAR *attr_name, struct rb_tree *types) +{ + struct content_type_entry *type; + WCHAR *ext_or_part, *content_type; + HRESULT hr; + + if (FAILED(hr = xml_get_attribute(reader, attr_name, &ext_or_part))) return hr; + if (FAILED(hr = xml_get_attribute(reader, L"ContentType", &content_type))) + { + free(ext_or_part); + return hr; + } + if (!(type = malloc(sizeof(*type)))) + { + free(content_type); + free(ext_or_part); + return E_OUTOFMEMORY; + } + type->extension_or_part_name = ext_or_part; + type->type = content_type; + if (rb_put(types, ext_or_part, &type->entry)) + { + free(content_type); + free(ext_or_part); + free(type); + return OPC_E_DUPLICATE_DEFAULT_EXTENSION; + } + return S_OK; +} + +static HRESULT content_types_add_default(IXmlReader *reader, struct rb_tree *defaults) +{ + return content_types_add_type(reader, L"Extension", defaults); +} + +static HRESULT content_types_add_override(IXmlReader *reader, struct rb_tree *overrides) +{ + return content_types_add_type(reader, L"PartName", overrides); +} + +static HRESULT part_create_content_stream(IStream *archive, const struct zip_entry *entry, IStream **out); + +static HRESULT compress_read_content_types(IStream *archive, const struct zip_entry *content_types, + struct rb_tree *defaults, struct rb_tree *overrides) +{ + IXmlReader *reader; + IStream *xml; + HRESULT hr; + + if (FAILED(hr = part_create_content_stream(archive, content_types, &xml))) + return hr; + if (FAILED(hr = CreateXmlReader(&IID_IXmlReader, (void *)&reader, NULL))) + { + IStream_Release(xml); + return hr; + } + + hr = IXmlReader_SetInput(reader, (IUnknown *)xml); + IStream_Release(xml); + if (FAILED(hr)) + { + IXmlReader_Release(reader); + return hr; + } + + if (FAILED(hr = xml_get_next_node(reader, XmlNodeType_XmlDeclaration, L"xml"))) + goto fail; + if (FAILED(hr = xml_get_next_node(reader, XmlNodeType_Element, L"Types"))) + goto fail; + while(SUCCEEDED(hr = xml_get_next_node(reader, XmlNodeType_None, NULL))) + { + const WCHAR *name; + XmlNodeType type; + + if (FAILED(hr = IXmlReader_GetNodeType(reader, &type))) goto fail; + if (type != XmlNodeType_Element && type != XmlNodeType_EndElement) continue; + if (FAILED(hr = IXmlReader_GetLocalName(reader, &name, NULL))) goto fail; + if (type == XmlNodeType_EndElement) break; + + if (!wcscmp(name, L"Default")) + hr = content_types_add_default(reader, defaults); + else if (!wcscmp(name, L"Override")) + hr = content_types_add_override(reader, overrides); + else + FIXME("Unknown node: %s\n", debugstr_w(name)); + + if (FAILED(hr)) goto fail; + } + if (SUCCEEDED(hr)) + { + IXmlReader_Release(reader); + return hr; + } +fail: + rb_destroy(defaults, content_type_entry_destroy, NULL); + rb_destroy(overrides, content_type_entry_destroy, NULL); + IXmlReader_Release(reader); + return hr; +} + +static HRESULT zip_parts_set_content_types(const struct rb_tree *defaults, const struct rb_tree *overrides, + struct zip_part **parts, size_t count) +{ + size_t i; + + for (i = 0; i < count; i++) + { + struct rb_entry *entry; + HRESULT hr; + BSTR str; + + if (FAILED(hr = IOpcPartUri_GetAbsoluteUri(parts[i]->name, &str))) + return hr; + entry = rb_get(overrides, str); + SysFreeString(str); + if (!entry) + { + if (FAILED(hr = IOpcPartUri_GetExtension(parts[i]->name, &str))) + return hr; + entry = rb_get(defaults, &str[1]); /* Omit the leading dot */ + SysFreeString(str); + } + if (!entry) + return E_INVALIDARG; + parts[i]->content_type = wcsdup(RB_ENTRY_VALUE(entry, struct content_type_entry, entry)->type); + if (!parts[i]->content_type) + return E_OUTOFMEMORY; + } + return S_OK; }
/* The stream should be at the start of the central directory. */ -static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, IOpcPart ***parts, +static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, struct zip_part ***parts, size_t *parts_len) { static const char *content_types_name = "[Content_Types].xml"; + struct zip_entry content_types = {0}; BOOL found_content_types = FALSE; + struct rb_tree type_overrides; + struct rb_tree type_defaults; HRESULT hr; ULONG i;
+ rb_init(&type_defaults, content_type_entry_compare); + rb_init(&type_overrides, content_type_entry_compare); *parts = NULL; *parts_len = 0; for (i = 0; i < dir_records; i++) @@ -1004,11 +1196,12 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON hr = OPC_E_ZIP_DUPLICATE_NAME; goto failed; } + content_types = entry; found_content_types = TRUE; } else { - IOpcPart *new_part, **tmp; + struct zip_part *new_part, **tmp; IOpcPartUri *name_uri; BOOL dup = FALSE; WCHAR *nameW; @@ -1042,7 +1235,7 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON if (!(tmp = realloc(*parts, sizeof(**parts) * (*parts_len + 1)))) { hr = E_OUTOFMEMORY; - IOpcPart_Release(new_part); + IOpcPart_Release(&new_part->IOpcPart_iface); goto failed; } *parts = tmp; @@ -1050,11 +1243,21 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON *parts_len += 1; } } - return S_OK; - + if (!found_content_types) + { + hr = OPC_E_MISSING_CONTENT_TYPES; + goto failed; + } + if (FAILED(hr = compress_read_content_types(stream, &content_types, &type_defaults, &type_overrides))) + goto failed; + hr = zip_parts_set_content_types(&type_defaults, &type_overrides, *parts, *parts_len); + rb_destroy(&type_defaults, content_type_entry_destroy, NULL); + rb_destroy(&type_overrides, content_type_entry_destroy, NULL); + if (SUCCEEDED(hr)) + return S_OK; failed: for (i = 0; i < *parts_len; i++) - IOpcPart_Release((*parts)[i]); + IOpcPart_Release(&(*parts)[i]->IOpcPart_iface); free(*parts); *parts = NULL; *parts_len = 0; @@ -1064,8 +1267,8 @@ failed: HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, IOpcPartSet **part_set) { struct central_directory_end end = {0}; + struct zip_part **parts = NULL; ULARGE_INTEGER end_dir_start; - IOpcPart **parts = NULL; size_t parts_count = 0; LARGE_INTEGER off; HRESULT hr; diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index f671b5022e3..e0045537338 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1338,7 +1338,7 @@ static void test_read_package(void) ok(hr == S_OK, "Failed to get part, hr %#lx.\n", hr); if (FAILED(hr)) continue; hr = IOpcPart_GetContentType(part, &type); - todo_wine ok(hr == S_OK, "Failed to get content type, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get content type, hr %#lx.\n", hr); if (SUCCEEDED(hr)) { ok(!wcscmp(type, parts[i].type), "got type %s != %s.\n", debugstr_w(type), debugstr_w(parts[i].type)); diff --git a/include/opcbase.idl b/include/opcbase.idl index 8418f8475ae..08f467f02d7 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -43,6 +43,9 @@ typedef [v1_enum] enum
cpp_quote("#define OPC_E_NONCONFORMING_URI MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1)") cpp_quote("#define OPC_E_RELATIONSHIP_URI_REQUIRED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x3)") +cpp_quote("#define OPC_E_INVALID_CONTENT_TYPE_XML MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x6)") +cpp_quote("#define OPC_E_MISSING_CONTENT_TYPES MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x7)") +cpp_quote("#define OPC_E_DUPLICATE_DEFAULT_EXTENSION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0xf)") cpp_quote("#define OPC_E_DUPLICATE_PART MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0xb)") cpp_quote("#define OPC_E_INVALID_RELATIONSHIP_TARGET MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x12)") cpp_quote("#define OPC_E_DUPLICATE_RELATIONSHIP MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x13)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 157 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 2 +- 2 files changed, 156 insertions(+), 3 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index e32396d0d00..206b3e0701a 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -488,6 +488,156 @@ static inline struct zip_part_set *impl_from_IOpcPartSet(IOpcPartSet *iface) return CONTAINING_RECORD(iface, struct zip_part_set, IOpcPartSet_iface); }
+struct zip_part_enum +{ + IOpcPartEnumerator IOpcPartEnumerator_iface; + struct zip_part_set *part_set; + size_t pos; + LONG ref; +}; + +static inline struct zip_part_enum *impl_from_IOpcPartEnumerator(IOpcPartEnumerator *iface) +{ + return CONTAINING_RECORD(iface, struct zip_part_enum, IOpcPartEnumerator_iface); +} + +static HRESULT WINAPI zip_part_enum_QueryInterface(IOpcPartEnumerator *iface, REFIID iid, void **out) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + + TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IOpcPartEnumerator)) + { + *out = &part_enum->IOpcPartEnumerator_iface; + IOpcPartEnumerator_AddRef(iface); + return S_OK; + } + + *out = NULL; + WARN("Unsupported interface %s.\n", debugstr_guid(iid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI zip_part_enum_AddRef(IOpcPartEnumerator *iface) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + + TRACE("(%p)\n", iface); + + return InterlockedIncrement(&part_enum->ref); +} + +static ULONG WINAPI zip_part_enum_Release(IOpcPartEnumerator *iface) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + ULONG ref = InterlockedDecrement(&part_enum->ref); + + TRACE("(%p)\n", iface); + + if (!ref) + { + IOpcPartSet_Release(&part_enum->part_set->IOpcPartSet_iface); + free(part_enum); + } + return ref; +} + +static HRESULT WINAPI zip_part_enum_MoveNext(IOpcPartEnumerator *iface, BOOL *has_next) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + HRESULT hr = S_OK; + + TRACE("(%p, %p)\n", iface, has_next); + + if (!has_next) + return E_POINTER; + + if (part_enum->part_set->count && (part_enum->pos == ~(size_t)0 || part_enum->pos < part_enum->part_set->count)) + part_enum->pos++; + else if (part_enum->pos >= part_enum->part_set->count) + hr = OPC_E_ENUM_CANNOT_MOVE_NEXT; + *has_next = part_enum->pos < part_enum->part_set->count; + return hr; +} + +static HRESULT WINAPI zip_part_enum_MovePrevious(IOpcPartEnumerator *iface, BOOL *has_previous) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + + TRACE("(%p, %p)\n", iface, has_previous); + + if (!has_previous) + return E_POINTER; + + if (part_enum->pos != ~(size_t)0) + part_enum->pos--; + *has_previous = part_enum->pos != ~(size_t)0; + return S_OK; +} + +static HRESULT WINAPI zip_part_enum_GetCurrent(IOpcPartEnumerator *iface, IOpcPart **part) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + + TRACE("(%p, %p)\n", iface, part); + + if (!part) + return E_POINTER; + + if (part_enum->pos < part_enum->part_set->count) + { + IOpcPart_AddRef((*part = &part_enum->part_set->parts[part_enum->pos]->IOpcPart_iface)); + return S_OK; + } + *part = NULL; + return OPC_E_ENUM_INVALID_POSITION; +} + +static HRESULT zip_part_enum_create(struct zip_part_set *part_set, IOpcPartEnumerator **out); + +static HRESULT WINAPI zip_part_enum_Clone(IOpcPartEnumerator *iface, IOpcPartEnumerator **out) +{ + struct zip_part_enum *part_enum = impl_from_IOpcPartEnumerator(iface); + + TRACE("(%p, %p)\n", iface, out); + + if (!out) + return E_POINTER; + + return zip_part_enum_create(part_enum->part_set, out); +} + +static const IOpcPartEnumeratorVtbl zip_part_enum_vtbl = +{ + /* IUnknown */ + zip_part_enum_QueryInterface, + zip_part_enum_AddRef, + zip_part_enum_Release, + /* IOpcPartEnumeratorVtbl */ + zip_part_enum_MoveNext, + zip_part_enum_MovePrevious, + zip_part_enum_GetCurrent, + zip_part_enum_Clone, +}; + +static HRESULT zip_part_enum_create(struct zip_part_set *part_set, IOpcPartEnumerator **out) +{ + struct zip_part_enum *part_enum; + + if (!(part_enum = calloc(1, sizeof(*part_enum)))) + return E_OUTOFMEMORY; + + part_enum->IOpcPartEnumerator_iface.lpVtbl = &zip_part_enum_vtbl; + part_enum->ref =1; + part_enum->part_set = part_set; + part_enum->pos = ~(size_t)0; + IOpcPartSet_AddRef(&part_set->IOpcPartSet_iface); + + *out = &part_enum->IOpcPartEnumerator_iface; + return S_OK; +} + static HRESULT WINAPI zip_part_set_QueryInterface(IOpcPartSet *iface, REFIID iid, void **out) { struct zip_part_set *part_set = impl_from_IOpcPartSet(iface); @@ -608,9 +758,12 @@ static HRESULT WINAPI zip_part_set_PartExists(IOpcPartSet *iface, IOpcPartUri *n
static HRESULT WINAPI zip_part_set_GetEnumerator(IOpcPartSet *iface, IOpcPartEnumerator **out) { - FIXME("(%p, %p) stub\n", iface, out); + struct zip_part_set *part_set = impl_from_IOpcPartSet(iface); + + TRACE("(%p, %p)\n", iface, out); + *out = NULL; - return E_NOTIMPL; + return zip_part_enum_create(part_set, out); }
static const IOpcPartSetVtbl zip_part_set_vtbl = diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index e0045537338..2518742ce04 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1367,7 +1367,7 @@ static void test_read_package(void) }
hr = IOpcPartSet_GetEnumerator(partset, &partenum); - todo_wine ok(hr == S_OK, "Failed to get part enumerator, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get part enumerator, hr %#lx.\n", hr); if (FAILED(hr)) { IOpcPartSet_Release(partset);
On Tue Aug 26 15:08:33 2025 +0000, Vibhav Pant wrote:
Implementing `Windows.Management.Deployment.PackageManager` requires support for reading appx/msix files. As we already had opcservices, I thought it'd be better to use `IOpcPackage/PartSet` to do that, instead of having separate ZIP + inflate code.
It's a lot of new code, so my question is about where this is supposed to lead. How are this packages distributed, how users get them, are they possibly signed, is it related in any way to Store packages?
How are this packages distributed, how users get them, are they possibly signed, is it related in any way to Store packages?
<span dir="">Yeah, I believe the Windows Store does distribute apps in the form of appx packages, but they can be sideloaded as well. There are also FOSS tools like </span>[`winget`](https://github.com/microsoft/winget-cli/tree/master)<span dir="">/App Installer that use </span>`Windows.Management.Deployment.PackageManager`<span dir=""> APIs to install AppX packages, </span>and provide access to community-maintained repos for AppX packages as well.
AppX packages contain signing info in the `AppxSignature.p7x` (which is just PKCS #7 data), there's also support for codeintegrity signatures in the `AppxMetadata/CodeIntegrity.cat` file in the archive. However, this is out of scope for opcservices.dll, as these APIs only get used to decompress and read appx files.
But yes, the idea is that the `PackageManager` code would open and read AppX packages using `ReadPackageFromStream`.
On Tue Aug 26 22:40:54 2025 +0000, Vibhav Pant wrote:
How are this packages distributed, how users get them, are they
possibly signed, is it related in any way to Store packages? <span dir="">Yeah, I believe the Windows Store does distribute apps in the form of appx packages, but they can be sideloaded as well. There are also FOSS tools like </span>[`winget`](https://github.com/microsoft/winget-cli/tree/master)<span dir="">/App Installer that use </span>`Windows.Management.Deployment.PackageManager`<span dir=""> APIs to install packages, </span>and provide access to community-maintained repos for AppX packages as well. AppX packages contain signing info in `AppxSignature.p7x` (which is just PKCS #7 data), there's also support for codeintegrity signatures in `AppxMetadata/CodeIntegrity.cat` However, this is out of scope for opcservices.dll, as this only get used to decompress and read appx files. Signature vertification would need to be done separately, likely in `PackageManager`. But yes, the idea is that the `PackageManager` code would open and read AppX packages using `ReadPackageFromStream`.
Is this just for an experiment, or what are the real use cases?
On Thu Aug 28 09:47:36 2025 +0000, Nikolay Sivov wrote:
Is this just for an experiment, or what are the real use cases?
Apps distributed through the Windows Store/winget repos are only available as appx/msix(bundle) packages. Currently, the only way to get them to run is to decompress the ZIP archive, and then try running the executable. This isn't guaranteed to work because appx packages also contain COM classes that need to be registered, file extension associations, etc.
Additionally, some UWP/WinRT apps are actually launched by activating an out of COM server described in the package manifest, and not just launching the executable.
This allows us to be able to read and traverse OPC packages, which is the first step towards supporting appx/msix packages.
On Thu Aug 28 14:00:07 2025 +0000, Vibhav Pant wrote:
Apps distributed through the Windows Store/winget repos are only available as appx/msix(bundle) packages. Currently, the only way to get them to run is to decompress the ZIP archive, and then try running the executable. This isn't guaranteed to work because appx packages also contain COM classes that need to be registered, file extension associations, etc. Additionally, some UWP/WinRT apps are actually launched by activating an out of COM server described in the package manifest, and not just launching the executable. This MR allows us to be able to read and traverse OPC packages, which is the first step towards supporting appx/msix packages. Getting appx packages to run under Wine comes up frequently on [discussion forums](https://askubuntu.com/questions/991858/is-it-possible-to-run-windows-10-uwp-...), and upstream projects like [winetricks](https://github.com/Winetricks/winetricks/issues/2225) and [bottles](https://github.com/bottlesdevs/Bottles/issues/672).
To me such method of obtaining Store package looks questionable.
On Thu Aug 28 14:54:38 2025 +0000, Nikolay Sivov wrote:
To me such method of obtaining Store package looks questionable.
From my understanding, this is also how the Windows Package Manager/`winget` [downloads appx packages from the Windows Store.](https://github.com/microsoft/winget-cli/blob/ddb86d48d52bbe2a96a5c18a342bb32...) That being said, I believe obtaining AppX files is not within Wine's scope. We only need to implement the APIs for installing an appx package given to us. The `PackageManager` APIs in WinRT only support installing locally present files, as documented [here:](https://github.com/microsoft/winget-cli/blob/ddb86d48d52bbe2a96a5c18a342bb32...)
The URI must follow the file URI scheme (file://) since the only supported URI schemes are local file paths and local network paths.
To me such method of obtaining Store package looks questionable.
Why?
Nikolay Sivov (@nsivov) commented about dlls/opcservices/tests/opcservices.c:
- IOpcPartEnumerator *partenum;
- LARGE_INTEGER start = {0};
- IOpcFactory *factory;
- IOpcPackage *package;
- IOpcPartSet *partset;
- IOpcPartUri *uri;
- IStream *stream;
- IOpcPart *part;
- BYTE buf[256];
- HRESULT hr;
- BOOL next;
- int i;
- factory = create_factory();
- hr = IOpcFactory_CreatePackage(factory, &package);
- ok(hr == S_OK || broken(hr == E_NOTIMPL) /* Vista */, "Failed to create a package, hr %#lx.\n", hr);
I don't think we test on Vista anymore, so I wouldn't bother.
Nikolay Sivov (@nsivov) commented about dlls/opcservices/tests/opcservices.c:
- hr = CreateStreamOnHGlobal(NULL, TRUE, &stream);
- ok(hr == S_OK, "Failed to create a stream, hr %#lx.\n", hr);
- hr = IOpcFactory_WritePackageToStream(factory, package, OPC_WRITE_FORCE_ZIP32, stream);
- ok(hr == S_OK, "Failed to write package, hr %#lx.\n", hr);
- IOpcPackage_Release(package);
- hr = IStream_Seek(stream, start, STREAM_SEEK_SET, NULL);
- ok(hr == S_OK, "Failed to seek stream, hr %#lx.\n", hr);
- hr = IOpcFactory_ReadPackageFromStream(factory, stream, OPC_READ_DEFAULT, &package);
- todo_wine ok(hr == S_OK, "Failed to read package, hr %#lx.\n", hr);
- IStream_Release(stream);
- if (FAILED(hr))
- {
IOpcPackage_Release(package);
goto done;
- }
If it failed you shouldn't release the package. The fact that it works means there is a leak somewhere probably.
Nikolay Sivov (@nsivov) commented about dlls/opcservices/compress.c:
case OPC_COMPRESSION_MAXIMUM:
local_header.flags = DEFLATE_MAX;
break;
case OPC_COMPRESSION_FAST:
local_header.flags = DEFLATE_FAST;
break;
case OPC_COMPRESSION_SUPERFAST:
local_header.flags = DEFLATE_SUPERFAST;
break;
default:
WARN("Unsupported compression options %d.\n", options);
case OPC_COMPRESSION_NORMAL:
local_header.flags = DEFLATE_NORMAL;
break;
}
- }
Was this verified somehow?
First think to do is to reuse as much as possible from existing OpcPart OpcPartSet code. I don't think we need to duplicate everything.
On Fri Aug 29 09:22:04 2025 +0000, Nikolay Sivov wrote:
If it failed you shouldn't release the package. The fact that it works means there is a leak somewhere probably.
Yes, the part set was being leaked. Thanks.
On Fri Aug 29 09:22:04 2025 +0000, Nikolay Sivov wrote:
Was this verified somehow?
Yes, native will set these fields in the generated ZIP file as well. I was able to confirm this with a hex editor. Also, we need to set these fields to implement `GetCompressionOptions` while reading parts.
On Fri Aug 29 11:26:45 2025 +0000, Nikolay Sivov wrote:
First think to do is to reuse as much as possible from existing OpcPart OpcPartSet code. I don't think we need to duplicate everything.
Sure, but that would then mean that we immediately decompress all parts when a package gets opened. Is that okay?
On Fri Aug 29 11:26:45 2025 +0000, Vibhav Pant wrote:
Sure, but that would then mean that we immediately decompress all parts when a package gets opened. Is that okay?
It could be done differently. There are read flags to control that too. The problem again is that it's not clear to me how this is going to be used from higher level code. Or if we get that far at all, in case something is encrypted for instance.
Do you have necessary changes that will make such installers work?
On Fri Aug 29 11:30:13 2025 +0000, Nikolay Sivov wrote:
It could be done differently. There are read flags to control that too. The problem again is that it's not clear to me how this is going to be used from higher level code. Or if we get that far at all, in case something is encrypted for instance. Do you have necessary changes that will make such installers work?
Encrypted AppX files don't use ZIP encryption, the OPC standard doesn't allow it. They're encrypted and stored using a different container format. Decryption is performed with `IAppxEncryptionFactory::Decrypt{Package, Bundle}`, which returns an OPC/Appx stream that can then be read normally.
The problem again is that it's not clear to me how this is going to be used from higher level code.
Roughly speaking, the package manager would read the provided package into an `IOpcPackage`, use the partset to get `AppXManifest.xml`, and then install executables/register components/associations as details in the manifest.
On Fri Aug 29 09:22:04 2025 +0000, Nikolay Sivov wrote:
I don't think we test on Vista anymore, so I wouldn't bother.
Sure.
On Fri Aug 29 12:04:13 2025 +0000, Vibhav Pant wrote:
Encrypted AppX files don't use ZIP encryption, the OPC standard doesn't allow it. They're encrypted and stored using a different container format. Decryption is performed with `IAppxEncryptionFactory::Decrypt{Package, Bundle}`, which returns an OPC/Appx stream that can then be read normally.
The problem again is that it's not clear to me how this is going to be
used from higher level code. Roughly speaking, the package manager would read the provided package into an `IOpcPackage`, use the partset to get `AppXManifest.xml`, and then install executables/register components/associations as details in the manifest.
Well again, what's the actual use case for this? Is there any developer who distributes .appx archives directly?
Well again, what's the actual use case for this? Is there any developer who distributes .appx archives directly?
On the winget community repository, there are a lot of packages distributed as msix/appx: https://github.com/search?q=repo%3Amicrosoft%2Fwinget-pkgs+%22InstallerType%...
Winget also officially [supports installing apps from the Windows Store](https://learn.microsoft.com/en-us/windows/package-manager/winget/source), which requires appx support as well.
Outside of winget, there are projects that also release Windows builds as an appx package. Kiwi comes to mind, it recommends that users use the appx: https://github.com/kiwix/kiwix-js-pwa/releases/tag/v3.7.1