[PATCH v9 0/5] MR8837: opcservices: Add basic support for reading OPC packages.
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`). -- v9: opcservices: Implement IOpcPart::GetContentType for opened packages. opcservices: Implement IOpcPart::GetContentStream for opened packages. opcservices: Create IOpcPart objects for ZIP entries in an opened package. https://gitlab.winehq.org/wine/wine/-/merge_requests/8837
From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 591f0a0bd01..abfa8aa6912 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -252,7 +252,7 @@ HRESULT compress_finalize_archive(struct zip_archive *archive) cdh.signature = ZIP32_CDFH; cdh.version = ZIP64_VERSION; cdh.min_version = ZIP64_VERSION; - cdh.flags = USE_DATA_DESCRIPTOR; + cdh.flags = file->flags; cdh.method = file->method; cdh.mtime = archive->mtime; cdh.crc32 = file->crc32; @@ -317,7 +317,7 @@ HRESULT compress_finalize_archive(struct zip_archive *archive) cdh.signature = ZIP32_CDFH; cdh.version = ZIP32_VERSION; cdh.min_version = ZIP32_VERSION; - cdh.flags = USE_DATA_DESCRIPTOR; + cdh.flags = file->flags; cdh.method = file->method; cdh.mtime = archive->mtime; cdh.crc32 = file->crc32; -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8837
From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 7 +++++++ dlls/opcservices/factory.c | 21 ++++++++++++++++++--- dlls/opcservices/opc_private.h | 7 ++++++- dlls/opcservices/package.c | 22 +++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 10 ++++++++-- include/opcbase.idl | 2 ++ 6 files changed, 62 insertions(+), 7 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index abfa8aa6912..fcdbe8654f6 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -560,3 +560,10 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, return S_OK; } + +HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, struct opc_part_set *part_set) +{ + FIXME("(%p, %p, %p) stub\n", factory, stream, part_set); + + return S_OK; +} diff --git a/dlls/opcservices/factory.c b/dlls/opcservices/factory.c index fcebb977c1d..fef1a190483 100644 --- a/dlls/opcservices/factory.c +++ b/dlls/opcservices/factory.c @@ -369,15 +369,30 @@ static HRESULT WINAPI opc_factory_CreatePackage(IOpcFactory *iface, IOpcPackage { TRACE("iface %p, package %p.\n", iface, package); - return opc_package_create(iface, package); + return opc_package_create(iface, NULL, package); } 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); + struct opc_part_set *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 = opc_part_set_create(&part_set))) + return hr; + if (FAILED(hr = compress_open_archive(iface, stream, part_set))) + { + opc_part_set_release(part_set); + return hr; + } + hr = opc_package_create(iface, part_set, package); + opc_part_set_release(part_set); + return hr; } 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 8365916eebb..0147a28f923 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -56,16 +56,21 @@ struct opc_uri IUri *rels_part_uri; struct opc_uri *source_uri; }; +struct opc_part_set; -extern HRESULT opc_package_create(IOpcFactory *factory, IOpcPackage **package); +extern HRESULT opc_package_create(IOpcFactory *factory, struct opc_part_set *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); extern HRESULT opc_package_write(IOpcPackage *package, OPC_WRITE_FLAGS flags, IStream *stream); +extern HRESULT opc_part_set_create(struct opc_part_set **part_set); +extern void opc_part_set_release(struct opc_part_set *part_set); + struct zip_archive; extern HRESULT compress_create_archive(IStream *output, bool zip64, struct zip_archive **archive); extern HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, IStream *content, OPC_COMPRESSION_OPTIONS options); extern HRESULT compress_finalize_archive(struct zip_archive *archive); extern void compress_release_archive(struct zip_archive *archive); +extern HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, struct opc_part_set *part_set); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index b5a8537bb3d..847f917103e 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -1513,7 +1513,7 @@ static const IOpcPackageVtbl opc_package_vtbl = opc_package_GetRelationshipSet, }; -HRESULT opc_package_create(IOpcFactory *factory, IOpcPackage **out) +HRESULT opc_package_create(IOpcFactory *factory, struct opc_part_set *part_set, IOpcPackage **out) { struct opc_package *package; HRESULT hr; @@ -1529,6 +1529,8 @@ HRESULT opc_package_create(IOpcFactory *factory, IOpcPackage **out) free(package); return hr; } + if (part_set) + IOpcPartSet_AddRef((package->part_set = &part_set->IOpcPartSet_iface)); *out = &package->IOpcPackage_iface; TRACE("Created package %p.\n", *out); @@ -2054,3 +2056,21 @@ HRESULT opc_package_write(IOpcPackage *package, OPC_WRITE_FLAGS flags, IStream * return hr; } + +HRESULT opc_part_set_create(struct opc_part_set **out) +{ + struct opc_part_set *part_set; + + if (!(part_set = calloc(1, sizeof(*part_set)))) + return E_OUTOFMEMORY; + + part_set->IOpcPartSet_iface.lpVtbl = &opc_part_set_vtbl; + part_set->refcount = 1; + *out = part_set; + return S_OK; +} + +void opc_part_set_release(struct opc_part_set *part_set) +{ + IOpcPartSet_Release(&part_set->IOpcPartSet_iface); +} diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 9ead97f9c62..aca7f91c8cf 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1319,7 +1319,7 @@ static void test_read_package(void) ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IOpcFactory_ReadPackageFromStream(factory, stream, OPC_READ_DEFAULT, &package); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); IStream_Release(stream); if (FAILED(hr)) goto done; @@ -1338,7 +1338,13 @@ static void test_read_package(void) hr = IOpcFactory_CreatePartUri(factory, parts[i].uri, &uri); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IOpcPartSet_GetPart(partset, uri, &part); - ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); + if (FAILED(hr)) + { + IOpcPartUri_Release(uri); + winetest_pop_context(); + continue; + } hr = IOpcPart_GetContentType(part, &type); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); diff --git a/include/opcbase.idl b/include/opcbase.idl index a34c4ec27c1..c8419d3c623 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -53,3 +53,5 @@ cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_NEXT MAKE_HRESULT(SEVERITY_ERROR, FACI cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_PREVIOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x52)") cpp_quote("#define OPC_E_ENUM_INVALID_POSITION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x53)") cpp_quote("#define OPC_E_ZIP_COMPRESSION_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1003)") +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)") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8837
From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 244 ++++++++++++++++++++++++++- dlls/opcservices/factory.c | 5 +- dlls/opcservices/opc_private.h | 11 +- dlls/opcservices/package.c | 49 +++++- dlls/opcservices/tests/opcservices.c | 10 +- include/opcbase.idl | 1 + 6 files changed, 302 insertions(+), 18 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index fcdbe8654f6..b8f8a0ddaeb 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -18,6 +18,7 @@ #define COBJMACROS +#include <stdint.h> #include <stdarg.h> #include <stdint.h> #include <zlib.h> @@ -144,12 +145,21 @@ enum zip_versions ZIP64_VERSION = 45, }; +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, DEFLATE_MAX = 0x2, DEFLATE_FAST = 0x4, DEFLATE_SUPERFAST = 0x6, + DEFLATE_LEVEL_MASK = 0x6, USE_DATA_DESCRIPTOR = 0x8, }; @@ -561,9 +571,237 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, return S_OK; } -HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, struct opc_part_set *part_set) +static const char *debugstr_OPC_COMPRESSION_OPTIONS(OPC_COMPRESSION_OPTIONS opt) { - FIXME("(%p, %p, %p) stub\n", factory, stream, part_set); + static const char *str[] = {"OPC_COMPRESSION_NONE", "OPC_COMPRESSION_NORMAL", "OPC_COMPRESSION_MAXIMUM", + "OPC_COMPRESSION_FAST", "OPC_COMPRESSION_SUPERFAST"}; - return S_OK; + if (opt + 1 < ARRAY_SIZE(str)) + return str[opt + 1]; + return wine_dbg_sprintf("%#x", opt); +} + +static const char *debugstr_zip_entry(const struct zip_entry *entry) +{ + return wine_dbg_sprintf("{%#I64x}", entry->local_file_offset.QuadPart); +} +struct zip_part +{ + struct zip_entry entry; + OPC_COMPRESSION_OPTIONS opt; + IOpcPartUri *name; +}; + +/* The stream should be at the start of the central directory. */ +static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, ULONG dir_records, + struct opc_part_set *partset) +{ + static const char *content_types_name = "[Content_Types].xml"; + ULONG i, part_count = 0, nameW_len = 0, nameA_len = 0; + BOOL found_content_types = FALSE; + struct zip_part *parts = NULL; + WCHAR *nameW = NULL; + char *nameA = NULL; + HRESULT hr; + + for (i = 0; i < dir_records; i++) + { + struct zip32_central_directory_header dir = {0}; + struct zip64_extra_field ext = {0}; + struct zip_entry entry = {0}; + OPC_COMPRESSION_OPTIONS opt; + ULONG read, ext_size = 0; + LARGE_INTEGER off; + + hr = IStream_Read(stream, &dir, sizeof(dir), &read); + if (hr != S_OK) + goto done; + if (dir.name_length + 1 > nameA_len) + { + char *tmp; + + nameA_len = dir.name_length + 1; + if (!(tmp = realloc(nameA, nameA_len))) + { + hr = E_OUTOFMEMORY; + goto done; + } + nameA = tmp; + } + nameA[dir.name_length] = '\0'; + + hr = IStream_Read(stream, nameA, dir.name_length, &read); + if (hr != S_OK) + goto done; + /* 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; + goto done; + } + if (ext_size && ((hr = IStream_Read(stream, &ext, ext_size, &read) != S_OK))) + goto done; + + 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))) + goto done; + 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.offset : dir.local_file_offset; + off.QuadPart = dir.comment_length; + if (dir.comment_length && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) + goto done; + if (!strcmp(nameA, content_types_name)) + { + if (found_content_types) + { + hr = OPC_E_ZIP_DUPLICATE_NAME; + goto done; + } + found_content_types = TRUE; + } + else + { + IOpcPartUri *name_uri; + struct zip_part *tmp; + INT len; + + TRACE("Adding new OPC part %s: %s, %s\n", debugstr_a(nameA), debugstr_OPC_COMPRESSION_OPTIONS(opt), + debugstr_zip_entry(&entry)); + + len = MultiByteToWideChar(CP_ACP, 0, nameA, -1, NULL, 0); + if (len > nameW_len) + { + WCHAR *tmp; + + nameW_len = len; + if (!(tmp = realloc(nameW, len * sizeof(WCHAR)))) + { + hr = E_OUTOFMEMORY; + goto done; + } + nameW = tmp; + } + MultiByteToWideChar(CP_ACP, 0, nameA, -1, nameW, len); + if (FAILED(hr = IOpcFactory_CreatePartUri(factory, nameW, &name_uri))) + goto done; + if (!(tmp = realloc(parts, (part_count + 1) * sizeof(*parts)))) + { + IOpcPartUri_Release(name_uri); + hr = E_OUTOFMEMORY; + goto done; + } + parts = tmp; + tmp = &parts[part_count++]; + tmp->entry = entry; + tmp->opt = opt; + tmp->name = name_uri; + } + } + + if (!found_content_types) + { + hr = OPC_E_MISSING_CONTENT_TYPES; + goto done; + } + + for (i = 0; i < part_count; i++) + { + hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, 0, parts[i].name, L""); + if (FAILED(hr)) goto done; + } + +done: + for (i = 0; i < part_count; i++) + IOpcPartUri_Release(parts[i].name); + free(parts); + free(nameA); + free(nameW); + return hr == S_FALSE ? OPC_E_ZIP_CORRUPTED_ARCHIVE : hr; +} + +HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, + struct opc_part_set *part_set) +{ + struct zip32_end_of_central_directory end = {0}; + ULARGE_INTEGER end_dir_start; + LARGE_INTEGER off; + HRESULT hr; + + off.QuadPart = -sizeof(end); + 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 zip64_end_of_central_directory_locator locator = {0}; + struct zip64_end_of_central_directory 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 != ZIP32_EOCD) + 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 != ZIP64_EOCD64_LOCATOR || + locator.eocd64_offset >= loc_start.QuadPart || + loc_start.QuadPart - locator.eocd64_offset < sizeof(end64)) + goto next; + off.QuadPart = locator.eocd64_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 != ZIP64_EOCD64 || 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; + return compress_read_entries(factory, stream, flags, dir_records, 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 hr; } diff --git a/dlls/opcservices/factory.c b/dlls/opcservices/factory.c index fef1a190483..6b096ee73fb 100644 --- a/dlls/opcservices/factory.c +++ b/dlls/opcservices/factory.c @@ -380,12 +380,9 @@ static HRESULT WINAPI opc_factory_ReadPackageFromStream(IOpcFactory *iface, IStr 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 = opc_part_set_create(&part_set))) return hr; - if (FAILED(hr = compress_open_archive(iface, stream, part_set))) + if (FAILED(hr = compress_open_archive(iface, stream, flags, part_set))) { opc_part_set_release(part_set); return hr; diff --git a/dlls/opcservices/opc_private.h b/dlls/opcservices/opc_private.h index 0147a28f923..c99aacc3913 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -58,6 +58,11 @@ struct opc_uri }; struct opc_part_set; +struct zip_entry +{ + ULARGE_INTEGER local_file_offset; +}; + extern HRESULT opc_package_create(IOpcFactory *factory, struct opc_part_set *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,6 +71,9 @@ extern HRESULT opc_package_write(IOpcPackage *package, OPC_WRITE_FLAGS flags, IS extern HRESULT opc_part_set_create(struct opc_part_set **part_set); extern void opc_part_set_release(struct opc_part_set *part_set); +extern HRESULT opc_part_set_add_zip_part(struct opc_part_set *part_set, IStream *archive, const struct zip_entry *entry, + OPC_COMPRESSION_OPTIONS compress_opt, OPC_READ_FLAGS read_flags, + IOpcPartUri *name, const WCHAR *content_type); struct zip_archive; extern HRESULT compress_create_archive(IStream *output, bool zip64, struct zip_archive **archive); @@ -73,4 +81,5 @@ extern HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, OPC_COMPRESSION_OPTIONS options); extern HRESULT compress_finalize_archive(struct zip_archive *archive); extern void compress_release_archive(struct zip_archive *archive); -extern HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, struct opc_part_set *part_set); +extern HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, + struct opc_part_set *part_set); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index 847f917103e..64c80b2715b 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -76,6 +76,8 @@ struct opc_part WCHAR *content_type; DWORD compression_options; IOpcRelationshipSet *relationship_set; + IStream *archive; + struct zip_entry zip_entry; struct opc_content *content; }; @@ -780,9 +782,13 @@ static ULONG WINAPI opc_part_Release(IOpcPart *iface) { if (part->relationship_set) IOpcRelationshipSet_Release(part->relationship_set); - IOpcPartUri_Release(part->name); + if (part->name) + IOpcPartUri_Release(part->name); CoTaskMemFree(part->content_type); - opc_content_release(part->content); + if (part->content) + opc_content_release(part->content); + if (part->archive) + IStream_Release(part->archive); free(part); } @@ -814,6 +820,9 @@ static HRESULT WINAPI opc_part_GetContentStream(IOpcPart *iface, IStream **strea if (!stream) return E_POINTER; + if (part->archive) + FIXME("stub!\n"); + return opc_content_stream_create(part->content, stream); } @@ -900,6 +909,42 @@ static HRESULT opc_part_create(struct opc_part_set *set, IOpcPartUri *name, cons return S_OK; } +HRESULT opc_part_set_add_zip_part(struct opc_part_set *set, IStream *archive, const struct zip_entry *entry, + OPC_COMPRESSION_OPTIONS opt, OPC_READ_FLAGS read_flags, IOpcPartUri *name, + const WCHAR *content_type) +{ + struct opc_part *part; + + if (!opc_array_reserve((void **)&set->parts, &set->size, set->count + 1, sizeof(*set->parts))) + return E_OUTOFMEMORY; + + if (!(part = calloc(1, sizeof(*part)))) + return E_OUTOFMEMORY; + + part->IOpcPart_iface.lpVtbl = &opc_part_vtbl; + part->refcount = 1; + part->content = calloc(1, sizeof(*part->content)); + if (!part->content) + { + IOpcPart_Release(&part->IOpcPart_iface); + return E_OUTOFMEMORY; + } + part->content->refcount = 1; + IOpcPartUri_AddRef((part->name = name)); + part->compression_options = opt; + IStream_AddRef((part->archive = archive)); + part->zip_entry = *entry; + if (!(part->content_type = opc_strdupW(content_type))) + { + IOpcPart_Release(&part->IOpcPart_iface); + return E_OUTOFMEMORY; + } + set->parts[set->count++] = part; + CoCreateGuid(&set->id); + + return S_OK; +} + static struct opc_part *opc_part_set_get_part(const struct opc_part_set *part_set, IOpcPartUri *name) { BOOL is_equal; diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index aca7f91c8cf..31508d860d4 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1338,13 +1338,7 @@ static void test_read_package(void) hr = IOpcFactory_CreatePartUri(factory, parts[i].uri, &uri); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IOpcPartSet_GetPart(partset, uri, &part); - todo_wine ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - if (FAILED(hr)) - { - IOpcPartUri_Release(uri); - winetest_pop_context(); - continue; - } + ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); hr = IOpcPart_GetContentType(part, &type); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); @@ -1362,7 +1356,7 @@ static void test_read_package(void) 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)); + todo_wine 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); diff --git a/include/opcbase.idl b/include/opcbase.idl index c8419d3c623..9ba7b734cfa 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -43,6 +43,7 @@ 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_MISSING_CONTENT_TYPES MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x7)") 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)") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8837
From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 145 ++++++++++++++++++++++++++- dlls/opcservices/opc_private.h | 4 + dlls/opcservices/package.c | 54 +++++++++- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 5 files changed, 203 insertions(+), 4 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index b8f8a0ddaeb..8a1f9f07b1a 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -583,7 +583,8 @@ static const char *debugstr_OPC_COMPRESSION_OPTIONS(OPC_COMPRESSION_OPTIONS opt) static const char *debugstr_zip_entry(const struct zip_entry *entry) { - return wine_dbg_sprintf("{%#I64x}", entry->local_file_offset.QuadPart); + return wine_dbg_sprintf("{%I64u %I64u %#lx %#I64x}", entry->compressed_size, entry->uncompressed_size, entry->crc32, + entry->local_file_offset.QuadPart); } struct zip_part { @@ -592,6 +593,142 @@ struct zip_part IOpcPartUri *name; }; +static BOOL compress_validate_part_size(const struct zip_entry *entry, const struct local_file_header *file, + const struct zip64_extra_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; + } +} + +/* Decompress the ZIP file described by entry in IStream archive into out. */ +HRESULT decompress_to_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 buffer_size = 0x8000; + struct zip64_extra_field ext = {0}; + struct local_file_header file = {0}; + BYTE *input_buf, *output_buf; + z_stream z_str = {0}; + LARGE_INTEGER off; + HRESULT hr; + int ret; + + 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 (!(input_buf = malloc(buffer_size))) return E_OUTOFMEMORY; + if (!(output_buf = malloc(buffer_size))) + { + free(input_buf); + return E_OUTOFMEMORY; + } + + z_str.zalloc = zalloc; + z_str.zfree = zfree; + z_str.next_in = input_buf; + z_str.next_out = output_buf; + z_str.avail_out = buffer_size; + ret = inflateInit2(&z_str, -MAX_WBITS); + if (ret) + { + free(input_buf); + free(output_buf); + return OPC_E_ZIP_DECOMPRESSION_FAILED; + } + hr = OPC_E_ZIP_DECOMPRESSION_FAILED; + crc32 = RtlComputeCrc32(0, NULL, 0); + while (compressed_data_read < entry->compressed_size && data_uncompressed < entry->uncompressed_size) + { + ULONG to_read = min(entry->compressed_size - compressed_data_read, buffer_size); + + 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; + break; + } + z_str.avail_in = read; + ret = inflate(&z_str, Z_SYNC_FLUSH); + if (ret && ret != Z_STREAM_END) + { + hr = OPC_E_ZIP_DECOMPRESSION_FAILED; + break; + } + if (FAILED(hr = IStream_Write(out, output_buf, z_str.total_out, NULL))) + break; + crc32 = RtlComputeCrc32(crc32, output_buf, z_str.total_out); + compressed_data_read += read; + data_uncompressed += z_str.total_out; + } + inflateEnd(&z_str); + free(input_buf); + free(output_buf); + if (FAILED(hr)) + return hr; + if (compressed_data_read != entry->compressed_size || data_uncompressed != entry->uncompressed_size) + return OPC_E_ZIP_DECOMPRESSION_FAILED; + + if (file.flags & USE_DATA_DESCRIPTOR) + { + ULONG64 exp_compressed, exp_uncompressed; + union { + struct zip32_data_descriptor desc32; + struct zip64_data_descriptor desc64; + } desc = {0}; + + hr = IStream_Read(archive, &desc, ext_size ? sizeof(desc.desc64) : sizeof(desc.desc32), &read); + if (hr != S_OK) 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) + return OPC_E_ZIP_CORRUPTED_ARCHIVE; + exp_crc32 = desc.desc64.crc32; + } + return exp_crc32 != crc32 ? OPC_E_ZIP_CORRUPTED_ARCHIVE : S_OK; +} + /* The stream should be at the start of the central directory. */ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, ULONG dir_records, struct opc_part_set *partset) @@ -656,7 +793,11 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_ if (off.QuadPart && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) goto done; 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.offset : dir.local_file_offset; + off.QuadPart = dir.comment_length; if (dir.comment_length && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) goto done; @@ -716,7 +857,7 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_ for (i = 0; i < part_count; i++) { - hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, 0, parts[i].name, L""); + hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, flags, parts[i].name, L""); if (FAILED(hr)) goto done; } diff --git a/dlls/opcservices/opc_private.h b/dlls/opcservices/opc_private.h index c99aacc3913..884ccbb4946 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -60,6 +60,9 @@ struct opc_part_set; struct zip_entry { + ULONG64 compressed_size; + ULONG64 uncompressed_size; + DWORD crc32; ULARGE_INTEGER local_file_offset; }; @@ -83,3 +86,4 @@ extern HRESULT compress_finalize_archive(struct zip_archive *archive); extern void compress_release_archive(struct zip_archive *archive); extern HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, struct opc_part_set *part_set); +extern HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index 64c80b2715b..4066b310d76 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -78,6 +78,7 @@ struct opc_part IOpcRelationshipSet *relationship_set; IStream *archive; struct zip_entry zip_entry; + BOOL cache_on_decompress; struct opc_content *content; }; @@ -821,7 +822,45 @@ static HRESULT WINAPI opc_part_GetContentStream(IOpcPart *iface, IStream **strea return E_POINTER; if (part->archive) - FIXME("stub!\n"); + { + struct opc_content *content; + LARGE_INTEGER zero = {0}; + IStream *decompressed; + HRESULT hr; + + if (!(content = calloc(1, sizeof(*content)))) + return E_OUTOFMEMORY; + + content->refcount = 1; + if (FAILED(hr = opc_content_stream_create(content, &decompressed))) + { + opc_content_release(content); + return hr; + } + if (FAILED(hr = decompress_to_stream(part->archive, &part->zip_entry, decompressed))) + { + IStream_Release(decompressed); + opc_content_release(content); + return hr; + } + + if (part->cache_on_decompress) + { + part->content = content; + IStream_Release(part->archive); + part->archive = NULL; + } + else + opc_content_release(content); + + if (FAILED(hr = IStream_Seek(decompressed, zero, STREAM_SEEK_SET, NULL))) + { + IStream_Release(decompressed); + return hr; + } + *stream = decompressed; + return S_OK; +} return opc_content_stream_create(part->content, stream); } @@ -934,11 +973,24 @@ HRESULT opc_part_set_add_zip_part(struct opc_part_set *set, IStream *archive, co part->compression_options = opt; IStream_AddRef((part->archive = archive)); part->zip_entry = *entry; + part->cache_on_decompress = !!(read_flags & OPC_CACHE_ON_ACCESS); if (!(part->content_type = opc_strdupW(content_type))) { IOpcPart_Release(&part->IOpcPart_iface); return E_OUTOFMEMORY; } + if (read_flags & OPC_VALIDATE_ON_LOAD) + { + IStream *content; + HRESULT hr; + + if (FAILED(hr = IOpcPart_GetContentStream(&part->IOpcPart_iface, &content))) + { + IOpcPart_Release(&part->IOpcPart_iface); + return hr; + } + IStream_Release(content); + } set->parts[set->count++] = part; CoCreateGuid(&set->id); diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 31508d860d4..eaaf68f6516 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1356,7 +1356,7 @@ static void test_read_package(void) 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); - todo_wine ok(read == sizeof(buf), "Got read %lu != %Iu.\n", read, sizeof(buf)); + 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); diff --git a/include/opcbase.idl b/include/opcbase.idl index 9ba7b734cfa..ec9bc88b72a 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -55,4 +55,6 @@ cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_PREVIOUS MAKE_HRESULT(SEVERITY_ERROR, cpp_quote("#define OPC_E_ENUM_INVALID_POSITION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x53)") cpp_quote("#define OPC_E_ZIP_COMPRESSION_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1003)") 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)") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8837
From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 193 ++++++++++++++++++++++++++- dlls/opcservices/opc_private.h | 2 +- dlls/opcservices/package.c | 2 +- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 5 files changed, 195 insertions(+), 6 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 8a1f9f07b1a..372edb06007 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -26,9 +26,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); @@ -610,7 +612,7 @@ static BOOL compress_validate_part_size(const struct zip_entry *entry, const str } /* Decompress the ZIP file described by entry in IStream archive into out. */ -HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out) +HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out, BOOL set_size) { ULONG64 compressed_data_read = 0, data_uncompressed = 0; ULONG ext_size = 0, read, crc32 = 0, exp_crc32 = 0; @@ -647,6 +649,8 @@ HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IS 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 (set_size && FAILED(hr = IStream_SetSize(out, *(ULARGE_INTEGER *)&entry->uncompressed_size))) + return hr; if (!(file.flags & USE_DATA_DESCRIPTOR)) exp_crc32 = file.crc32; if (!(input_buf = malloc(buffer_size))) return E_OUTOFMEMORY; @@ -729,18 +733,176 @@ HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IS return exp_crc32 != crc32 ? OPC_E_ZIP_CORRUPTED_ARCHIVE : 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) +{ + struct content_type_entry *ct = RB_ENTRY_VALUE(entry, struct content_type_entry, entry); + + free(ct->extension_or_part_name); + free(ct->type); + free(ct); +} + +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 (SUCCEEDED(hr = IXmlReader_GetValue(reader, &val, NULL))) + hr = (*val_ret = wcsdup(val)) ? S_OK : E_OUTOFMEMORY; + return hr; +} + +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 (SUCCEEDED(hr = IXmlReader_GetLocalName(reader, &name, NULL)) && wcscmp(name, exp_name)) + hr = OPC_E_INVALID_CONTENT_TYPE_XML; + } + return hr; +} + +static HRESULT content_types_add_type(IXmlReader *reader, const WCHAR *attr_name, struct rb_tree *types) +{ + WCHAR *ext_or_part = NULL, *content_type = NULL; + struct content_type_entry *type = NULL; + 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))) + goto done; + if (!(type = calloc(1, sizeof(*type)))) + { + hr = E_OUTOFMEMORY; + goto done; + } + type->extension_or_part_name = ext_or_part; + type->type = content_type; + if (rb_put(types, ext_or_part, &type->entry)) + hr = OPC_E_DUPLICATE_DEFAULT_EXTENSION; +done: + if (FAILED(hr)) + { + free(content_type); + free(ext_or_part); + free(type); + } + return hr; +} + +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 compress_read_content_types(IStream *archive, const struct zip_entry *content_types, + struct rb_tree *defaults, struct rb_tree *overrides) +{ + static const LARGE_INTEGER start = {0}; + IXmlReader *reader; + IStream *xml; + HRESULT hr; + + if (FAILED(hr = CreateStreamOnHGlobal(NULL, TRUE, &xml))) return hr; + if (FAILED(hr = decompress_to_stream(archive, content_types, xml, TRUE))) + { + IStream_Release(xml); + return hr; + } + if (FAILED(hr = IStream_Seek(xml, start, STREAM_SEEK_SET, NULL))) + { + IStream_Release(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)) goto done; + + if (FAILED(hr = xml_get_next_node(reader, XmlNodeType_XmlDeclaration, L"xml"))) + goto done; + if (FAILED(hr = xml_get_next_node(reader, XmlNodeType_Element, L"Types"))) + goto done; + while(SUCCEEDED(hr = xml_get_next_node(reader, XmlNodeType_None, NULL))) + { + const WCHAR *name; + XmlNodeType type; + + if (FAILED(hr = IXmlReader_GetNodeType(reader, &type))) break; + if (type != XmlNodeType_Element && type != XmlNodeType_EndElement) continue; + if (FAILED(hr = IXmlReader_GetLocalName(reader, &name, NULL))) break; + 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)) break; + } +done: + IXmlReader_Release(reader); + return hr; +} + /* The stream should be at the start of the central directory. */ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, ULONG dir_records, struct opc_part_set *partset) { static const char *content_types_name = "[Content_Types].xml"; ULONG i, part_count = 0, nameW_len = 0, nameA_len = 0; + struct zip_entry content_types = {0}; BOOL found_content_types = FALSE; struct zip_part *parts = NULL; + struct rb_tree type_overrides; + struct rb_tree type_defaults; WCHAR *nameW = NULL; char *nameA = NULL; HRESULT hr; + rb_init(&type_defaults, content_type_entry_compare); + rb_init(&type_overrides, content_type_entry_compare); + for (i = 0; i < dir_records; i++) { struct zip32_central_directory_header dir = {0}; @@ -808,6 +970,7 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_ hr = OPC_E_ZIP_DUPLICATE_NAME; goto done; } + content_types = entry; found_content_types = TRUE; } else @@ -854,14 +1017,38 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_ hr = OPC_E_MISSING_CONTENT_TYPES; goto done; } + hr = compress_read_content_types(stream, &content_types, &type_defaults, &type_overrides); - for (i = 0; i < part_count; i++) + for (i = 0; i < part_count && SUCCEEDED(hr); i++) { - hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, flags, parts[i].name, L""); + const struct content_type_entry *content_type; + const struct rb_entry *entry; + BSTR str; + + if (FAILED(hr = IOpcPartUri_GetAbsoluteUri(parts[i].name, &str))) goto done; + /* First, check if if there is "Override" entry for this part. */ + entry = rb_get(&type_overrides, str); + SysFreeString(str); + if (!entry) + { + if (FAILED(hr = IOpcPartUri_GetExtension(parts[i].name, &str))) goto done; + entry = rb_get(&type_defaults, &str[1]); /* Omit the leading dot. */ + } + if (!entry) + { + hr = E_INVALIDARG; + goto done; + } + + content_type = RB_ENTRY_VALUE(entry, struct content_type_entry, entry); + hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, flags, parts[i].name, + content_type->type); if (FAILED(hr)) goto done; } done: + rb_destroy(&type_defaults, content_type_entry_destroy, NULL); + rb_destroy(&type_overrides, content_type_entry_destroy, NULL); for (i = 0; i < part_count; i++) IOpcPartUri_Release(parts[i].name); free(parts); diff --git a/dlls/opcservices/opc_private.h b/dlls/opcservices/opc_private.h index 884ccbb4946..f16a3262257 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -86,4 +86,4 @@ extern HRESULT compress_finalize_archive(struct zip_archive *archive); extern void compress_release_archive(struct zip_archive *archive); extern HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, OPC_READ_FLAGS flags, struct opc_part_set *part_set); -extern HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out); +extern HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out, BOOL set_size); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index 4066b310d76..0b8cd8f8b28 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -837,7 +837,7 @@ static HRESULT WINAPI opc_part_GetContentStream(IOpcPart *iface, IStream **strea opc_content_release(content); return hr; } - if (FAILED(hr = decompress_to_stream(part->archive, &part->zip_entry, decompressed))) + if (FAILED(hr = decompress_to_stream(part->archive, &part->zip_entry, decompressed, FALSE))) { IStream_Release(decompressed); opc_content_release(content); diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index eaaf68f6516..3895fdda8f7 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1342,7 +1342,7 @@ static void test_read_package(void) hr = IOpcPart_GetContentType(part, &type); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - todo_wine ok(!wcscmp(type, parts[i].type), "Unexpected type %s != %s.\n", debugstr_w(type), debugstr_w(parts[i].type)); + ok(!wcscmp(type, parts[i].type), "Unexpected type %s != %s.\n", debugstr_w(type), debugstr_w(parts[i].type)); CoTaskMemFree(type); options = ~0u; diff --git a/include/opcbase.idl b/include/opcbase.idl index ec9bc88b72a..dc91627c0b9 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -43,7 +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)") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8837
v9: * Add support for lazy decompression. * Refactor compression code. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/8837#note_124722
On Tue Dec 2 15:44:04 2025 +0000, Nikolay Sivov wrote:
I think we need at least make it easier to add lazy decompression later, not necessarily implement it right away. The latest version adds support for the `OPC_VALIDATE_ON_LOAD` and `OPC_CACHE_ON_ACCESS` flags. Does it look better?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/8837#note_124726
Nicholas Ansell (@roadrunnerto) commented about include/opcbase.idl:
cpp_quote("#define OPC_E_ENUM_CANNOT_MOVE_PREVIOUS MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x52)") cpp_quote("#define OPC_E_ENUM_INVALID_POSITION MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x53)") cpp_quote("#define OPC_E_ZIP_COMPRESSION_FAILED MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1003)") +cpp_quote("#define OPC_E_ZIP_CORRUPTED_ARCHIVE MAKE_HRESULT(SEVERITY_ERROR, FACILITY_OPC, 0x1002)") This is out of order, should be above OPC_E_ZIP_COMPRESSION_FAILED
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/8837#note_125522
participants (3)
-
Nicholas Ansell (@roadrunnerto) -
Vibhav Pant -
Vibhav Pant (@vibhavp)