From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 143 ++++++++++++++++++++++++++- dlls/opcservices/opc_private.h | 4 + dlls/opcservices/package.c | 42 +++++++- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 5 files changed, 190 insertions(+), 3 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 83b60bcf2eb..3b62709e40e 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, ULONG dir_records, struct opc_part_set *partset) { @@ -655,7 +792,11 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULON 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; diff --git a/dlls/opcservices/opc_private.h b/dlls/opcservices/opc_private.h index 72508994462..b407bb7466e 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; }; @@ -82,3 +85,4 @@ extern HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, 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 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 45d3a4ce734..247f8e6cea1 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; }; @@ -819,7 +820,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); } @@ -932,6 +971,7 @@ 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); 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