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