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)")