From: Vibhav Pant vibhavp@gmail.com
--- dlls/opcservices/compress.c | 270 ++++++++++++++++++++++++++- dlls/opcservices/tests/opcservices.c | 10 +- include/opcbase.idl | 1 + 3 files changed, 271 insertions(+), 10 deletions(-)
diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 76f4881d859..d6853d14232 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> @@ -127,6 +128,27 @@ struct zip64_end_of_central_directory_locator uint32_t disk_num; };
+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)
enum zip_signatures @@ -145,12 +167,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 +599,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 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 != 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.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 != 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)")