From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 473 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 6 +- 2 files changed, 468 insertions(+), 11 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index d9251a20893..7098734ab3a 100644 --- a/dlls/opcservices/compress.c +++ b/dlls/opcservices/compress.c @@ -18,6 +18,7 @@
#define COBJMACROS
+#include <stdint.h> #include <stdarg.h> #include <zlib.h>
@@ -74,6 +75,16 @@ struct central_directory_header DWORD local_file_offset; };
+struct zip64_extended_field +{ + WORD signature; + WORD size; + ULONG64 uncompressed_size; + ULONG64 compressed_size; + ULONG64 local_file_offset; + DWORD diskid; +}; + struct central_directory_end { DWORD signature; @@ -86,14 +97,45 @@ struct central_directory_end WORD comment_length; };
+struct central_directory_end_locator +{ + DWORD signature; + DWORD diskid; + ULONG64 end_directory_offset; + DWORD num_disks; +}; + +struct central_directory_end64 +{ + DWORD signature; + ULONG64 size; + WORD version; + WORD version_needed; + DWORD diskid; + DWORD firstdisk; + ULONG64 records_num; + ULONG64 records_total; + ULONG64 directory_size; + ULONG64 directory_offset; +}; #pragma pack(pop)
#define CENTRAL_DIR_SIGNATURE 0x02014b50 #define LOCAL_HEADER_SIGNATURE 0x04034b50 #define DIRECTORY_END_SIGNATURE 0x06054b50 +#define DIRECTORY_END_LOCATOR_SIGNATURE 0x07064b50 +#define DIRECTORY_END64_SIGNATURE 0x06064b50 #define DATA_DESCRIPTOR_SIGNATURE 0x08074b50 +#define ZIP64_EXTENDED_INFO_SIGNATURE 0x00000001 #define VERSION 20
+static const OPC_COMPRESSION_OPTIONS deflate_opts[] = { + OPC_COMPRESSION_NORMAL, + OPC_COMPRESSION_MAXIMUM, + OPC_COMPRESSION_FAST, + OPC_COMPRESSION_SUPERFAST +}; + enum entry_flags { DEFLATE_NORMAL = 0x0, @@ -379,9 +421,36 @@ HRESULT compress_add_file(struct zip_archive *archive, const WCHAR *path, return S_OK; }
+struct zip_entry +{ + OPC_COMPRESSION_OPTIONS opt; + ULARGE_INTEGER local_file_offset; +}; + +static const char *debugstr_zip_entry(const struct zip_entry *entry) +{ + static const char *opts[] = { + "OPC_COMPRESSION_NONE", + "OPC_COMPRESSION_NORMAL", + "OPC_COMPRESSION_MAXIMUM", + "OPC_COMPRESSION_FAST", + "OPC_COMPRESSION_SUPERFAST" + }; + const char *opt_str; + + if (entry->opt + 1 < ARRAY_SIZE(opts)) + opt_str = opts[entry->opt + 1]; + else + opt_str = wine_dbg_sprintf("%d", entry->opt); + + return wine_dbg_sprintf("{%s, %#I64x}", opt_str, entry->local_file_offset.QuadPart); +} + struct zip_part_set { IOpcPartSet IOpcPartSet_iface; + IOpcPart **parts; + size_t count; LONG ref; };
@@ -425,16 +494,45 @@ static ULONG WINAPI zip_part_set_Release(IOpcPartSet *iface) TRACE("(%p)\n", iface);
if (!ref) + { + size_t i; + + for (i = 0; i < part_set->count; i++) + IOpcPart_Release(part_set->parts[i]); + free(part_set->parts); free(part_set); + } return ref; }
static HRESULT WINAPI zip_part_set_GetPart(IOpcPartSet *iface, IOpcPartUri *name, IOpcPart **part) { - FIXME("(%p, %p, %p) stub\n", iface, name, part); + struct zip_part_set *part_set= impl_from_IOpcPartSet(iface); + size_t i; + + TRACE("(%p, %p, %p)\n", iface, name, part); + + for (i = 0; i < part_set->count; i++) + { + BOOL exists = FALSE; + IOpcPartUri *name2; + HRESULT hr; + + if (FAILED(hr = IOpcPart_GetName(part_set->parts[i], &name2))) + return hr; + hr = IOpcPartUri_IsEqual(name, (IUri *)name2, &exists); + IOpcPartUri_Release(name2); + if (FAILED(hr)) + return hr; + if (exists) + { + IOpcPart_AddRef((*part = part_set->parts[i])); + return S_OK; + } + }
*part = NULL; - return E_NOTIMPL; + return OPC_E_NO_SUCH_PART; }
static HRESULT WINAPI zip_part_set_CreatePart(IOpcPartSet *iface, IOpcPartUri *name, const WCHAR *type, @@ -453,17 +551,43 @@ static HRESULT WINAPI zip_part_set_DeletePart(IOpcPartSet *iface, IOpcPartUri *n return E_NOTIMPL; }
+static HRESULT part_name_exists(IOpcPartUri *name, IOpcPart **parts, size_t parts_len, BOOL *exists) +{ + size_t i; + + *exists = FALSE; + for (i = 0; i < parts_len; i++) + { + IOpcPartUri *name2; + HRESULT hr; + + if (FAILED(hr = IOpcPart_GetName(parts[i], &name2))) + return hr; + hr = IOpcPartUri_IsEqual(name, (IUri *)name2, exists); + IOpcPartUri_Release(name2); + if (FAILED(hr)) + return hr; + if (*exists) + { + *exists = TRUE; + return S_OK; + } + } + return S_OK; +} + static HRESULT WINAPI zip_part_set_PartExists(IOpcPartSet *iface, IOpcPartUri *name, BOOL *exists) { - FIXME("(%p, %p, %p) stub\n", iface, name, exists); + struct zip_part_set *part_set = impl_from_IOpcPartSet(iface);
- return E_NOTIMPL; + TRACE("(%p, %p, %p)\n", iface, name, exists); + + return part_name_exists(name, part_set->parts, part_set->count, exists); }
static HRESULT WINAPI zip_part_set_GetEnumerator(IOpcPartSet *iface, IOpcPartEnumerator **out) { FIXME("(%p, %p) stub\n", iface, out); - *out = NULL; return E_NOTIMPL; } @@ -482,7 +606,7 @@ static const IOpcPartSetVtbl zip_part_set_vtbl = zip_part_set_GetEnumerator, };
-static HRESULT zip_part_set_create(IOpcPartSet **out) +static HRESULT zip_part_set_create(IOpcPart **parts, size_t len, IOpcPartSet **out) { struct zip_part_set *part_set;
@@ -492,14 +616,347 @@ static HRESULT zip_part_set_create(IOpcPartSet **out) *out = &part_set->IOpcPartSet_iface; part_set->IOpcPartSet_iface.lpVtbl = &zip_part_set_vtbl; part_set->ref = 1; + part_set->count = len; + part_set->parts = parts; *out = &part_set->IOpcPartSet_iface;
return S_OK; }
+struct zip_part +{ + IOpcPart IOpcPart_iface; + struct zip_entry entry; + IOpcPartUri *name; + LONG ref; +}; + +static inline struct zip_part *impl_from_IOpcPart(IOpcPart *iface) +{ + return CONTAINING_RECORD(iface, struct zip_part, IOpcPart_iface); +} + +static HRESULT WINAPI zip_part_QueryInterface(IOpcPart *iface, REFIID iid, void **out) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %s, %p)\n", iface, debugstr_guid(iid), out); + + if (IsEqualGUID(iid, &IID_IUnknown) || IsEqualGUID(iid, &IID_IOpcPart)) + { + IOpcPart_AddRef((*out = &part->IOpcPart_iface)); + return S_OK; + } + + *out = NULL; + ERR("%s not implemented, returning E_NOINTERFACE.\n", debugstr_guid(iid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI zip_part_AddRef(IOpcPart *iface) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + TRACE("(%p)\n", part); + return InterlockedIncrement(&part->ref); +} + +static ULONG WINAPI zip_part_Release(IOpcPart *iface) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + ULONG ref = InterlockedDecrement(&part->ref); + + TRACE("(%p)\n", part); + + if (!ref) + { + IOpcPartUri_Release(part->name); + free(part); + } + return ref; +} + +static HRESULT WINAPI zip_part_GetRelationshipSet(IOpcPart *iface, IOpcRelationshipSet **rels_set) +{ + FIXME("(%p, %p) stub!\n", iface, rels_set); + *rels_set = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_GetContentStream(IOpcPart *iface, IStream **stream) +{ + FIXME("(%p, %p) stub!\n", iface, stream); + *stream = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_GetName(IOpcPart *iface, IOpcPartUri **name) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %p)\n", iface, name); + + IOpcPartUri_AddRef((*name = part->name)); + return S_OK; +} + +static HRESULT WINAPI zip_part_GetContentType(IOpcPart *iface, WCHAR **type) +{ + FIXME("(%p, %p) stub!\n", iface, type); + *type = NULL; + return E_NOTIMPL; +} + +static HRESULT WINAPI zip_part_GetCompressionOptions(IOpcPart *iface, OPC_COMPRESSION_OPTIONS *options) +{ + struct zip_part *part = impl_from_IOpcPart(iface); + + TRACE("(%p, %p)\n", iface, options); + + *options = part->entry.opt; + return S_OK; +} + +static const IOpcPartVtbl zip_part_vtbl = +{ + zip_part_QueryInterface, + zip_part_AddRef, + zip_part_Release, + zip_part_GetRelationshipSet, + zip_part_GetContentStream, + zip_part_GetName, + zip_part_GetContentType, + zip_part_GetCompressionOptions, +}; + +static HRESULT zip_part_create(IOpcPartUri *name, struct zip_entry entry, IOpcPart **part) +{ + struct zip_part *zip_part; + + if (!(zip_part = calloc(1, sizeof(*zip_part)))) + return E_OUTOFMEMORY; + + zip_part->IOpcPart_iface.lpVtbl = &zip_part_vtbl; + IOpcPartUri_AddRef((zip_part->name = name)); + zip_part->entry = entry; + zip_part->ref = 1; + *part = &zip_part->IOpcPart_iface; + return S_OK; +} + +/* The stream should be at the start of the central directory. */ +static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, ULONG dir_records, IOpcPart ***parts, + size_t *parts_len) +{ + static const char *content_types_name = "[Content_Types].xml"; + BOOL found_content_types = FALSE; + HRESULT hr; + ULONG i; + + *parts = NULL; + *parts_len = 0; + for (i = 0; i < dir_records; i++) + { + struct central_directory_header dir = {0}; + struct zip64_extended_field ext = {0}; + struct zip_entry entry = {0}; + ULONG read, ext_size = 0; + LARGE_INTEGER off; + char *name; + + hr = IStream_Read(stream, &dir, sizeof(dir), &read); + if (hr != S_OK) + goto failed; + if (!(name = calloc(1, dir.name_length + 1))) + { + hr = E_OUTOFMEMORY; + goto failed; + } + + hr = IStream_Read(stream, name, dir.name_length, &read); + if (hr != S_OK) + { + free(name); + goto failed; + } + /* If any of the fields are UINT32_MAX, there is an extra ZIP64 extended information field */ + if (dir.uncompressed_size == UINT32_MAX) + ext_size = 8; + if (dir.compressed_size == UINT32_MAX) + ext_size = 16; + if (dir.local_file_offset == UINT32_MAX) + ext_size = 24; + if (ext_size) + ext_size += 4; /* For the signature and size fields. */ + /* Make sure that the central directory record actually indicates there is a ZIP64 extended field. */ + if (dir.extra_length < ext_size) + { + hr = OPC_E_ZIP_CORRUPTED_ARCHIVE; + free(name); + goto failed; + } + if (ext_size && ((hr = IStream_Read(stream, &ext, ext_size, &read) != S_OK))) + { + free(name); + goto failed; + } + + off.QuadPart = dir.extra_length - ext_size; + /* Skip any extra data that we haven't read. */ + if (off.QuadPart && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) + { + free(name); + goto failed; + } + entry.opt = dir.method ? deflate_opts[(dir.flags & DEFLATE_LEVEL_MASK) >> 1] : OPC_COMPRESSION_NONE; + entry.local_file_offset.QuadPart = (dir.local_file_offset == UINT32_MAX) ? ext.local_file_offset : dir.local_file_offset; + off.QuadPart = dir.comment_length; + if (dir.comment_length && FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, NULL))) + { + free(name); + goto failed; + } + if (!strcmp(name, content_types_name)) + { + free(name); + if (found_content_types) + { + hr = OPC_E_ZIP_DUPLICATE_NAME; + goto failed; + } + found_content_types = TRUE; + } + else + { + IOpcPart *new_part, **tmp; + IOpcPartUri *name_uri; + BOOL dup = FALSE; + WCHAR *nameW; + INT len; + + TRACE("Adding new OPC part %s: %s\n", debugstr_a(name), debugstr_zip_entry(&entry)); + + len = MultiByteToWideChar(CP_ACP, 0, name, -1, NULL, 0); + if (!(nameW = calloc(sizeof(WCHAR), len))) + { + hr = E_OUTOFMEMORY; + free(name); + goto failed; + } + MultiByteToWideChar(CP_ACP, 0, name, -1, nameW, len); + free(name); + hr = IOpcFactory_CreatePartUri(factory, nameW, &name_uri); + free(nameW); + if (FAILED(hr)) + goto failed; + if (FAILED(hr = part_name_exists(name_uri, *parts, *parts_len, &dup)) || dup) + { + IOpcPartUri_Release(name_uri); + if (dup) hr = OPC_E_ZIP_DUPLICATE_NAME; + goto failed; + } + hr = zip_part_create(name_uri, entry, &new_part); + IOpcPartUri_Release(name_uri); + if (FAILED(hr)) + goto failed; + if (!(tmp = realloc(*parts, sizeof(**parts) * (*parts_len + 1)))) + { + hr = E_OUTOFMEMORY; + IOpcPart_Release(new_part); + goto failed; + } + *parts = tmp; + (*parts)[*parts_len] = new_part; + *parts_len += 1; + } + } + return S_OK; + +failed: + for (i = 0; i < *parts_len; i++) + IOpcPart_Release((*parts)[i]); + free(*parts); + *parts = NULL; + *parts_len = 0; + return hr == S_FALSE ? OPC_E_ZIP_CORRUPTED_ARCHIVE : hr; +} + HRESULT compress_open_archive(IOpcFactory *factory, IStream *stream, IOpcPartSet **part_set) { - FIXME("(%p, %p, %p) stub\n", factory, stream, part_set); + struct central_directory_end end = {0}; + ULARGE_INTEGER end_dir_start; + IOpcPart **parts = NULL; + size_t parts_count = 0; + LARGE_INTEGER off; + HRESULT hr; + + off.QuadPart = -sizeof(end); + *part_set = NULL; + hr = IStream_Seek(stream, off, STREAM_SEEK_END, &end_dir_start); + /* Start from the end of the archive to find a end of central directory header. */ + while (SUCCEEDED(hr)) + { + struct central_directory_end_locator locator = {0}; + struct central_directory_end64 end64 = {0}; + LARGE_INTEGER central_dir_start; + ULONG read, dir_records; + + if (FAILED(hr = IStream_Read(stream, &end, sizeof(end), &read))) + return hr; + if (hr == S_FALSE || end.signature != DIRECTORY_END_SIGNATURE) + goto next; + /* Check any of the fields in the header are UINT16_MAX, and there are enough bytes for a EOCD locator and EOCD64 record. + * In that case, this might be a ZIP64 archive. */ + if ((end.records_total == UINT16_MAX || end.directory_size == UINT16_MAX || end.directory_offset == UINT16_MAX) && + end_dir_start.QuadPart >= (sizeof(locator) + sizeof(end64))) + { + ULARGE_INTEGER loc_start, end64_start; + + /* ZIP64 uses an additional End of Central Directory Locator record. */ + off.QuadPart = end_dir_start.QuadPart - sizeof(locator); + if (FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_SET, &loc_start))) + goto next; + if (FAILED(hr = IStream_Read(stream, &locator, sizeof(locator), &read))) + return hr; + if (hr == S_FALSE || locator.signature != DIRECTORY_END_LOCATOR_SIGNATURE || + locator.end_directory_offset >= loc_start.QuadPart || + loc_start.QuadPart - locator.end_directory_offset < sizeof(end64)) + goto next; + off.QuadPart = locator.end_directory_offset; + if (FAILED(hr = IStream_Seek(stream, off, STREAM_SEEK_SET, &end64_start))) + goto next; + if (FAILED(hr = IStream_Read(stream, &end64, sizeof(end64), &read))) + return hr; + if (hr == S_FALSE || end64.signature != DIRECTORY_END64_SIGNATURE || end64.size < (sizeof(end64) - 12)) + goto next; + central_dir_start.QuadPart = end64.directory_offset; + dir_records = end64.records_total; + } + else /* Otherwise, treat it as ZIP32. */ + { + /* Check if end.directory_offset actually points to the start of central directory. */ + if (end.directory_offset >= end_dir_start.QuadPart || + end_dir_start.QuadPart - end.directory_offset < sizeof(end)) + goto next; + central_dir_start.QuadPart = end.directory_offset; + dir_records = end.records_total; + } + + /* The central directory can't be located after the EOCD header. */ + if (central_dir_start.QuadPart >= end_dir_start.QuadPart) + goto next; + if (FAILED(hr = IStream_Seek(stream, central_dir_start, STREAM_SEEK_SET, NULL))) + goto next; + if (FAILED(hr = compress_read_entries(factory, stream, dir_records, &parts, &parts_count))) + return hr; + return zip_part_set_create(parts, parts_count, part_set); + + /* Seek back sizeof(end) bytes from where we initially were, and try again. */ + next: + if (FAILED(hr = IStream_Seek(stream, *(LARGE_INTEGER *)&end_dir_start, STREAM_SEEK_SET, NULL))) + return hr; + off.QuadPart = -sizeof(end); + hr = IStream_Seek(stream, off, STREAM_SEEK_CUR, &end_dir_start); + }
- return zip_part_set_create(part_set); + return hr; } diff --git a/dlls/opcservices/tests/opcservices.c b/dlls/opcservices/tests/opcservices.c index 8e878dff669..ef087e0d2ea 100644 --- a/dlls/opcservices/tests/opcservices.c +++ b/dlls/opcservices/tests/opcservices.c @@ -1335,7 +1335,7 @@ static void test_read_package(void) ok(hr == S_OK, "Failed to create part uri, hr %#lx.\n", hr); hr = IOpcPartSet_GetPart(partset, uri, &part); IOpcPartUri_Release(uri); - todo_wine ok(hr == S_OK, "Failed to get part, hr %#lx.\n", hr); + ok(hr == S_OK, "Failed to get part, hr %#lx.\n", hr); if (FAILED(hr)) continue; hr = IOpcPart_GetContentType(part, &type); todo_wine ok(hr == S_OK, "Failed to get content type, hr %#lx.\n", hr); @@ -1345,8 +1345,8 @@ static void test_read_package(void) CoTaskMemFree(type); } hr = IOpcPart_GetCompressionOptions(part, &opt); - todo_wine ok(hr == S_OK, "Failed to get compression options, hr %#lx.\n", hr); - todo_wine ok(opt == parts[i].opt, "Got opt %d != %d.\n", opt, parts[i].opt); + ok(hr == S_OK, "Failed to get compression options, hr %#lx.\n", hr); + ok(opt == parts[i].opt, "Got opt %d != %d.\n", opt, parts[i].opt); hr = IOpcPart_GetContentStream(part, &stream); IOpcPart_Release(part); todo_wine ok(hr == S_OK, "Failed to get content stream, hr %#lx.\n", hr);