[PATCH v4 0/6] MR10542: iertutil: Add URI parsing support for IUriRuntimeClass.
For React Native. -- v4: iertutil/tests: Add URI parser tests. iertutil: Add initial URI parsing support for IUriRuntimeClass. iertutil: Fix backslash processing in URIs. iertutil: Do not encode user info if the scheme is unknown. iertutil: Fix parsing URIs without a port value. 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 | 183 +++++++++++----------------------------- dlls/urlmon/tests/uri.c | 78 +++++++++++++++++ 2 files changed, 127 insertions(+), 134 deletions(-) diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index bdf9f104e42..a2ec466dd0e 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 @@ -149,8 +150,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 +676,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 +730,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 +816,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 +833,6 @@ static BOOL check_implicit_ipv4(const WCHAR **ptr, UINT *val) { if(*ptr == start) return FALSE; - *val = ret; return TRUE; } @@ -1215,13 +1201,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 +1226,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 +2078,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 +2097,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/uri.c | 8 ++++++- dlls/urlmon/tests/uri.c | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index a2ec466dd0e..92fc2fc54c4 100644 --- a/dlls/iertutil/uri.c +++ b/dlls/iertutil/uri.c @@ -1173,9 +1173,9 @@ static BOOL parse_port(const WCHAR **ptr, parse_data *data) { ++(*ptr); } - data->has_port = TRUE; data->port_value = port; data->port_len = *ptr - data->port; + data->has_port = data->port_len > 0; TRACE("(%p %p): Found port %s len=%ld value=%lu\n", ptr, data, debugstr_wn(data->port, data->port_len), data->port_len, data->port_value); @@ -1222,6 +1222,9 @@ static BOOL parse_ipv4address(const WCHAR **ptr, parse_data *data) { data->host = NULL; return FALSE; } + /* Include the ':' in the host for unknown schemes with no port value */ + if(!data->has_port && is_unknown) + data->host_len++; } else if(!is_auth_delim(**ptr, !is_unknown)) { /* Found more data which belongs to the host, so this isn't an IPv4. */ *ptr = data->host; @@ -1309,6 +1312,9 @@ static BOOL parse_reg_name(const WCHAR **ptr, parse_data *data, DWORD extras) { ignore_col = TRUE; } else { data->host_len = tmp - data->host; + /* Include the ':' in the host for unknown schemes with no port value */ + if(!data->has_port && data->scheme_type == URL_SCHEME_UNKNOWN) + data->host_len++; break; } } diff --git a/dlls/urlmon/tests/uri.c b/dlls/urlmon/tests/uri.c index 8e58cd4185c..4f3d45ccca7 100644 --- a/dlls/urlmon/tests/uri.c +++ b/dlls/urlmon/tests/uri.c @@ -1901,6 +1901,58 @@ static const uri_properties uri_tests[] = { {URLZONE_INVALID,E_NOTIMPL,FALSE} } }, + /* Empty port with a known scheme */ + { "http://example.com:/", 0, S_OK, FALSE, 0, + { + {"http://example.com/",S_OK,FALSE}, + {"example.com",S_OK,FALSE}, + {"http://example.com/",S_OK,FALSE}, + {"example.com",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"example.com",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"/",S_OK,FALSE}, + {"/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"http://example.com:/",S_OK,FALSE}, + {"http",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_DNS,S_OK,FALSE}, + {80,S_OK,FALSE}, + {URL_SCHEME_HTTP,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, + /* Empty port with an unknown scheme */ + { "unknown://example.com:/", 0, S_OK, FALSE, 0, + { + {"unknown://example.com:/",S_OK,FALSE}, + {"example.com:",S_OK,FALSE}, + {"unknown://example.com:/",S_OK,FALSE}, + {"example.com:",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"example.com:",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"/",S_OK,FALSE}, + {"/",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"unknown://example.com:/",S_OK,FALSE}, + {"unknown",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_DNS,S_OK,FALSE}, + {0,S_FALSE,FALSE}, + {URL_SCHEME_UNKNOWN,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, { "zip://google.com:65536", 0, S_OK, FALSE, 0, { {"zip://google.com:65536/",S_OK,FALSE}, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/uri.c | 8 ++++---- dlls/urlmon/tests/uri.c | 26 ++++++++++++++++++++++++++ 2 files changed, 30 insertions(+), 4 deletions(-) diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index 92fc2fc54c4..d42a112dff4 100644 --- a/dlls/iertutil/uri.c +++ b/dlls/iertutil/uri.c @@ -1856,9 +1856,9 @@ static BOOL canonicalize_username(const parse_data *data, Uri *uri, DWORD flags, } } else if(is_ascii(*ptr) && !is_reserved(*ptr) && !is_unreserved(*ptr) && *ptr != '\\') { /* Only percent encode forbidden characters if the NO_ENCODE_FORBIDDEN_CHARACTERS flag - * is NOT set. + * is NOT set and the scheme is known. */ - if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS)) { + if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && data->scheme_type != URL_SCHEME_UNKNOWN) { if(!computeOnly) pct_encode_val(*ptr, uri->canon_uri + uri->canon_len); @@ -1914,9 +1914,9 @@ static BOOL canonicalize_password(const parse_data *data, Uri *uri, DWORD flags, } } else if(is_ascii(*ptr) && !is_reserved(*ptr) && !is_unreserved(*ptr) && *ptr != '\\') { /* Only percent encode forbidden characters if the NO_ENCODE_FORBIDDEN_CHARACTERS flag - * is NOT set. + * is NOT set and the scheme is known. */ - if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS)) { + if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && data->scheme_type != URL_SCHEME_UNKNOWN) { if(!computeOnly) pct_encode_val(*ptr, uri->canon_uri + uri->canon_len); diff --git a/dlls/urlmon/tests/uri.c b/dlls/urlmon/tests/uri.c index 4f3d45ccca7..da49bd43ace 100644 --- a/dlls/urlmon/tests/uri.c +++ b/dlls/urlmon/tests/uri.c @@ -2565,6 +2565,32 @@ static const uri_properties uri_tests[] = { {URLZONE_INVALID,E_NOTIMPL,FALSE} } }, + /* Forbidden characters are never encoded for unknown scheme types. */ + { "unknown://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment", 0, S_OK, FALSE, 0, + { + {"unknown://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment",S_OK,FALSE}, + {"us er:pass word@exam ple.com",S_OK,FALSE}, + {"unknown://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment",S_OK,FALSE}, + {"exam ple.com",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"#frag ment",S_OK,FALSE}, + {"exam ple.com",S_OK,FALSE}, + {"pass word",S_OK,FALSE}, + {"/pa th",S_OK,FALSE}, + {"/pa th?qu ery=val ue",S_OK,FALSE}, + {"?qu ery=val ue",S_OK,FALSE}, + {"unknown://us er:pass word@exam ple.com/pa th?qu ery=val ue#frag ment",S_OK,FALSE}, + {"unknown",S_OK,FALSE}, + {"us er:pass word",S_OK,FALSE}, + {"us er",S_OK,FALSE} + }, + { + {Uri_HOST_DNS,S_OK,FALSE}, + {0,S_FALSE,FALSE}, + {URL_SCHEME_UNKNOWN,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, /* Make sure forbidden characters are percent encoded. */ { "http://gov.uk/<|> test<|>", 0, S_OK, FALSE, 0, { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/uri.c | 6 ++--- dlls/urlmon/tests/uri.c | 52 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index d42a112dff4..38408c4baf5 100644 --- a/dlls/iertutil/uri.c +++ b/dlls/iertutil/uri.c @@ -2427,7 +2427,7 @@ static DWORD canonicalize_path_hierarchical(const WCHAR *path, DWORD path_len, U } } - if(!is_file && *path && *path != '/') { + if(!is_file && *path && !is_slash(*path)) { /* Prepend a '/' to the path if it doesn't have one. */ if(ret_path) ret_path[len] = '/'; @@ -2755,7 +2755,7 @@ static BOOL canonicalize_query(const parse_data *data, Uri *uri, DWORD flags, BO continue; } } - } else if(known_scheme && is_ascii(*ptr) && !is_unreserved(*ptr) && !is_reserved(*ptr)) { + } else if(known_scheme && is_ascii(*ptr) && !is_unreserved(*ptr) && !is_reserved(*ptr) && !is_slash(*ptr)) { if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { if(!computeOnly) @@ -2805,7 +2805,7 @@ static BOOL canonicalize_fragment(const parse_data *data, Uri *uri, DWORD flags, continue; } } - } else if(known_scheme && is_ascii(*ptr) && !is_unreserved(*ptr) && !is_reserved(*ptr)) { + } else if(known_scheme && is_ascii(*ptr) && !is_unreserved(*ptr) && !is_reserved(*ptr) && !is_slash(*ptr)) { if(!(flags & Uri_CREATE_NO_ENCODE_FORBIDDEN_CHARACTERS) && !(flags & Uri_CREATE_NO_DECODE_EXTRA_INFO)) { if(!computeOnly) diff --git a/dlls/urlmon/tests/uri.c b/dlls/urlmon/tests/uri.c index da49bd43ace..4a0ae350b33 100644 --- a/dlls/urlmon/tests/uri.c +++ b/dlls/urlmon/tests/uri.c @@ -4010,6 +4010,58 @@ static const uri_properties uri_tests[] = { {URLZONE_INVALID,E_NOTIMPL,FALSE} } }, + /* '\' are converted to '/' for known schemes */ + { "http://a\\b//c\\d?e\\f#g\\h", 0, S_OK, FALSE, 0, + { + {"http://a/b//c/d?e\\f#g\\h",S_OK,FALSE}, + {"a",S_OK,FALSE}, + {"http://a/b//c/d?e\\f#g\\h",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"#g\\h",S_OK,FALSE}, + {"a",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"/b//c/d",S_OK,FALSE}, + {"/b//c/d?e\\f",S_OK,FALSE}, + {"?e\\f",S_OK,FALSE}, + {"http://a\\b//c\\d?e\\f#g\\h",S_OK,FALSE}, + {"http",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_DNS,S_OK,FALSE}, + {80,S_OK,FALSE}, + {URL_SCHEME_HTTP,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, + /* '\' are not converted to '/' for unknown schemes */ + { "unknown://a\\b//c\\d?e\\f#g\\h", 0, S_OK, FALSE, 0, + { + {"unknown://a\\b//c\\d?e\\f#g\\h",S_OK,FALSE}, + {"a\\b",S_OK,FALSE}, + {"unknown://a\\b//c\\d?e\\f#g\\h",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE}, + {"#g\\h",S_OK,FALSE}, + {"a\\b",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"//c\\d",S_OK,FALSE}, + {"//c\\d?e\\f",S_OK,FALSE}, + {"?e\\f",S_OK,FALSE}, + {"unknown://a\\b//c\\d?e\\f#g\\h",S_OK,FALSE}, + {"unknown",S_OK,FALSE}, + {"",S_FALSE,FALSE}, + {"",S_FALSE,FALSE} + }, + { + {Uri_HOST_DNS,S_OK,FALSE}, + {0,S_FALSE,FALSE}, + {URL_SCHEME_UNKNOWN,S_OK,FALSE}, + {URLZONE_INVALID,E_NOTIMPL,FALSE} + } + }, /* Forbidden characters aren't percent encoded. */ { "file:c:\\in^|dex.html", Uri_CREATE_FILE_USE_DOS_PATH, 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 ++++++++++++++++++++++++++++++++----------- 1 file changed, 114 insertions(+), 37 deletions(-) diff --git a/dlls/iertutil/main.c b/dlls/iertutil/main.c index fd6dd3c9372..0ad7e38ddfb 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 = CreateUri(raw_buffer, Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME | Uri_CREATE_NO_DECODE_EXTRA_INFO, 0, &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; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/tests/iertutil.c | 263 +++++++++++++++++++++++++++++---- 1 file changed, 231 insertions(+), 32 deletions(-) diff --git a/dlls/iertutil/tests/iertutil.c b/dlls/iertutil/tests/iertutil.c index 0f77b8a7f28..13d45654b42 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,160 @@ 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"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}, + {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"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}, + /* 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 +269,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
On Fri Apr 10 02:33:09 2026 +0000, Zhiyi Zhang wrote:
changed this line in [version 4 of the diff](/wine/wine/-/merge_requests/10542/diffs?diff_id=259582&start_sha=efcc327034f19aa7ef40311fbe0520e552c1ad54#85b337fd4648db32663daa7f86493a9dcf18bd96_236_235) Moving this commit to another MR.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_135768
On Fri Apr 10 02:33:33 2026 +0000, Zhiyi Zhang wrote:
Moving this commit to another MR. You're right. I thought it needed Uri_DISPLAY_IDN_HOST for the parser to decode the Punycode encoded host. I have a fix now, and I will send it later.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_135784
This merge request was approved by Jacek Caban. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
participants (3)
-
Jacek Caban (@jacek) -
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)