From: Vibhav Pant <vibhavp(a)gmail.com> --- dlls/opcservices/compress.c | 193 ++++++++++++++++++++++++++- dlls/opcservices/opc_private.h | 2 +- dlls/opcservices/package.c | 2 +- dlls/opcservices/tests/opcservices.c | 2 +- include/opcbase.idl | 2 + 5 files changed, 195 insertions(+), 6 deletions(-) diff --git a/dlls/opcservices/compress.c b/dlls/opcservices/compress.c index 8a1f9f07b1a..372edb06007 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); @@ -610,7 +612,7 @@ static BOOL compress_validate_part_size(const struct zip_entry *entry, const str } /* 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) +HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out, BOOL set_size) { ULONG64 compressed_data_read = 0, data_uncompressed = 0; ULONG ext_size = 0, read, crc32 = 0, exp_crc32 = 0; @@ -647,6 +649,8 @@ HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IS 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 (set_size && FAILED(hr = IStream_SetSize(out, *(ULARGE_INTEGER *)&entry->uncompressed_size))) + return hr; if (!(file.flags & USE_DATA_DESCRIPTOR)) exp_crc32 = file.crc32; if (!(input_buf = malloc(buffer_size))) return E_OUTOFMEMORY; @@ -729,18 +733,176 @@ HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IS return exp_crc32 != crc32 ? OPC_E_ZIP_CORRUPTED_ARCHIVE : S_OK; } +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) +{ + struct content_type_entry *ct = RB_ENTRY_VALUE(entry, struct content_type_entry, entry); + + free(ct->extension_or_part_name); + free(ct->type); + free(ct); +} + +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 (SUCCEEDED(hr = IXmlReader_GetValue(reader, &val, NULL))) + hr = (*val_ret = wcsdup(val)) ? S_OK : E_OUTOFMEMORY; + return hr; +} + +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 (SUCCEEDED(hr = IXmlReader_GetLocalName(reader, &name, NULL)) && wcscmp(name, exp_name)) + hr = OPC_E_INVALID_CONTENT_TYPE_XML; + } + return hr; +} + +static HRESULT content_types_add_type(IXmlReader *reader, const WCHAR *attr_name, struct rb_tree *types) +{ + WCHAR *ext_or_part = NULL, *content_type = NULL; + struct content_type_entry *type = NULL; + 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))) + goto done; + if (!(type = calloc(1, sizeof(*type)))) + { + hr = E_OUTOFMEMORY; + goto done; + } + type->extension_or_part_name = ext_or_part; + type->type = content_type; + if (rb_put(types, ext_or_part, &type->entry)) + hr = OPC_E_DUPLICATE_DEFAULT_EXTENSION; +done: + if (FAILED(hr)) + { + free(content_type); + free(ext_or_part); + free(type); + } + return hr; +} + +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_to_stream(archive, content_types, xml, TRUE))) + { + 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)) goto done; + + if (FAILED(hr = xml_get_next_node(reader, XmlNodeType_XmlDeclaration, L"xml"))) + goto done; + if (FAILED(hr = xml_get_next_node(reader, XmlNodeType_Element, L"Types"))) + goto done; + while(SUCCEEDED(hr = xml_get_next_node(reader, XmlNodeType_None, NULL))) + { + const WCHAR *name; + XmlNodeType type; + + if (FAILED(hr = IXmlReader_GetNodeType(reader, &type))) break; + if (type != XmlNodeType_Element && type != XmlNodeType_EndElement) continue; + if (FAILED(hr = IXmlReader_GetLocalName(reader, &name, NULL))) break; + 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)) break; + } +done: + 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, OPC_READ_FLAGS flags, ULONG dir_records, struct opc_part_set *partset) { static const char *content_types_name = "[Content_Types].xml"; ULONG i, part_count = 0, nameW_len = 0, nameA_len = 0; + 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; WCHAR *nameW = NULL; char *nameA = NULL; 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}; @@ -808,6 +970,7 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_ hr = OPC_E_ZIP_DUPLICATE_NAME; goto done; } + content_types = entry; found_content_types = TRUE; } else @@ -854,14 +1017,38 @@ static HRESULT compress_read_entries(IOpcFactory *factory, IStream *stream, OPC_ 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++) { - hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, flags, parts[i].name, L""); + const struct content_type_entry *content_type; + const struct rb_entry *entry; + 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; + } + + content_type = RB_ENTRY_VALUE(entry, struct content_type_entry, entry); + hr = opc_part_set_add_zip_part(partset, stream, &parts[i].entry, parts[i].opt, flags, parts[i].name, + content_type->type); 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/opc_private.h b/dlls/opcservices/opc_private.h index 884ccbb4946..f16a3262257 100644 --- a/dlls/opcservices/opc_private.h +++ b/dlls/opcservices/opc_private.h @@ -86,4 +86,4 @@ 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, OPC_READ_FLAGS flags, struct opc_part_set *part_set); -extern HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out); +extern HRESULT decompress_to_stream(IStream *archive, const struct zip_entry *entry, IStream *out, BOOL set_size); diff --git a/dlls/opcservices/package.c b/dlls/opcservices/package.c index 4066b310d76..0b8cd8f8b28 100644 --- a/dlls/opcservices/package.c +++ b/dlls/opcservices/package.c @@ -837,7 +837,7 @@ static HRESULT WINAPI opc_part_GetContentStream(IOpcPart *iface, IStream **strea opc_content_release(content); return hr; } - if (FAILED(hr = decompress_to_stream(part->archive, &part->zip_entry, decompressed))) + if (FAILED(hr = decompress_to_stream(part->archive, &part->zip_entry, decompressed, FALSE))) { IStream_Release(decompressed); opc_content_release(content); 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)") -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/8837