[PATCH 0/3] MR10542: iertutil: Add URI parsing support for IUriRuntimeClass.
For React Native. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
From: Zhiyi Zhang <zzhang@codeweavers.com> IPv4 addresses like 1.2 and 1.2.3 were not normalized correctly before this commit. --- dlls/iertutil/uri.c | 182 +++++++++++----------------------------- dlls/urlmon/tests/uri.c | 78 +++++++++++++++++ 2 files changed, 126 insertions(+), 134 deletions(-) diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index bdf9f104e42..f6550e19fef 100644 --- a/dlls/iertutil/uri.c +++ b/dlls/iertutil/uri.c @@ -149,8 +149,6 @@ typedef struct { BOOL is_relative; BOOL is_opaque; BOOL has_implicit_scheme; - BOOL has_implicit_ip; - UINT implicit_ipv4; BOOL must_have_path; const WCHAR *scheme; @@ -677,6 +675,21 @@ static INT find_file_extension(const WCHAR *path, DWORD path_len) { return -1; } +static WCHAR *strdupwn(const WCHAR *str, DWORD length) +{ + WCHAR *ret; + + if (!str) + return NULL; + + if ((ret = malloc((length+1)*sizeof(WCHAR)))) + { + memcpy(ret, str, length*sizeof(WCHAR)); + ret[length] = '\0'; + } + return ret; +} + /* Removes all the leading and trailing white spaces or * control characters from the URI and removes all control * characters inside of the URI string. @@ -716,32 +729,6 @@ static BSTR pre_process_uri(LPCWSTR uri) { return ret; } -/* Converts an IPv4 address in numerical form into its fully qualified - * string form. This function returns the number of characters written - * to 'dest'. If 'dest' is NULL this function will return the number of - * characters that would have been written. - * - * It's up to the caller to ensure there's enough space in 'dest' for the - * address. - */ -static DWORD ui2ipv4(WCHAR *dest, UINT address) { - DWORD ret = 0; - UCHAR digits[4]; - - digits[0] = (address >> 24) & 0xff; - digits[1] = (address >> 16) & 0xff; - digits[2] = (address >> 8) & 0xff; - digits[3] = address & 0xff; - - if(!dest) { - WCHAR tmp[16]; - ret = swprintf(tmp, ARRAY_SIZE(tmp), L"%u.%u.%u.%u", digits[0], digits[1], digits[2], digits[3]); - } else - ret = swprintf(dest, 16, L"%u.%u.%u.%u", digits[0], digits[1], digits[2], digits[3]); - - return ret; -} - static DWORD ui2str(WCHAR *dest, UINT value) { DWORD ret = 0; @@ -828,10 +815,9 @@ static BOOL check_dec_octet(const WCHAR **ptr) { * Ex: * "234567" would be considered an implicit IPv4 address. */ -static BOOL check_implicit_ipv4(const WCHAR **ptr, UINT *val) { +static BOOL check_implicit_ipv4(const WCHAR **ptr) { const WCHAR *start = *ptr; ULONGLONG ret = 0; - *val = 0; while(is_num(**ptr)) { ret = ret*10 + (**ptr - '0'); @@ -846,7 +832,6 @@ static BOOL check_implicit_ipv4(const WCHAR **ptr, UINT *val) { if(*ptr == start) return FALSE; - *val = ret; return TRUE; } @@ -1215,13 +1200,12 @@ static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data) { data->host = *ptr; if(!check_ipv4address(ptr, FALSE)) { - if(!check_implicit_ipv4(ptr, &data->implicit_ipv4)) { + if(!check_implicit_ipv4(ptr)) { TRACE("(%p %p): URI didn't contain anything looking like an IPv4 address.\n", ptr, data); *ptr = data->host; data->host = NULL; return FALSE; - } else - data->has_implicit_ip = TRUE; + } } data->host_len = *ptr - data->host; @@ -1241,7 +1225,6 @@ static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data) { /* Found more data which belongs to the host, so this isn't an IPv4. */ *ptr = data->host; data->host = NULL; - data->has_implicit_ip = FALSE; return FALSE; } @@ -2094,38 +2077,6 @@ static BOOL canonicalize_reg_name(const parse_data *data, Uri *uri, return TRUE; } -/* Attempts to canonicalize an implicit IPv4 address. */ -static BOOL canonicalize_implicit_ipv4address(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { - uri->host_start = uri->canon_len; - - TRACE("%u\n", data->implicit_ipv4); - /* For unknown scheme types Windows doesn't convert - * the value into an IP address, but it still considers - * it an IPv4 address. - */ - if(data->scheme_type == URL_SCHEME_UNKNOWN) { - if(!computeOnly) - memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); - uri->canon_len += data->host_len; - } else { - if(!computeOnly) - uri->canon_len += ui2ipv4(uri->canon_uri+uri->canon_len, data->implicit_ipv4); - else - uri->canon_len += ui2ipv4(NULL, data->implicit_ipv4); - } - - uri->host_len = uri->canon_len - uri->host_start; - uri->host_type = Uri_HOST_IPV4; - - if(!computeOnly) - TRACE("%p %p %lx %d): Canonicalized implicit IP address=%s len=%ld\n", - data, uri, flags, computeOnly, - debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), - uri->host_len); - - return TRUE; -} - /* Attempts to canonicalize an IPv4 address. * * If the parse_data represents a URI that has an implicit IPv4 address @@ -2145,76 +2096,39 @@ static BOOL canonicalize_implicit_ipv4address(const parse_data *data, Uri *uri, * host type as HOST_IPV4. */ static BOOL canonicalize_ipv4address(const parse_data *data, Uri *uri, DWORD flags, BOOL computeOnly) { - if(data->has_implicit_ip) - return canonicalize_implicit_ipv4address(data, uri, flags, computeOnly); - else { - uri->host_start = uri->canon_len; - - /* Windows only normalizes for known scheme types. */ - if(data->scheme_type != URL_SCHEME_UNKNOWN) { - /* parse_data contains a partial or full IPv4 address, so normalize it. */ - DWORD i, octetDigitCount = 0, octetCount = 0; - BOOL octetHasDigit = FALSE; - - for(i = 0; i < data->host_len; ++i) { - if(data->host[i] == '0' && !octetHasDigit) { - /* Can ignore leading zeros if: - * 1) It isn't the last digit of the octet. - * 2) i+1 != data->host_len - * 3) i+1 != '.' - */ - if(octetDigitCount == 2 || - i+1 == data->host_len || - data->host[i+1] == '.') { - if(!computeOnly) - uri->canon_uri[uri->canon_len] = data->host[i]; - ++uri->canon_len; - TRACE("Adding zero\n"); - } - } else if(data->host[i] == '.') { - if(!computeOnly) - uri->canon_uri[uri->canon_len] = data->host[i]; - ++uri->canon_len; - - octetDigitCount = 0; - octetHasDigit = FALSE; - ++octetCount; - } else { - if(!computeOnly) - uri->canon_uri[uri->canon_len] = data->host[i]; - ++uri->canon_len; - - ++octetDigitCount; - octetHasDigit = TRUE; - } - } - - /* Make sure the canonicalized IP address has 4 dec-octets. - * If doesn't add "0" ones until there is 4; - */ - for( ; octetCount < 3; ++octetCount) { - if(!computeOnly) { - uri->canon_uri[uri->canon_len] = '.'; - uri->canon_uri[uri->canon_len+1] = '0'; - } - - uri->canon_len += 2; - } - } else { - /* Windows doesn't normalize addresses in unknown schemes. */ - if(!computeOnly) - memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); - uri->canon_len += data->host_len; - } + uri->host_start = uri->canon_len; - uri->host_len = uri->canon_len - uri->host_start; - if(!computeOnly) - TRACE("(%p %p %lx %d): Canonicalized IPv4 address, ip=%s len=%ld\n", - data, uri, flags, computeOnly, - debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), - uri->host_len); + if (data->scheme_type != URL_SCHEME_UNKNOWN) + { + WCHAR *host, full_ipv4[16]; + const WCHAR *terminator; + IN_ADDR in_addr; + DWORD len; + + /* IPv4 address may be partial, normalize fully */ + host = strdupwn(data->host, data->host_len); + RtlIpv4StringToAddressW(host, FALSE, &terminator, &in_addr); + RtlIpv4AddressToStringW(&in_addr, full_ipv4); + free(host); + + len = wcslen(full_ipv4); + if (!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, full_ipv4, len*sizeof(WCHAR)); + uri->canon_len += len; + uri->host_len = len; + } + else + { + /* Windows doesn't normalize addresses in unknown schemes. */ + if (!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); + uri->canon_len += data->host_len; + uri->host_len = uri->canon_len-uri->host_start; } + if (!computeOnly) + TRACE("(%p %p %lx %d): Canonicalized IPv4 address, ip=%s len=%ld\n", data, uri, flags, + computeOnly, debugstr_wn(uri->canon_uri+uri->host_start, uri->host_len), uri->host_len); return TRUE; } diff --git a/dlls/urlmon/tests/uri.c b/dlls/urlmon/tests/uri.c index a26036f69ef..8e58cd4185c 100644 --- a/dlls/urlmon/tests/uri.c +++ b/dlls/urlmon/tests/uri.c @@ -1252,6 +1252,84 @@ static const uri_properties uri_tests[] = { {URLZONE_INVALID,E_NOTIMPL,FALSE} } }, + /* Make sure it normalizes partial IPv4 addresses correctly. */ + { "http://1/", 0, S_OK, FALSE, 0, + { + {"http://0.0.0.1/",S_OK,FALSE}, + {"0.0.0.1",S_OK,FALSE}, + {"http://0.0.0.1/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"0.0.0.1",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"/",S_OK,FALSE}, + {"/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"http://1/",S_OK,FALSE}, + {"http",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_IPV4,S_OK,FALSE}, + {80,S_OK,FALSE}, + {URL_SCHEME_HTTP,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, + /* Make sure it normalizes partial IPv4 addresses correctly. */ + { "http://1.2/", 0, S_OK, FALSE, 0, + { + {"http://1.0.0.2/",S_OK,FALSE}, + {"1.0.0.2",S_OK,FALSE}, + {"http://1.0.0.2/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"1.0.0.2",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"/",S_OK,FALSE}, + {"/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"http://1.2/",S_OK,FALSE}, + {"http",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_IPV4,S_OK,FALSE}, + {80,S_OK,FALSE}, + {URL_SCHEME_HTTP,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, + /* Make sure it normalizes partial IPv4 addresses correctly. */ + { "http://1.2.3/", 0, S_OK, FALSE, 0, + { + {"http://1.2.0.3/",S_OK,FALSE}, + {"1.2.0.3",S_OK,FALSE}, + {"http://1.2.0.3/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"1.2.0.3",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"/",S_OK,FALSE}, + {"/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"http://1.2.3/",S_OK,FALSE}, + {"http",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_IPV4,S_OK,FALSE}, + {80,S_OK,FALSE}, + {URL_SCHEME_HTTP,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, /* UINT_MAX */ { "http://4294967295/", 0, S_OK, FALSE, 0, { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/main.c | 151 ++++++++++++++++++++++-------- dlls/iertutil/private.h | 2 + dlls/iertutil/uri.c | 203 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 318 insertions(+), 38 deletions(-) diff --git a/dlls/iertutil/main.c b/dlls/iertutil/main.c index fd6dd3c9372..073b84277c0 100644 --- a/dlls/iertutil/main.c +++ b/dlls/iertutil/main.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 Zhiyi Zhang for CodeWeavers + * Copyright 2024-2026 Zhiyi Zhang for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -24,7 +24,7 @@ WINE_DEFAULT_DEBUG_CHANNEL(iertutil); struct uri { IUriRuntimeClass IUriRuntimeClass_iface; - HSTRING raw_uri; + IUri *uri; LONG ref; }; @@ -68,7 +68,7 @@ static ULONG STDMETHODCALLTYPE uri_Release(IUriRuntimeClass *iface) if (!ref) { - WindowsDeleteString(impl->raw_uri); + IUri_Release(impl->uri); free(impl); } @@ -95,60 +95,109 @@ static HRESULT STDMETHODCALLTYPE uri_GetTrustLevel(IUriRuntimeClass *iface, Trus return E_NOTIMPL; } +static HRESULT uri_prop_to_hstring(IUri *uri, Uri_PROPERTY prop, HSTRING *out) +{ + BSTR bstr = NULL; + HRESULT hr; + + hr = IUri_GetPropertyBSTR(uri, prop, &bstr, 0); + if (FAILED(hr)) + { + *out = NULL; + return hr; + } + + if (hr == S_FALSE || !SysStringLen(bstr)) + { + SysFreeString(bstr); + *out = NULL; + return S_OK; + } + + hr = WindowsCreateString(bstr, SysStringLen(bstr), out); + SysFreeString(bstr); + return hr; +} + static HRESULT STDMETHODCALLTYPE uri_AbsoluteUri(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p semi-stub!\n", iface, value); + struct uri *impl = impl_from_IUriRuntimeClass(iface); - /* TODO: Parse the raw URI and reconstruct it from parts according to RFC 3986 or RFC 3987 */ - return IUriRuntimeClass_get_RawUri(iface, value); + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_ABSOLUTE_URI, value); } static HRESULT STDMETHODCALLTYPE uri_DisplayUri(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_DISPLAY_URI, value); } static HRESULT STDMETHODCALLTYPE uri_Domain(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_DOMAIN, value); } static HRESULT STDMETHODCALLTYPE uri_Extension(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_EXTENSION, value); } static HRESULT STDMETHODCALLTYPE uri_Fragment(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_FRAGMENT, value); } static HRESULT STDMETHODCALLTYPE uri_Host(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_HOST, value); } static HRESULT STDMETHODCALLTYPE uri_Password(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_PASSWORD, value); } static HRESULT STDMETHODCALLTYPE uri_Path(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_PATH, value); } static HRESULT STDMETHODCALLTYPE uri_Query(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_QUERY, value); } static HRESULT STDMETHODCALLTYPE uri_QueryParsed(IUriRuntimeClass *iface, @@ -164,25 +213,43 @@ static HRESULT STDMETHODCALLTYPE uri_RawUri(IUriRuntimeClass *iface, HSTRING *va TRACE("iface %p, value %p.\n", iface, value); - return WindowsDuplicateString(impl->raw_uri, value); + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_RAW_URI, value); } static HRESULT STDMETHODCALLTYPE uri_SchemeName(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_SCHEME_NAME, value); } static HRESULT STDMETHODCALLTYPE uri_UserName(IUriRuntimeClass *iface, HSTRING *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + + TRACE("iface %p, value %p.\n", iface, value); + + return uri_prop_to_hstring(impl->uri, Uri_PROPERTY_USER_NAME, value); } static HRESULT STDMETHODCALLTYPE uri_Port(IUriRuntimeClass *iface, INT32 *value) { - FIXME("iface %p, value %p stub!\n", iface, value); - return E_NOTIMPL; + struct uri *impl = impl_from_IUriRuntimeClass(iface); + DWORD port; + HRESULT hr; + + TRACE("iface %p, value %p.\n", iface, value); + + hr = IUri_GetPropertyDWORD(impl->uri, Uri_PROPERTY_PORT, &port, 0); + if (hr == S_OK) + { + *value = port; + return S_OK; + } + + return S_FALSE; } static HRESULT STDMETHODCALLTYPE uri_Suspicious(IUriRuntimeClass *iface, boolean *value) @@ -333,29 +400,39 @@ static const struct IActivationFactoryVtbl activation_factory_vtbl = DEFINE_IINSPECTABLE(uri_factory, IUriRuntimeClassFactory, struct iertutil, IActivationFactory_iface) -static HRESULT STDMETHODCALLTYPE uri_factory_CreateUri(IUriRuntimeClassFactory *iface, HSTRING uri, +static HRESULT STDMETHODCALLTYPE uri_factory_CreateUri(IUriRuntimeClassFactory *iface, + HSTRING uri_string, IUriRuntimeClass **instance) { const WCHAR *raw_buffer; struct uri *uri_impl; + HRESULT hr; + IUri *uri; - FIXME("iface %p, uri %s, instance %p semi-stub!\n", iface, debugstr_hstring(uri), instance); + TRACE("iface %p, uri_string %s, instance %p.\n", iface, debugstr_hstring(uri_string), instance); - if (!uri) + if (!uri_string) return E_POINTER; + raw_buffer = WindowsGetStringRawBuffer(uri_string, NULL); + hr = uri_runtime_parse(raw_buffer, &uri); + if (FAILED(hr)) + { + *instance = NULL; + return E_INVALIDARG; + } + uri_impl = calloc(1, sizeof(*uri_impl)); if (!uri_impl) + { + IUri_Release(uri); return E_OUTOFMEMORY; + } uri_impl->IUriRuntimeClass_iface.lpVtbl = &uri_vtbl; + uri_impl->uri = uri; uri_impl->ref = 1; - raw_buffer = WindowsGetStringRawBuffer(uri, NULL); - WindowsCreateString(raw_buffer, wcslen(raw_buffer), &uri_impl->raw_uri); - - /* TODO: Parse the URI according to RFC 3986 and RFC 3987 */ - *instance = &uri_impl->IUriRuntimeClass_iface; return S_OK; } diff --git a/dlls/iertutil/private.h b/dlls/iertutil/private.h index 5c416aba80a..469f0fc7a93 100644 --- a/dlls/iertutil/private.h +++ b/dlls/iertutil/private.h @@ -27,6 +27,7 @@ #include "winstring.h" #include "wine/debug.h" #include "activation.h" +#include "urlmon.h" #define WIDL_using_Windows_Foundation #include "windows.foundation.h" @@ -72,5 +73,6 @@ &impl->base_iface) extern HRESULT Uri_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); +extern HRESULT uri_runtime_parse(const WCHAR *uri_string, IUri **uri); #endif /* __WINE_IERTUTIL_PRIVATE_H */ diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index f6550e19fef..3a5454b27c1 100644 --- a/dlls/iertutil/uri.c +++ b/dlls/iertutil/uri.c @@ -1,6 +1,7 @@ /* * Copyright 2010 Jacek Caban for CodeWeavers * Copyright 2010 Thomas Mullaly + * Copyright 2026 Zhiyi Zhang for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -45,6 +46,8 @@ WINE_DEFAULT_DEBUG_CHANNEL(iertutil); +#define URI_CREATE_URI_RUNTIME_CLASS 0x80000000 /* IUriRuntimeClass URI parser */ + #define URI_DISPLAY_NO_ABSOLUTE_URI 0x1 #define URI_DISPLAY_NO_DEFAULT_PORT_AUTH 0x2 @@ -690,6 +693,66 @@ static WCHAR *strdupwn(const WCHAR *str, DWORD length) return ret; } +static WCHAR *strdupwn_lower(const WCHAR *src, DWORD len) +{ + WCHAR *dst = strdupwn(src, len); + DWORD i; + + if (!dst) + return NULL; + + for (i = 0; i < len; i++) + dst[i] = towlower(dst[i]); + + return dst; +} + +/* Duplicate string, percent-encoding ASCII characters that are not unreserved or reserved according + * to RFC 3986, while preserving existing valid %XX sequences. Free the result when it's done */ +static WCHAR *pct_encode_forbidden(const WCHAR *src, DWORD src_len, DWORD *out_len) +{ + DWORD i, j, extra = 0; + WCHAR *dst; + + for (i = 0; i < src_len; i++) + { + if (src[i] == '%' && i+2 < src_len && is_hexdigit(src[i+1]) && is_hexdigit(src[i+2])) + { + i += 2; + continue; + } + if (is_ascii(src[i]) && !is_unreserved(src[i]) && !is_reserved(src[i])) + extra += 2; + } + + *out_len = src_len+extra; + dst = malloc((*out_len+1)*sizeof(WCHAR)); + if (!dst) + return NULL; + + for (i = 0, j = 0; i < src_len; i++) + { + if (src[i] == '%' && i+2 < src_len && is_hexdigit(src[i+1]) && is_hexdigit(src[i+2])) + { + dst[j++] = src[i++]; + dst[j++] = src[i++]; + dst[j++] = src[i]; + continue; + } + if (is_ascii(src[i]) && !is_unreserved(src[i]) && !is_reserved(src[i])) + { + pct_encode_val(src[i], dst + j); + j += 3; + } + else + { + dst[j++] = src[i]; + } + } + dst[j] = 0; + return dst; +} + /* Removes all the leading and trailing white spaces or * control characters from the URI and removes all control * characters inside of the URI string. @@ -1830,6 +1893,25 @@ static BOOL canonicalize_username(const parse_data *data, Uri *uri, DWORD flags, } uri->userinfo_start = uri->canon_len; + + if(flags & URI_CREATE_URI_RUNTIME_CLASS) { + if(is_hierarchical_scheme(data->scheme_type)) { + DWORD encoded_len; + WCHAR *encoded; + + encoded = pct_encode_forbidden(data->username, data->username_len, &encoded_len); + if (!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, encoded, encoded_len*sizeof(WCHAR)); + uri->canon_len += encoded_len; + free(encoded); + } else { + if(!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, data->username, data->username_len*sizeof(WCHAR)); + uri->canon_len += data->username_len; + } + return TRUE; + } + for(ptr = data->username; ptr < data->username+data->username_len; ++ptr) { if(*ptr == '%') { /* Only decode % encoded values for known scheme types. */ @@ -1888,6 +1970,24 @@ static BOOL canonicalize_password(const parse_data *data, Uri *uri, DWORD flags, uri->canon_uri[uri->canon_len] = ':'; ++uri->canon_len; + if(flags & URI_CREATE_URI_RUNTIME_CLASS) { + if(is_hierarchical_scheme(data->scheme_type)) { + DWORD encoded_len; + WCHAR *encoded; + + encoded = pct_encode_forbidden(data->password, data->password_len, &encoded_len); + if (!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, encoded, encoded_len*sizeof(WCHAR)); + uri->canon_len += encoded_len; + free(encoded); + } else { + if(!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, data->password, data->password_len*sizeof(WCHAR)); + uri->canon_len += data->password_len; + } + return TRUE; + } + for(ptr = data->password; ptr < data->password+data->password_len; ++ptr) { if(*ptr == '%') { /* Only decode % encoded values for known scheme types. */ @@ -1997,6 +2097,51 @@ static BOOL canonicalize_reg_name(const parse_data *data, Uri *uri, } } + if(flags & URI_CREATE_URI_RUNTIME_CLASS) { + uri->host_start = uri->canon_len; + + if(is_hierarchical_scheme(data->scheme_type)) { + DWORD encoded_len, lower_len; + WCHAR *encoded, *lower; + int decoded_len; + + lower = strdupwn_lower(data->host, data->host_len); + lower_len = data->host_len; + + /* Punycode decode */ + decoded_len = IdnToUnicode(0, lower, lower_len, NULL, 0); + if(decoded_len > 0 && decoded_len != lower_len) { + WCHAR *decoded = malloc(decoded_len * sizeof(WCHAR)); + if(decoded) { + IdnToUnicode(0, lower, lower_len, decoded, decoded_len); + free(lower); + lower = decoded; + lower_len = decoded_len; + } + } + + /* Percent-encode forbidden characters */ + encoded = pct_encode_forbidden(lower, lower_len, &encoded_len); + free(lower); + + if(!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, encoded, encoded_len*sizeof(WCHAR)); + uri->canon_len += encoded_len; + free(encoded); + } else { + if(!computeOnly) + memcpy(uri->canon_uri+uri->canon_len, data->host, data->host_len*sizeof(WCHAR)); + uri->canon_len += data->host_len; + } + + uri->host_len = uri->canon_len-uri->host_start; + + if(!computeOnly) + find_domain_name(uri->canon_uri+uri->host_start, uri->host_len, &uri->domain_offset); + + return TRUE; + } + if(data->scheme_type == URL_SCHEME_FILE && flags & Uri_CREATE_FILE_USE_DOS_PATH) { if(!computeOnly) { uri->canon_uri[uri->canon_len] = '\\'; @@ -2274,6 +2419,13 @@ static BOOL canonicalize_port(const parse_data *data, Uri *uri, DWORD flags, BOO uri->has_port = data->has_port || has_default_port; + /* For IUriRuntimeClass, treat empty port (e.g., "http://host:/") as no explicit port */ + if((flags & URI_CREATE_URI_RUNTIME_CLASS) && data->port_len == 0) { + if(has_default_port) + uri->port = default_port; + return TRUE; + } + /* Possible cases: * 1) Has a port which is the default port. * 2) Has a port (not the default). @@ -2390,6 +2542,49 @@ static DWORD canonicalize_path_hierarchical(const WCHAR *path, DWORD path_len, U if(!path) return 0; + if(flags & URI_CREATE_URI_RUNTIME_CLASS) { + DWORD tmp_len, i, encoded_len; + WCHAR *tmp, *encoded; + + /* Implicit file scheme paths need a leading '/' */ + if(is_implicit_scheme && is_file) { + tmp = malloc((path_len+2)*sizeof(WCHAR)); + tmp[0] = '/'; + memcpy(tmp+1, path, path_len*sizeof(WCHAR)); + tmp_len = path_len+1; + tmp[tmp_len] = 0; + } else { + tmp = strdupwn(path, path_len); + tmp_len = path_len; + } + + if(is_hierarchical_scheme(scheme_type)) { + /* Convert backslashes to forward slashes for known schemes */ + for(i = 0; i < tmp_len; i++) { + if(tmp[i] == '\\') + tmp[i] = '/'; + } + + /* Remove dot segments */ + tmp_len = remove_dot_segments(tmp, tmp_len); + tmp[tmp_len] = 0; + + /* Percent-encode forbidden characters */ + encoded = pct_encode_forbidden(tmp, tmp_len, &encoded_len); + free(tmp); + + if(ret_path) + memcpy(ret_path, encoded, encoded_len*sizeof(WCHAR)); + free(encoded); + return encoded_len; + } + + if(ret_path) + memcpy(ret_path, tmp, tmp_len*sizeof(WCHAR)); + free(tmp); + return tmp_len; + } + ptr = path; if(is_file && !has_host) { @@ -5317,7 +5512,8 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME|Uri_CREATE_NO_CANONICALIZE|Uri_CREATE_CANONICALIZE| Uri_CREATE_DECODE_EXTRA_INFO|Uri_CREATE_NO_DECODE_EXTRA_INFO|Uri_CREATE_CRACK_UNKNOWN_SCHEMES| Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES|Uri_CREATE_PRE_PROCESS_HTML_URI|Uri_CREATE_NO_PRE_PROCESS_HTML_URI| - Uri_CREATE_NO_IE_SETTINGS|Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS|Uri_CREATE_FILE_USE_DOS_PATH; + Uri_CREATE_NO_IE_SETTINGS|Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS|Uri_CREATE_FILE_USE_DOS_PATH| + URI_CREATE_URI_RUNTIME_CLASS; Uri *ret; HRESULT hr; parse_data data; @@ -6857,3 +7053,8 @@ HRESULT WINAPI PrivateCoInternetParseIUri(IUri *pIUri, PARSEACTION ParseAction, return hr; } + +HRESULT uri_runtime_parse(const WCHAR *uri_string, IUri **uri) +{ + return CreateUri(uri_string, Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME | Uri_CREATE_NO_DECODE_EXTRA_INFO | URI_CREATE_URI_RUNTIME_CLASS, 0, uri); +} -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/tests/iertutil.c | 266 +++++++++++++++++++++++++++++---- 1 file changed, 234 insertions(+), 32 deletions(-) diff --git a/dlls/iertutil/tests/iertutil.c b/dlls/iertutil/tests/iertutil.c index 0f77b8a7f28..71084310508 100644 --- a/dlls/iertutil/tests/iertutil.c +++ b/dlls/iertutil/tests/iertutil.c @@ -1,5 +1,5 @@ /* - * Copyright 2024 Zhiyi Zhang for CodeWeavers + * Copyright 2024-2026 Zhiyi Zhang for CodeWeavers * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public @@ -96,15 +96,163 @@ static void test_IUriRuntimeClassFactory(void) static void test_IUriRuntimeClass(void) { + static const struct + { + const WCHAR *src; + const WCHAR *RawUri; + const WCHAR *AbsoluteUri; + const WCHAR *DisplayUri; + const WCHAR *Domain; + const WCHAR *Extension; + const WCHAR *Fragment; + const WCHAR *Host; + const WCHAR *Password; + const WCHAR *Path; + const WCHAR *Query; + const WCHAR *SchemeName; + const WCHAR *UserName; + INT32 Port; + BOOL invalid; + } + tests[] = + { + /* Invalid URIs */ + {L"://example.com", .invalid = TRUE}, + {L"//", .invalid = TRUE}, + {L"//a", .invalid = TRUE}, + {L"a", .invalid = TRUE}, + {L"///a", .invalid = TRUE}, + {L"http://www.host.com:port", .invalid = TRUE}, + {L"http://example.com:+80/", .invalid = TRUE}, + {L"http://example.com:-80/", .invalid = TRUE}, + {L"http_s://example.com", .invalid = TRUE}, + {L"/a/b/c/./../../g", .invalid = TRUE}, + {L"mid/content=5/../6", .invalid = TRUE}, + {L"#frag", .invalid = TRUE}, + {L"http://example.com/a%2z", .invalid = TRUE}, + {L"http://example.com/a%z", .invalid = TRUE}, + {L"http://u:p?a@", .invalid = TRUE}, + {L"http://[1]/", .invalid = TRUE}, + {L"http://[fe80::1ff:fe23:4567:890a%eth0]/", .invalid = TRUE}, + {L"http://[fe80::1ff:fe23:4567:890a%25eth0]/", .invalid = TRUE}, + /* Valid URIs */ + /* URI syntax tests */ + {L"http:", L"http:", NULL, L"http:", NULL, NULL, NULL, NULL, NULL, NULL, NULL, L"http", NULL, 80}, + {L"http:/", L"http:/", NULL, L"http:/", NULL, NULL, NULL, NULL, NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://u:p@", L"http://u:p@", L"http://u:p@/", L"http:///", NULL, NULL, NULL, NULL, L"p", L"/", NULL, L"http", L"u", 80}, + {L"http://u:p;a@", L"http://u:p;a@", L"http://u:p;a@/", L"http:///", NULL, NULL, NULL, NULL, L"p;a", L"/", NULL, L"http", L"u", 80}, + {L"http://u;a:p@", L"http://u;a:p@", L"http://u;a:p@/", L"http:///", NULL, NULL, NULL, NULL, L"p", L"/", NULL, L"http", L"u;a", 80}, + {L"http://u:p:a@", L"http://u:p:a@", L"http://u:p:a@/", L"http:///", NULL, NULL, NULL, NULL, L"p:a", L"/", NULL, L"http", L"u", 80}, + {L"http://example.com", L"http://example.com", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://example.com/", L"http://example.com/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://example.com:/", L"http://example.com:/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://example.com:80/", L"http://example.com:80/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"https://example.com:443/", L"https://example.com:443/", L"https://example.com/", L"https://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"https", NULL, 443}, + {L"http://example.com/data", L"http://example.com/data", L"http://example.com/data", L"http://example.com/data", L"example.com", NULL, NULL, L"example.com", NULL, L"/data", NULL, L"http", NULL, 80}, + {L"http://example.com/data/", L"http://example.com/data/", L"http://example.com/data/", L"http://example.com/data/", L"example.com", NULL, NULL, L"example.com", NULL, L"/data/", NULL, L"http", NULL, 80}, + {L"http://u:p@example.com/", L"http://u:p@example.com/", L"http://u:p@example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", L"p", L"/", NULL, L"http", L"u", 80}, + {L"http://u@example.com/", L"http://u@example.com/", L"http://u@example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", L"u", 80}, + {L"http://user:@example.com", L"http://user:@example.com", L"http://user:@example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", L"user", 80}, + {L"http://:pass@example.com", L"http://:pass@example.com", L"http://:pass@example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", L"pass", L"/", NULL, L"http", NULL, 80}, + {L"http://:@example.com", L"http://:@example.com", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"1http://example.com", L"1http://example.com", L"1http://example.com/", L"1http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"1http", NULL, -1}, + {L"ahttp://example.com", L"ahttp://example.com", L"ahttp://example.com/", L"ahttp://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"ahttp", NULL, -1}, + {L"http://example.com?q=a?b", L"http://example.com?q=a?b", L"http://example.com/?q=a?b", L"http://example.com/?q=a?b", L"example.com", NULL, NULL, L"example.com", NULL, L"/", L"?q=a?b", L"http", NULL, 80}, + {L"http://example.com?q=a b", L"http://example.com?q=a b", L"http://example.com/?q=a b", L"http://example.com/?q=a b", L"example.com", NULL, NULL, L"example.com", NULL, L"/", L"?q=a b", L"http", NULL, 80}, + {L"http://example.com#a b", L"http://example.com#a b", L"http://example.com/#a b", L"http://example.com/#a b", L"example.com", NULL, L"#a b", L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://a/b/c?d#e", L"http://a/b/c?d#e", L"http://a/b/c?d#e", L"http://a/b/c?d#e", NULL, NULL, L"#e", L"a", NULL, L"/b/c", L"?d", L"http", NULL, 80}, + {L"mailto:John.Doe@example.com", L"mailto:John.Doe@example.com", L"mailto:John.Doe@example.com", L"mailto:John.Doe@example.com", NULL, L".com", NULL, NULL, NULL, L"John.Doe@example.com", NULL, L"mailto", NULL, -1}, + {L"news:comp.infosystems.www.servers.unix", L"news:comp.infosystems.www.servers.unix", L"news:comp.infosystems.www.servers.unix", L"news:comp.infosystems.www.servers.unix", NULL, L".unix", NULL, NULL, NULL, L"comp.infosystems.www.servers.unix", NULL, L"news", NULL, -1}, + {L"tel:+1-816-555-1212", L"tel:+1-816-555-1212", L"tel:+1-816-555-1212", L"tel:+1-816-555-1212", NULL, NULL, NULL, NULL, NULL, L"+1-816-555-1212", NULL, L"tel", NULL, -1}, + {L"telnet://192.0.2.16:80/", L"telnet://192.0.2.16:80/", L"telnet://192.0.2.16:80/", L"telnet://192.0.2.16:80/", NULL, NULL, NULL, L"192.0.2.16", NULL, L"/", NULL, L"telnet", NULL, 80}, + {L"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", L"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", L"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", L"urn:oasis:names:specification:docbook:dtd:xml:4.1.2", NULL, L".2", NULL, NULL, NULL, L"oasis:names:specification:docbook:dtd:xml:4.1.2", NULL, L"urn", NULL, -1}, + {L"urn:ietf:rfc:2648", L"urn:ietf:rfc:2648", L"urn:ietf:rfc:2648", L"urn:ietf:rfc:2648", NULL, NULL, NULL, NULL, NULL, L"ietf:rfc:2648", NULL, L"urn", NULL, -1}, + /* IPv4 hosts */ + {L"http://1/", L"http://1/", L"http://0.0.0.1/", L"http://0.0.0.1/", NULL, NULL, NULL, L"0.0.0.1", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://1.2/", L"http://1.2/", L"http://1.0.0.2/", L"http://1.0.0.2/", NULL, NULL, NULL, L"1.0.0.2", NULL, L"/", NULL, L"http", NULL, 80}, + {L"dummy://1.2/", L"dummy://1.2/", L"dummy://1.2/", L"dummy://1.2/", NULL, NULL, NULL, L"1.2", NULL, L"/", NULL, L"dummy", NULL, -1}, + {L"http://1.2.3/", L"http://1.2.3/", L"http://1.2.0.3/", L"http://1.2.0.3/", NULL, NULL, NULL, L"1.2.0.3", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://1.2.3.4/", L"http://1.2.3.4/", L"http://1.2.3.4/", L"http://1.2.3.4/", NULL, NULL, NULL, L"1.2.3.4", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://4294967296/", L"http://4294967296/", L"http://4294967296/", L"http://4294967296/", NULL, NULL, NULL, L"4294967296", NULL, L"/", NULL, L"http", NULL, 80}, + /* IPv6 hosts */ + {L"http://[::1]/", L"http://[::1]/", L"http://[::1]/", L"http://[::1]/", NULL, NULL, NULL, L"::1", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://[::1]:8080/", L"http://[::1]:8080/", L"http://[::1]:8080/", L"http://[::1]:8080/", NULL, NULL, NULL, L"::1", NULL, L"/", NULL, L"http", NULL, 8080}, + {L"http://[::ffff:127.0.0.1]/", L"http://[::ffff:127.0.0.1]/", L"http://[::ffff:127.0.0.1]/", L"http://[::ffff:127.0.0.1]/", NULL, NULL, NULL, L"::ffff:127.0.0.1", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://[2001:db8::7]/", L"http://[2001:db8::7]/", L"http://[2001:db8::7]/", L"http://[2001:db8::7]/", NULL, NULL, NULL, L"2001:db8::7", NULL, L"/", NULL, L"http", NULL, 80}, + /* Domain. Domain is the registrable domain name, which is the effective top-level domain plus one */ + {L"http://www.example.com", L"http://www.example.com", L"http://www.example.com/", L"http://www.example.com/", L"example.com", NULL, NULL, L"www.example.com", NULL, L"/", NULL, L"http", NULL, 80}, + /* Default ports */ + {L"ftp://", L"ftp://", L"ftp:///", L"ftp:///", NULL, NULL, NULL, NULL, NULL, L"/", NULL, L"ftp", NULL, 21}, + {L"telnet://", L"telnet://", L"telnet:///", L"telnet:///", NULL, NULL, NULL, NULL, NULL, L"/", NULL, L"telnet", NULL, 23}, + {L"http://", L"http://", L"http:///", L"http:///", NULL, NULL, NULL, NULL, NULL, L"/", NULL, L"http", NULL, 80}, + {L"https://", L"https://", L"https:///", L"https:///", NULL, NULL, NULL, NULL, NULL, L"/", NULL, L"https", NULL, 443}, + {L"https://example.com:0", L"https://example.com:0", L"https://example.com:0/", L"https://example.com:0/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"https", NULL, 0}, + {L"dummy://example.com:0", L"dummy://example.com:0", L"dummy://example.com:0/", L"dummy://example.com:0/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"dummy", NULL, 0}, + /* Extension */ + {L"http://example.com/.txt", L"http://example.com/.txt", L"http://example.com/.txt", L"http://example.com/.txt", L"example.com", L".txt", NULL, L"example.com", NULL, L"/.txt", NULL, L"http", NULL, 80}, + {L"http://example.com/1.txt", L"http://example.com/1.txt", L"http://example.com/1.txt", L"http://example.com/1.txt", L"example.com", L".txt", NULL, L"example.com", NULL, L"/1.txt", NULL, L"http", NULL, 80}, + {L"http://example.com/1..txt", L"http://example.com/1..txt", L"http://example.com/1..txt", L"http://example.com/1..txt", L"example.com", L".txt", NULL, L"example.com", NULL, L"/1..txt", NULL, L"http", NULL, 80}, + {L"http://example.com/1.2.txt", L"http://example.com/1.2.txt", L"http://example.com/1.2.txt", L"http://example.com/1.2.txt", L"example.com", L".txt", NULL, L"example.com", NULL, L"/1.2.txt", NULL, L"http", NULL, 80}, + /* Backslash normalization */ + {L"dummy://a\\b//c\\d?e\\f#g\\h", L"dummy://a\\b//c\\d?e\\f#g\\h", L"dummy://a\\b//c\\d?e\\f#g\\h", L"dummy://a\\b//c\\d?e\\f#g\\h", NULL, NULL, L"#g\\h", L"a\\b", NULL, L"//c\\d", L"?e\\f", L"dummy", NULL, -1}, + {L"http://a\\b//c\\d?e\\f#g\\h", L"http://a\\b//c\\d?e\\f#g\\h", L"http://a/b//c/d?e\\f#g\\h", L"http://a/b//c/d?e\\f#g\\h", NULL, NULL, L"#g\\h", L"a", NULL, L"/b//c/d", L"?e\\f", L"http", NULL, 80}, + {L"https://a\\b//c\\d?e\\f#g\\h", L"https://a\\b//c\\d?e\\f#g\\h", L"https://a/b//c/d?e\\f#g\\h", L"https://a/b//c/d?e\\f#g\\h", NULL, NULL, L"#g\\h", L"a", NULL, L"/b//c/d", L"?e\\f", L"https", NULL, 443}, + {L"ftp://a\\b//c\\d?e\\f#g\\h", L"ftp://a\\b//c\\d?e\\f#g\\h", L"ftp://a/b//c/d?e\\f#g\\h", L"ftp://a/b//c/d?e\\f#g\\h", NULL, NULL, L"#g\\h", L"a", NULL, L"/b//c/d", L"?e\\f", L"ftp", NULL, 21}, + {L"file://\\sample\\sample.bundle", L"file://\\sample\\sample.bundle", L"file:///sample/sample.bundle", L"file:///sample/sample.bundle", NULL, L".bundle", NULL, NULL, NULL, L"/sample/sample.bundle", NULL, L"file", NULL, -1}, + /* Dot segments removal */ + {L"http://example.com/.", L"http://example.com/.", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://example.com/..", L"http://example.com/..", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://example.com/...", L"http://example.com/...", L"http://example.com/...", L"http://example.com/...", L"example.com", L".", NULL, L"example.com", NULL, L"/...", NULL, L"http", NULL, 80}, + {L"http://example.com/a/.", L"http://example.com/a/.", L"http://example.com/a/", L"http://example.com/a/", L"example.com", NULL, NULL, L"example.com", NULL, L"/a/", NULL, L"http", NULL, 80}, + {L"http://example.com/a/..", L"http://example.com/a/..", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://example.com/a/b/../../c", L"http://example.com/a/b/../../c", L"http://example.com/c", L"http://example.com/c", L"example.com", NULL, NULL, L"example.com", NULL, L"/c", NULL, L"http", NULL, 80}, + {L"http://example.com/a/b/.././c", L"http://example.com/a/b/.././c", L"http://example.com/a/c", L"http://example.com/a/c", L"example.com", NULL, NULL, L"example.com", NULL, L"/a/c", NULL, L"http", NULL, 80}, + {L"http://example.com/a/b/..", L"http://example.com/a/b/..", L"http://example.com/a/", L"http://example.com/a/", L"example.com", NULL, NULL, L"example.com", NULL, L"/a/", NULL, L"http", NULL, 80}, + {L"http://example.com/a/b/.", L"http://example.com/a/b/.", L"http://example.com/a/b/", L"http://example.com/a/b/", L"example.com", NULL, NULL, L"example.com", NULL, L"/a/b/", NULL, L"http", NULL, 80}, + /* Implicit file scheme */ + {L"a:", L"a:", L"file:///a:", L"file:///a:", NULL, NULL, NULL, NULL, NULL, L"/a:", NULL, L"file", NULL, -1}, + {L"A:", L"A:", L"file:///A:", L"file:///A:", NULL, NULL, NULL, NULL, NULL, L"/A:", NULL, L"file", NULL, -1}, + {L"a:/", L"a:/", L"file:///a:/", L"file:///a:/", NULL, NULL, NULL, NULL, NULL, L"/a:/", NULL, L"file", NULL, -1}, + {L"z:/", L"z:/", L"file:///z:/", L"file:///z:/", NULL, NULL, NULL, NULL, NULL, L"/z:/", NULL, L"file", NULL, -1}, + {L"a://", L"a://", L"file:///a://", L"file:///a://", NULL, NULL, NULL, NULL, NULL, L"/a://", NULL, L"file", NULL, -1}, + {L"a:b/c", L"a:b/c", L"file:///a:b/c", L"file:///a:b/c", NULL, NULL, NULL, NULL, NULL, L"/a:b/c", NULL, L"file", NULL, -1}, + {L"ab:c/d", L"ab:c/d", L"ab:c/d", L"ab:c/d", NULL, NULL, NULL, NULL, NULL, L"c/d", NULL, L"ab", NULL, -1}, + /* White spaces */ + {L"http:// example.com/", L"http:// example.com/", L"http://%20example.com/", L"http://%20example.com/", L"%20example.com", NULL, NULL, L"%20example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://\texample.com/", L"http://example.com/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://\rexample.com/", L"http://example.com/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"http://\nexample.com/", L"http://example.com/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + /* Percent encoding */ + {L"http://example.com/a%20b", L"http://example.com/a%20b", L"http://example.com/a%20b", L"http://example.com/a%20b", L"example.com", NULL, NULL, L"example.com", NULL, L"/a%20b", NULL, L"http", NULL, 80}, + {L"http://example.com/a%2f/b", L"http://example.com/a%2f/b", L"http://example.com/a%2f/b", L"http://example.com/a%2f/b", L"example.com", NULL, NULL, L"example.com", NULL, L"/a%2f/b", NULL, L"http", NULL, 80}, + {L"http://example.com/a%2F/b", L"http://example.com/a%2F/b", L"http://example.com/a%2F/b", L"http://example.com/a%2F/b", L"example.com", NULL, NULL, L"example.com", NULL, L"/a%2F/b", NULL, L"http", NULL, 80}, + {L"http://%FF%FF%FF.com", L"http://%FF%FF%FF.com", L"http://%ff%ff%ff.com/", L"http://%ff%ff%ff.com/", L"%ff%ff%ff.com", NULL, NULL, L"%ff%ff%ff.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"dummy://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"dummy://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"dummy://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"dummy://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"exam ple.com", NULL, L"#frag ment", L"exam ple.com", L"pass word", L"/pa th", L"?qu ery=val ue", L"dummy", L"us er", -1}, + {L"http://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"http://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"http://us%20er:pass%20word@exam%20ple.com/pa%20th?qu ery=val ue#frag ment", L"http://exam%20ple.com/pa%20th?qu ery=val ue#frag ment", L"exam%20ple.com", NULL, L"#frag ment", L"exam%20ple.com", L"pass%20word", L"/pa%20th", L"?qu ery=val ue", L"http", L"us%20er", 80}, + {L"https://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"https://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"https://us%20er:pass%20word@exam%20ple.com/pa%20th?qu ery=val ue#frag ment", L"https://exam%20ple.com/pa%20th?qu ery=val ue#frag ment", L"exam%20ple.com", NULL, L"#frag ment", L"exam%20ple.com", L"pass%20word", L"/pa%20th", L"?qu ery=val ue", L"https", L"us%20er", 443}, + {L"ftp://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"ftp://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", L"ftp://us%20er:pass%20word@exam%20ple.com/pa%20th?qu ery=val ue#frag ment", L"ftp://exam%20ple.com/pa%20th?qu ery=val ue#frag ment", L"exam%20ple.com", NULL, L"#frag ment", L"exam%20ple.com", L"pass%20word", L"/pa%20th", L"?qu ery=val ue", L"ftp", L"us%20er", 21}, + {L"file://a b/c d", L"file://a b/c d", L"file://a%20b/c%20d", L"file://a%20b/c%20d", NULL, NULL, NULL, L"a%20b", NULL, L"/c%20d", NULL, L"file", NULL, -1}, + {L"http://example.com/path/to /resource", L"http://example.com/path/to /resource", L"http://example.com/path/to%20/resource", L"http://example.com/path/to%20/resource", L"example.com", NULL, NULL, L"example.com", NULL, L"/path/to%20/resource", NULL, L"http", NULL, 80}, + /* Punycode */ + {L"http://xn--0zwm56d.example.com/", L"http://xn--0zwm56d.example.com/", L"http://\u6d4b\u8bd5.example.com/", L"http://\u6d4b\u8bd5.example.com/", L"example.com", NULL, NULL, L"\u6d4b\u8bd5.example.com", NULL, L"/", NULL, L"http", NULL, 80}, + /* Unicode */ + {L"http://example.com/\u00e4\u00f6\u00fc", L"http://example.com/\u00e4\u00f6\u00fc", L"http://example.com/\u00e4\u00f6\u00fc", L"http://example.com/\u00e4\u00f6\u00fc", L"example.com", NULL, NULL, L"example.com", NULL, L"/\u00e4\u00f6\u00fc", NULL, L"http", NULL, 80}, + /* Miscellaneous tests */ + {L"HTTP://EXAMPLE.COM/", L"HTTP://EXAMPLE.COM/", L"http://example.com/", L"http://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"http", NULL, 80}, + {L"dummy:", L"dummy:", L"dummy:", L"dummy:", NULL, NULL, NULL, NULL, NULL, NULL, NULL, L"dummy", NULL, -1}, + {L"dummy://example.com", L"dummy://example.com", L"dummy://example.com/", L"dummy://example.com/", L"example.com", NULL, NULL, L"example.com", NULL, L"/", NULL, L"dummy", NULL, -1}, + }; static const WCHAR *class_name = L"Windows.Foundation.Uri"; IActivationFactory *activation_factory = NULL; IUriRuntimeClassFactory *uri_factory = NULL; IUriRuntimeClass *uri_class = NULL; IInspectable *inspectable = NULL; - IPropertyValue *value; + IPropertyValue *prop_value; + const WCHAR *buffer; + INT32 int32_value; HSTRING str, uri; + unsigned int i; HRESULT hr; - INT32 res; hr = RoInitialize(RO_INIT_MULTITHREADED); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); @@ -124,40 +272,94 @@ static void test_IUriRuntimeClass(void) hr = IActivationFactory_QueryInterface(activation_factory, &IID_IUriRuntimeClassFactory, (void **)&uri_factory); ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - hr = WindowsCreateString(L"https://www.winehq.org/", wcslen(L"https://www.winehq.org/"), &uri); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - hr = IUriRuntimeClassFactory_CreateUri(uri_factory, uri, &uri_class); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + for (i = 0; i < ARRAY_SIZE(tests); i++) + { + winetest_push_context("%s", wine_dbgstr_w(tests[i].src)); - hr = IUriRuntimeClass_QueryInterface(uri_class, &IID_IInspectable, (void **)&inspectable); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(uri_class == (void *)inspectable, "QueryInterface IID_IInspectable returned %p, expected %p.\n", - inspectable, uri_factory); - IInspectable_Release(inspectable); + hr = WindowsCreateString(tests[i].src, wcslen(tests[i].src), &uri); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + hr = IUriRuntimeClassFactory_CreateUri(uri_factory, uri, &uri_class); + if (tests[i].invalid) + { + ok(hr == E_INVALIDARG, "Got unexpected hr %#lx.\n", hr); + WindowsDeleteString(uri); + winetest_pop_context(); + continue; + } + else + { + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + } - hr = IUriRuntimeClass_QueryInterface(uri_class, &IID_IPropertyValue, (void **)&value); - ok(hr == E_NOINTERFACE, "Got unexpected hr %#lx.\n", hr); + if (i == 0) + { + hr = IUriRuntimeClass_QueryInterface(uri_class, &IID_IInspectable, (void **)&inspectable); + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); + ok(uri_class == (void *)inspectable, "QueryInterface IID_IInspectable returned %p, expected %p.\n", + inspectable, uri_factory); + IInspectable_Release(inspectable); - /* Test IUriRuntimeClass_get_RawUri() */ - hr = IUriRuntimeClass_get_RawUri(uri_class, &str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(str != uri, "Expected a different pointer.\n"); - hr = WindowsCompareStringOrdinal(uri, str, &res); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(res == 0, "Expected %s, got %s.\n", debugstr_hstring(uri), debugstr_hstring(str)); - WindowsDeleteString(str); + hr = IUriRuntimeClass_QueryInterface(uri_class, &IID_IPropertyValue, (void **)&prop_value); + ok(hr == E_NOINTERFACE, "Got unexpected hr %#lx.\n", hr); + } - /* Test IUriRuntimeClass_get_AbsoluteUri() */ - hr = IUriRuntimeClass_get_AbsoluteUri(uri_class, &str); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(str != uri, "Expected a different pointer.\n"); - hr = WindowsCompareStringOrdinal(uri, str, &res); - ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); - ok(res == 0, "Expected %s, got %s.\n", debugstr_hstring(uri), debugstr_hstring(str)); - WindowsDeleteString(str); +#define TEST_HSTRING(prop) \ + winetest_push_context(#prop); \ + str = NULL; \ + hr = IUriRuntimeClass_get_##prop(uri_class, &str); \ + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); \ + ok(str != uri, "Expected a different pointer.\n"); \ + if (tests[i].prop) \ + { \ + ok(!!str, "Expected a valid pointer.\n"); \ + buffer = WindowsGetStringRawBuffer(str, NULL); \ + ok(!wcscmp(buffer, tests[i].prop), "Expected %s, got %s.\n", wine_dbgstr_w(tests[i].prop), \ + debugstr_hstring(str)); \ + WindowsDeleteString(str); \ + } \ + else \ + { \ + ok(!str, "Expected a NULL pointer.\n"); \ + } \ + winetest_pop_context(); + +#define TEST_INT32(prop) \ + winetest_push_context(#prop); \ + hr = IUriRuntimeClass_get_##prop(uri_class, &int32_value); \ + if (tests[i].prop == -1) \ + { \ + ok(hr == S_FALSE, "Got unexpected hr %#lx.\n", hr); \ + } \ + else \ + { \ + ok(hr == S_OK, "Got unexpected hr %#lx.\n", hr); \ + ok(int32_value == tests[i].prop, "Expected " #prop " %d, got %d.\n", tests[i].prop, \ + int32_value); \ + } \ + winetest_pop_context(); + + TEST_HSTRING(AbsoluteUri) + TEST_HSTRING(DisplayUri) + TEST_HSTRING(Domain) + TEST_HSTRING(Extension) + TEST_HSTRING(Fragment) + TEST_HSTRING(Host) + TEST_HSTRING(Password) + TEST_HSTRING(Path) + TEST_HSTRING(Query) + TEST_HSTRING(RawUri) + TEST_HSTRING(SchemeName) + TEST_HSTRING(UserName) + TEST_INT32(Port) + +#undef TEST_INT32 +#undef TEST_HSTRING + + IUriRuntimeClass_Release(uri_class); + WindowsDeleteString(uri); + winetest_pop_context(); + } - WindowsDeleteString(uri); - IUriRuntimeClass_Release(uri_class); IUriRuntimeClassFactory_Release(uri_factory); IActivationFactory_Release(activation_factory); RoUninitialize(); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
Jacek Caban (@jacek) commented about dlls/iertutil/uri.c:
Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME|Uri_CREATE_NO_CANONICALIZE|Uri_CREATE_CANONICALIZE| Uri_CREATE_DECODE_EXTRA_INFO|Uri_CREATE_NO_DECODE_EXTRA_INFO|Uri_CREATE_CRACK_UNKNOWN_SCHEMES| Uri_CREATE_NO_CRACK_UNKNOWN_SCHEMES|Uri_CREATE_PRE_PROCESS_HTML_URI|Uri_CREATE_NO_PRE_PROCESS_HTML_URI| - Uri_CREATE_NO_IE_SETTINGS|Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS|Uri_CREATE_FILE_USE_DOS_PATH; + Uri_CREATE_NO_IE_SETTINGS|Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS|Uri_CREATE_FILE_USE_DOS_PATH| + URI_CREATE_URI_RUNTIME_CLASS;
Please avoid exposing the flag externally. For example, you could introduce an internal version of `CreateUri` that does not validate flags, while the public `CreateUri` would validate and clear unsupported flags before calling the internal variant. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_134970
It would be great if you could split this up a bit. I would suggest starting with a version without `URI_CREATE_URI_RUNTIME_CLASS`, with tests that pass without it. Then add each runtime class special case separately, along with the relevant tests. This would make the changes much easier to review. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_134971
participants (3)
-
Jacek Caban (@jacek) -
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)