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`).
-- v4: 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. opcservices: Write the correct compression-related fields while adding files to a ZIP archive. opcservices: Add stubs for ReadPackageFromStream.
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 8 ++++++++ dlls/opcservices/factory.c | 24 ++++++++++++++++++++++-- dlls/opcservices/opc_private.h | 2 ++ dlls/opcservices/package.c | 6 ++++++ dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 ++ 6 files changed, 41 insertions(+), 3 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 748293d7acd..10d746d1994 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,10 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path,
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 S_OK; +} diff --git a/dlls/opcservices/factory.c b/dlls/opcservices/factory.c index fcebb977c1d..dbe851ae613 100644 --- a/dlls/opcservices/factory.c +++ b/dlls/opcservices/factory.c @@ -375,9 +375,29 @@ 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 = opc_package_create(iface, package))) return hr; + if (FAILED(hr = IOpcPackage_GetPartSet(*package, &part_set))) + { + IOpcPackage_Release(*package); + *package = NULL; + return hr; + } + hr = compress_open_archive(iface, stream, part_set); + IOpcPartSet_Release(part_set); + if (FAILED(hr)) + { + IOpcPackage_Release(*package); + *package = NULL; + } + 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 cfd8ee864b2..e8539da7b63 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 17175006433..71a9d2968a1 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -1514,6 +1514,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; @@ -1529,6 +1534,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 396364037b8..081e0e686fc 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1299,7 +1299,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;
diff --git a/include/opcbase.idl b/include/opcbase.idl index 1d4aa253aa5..90c8dc60b15 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -52,3 +52,5 @@ 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_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_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 10d746d1994..55513be64c3 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 | 282 +++++++++++++++++++++++++++++++++++- include/opcbase.idl | 1 + 2 files changed, 281 insertions(+), 2 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 55513be64c3..05fe4e07d63 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,245 @@ 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 +{ + struct zip_entry entry; + IOpcPartUri *name; +}; + +/* The stream should be at the start of the central directory. */ +static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, IOpcPartSet *partset) +{ + static const char *content_types_name = "[Content_Types].xml"; + BOOL found_content_types = FALSE; + struct zip_part *parts = NULL; + ULONG i, part_count = 0; + HRESULT hr; + + 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 done; + if (!(name = calloc(1, dir.name_length + 1))) + { + hr = E_OUTOFMEMORY; + goto done; + } + + hr = IStream_Read(stream, name, dir.name_length, &read); + if (hr != S_OK) + { + free(name); + 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; + free(name); + goto done; + } + if (ext_size && ((hr = IStream_Read(stream, &ext, ext_size, &read) != S_OK))) + { + free(name); + 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))) + { + free(name); + goto done; + } + 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 done; + } + if (!strcmp(name, content_types_name)) + { + free(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; + 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 done; + } + MultiByteToWideChar(CP_ACP, 0, name, -1, nameW, len); + free(name); + hr = IOpcFactory_CreatePartUri(factory, nameW, &name_uri); + free(nameW); + if (FAILED(hr)) + 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->name = name_uri; + } + } + + if (!found_content_types) + { + hr = OPC_E_MISSING_CONTENT_TYPES; + goto done; + } + + for (i = 0; i < part_count; i++) + { + IOpcPart *part; + + hr = IOpcPartSet_CreatePart(partset, parts[i].name, L"", parts[i].entry.opt, &part); + if (FAILED(hr)) goto done; + IOpcPart_Release(part); + } + +done: + for (i = 0; i < part_count; i++) + IOpcPartUri_Release(parts[i].name); + free(parts); + 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; + LARGE_INTEGER off; + HRESULT hr;
- return S_OK; + 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 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; + return compress_read_entries(factory, stream, 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/include/opcbase.idl b/include/opcbase.idl index 90c8dc60b15..73d5a7fb82c 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)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 159 ++++++++++++++++++++++++++++++++++++ include/opcbase.idl | 2 + 2 files changed, 161 insertions(+)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 05fe4e07d63..d3b6754bf52 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; };
@@ -451,6 +462,140 @@ 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_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; + } +} + +/* Decompress the ZIP file described by entry in IStream archive into out. */ +static HRESULT decompress_file_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_extended_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; + goto fail; + } + z_str.avail_in = read; + ret = inflate(&z_str, Z_SYNC_FLUSH); + if (ret && ret != Z_STREAM_END) goto fail; + if (FAILED(hr = IStream_Write(out, output_buf, z_str.total_out, NULL ))) goto fail; + crc32 = RtlComputeCrc32(crc32, output_buf, z_str.total_out); + 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); + free(output_buf); + + 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) 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; + } + if (exp_crc32 != crc32) return OPC_E_ZIP_CORRUPTED_ARCHIVE; + return hr; + +fail: + inflateEnd(&z_str); + free(input_buf); + free(output_buf); + return hr; +} + /* The stream should be at the start of the central directory. */ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, IOpcPartSet *partset) { @@ -514,7 +659,11 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON goto done; } 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))) { @@ -574,11 +723,21 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON
for (i = 0; i < part_count; i++) { + IStream *content; IOpcPart *part;
hr = IOpcPartSet_CreatePart(partset, parts[i].name, L"", parts[i].entry.opt, &part); if (FAILED(hr)) goto done; + if (FAILED(hr = IOpcPart_GetContentStream(part, &content))) + { + IOpcPart_Release(part); + goto done; + } + + hr = decompress_file_to_stream(stream, &parts[i].entry, content); + IStream_Release(content); IOpcPart_Release(part); + if (FAILED(hr)) goto done; }
done: diff --git a/include/opcbase.idl b/include/opcbase.idl index 73d5a7fb82c..507834b0b4f 100644 --- a/include/opcbase.idl +++ b/include/opcbase.idl @@ -54,4 +54,6 @@ 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_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 | 200 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 3 files changed, 200 insertions(+), 4 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index d3b6754bf52..c37c32cfebd 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); @@ -596,15 +598,185 @@ fail: return hr; }
+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 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_file_to_stream(archive, content_types, xml))) + { + 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)) + { + 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; +} + /* The stream should be at the start of the central directory. */ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, IOpcPartSet *partset) { static const char *content_types_name = "[Content_Types].xml"; + 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; ULONG i, part_count = 0; 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 central_directory_header dir = {0}; @@ -678,6 +850,7 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON hr = OPC_E_ZIP_DUPLICATE_NAME; goto done; } + content_types = entry; found_content_types = TRUE; } else @@ -720,13 +893,33 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON 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++) { + const struct content_type_entry *content_type; + const struct rb_entry *entry; IStream *content; IOpcPart *part; + 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; + }
- hr = IOpcPartSet_CreatePart(partset, parts[i].name, L"", parts[i].entry.opt, &part); + content_type = RB_ENTRY_VALUE(entry, struct content_type_entry, entry); + hr = IOpcPartSet_CreatePart(partset, parts[i].name, content_type->type, parts[i].entry.opt, &part); if (FAILED(hr)) goto done; if (FAILED(hr = IOpcPart_GetContentStream(part, &content))) { @@ -737,10 +930,11 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON hr = decompress_file_to_stream(stream, &parts[i].entry, content); IStream_Release(content); IOpcPart_Release(part); - 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/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 081e0e686fc..f778e04f365 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1322,7 +1322,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 507834b0b4f..dd4f9a253ea 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)")