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`).
-- v6: 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.
From: Vibhav Pant vibhavp@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 6590fec1b19..fb148a2c42a 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;
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 | 10 ++++++++-- include/opcbase.idl | 2 ++ 6 files changed, 48 insertions(+), 4 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index fb148a2c42a..76f4881d859 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -126,6 +126,7 @@ struct zip64_end_of_central_directory_locator uint64_t eocd64_offset; uint32_t disk_num; }; + #pragma pack(pop)
enum zip_signatures @@ -567,3 +568,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 8365916eebb..8a9e136b6d3 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -58,6 +58,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);
@@ -69,3 +70,4 @@ 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, IOpcPartSet *part_set); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index 7b62224b846..1fd8d888c32 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 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)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 249 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 10 +- include/opcbase.idl | 1 + 3 files changed, 250 insertions(+), 10 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 76f4881d859..3b0f2a9eeb8 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> @@ -145,12 +146,20 @@ 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, };
@@ -569,9 +578,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 zip32_central_directory_header dir = {0}; + struct zip64_extra_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.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 zip32_end_of_central_directory 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 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, 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/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)")
From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 151 +++++++++++++++++++++++++++ dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 3 files changed, 154 insertions(+), 1 deletion(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 3b0f2a9eeb8..2963a0b22d6 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -581,6 +581,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; };
@@ -608,6 +611,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_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. */ +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_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; + 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 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; + } + 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) { @@ -671,7 +808,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.offset : dir.local_file_offset; + off.QuadPart = dir.comment_length; if (dir.comment_length && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) { @@ -731,11 +872,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/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)")
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 2963a0b22d6..f72fd0ec5b4 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); @@ -745,15 +747,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 zip32_central_directory_header dir = {0}; @@ -827,6 +999,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 @@ -869,13 +1042,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))) { @@ -886,10 +1079,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 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)")