From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/main.c | 158 +++++++++++--- dlls/iertutil/private.h | 37 ++++ dlls/iertutil/uri.c | 465 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 629 insertions(+), 31 deletions(-) diff --git a/dlls/iertutil/main.c b/dlls/iertutil/main.c index a8dbde03c86..e596955b390 100644 --- a/dlls/iertutil/main.c +++ b/dlls/iertutil/main.c @@ -17,6 +17,7 @@ */ #include "private.h" +#include "urlmon.h" #include "initguid.h" WINE_DEFAULT_DEBUG_CHANNEL(iertutil); @@ -32,6 +33,18 @@ struct uri { IUriRuntimeClass IUriRuntimeClass_iface; HSTRING raw_uri; + HSTRING absolute_uri; + HSTRING display_uri; + HSTRING extension; + HSTRING fragment; + HSTRING host; + HSTRING domain; + HSTRING password; + HSTRING path; + HSTRING query; + HSTRING scheme_name; + HSTRING user_name; + INT32 port; LONG ref; }; @@ -76,6 +89,17 @@ static ULONG STDMETHODCALLTYPE uri_Release(IUriRuntimeClass *iface) if (!ref) { WindowsDeleteString(impl->raw_uri); + WindowsDeleteString(impl->absolute_uri); + WindowsDeleteString(impl->display_uri); + WindowsDeleteString(impl->extension); + WindowsDeleteString(impl->fragment); + WindowsDeleteString(impl->host); + WindowsDeleteString(impl->domain); + WindowsDeleteString(impl->password); + WindowsDeleteString(impl->path); + WindowsDeleteString(impl->query); + WindowsDeleteString(impl->scheme_name); + WindowsDeleteString(impl->user_name); free(impl); } @@ -104,58 +128,83 @@ static HRESULT STDMETHODCALLTYPE uri_GetTrustLevel(IUriRuntimeClass *iface, Trus 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); + + TRACE("iface %p, value %p.\n", iface, value); - /* TODO: Parse the raw URI and reconstruct it from parts according to RFC 3986 or RFC 3987 */ - return IUriRuntimeClass_get_RawUri(iface, value); + return WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->query, value); } static HRESULT STDMETHODCALLTYPE uri_QueryParsed(IUriRuntimeClass *iface, @@ -176,20 +225,33 @@ static HRESULT STDMETHODCALLTYPE uri_RawUri(IUriRuntimeClass *iface, HSTRING *va 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 WindowsDuplicateString(impl->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 WindowsDuplicateString(impl->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); + + TRACE("iface %p, value %p.\n", iface, value); + + if (impl->port == -1) + return S_FALSE; + + *value = impl->port; + return S_OK; } static HRESULT STDMETHODCALLTYPE uri_Suspicious(IUriRuntimeClass *iface, boolean *value) @@ -340,29 +402,63 @@ static const struct IActivationFactoryVtbl activation_factory_vtbl = DEFINE_IINSPECTABLE(uri_factory, IUriRuntimeClassFactory, struct iertutil, IActivationFactory_iface) +static HRESULT wstr_to_hstring(const WCHAR *str, DWORD len, HSTRING *out) +{ + if (!str) + { + *out = NULL; + return S_OK; + } + + return WindowsCreateString(str, len, out); +} + static HRESULT STDMETHODCALLTYPE uri_factory_CreateUri(IUriRuntimeClassFactory *iface, HSTRING uri, IUriRuntimeClass **instance) { + struct uri_runtime_data data; const WCHAR *raw_buffer; struct uri *uri_impl; + HRESULT hr; - FIXME("iface %p, uri %s, instance %p semi-stub!\n", iface, debugstr_hstring(uri), instance); + TRACE("iface %p, uri %s, instance %p.\n", iface, debugstr_hstring(uri), instance); if (!uri) return E_POINTER; + raw_buffer = WindowsGetStringRawBuffer(uri, NULL); + hr = uri_runtime_parse(raw_buffer, &data); + if (FAILED(hr)) + { + *instance = NULL; + return E_INVALIDARG; + } + uri_impl = calloc(1, sizeof(*uri_impl)); if (!uri_impl) + { + uri_runtime_free(&data); return E_OUTOFMEMORY; + } uri_impl->IUriRuntimeClass_iface.lpVtbl = &uri_vtbl; 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 */ - + wstr_to_hstring(data.raw_uri, data.raw_uri_len, &uri_impl->raw_uri); + wstr_to_hstring(data.absolute_uri, data.absolute_uri_len, &uri_impl->absolute_uri); + wstr_to_hstring(data.display_uri, data.display_uri_len, &uri_impl->display_uri); + wstr_to_hstring(data.scheme_name, data.scheme_name_len, &uri_impl->scheme_name); + wstr_to_hstring(data.host, data.host_len, &uri_impl->host); + wstr_to_hstring(data.domain, data.domain_len, &uri_impl->domain); + wstr_to_hstring(data.path, data.path_len, &uri_impl->path); + wstr_to_hstring(data.query, data.query_len, &uri_impl->query); + wstr_to_hstring(data.fragment, data.fragment_len, &uri_impl->fragment); + wstr_to_hstring(data.user_name, data.user_name_len, &uri_impl->user_name); + wstr_to_hstring(data.password, data.password_len, &uri_impl->password); + wstr_to_hstring(data.extension, data.extension_len, &uri_impl->extension); + uri_impl->port = data.port; + + uri_runtime_free(&data); *instance = &uri_impl->IUriRuntimeClass_iface; return S_OK; } diff --git a/dlls/iertutil/private.h b/dlls/iertutil/private.h index 5c416aba80a..3147574206f 100644 --- a/dlls/iertutil/private.h +++ b/dlls/iertutil/private.h @@ -73,4 +73,41 @@ extern HRESULT Uri_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); +struct uri_runtime_data +{ + WCHAR *raw_uri; + DWORD raw_uri_len; + WCHAR *absolute_uri; + DWORD absolute_uri_len; + WCHAR *display_uri; + DWORD display_uri_len; + WCHAR *scheme_name; + DWORD scheme_name_len; + WCHAR *host; + DWORD host_len; + WCHAR *domain; + DWORD domain_len; + WCHAR *path; + DWORD path_len; + WCHAR *query; + DWORD query_len; + WCHAR *fragment; + DWORD fragment_len; + WCHAR *user_name; + DWORD user_name_len; + WCHAR *password; + DWORD password_len; + WCHAR *extension; + DWORD extension_len; + INT32 port; + BOOL is_default_port; + BOOL has_password; + BOOL is_ipv6; + BOOL is_opaque; + BOOL is_known_scheme; +}; + +extern HRESULT uri_runtime_parse(const WCHAR *uri, struct uri_runtime_data *data); +extern void uri_runtime_free(struct uri_runtime_data *data); + #endif /* __WINE_IERTUTIL_PRIVATE_H */ diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index 1c0e6f1b779..d4fdf64fc50 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 @@ -40,6 +41,8 @@ #include "in6addr.h" #include "ip2string.h" +#include "private.h" + #define URI_DISPLAY_NO_ABSOLUTE_URI 0x1 #define URI_DISPLAY_NO_DEFAULT_PORT_AUTH 0x2 @@ -6935,3 +6938,465 @@ HRESULT WINAPI PrivateCoInternetParseIUri(IUri *pIUri, PARSEACTION ParseAction, return hr; } + +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; +} + +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 per 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; +} + +static int find_scheme_default_port(URL_SCHEME scheme) +{ + for (unsigned int i = 0; i < ARRAY_SIZE(default_ports); i++) + { + if (default_ports[i].scheme == scheme) + return default_ports[i].port; + } + return -1; +} + +/* Build the host component for IUriRuntimeClass. Free result->host when it's done. */ +static void uri_runtime_build_host(const parse_data *data, struct uri_runtime_data *result) +{ + if (data->host_type == Uri_HOST_IPV6) + { + /* Find closing bracket. host_len may be unset when port follows IPv6 literal */ + const WCHAR *end = wcschr(data->host, ']'); + DWORD bracket_len = end ? (DWORD)(end - data->host + 1) : data->host_len; + + if (bracket_len < 2) + return; + + result->host = strdupwn(data->host + 1, bracket_len - 2); + result->host_len = bracket_len - 2; + result->is_ipv6 = TRUE; + } + else if (data->host_type == Uri_HOST_IPV4) + { + WCHAR *host, full_ipv4[16]; + const WCHAR *terminator; + IN_ADDR in_addr; + + /* IPv4 address may be partial */ + host = strdupwn(data->host, data->host_len); + RtlIpv4StringToAddressW(host, FALSE, &terminator, &in_addr); + RtlIpv4AddressToStringW(&in_addr, full_ipv4); + free(host); + + result->host_len = wcslen(full_ipv4); + result->host = strdupwn(full_ipv4, result->host_len); + } + else if (result->is_known_scheme) + { + WCHAR *lower = strdupwn_lower(data->host, data->host_len); + DWORD lower_len = data->host_len; + int decoded_len; + + /* Try Punycode decoding */ + 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 */ + result->host = pct_encode_forbidden(lower, lower_len, &result->host_len); + free(lower); + } + else + { + /* Unknown scheme: keep the host as it is */ + result->host = strdupwn(data->host, data->host_len); + result->host_len = data->host_len; + } +} + +/* Build the path component for IUriRuntimeClass. */ +static void uri_runtime_build_path(const parse_data *data, struct uri_runtime_data *result) +{ + DWORD path_len, i; + WCHAR *path; + + if (!data->path || data->path_len == 0) + return; + + /* Implicit file scheme paths need a leading '/' */ + if (data->has_implicit_scheme && data->scheme_type == URL_SCHEME_FILE) + { + path = malloc((data->path_len + 2) * sizeof(WCHAR)); + path[0] = '/'; + memcpy(path + 1, data->path, data->path_len * sizeof(WCHAR)); + path_len = data->path_len + 1; + path[path_len] = 0; + } + else + { + path = strdupwn(data->path, data->path_len); + path_len = data->path_len; + } + + if (result->is_known_scheme && !result->is_opaque) + { + /* Convert backslashes to forward slashes for known schemes */ + for (i = 0; i < path_len; i++) + { + if (path[i] == '\\') + path[i] = '/'; + } + + /* Remove dot segments */ + path_len = remove_dot_segments(path, path_len); + path[path_len] = 0; + } + + if (result->is_known_scheme) + { + result->path = pct_encode_forbidden(path, path_len, &result->path_len); + free(path); + } + else + { + result->path = path; + result->path_len = path_len; + } +} + +/* Build a URI string from components, e.g., for AbsoluteUri and DisplayUri */ +static WCHAR *uri_runtime_build_uri(const struct uri_runtime_data *data, + BOOL include_userinfo, DWORD *out_len) +{ + DWORD len, pos; + WCHAR *buf; + + len = data->scheme_name_len + 3; /* scheme:// */ + len += data->user_name_len + 1 + data->password_len + 1; /* user:pass@ */ + len += data->host_len + 2; /* [] for IPv6 */ + len += 6; /* :65535 */ + len += data->path ? data->path_len : 1; /* '/' */ + len += data->query_len + data->fragment_len; + + buf = malloc((len + 1) * sizeof(WCHAR)); + pos = 0; + + memcpy(buf + pos, data->scheme_name, data->scheme_name_len * sizeof(WCHAR)); + pos += data->scheme_name_len; + buf[pos++] = ':'; + buf[pos++] = '/'; + buf[pos++] = '/'; + + if (include_userinfo && (data->user_name_len > 0 || data->password_len > 0)) + { + if (data->user_name_len > 0) + { + memcpy(buf + pos, data->user_name, data->user_name_len * sizeof(WCHAR)); + pos += data->user_name_len; + } + if (data->has_password) + { + buf[pos++] = ':'; + if (data->password_len > 0) + { + memcpy(buf + pos, data->password, data->password_len * sizeof(WCHAR)); + pos += data->password_len; + } + } + buf[pos++] = '@'; + } + + if (data->host) + { + if (data->is_ipv6) + buf[pos++] = '['; + memcpy(buf + pos, data->host, data->host_len * sizeof(WCHAR)); + pos += data->host_len; + if (data->is_ipv6) + buf[pos++] = ']'; + } + + if (data->is_default_port) + pos += swprintf(buf + pos, 7, L":%u", data->port); + + if (data->path && data->path_len > 0) + { + memcpy(buf + pos, data->path, data->path_len * sizeof(WCHAR)); + pos += data->path_len; + } + else + { + buf[pos++] = '/'; + } + + if (data->query) + { + memcpy(buf + pos, data->query, data->query_len * sizeof(WCHAR)); + pos += data->query_len; + } + + if (data->fragment) + { + memcpy(buf + pos, data->fragment, data->fragment_len * sizeof(WCHAR)); + pos += data->fragment_len; + } + + buf[pos] = 0; + *out_len = pos; + return buf; +} + +/* Build AbsoluteUri and DisplayUri from components. */ +static void uri_runtime_build_uris(struct uri_runtime_data *result) +{ + /* AbsoluteUri is NULL for opaque URIs with known hierarchical scheme */ + if (result->is_opaque && result->is_known_scheme) + { + result->display_uri = strdupwn(result->raw_uri, result->raw_uri_len); + result->display_uri_len = result->raw_uri_len; + return; + } + + /* For opaque URIs with non-hierarchical/unknown scheme, AbsoluteUri = DisplayUri = RawUri */ + if (result->is_opaque) + { + result->absolute_uri = strdupwn(result->raw_uri, result->raw_uri_len); + result->absolute_uri_len = result->raw_uri_len; + result->display_uri = strdupwn(result->raw_uri, result->raw_uri_len); + result->display_uri_len = result->raw_uri_len; + return; + } + + /* AbsoluteUri includes userinfo. DisplayUri includes them only for unknown schemes */ + result->absolute_uri = uri_runtime_build_uri(result, TRUE, &result->absolute_uri_len); + result->display_uri = uri_runtime_build_uri(result, !result->is_known_scheme, &result->display_uri_len); +} + +/* Parse the URI using the URI parser for CreateUri(). Then build the URI components for + * IUriRuntimeClass, the behavior of which is a bit different compared to CreateUri() */ +HRESULT uri_runtime_parse(const WCHAR *uri, struct uri_runtime_data *result) +{ + DWORD flags = 0; + parse_data data; + BSTR processed; + + memset(result, 0, sizeof(*result)); + memset(&data, 0, sizeof(data)); + + processed = pre_process_uri(uri); + if (!processed) + return E_OUTOFMEMORY; + + /* Apply default flags */ + flags = Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME; + apply_default_flags(&flags); + + /* Parse the URI using the URI parser for CreateUri() */ + data.uri = processed; + if (!parse_uri(&data, flags)) + { + SysFreeString(processed); + return E_INVALIDARG; + } + + result->is_opaque = data.is_opaque; + result->is_known_scheme = is_hierarchical_scheme(data.scheme_type); + + /* RawUri */ + result->raw_uri_len = SysStringLen(processed); + result->raw_uri = strdupwn(processed, result->raw_uri_len); + + /* Scheme name */ + if (data.has_implicit_scheme && data.scheme_type == URL_SCHEME_FILE) + { + result->scheme_name = strdupwn(L"file", 4); + result->scheme_name_len = 4; + } + else + { + result->scheme_name = strdupwn_lower(data.scheme, data.scheme_len); + result->scheme_name_len = data.scheme_len; + } + + /* Host */ + uri_runtime_build_host(&data, result); + + /* Domain (registrable domain, i.e. eTLD+1) */ + if (result->host && result->host_len && (data.host_type == Uri_HOST_DNS || data.host_type == Uri_HOST_IDN)) + { + INT domain_start; + + find_domain_name(result->host, result->host_len, &domain_start); + if (domain_start > -1) + { + result->domain_len = result->host_len - domain_start; + result->domain = strdupwn(result->host + domain_start, result->domain_len); + } + } + + /* Path */ + uri_runtime_build_path(&data, result); + + /* Query */ + if (data.query && data.query_len > 0) + { + result->query = strdupwn(data.query, data.query_len); + result->query_len = data.query_len; + } + + /* Fragment */ + if (data.fragment && data.fragment_len > 0) + { + result->fragment = strdupwn(data.fragment, data.fragment_len); + result->fragment_len = data.fragment_len; + } + + /* User name */ + if (data.username && data.username_len > 0) + { + if (result->is_known_scheme) + { + result->user_name = pct_encode_forbidden(data.username, data.username_len, &result->user_name_len); + } + else + { + result->user_name = strdupwn(data.username, data.username_len); + result->user_name_len = data.username_len; + } + } + + /* Password */ + if (data.password && data.password_len > 0) + { + if (result->is_known_scheme) + { + result->password = pct_encode_forbidden(data.password, data.password_len, &result->password_len); + } + else + { + result->password = strdupwn(data.password, data.password_len); + result->password_len = data.password_len; + } + } + result->has_password = !!data.password; + + /* Port */ + if (data.has_port && data.port_value > 0) + { + result->port = data.port_value; + if (find_scheme_default_port(data.scheme_type) != data.port_value) + result->is_default_port = TRUE; + } + else + { + result->port = find_scheme_default_port(data.scheme_type); + } + + /* Extension */ + if (result->path && result->path_len > 0) + { + INT ext = find_file_extension(result->path, result->path_len); + if (ext >= 0) + { + DWORD ext_len = result->path_len - ext; + result->extension = strdupwn(result->path + ext, ext_len); + result->extension_len = ext_len; + } + } + + /* AbsoluteUri and DisplayUri */ + uri_runtime_build_uris(result); + + SysFreeString(processed); + return S_OK; +} + +void uri_runtime_free(struct uri_runtime_data *data) +{ + free(data->raw_uri); + free(data->absolute_uri); + free(data->display_uri); + free(data->scheme_name); + free(data->host); + free(data->domain); + free(data->path); + free(data->query); + free(data->fragment); + free(data->user_name); + free(data->password); + free(data->extension); +} -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/9131