diff --git a/dlls/windowscodecs/metadataquery.c b/dlls/windowscodecs/metadataquery.c index 0b7eb6c7c38..473836f8dc6 100644 --- a/dlls/windowscodecs/metadataquery.c +++ b/dlls/windowscodecs/metadataquery.c @@ -22,10 +22,12 @@ #include #define COBJMACROS +#define NONAMELESSUNION #include "windef.h" #include "winbase.h" #include "objbase.h" +#include "propvarutil.h" #include "wincodecs_private.h" @@ -33,6 +35,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(wincodecs); +static const WCHAR *map_shortname_to_schema(const GUID *format, const WCHAR *name); + typedef struct { IWICMetadataQueryReader IWICMetadataQueryReader_iface; LONG ref; @@ -125,12 +129,470 @@ static HRESULT WINAPI mqr_GetLocation(IWICMetadataQueryReader *iface, UINT len, return S_OK; } -static HRESULT WINAPI mqr_GetMetadataByName(IWICMetadataQueryReader *iface, - LPCWSTR wzName, PROPVARIANT *pvarValue) +struct string_t +{ + const WCHAR *str; + int len; +}; + +static const struct +{ + int len; + WCHAR str[10]; + VARTYPE vt; +} str2vt[] = +{ + { 4, {'c','h','a','r'}, VT_I1 }, + { 5, {'u','c','h','a','r'}, VT_UI1 }, + { 5, {'s','h','o','r','t'}, VT_I2 }, + { 6, {'u','s','h','o','r','t'}, VT_UI2 }, + { 4, {'l','o','n','g'}, VT_I4 }, + { 5, {'u','l','o','n','g'}, VT_UI4 }, + { 3, {'i','n','t'}, VT_I4 }, + { 4, {'u','i','n','t'}, VT_UI4 }, + { 8, {'l','o','n','g','l','o','n','g'}, VT_I8 }, + { 9, {'u','l','o','n','g','l','o','n','g'}, VT_UI8 }, + { 5, {'f','l','o','a','t'}, VT_R4 }, + { 6, {'d','o','u','b','l','e'}, VT_R8 }, + { 3, {'s','t','r'}, VT_LPSTR }, + { 4, {'w','s','t','r'}, VT_LPWSTR }, + { 4, {'g','u','i','d'}, VT_CLSID }, + { 4, {'b','o','o','l'}, VT_BOOL } +}; + +static VARTYPE map_type(struct string_t *str) +{ + UINT i; + + for (i = 0; i < sizeof(str2vt)/sizeof(str2vt[0]); i++) + { + if (str2vt[i].len == str->len) + { + if (CompareStringW(LOCALE_SYSTEM_DEFAULT, NORM_IGNORECASE, + str->str, str->len, str2vt[i].str, str2vt[i].len) == CSTR_EQUAL) + return str2vt[i].vt; + } + } + + WARN("type %s is not recognized\n", wine_dbgstr_wn(str->str, str->len)); + + return VT_ILLEGAL; +} + +static HRESULT get_token(struct string_t *elem, PROPVARIANT *id, PROPVARIANT *schema, int *idx) +{ + const WCHAR *start, *end, *p; + WCHAR *bstr; + struct string_t next_elem; + HRESULT hr; + + TRACE("%s, len %d\n", wine_dbgstr_wn(elem->str, elem->len), elem->len); + + PropVariantInit(id); + PropVariantInit(schema); + + if (!elem->len) return S_OK; + + start = elem->str; + + if (*start == '[') + { + WCHAR *idx_end; + + if (start[1] < '0' || start[1] > '9') return DISP_E_TYPEMISMATCH; + + *idx = strtolW(start + 1, &idx_end, 10); + if (idx_end > elem->str + elem->len) return WINCODEC_ERR_INVALIDQUERYREQUEST; + if (*idx_end != ']') return WINCODEC_ERR_INVALIDQUERYREQUEST; + if (*idx < 0) return WINCODEC_ERR_INVALIDQUERYREQUEST; + end = idx_end + 1; + + next_elem.str = end; + next_elem.len = elem->len - (end - start); + hr = get_token(&next_elem, id, schema, idx); + if (hr != S_OK) + { + TRACE("parse_elem error %#x\n", hr); + return hr; + } + elem->len = (end - start) + next_elem.len; + + TRACE("indexed %s [%d]\n", wine_dbgstr_wn(elem->str, elem->len), *idx); + return S_OK; + } + else if (*start == '{') + { + VARTYPE vt; + PROPVARIANT next_token; + + end = memchrW(start + 1, '=', elem->len - 1); + if (!end) return WINCODEC_ERR_INVALIDQUERYREQUEST; + if (end > elem->str + elem->len) return WINCODEC_ERR_INVALIDQUERYREQUEST; + + next_elem.str = start + 1; + next_elem.len = end - start - 1; + vt = map_type(&next_elem); + TRACE("type %s => %d\n", wine_dbgstr_wn(next_elem.str, next_elem.len), vt); + if (vt == VT_ILLEGAL) return WINCODEC_ERR_WRONGSTATE; + + next_token.vt = VT_BSTR; + next_token.u.bstrVal = SysAllocStringLen(NULL, elem->len - (end - start) + 1); + if (!next_token.u.bstrVal) return E_OUTOFMEMORY; + + bstr = next_token.u.bstrVal; + + end++; + p = end; + while (*end && *end != '}' && end - start < elem->len) + { + if (*end == '\\') end++; + *bstr++ = *end++; + } + if (!*end || *end != '}') + { + PropVariantClear(&next_token); + return WINCODEC_ERR_INVALIDQUERYREQUEST; + } + *bstr = 0; + TRACE("schema/id %s\n", wine_dbgstr_w(next_token.u.bstrVal)); + + if (vt == VT_CLSID) + { + id->vt = VT_CLSID; + id->u.puuid = CoTaskMemAlloc(sizeof(GUID)); + if (!id->u.puuid) return E_OUTOFMEMORY; + + hr = UuidFromStringW(next_token.u.bstrVal, id->u.puuid); + } + else + hr = PropVariantChangeType(id, &next_token, 0, vt); + PropVariantClear(&next_token); + if (hr != S_OK) + { + PropVariantClear(id); + PropVariantClear(schema); + return hr; + } + + end++; + if (*end == ':') + { + PROPVARIANT next_id, next_schema; + int next_idx = 0; + + next_elem.str = end + 1; + next_elem.len = elem->len - (end - start + 1); + hr = get_token(&next_elem, &next_id, &next_schema, &next_idx); + if (hr != S_OK) + { + TRACE("get_token error %#x\n", hr); + return hr; + } + elem->len = (end - start + 1) + next_elem.len; + + TRACE("id %s [%d]\n", wine_dbgstr_wn(elem->str, elem->len), *idx); + + if (next_schema.vt != VT_EMPTY) + { + PropVariantClear(&next_id); + PropVariantClear(&next_schema); + return WINCODEC_ERR_WRONGSTATE; + } + + *schema = *id; + *id = next_id; + + return S_OK; + } + + elem->len = end - start; + return S_OK; + } + + end = memchrW(start, '/', elem->len); + if (!end) end = start + elem->len; + + p = memchrW(start, ':', end - start); + if (p) + { + next_elem.str = p + 1; + next_elem.len = end - p - 1; + + elem->len = p - start; + } + else + elem->len = end - start; + + id->vt = VT_BSTR; + id->u.bstrVal = SysAllocStringLen(NULL, elem->len + 1); + if (!id->u.bstrVal) return E_OUTOFMEMORY; + + bstr = id->u.bstrVal; + p = elem->str; + while (p - elem->str < elem->len) + { + if (*p == '\\') p++; + *bstr++ = *p++; + } + *bstr = 0; + TRACE("%s [%d]\n", wine_dbgstr_variant((VARIANT *)id), *idx); + + if (*p == ':') + { + PROPVARIANT next_id, next_schema; + int next_idx = 0; + + hr = get_token(&next_elem, &next_id, &next_schema, &next_idx); + if (hr != S_OK) + { + TRACE("get_token error %#x\n", hr); + PropVariantClear(id); + PropVariantClear(schema); + return hr; + } + elem->len += next_elem.len + 1; + + TRACE("id %s [%d]\n", wine_dbgstr_wn(elem->str, elem->len), *idx); + + if (next_schema.vt != VT_EMPTY) + { + PropVariantClear(&next_id); + PropVariantClear(&next_schema); + PropVariantClear(id); + PropVariantClear(schema); + return WINCODEC_ERR_WRONGSTATE; + } + + *schema = *id; + *id = next_id; + } + + return S_OK; +} + +static HRESULT find_reader_from_block(IWICMetadataBlockReader *block_reader, UINT index, + GUID *guid, IWICMetadataReader **reader) +{ + HRESULT hr; + GUID format; + IWICMetadataReader *new_reader; + UINT count, i, matched_index; + + *reader = NULL; + + hr = IWICMetadataBlockReader_GetCount(block_reader, &count); + if (hr != S_OK) return hr; + + matched_index = 0; + + for (i = 0; i < count; i++) + { + hr = IWICMetadataBlockReader_GetReaderByIndex(block_reader, i, &new_reader); + if (hr != S_OK) return hr; + + hr = IWICMetadataReader_GetMetadataFormat(new_reader, &format); + if (hr == S_OK) + { + if (IsEqualGUID(&format, guid)) + { + if (matched_index == index) + { + *reader = new_reader; + return S_OK; + } + + matched_index++; + } + } + + IWICMetadataReader_Release(new_reader); + if (hr != S_OK) return hr; + } + + return WINCODEC_ERR_PROPERTYNOTFOUND; +} + +static HRESULT get_next_reader(IWICMetadataReader *reader, UINT index, + GUID *guid, IWICMetadataReader **new_reader) +{ + HRESULT hr; + PROPVARIANT schema, id, value; + + *new_reader = NULL; + + PropVariantInit(&schema); + PropVariantInit(&id); + PropVariantInit(&value); + + if (index) + { + schema.vt = VT_UI2; + schema.u.uiVal = index; + } + + id.vt = VT_CLSID; + id.u.puuid = guid; + hr = IWICMetadataReader_GetValue(reader, &schema, &id, &value); + if (hr != S_OK) return hr; + + if (value.vt == VT_UNKNOWN) + hr = IUnknown_QueryInterface(value.u.punkVal, &IID_IWICMetadataReader, (void **)new_reader); + else + hr = WINCODEC_ERR_UNEXPECTEDMETADATATYPE; + + PropVariantClear(&value); + return hr; +} + +static HRESULT WINAPI mqr_GetMetadataByName(IWICMetadataQueryReader *iface, LPCWSTR query, PROPVARIANT *value) { QueryReader *This = impl_from_IWICMetadataQueryReader(iface); - FIXME("(%p,%s,%p)\n", This, wine_dbgstr_w(wzName), pvarValue); - return E_NOTIMPL; + struct string_t elem; + WCHAR *full_query; + const WCHAR *p; + int index, len; + PROPVARIANT tk_id, tk_schema, new_value; + GUID guid; + IWICMetadataReader *reader; + HRESULT hr = S_OK; + + TRACE("(%p,%s,%p)\n", This, wine_dbgstr_w(query), value); + + len = lstrlenW(query) + 1; + if (This->root) len += lstrlenW(This->root); + full_query = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); + full_query[0] = 0; + if (This->root) + lstrcpyW(full_query, This->root); + lstrcatW(full_query, query); + + PropVariantInit(&tk_id); + PropVariantInit(&tk_schema); + PropVariantInit(&new_value); + + reader = NULL; + p = full_query; + + while (*p) + { + if (*p != '/') + { + WARN("query should start with '/'\n"); + hr = WINCODEC_ERR_PROPERTYNOTSUPPORTED; + break; + } + + p++; + + index = 0; + elem.str = p; + elem.len = lstrlenW(p); + hr = get_token(&elem, &tk_id, &tk_schema, &index); + if (hr != S_OK) + { + WARN("get_token error %#x\n", hr); + break; + } + TRACE("parsed %d characters: %s, index %d\n", elem.len, wine_dbgstr_wn(elem.str, elem.len), index); + TRACE("id %s, schema %s\n", wine_dbgstr_variant((VARIANT *)&tk_id), wine_dbgstr_variant((VARIANT *)&tk_schema)); + + if (!elem.len) break; + + if (tk_id.vt == VT_CLSID || (tk_id.vt == VT_BSTR && WICMapShortNameToGuid(tk_id.u.bstrVal, &guid) == S_OK)) + { + WCHAR *root; + + if (tk_schema.vt != VT_EMPTY) + { + FIXME("unsupported schema vt %u\n", tk_schema.vt); + PropVariantClear(&tk_schema); + } + + if (tk_id.vt == VT_CLSID) guid = *tk_id.u.puuid; + + if (reader) + { + IWICMetadataReader *new_reader; + + hr = get_next_reader(reader, index, &guid, &new_reader); + IWICMetadataReader_Release(reader); + reader = new_reader; + } + else + hr = find_reader_from_block(This->block, index, &guid, &reader); + + if (hr != S_OK) break; + + root = SysAllocStringLen(NULL, elem.str + elem.len - full_query + 2); + if (!root) + { + hr = E_OUTOFMEMORY; + break; + } + lstrcpynW(root, full_query, p - full_query + elem.len + 1); + + PropVariantClear(&new_value); + new_value.vt = VT_UNKNOWN; + hr = MetadataQueryReader_CreateInstance(This->block, root, (IWICMetadataQueryReader **)&new_value.u.punkVal); + SysFreeString(root); + if (hr != S_OK) break; + } + else + { + PROPVARIANT schema, id; + + if (!reader) + { + hr = WINCODEC_ERR_INVALIDQUERYREQUEST; + break; + } + + if (tk_schema.vt == VT_BSTR) + { + hr = IWICMetadataReader_GetMetadataFormat(reader, &guid); + if (hr != S_OK) break; + + schema.vt = VT_LPWSTR; + schema.u.pwszVal = (LPWSTR)map_shortname_to_schema(&guid, tk_schema.u.bstrVal); + if (!schema.u.pwszVal) + schema.u.pwszVal = tk_schema.u.bstrVal; + } + else + schema = tk_schema; + + if (tk_id.vt == VT_BSTR) + { + id.vt = VT_LPWSTR; + id.u.pwszVal = tk_id.u.bstrVal; + } + else + id = tk_id; + + PropVariantClear(&new_value); + hr = IWICMetadataReader_GetValue(reader, &schema, &id, &new_value); + if (hr != S_OK) break; + } + + p += elem.len; + + PropVariantClear(&tk_id); + PropVariantClear(&tk_schema); + } + + if (reader) + IWICMetadataReader_Release(reader); + + PropVariantClear(&tk_id); + PropVariantClear(&tk_schema); + + if (hr == S_OK) + *value = new_value; + else + PropVariantClear(&new_value); + + HeapFree(GetProcessHeap(), 0, full_query); + + return hr; } static HRESULT WINAPI mqr_GetEnumerator(IWICMetadataQueryReader *iface, @@ -407,6 +869,26 @@ static const struct { MPReg, MPReg_scheme } }; +static const WCHAR *map_shortname_to_schema(const GUID *format, const WCHAR *name) +{ + UINT i; + + /* It appears that the only metadata formats + * that support schemas are xmp and xmpstruct. + */ + if (!IsEqualGUID(format, &GUID_MetadataFormatXMP) && + !IsEqualGUID(format, &GUID_MetadataFormatXMPStruct)) + return NULL; + + for (i = 0; i < sizeof(name2schema)/sizeof(name2schema[0]); i++) + { + if (!lstrcmpW(name2schema[i].name, name)) + return name2schema[i].schema; + } + + return NULL; +} + HRESULT WINAPI WICMapSchemaToName(REFGUID format, LPWSTR schema, UINT len, WCHAR *name, UINT *ret_len) { UINT i; diff --git a/include/propidl.idl b/include/propidl.idl index fbe80d8174e..71401cb454c 100644 --- a/include/propidl.idl +++ b/include/propidl.idl @@ -141,6 +141,8 @@ interface IPropertyStorage : IUnknown [case(VT_BSTR_BLOB)] BSTRBLOB bstrblobVal; [case(VT_LPSTR)] LPSTR pszVal; [case(VT_LPWSTR)] LPWSTR pwszVal; + [case(VT_UNKNOWN)] IUnknown *punkVal; + [case(VT_DISPATCH)] IDispatch *pdispVal; [case(VT_UI1|VT_VECTOR)] CAUB caub; [case(VT_I2|VT_VECTOR)] CAI cai; [case(VT_UI2|VT_VECTOR)] CAUI caui;