[PATCH v3 0/7] MR10542: iertutil: Add URI parsing support for IUriRuntimeClass.
For React Native. -- v3: iertutil: Decode Punycode-encoded hostname for IUriRuntimeClass. 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..4797e12642f 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 = *ptr - data->host; } 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 = *ptr - data->host; 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 4797e12642f..e9410f79446 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 e9410f79446..7a32b6f0f4e 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 && *path != '/' && *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) && *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) && *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
From: Zhiyi Zhang <zzhang@codeweavers.com> --- dlls/iertutil/main.c | 2 +- dlls/iertutil/private.h | 4 + dlls/iertutil/tests/iertutil.c | 2 + dlls/iertutil/uri.c | 179 ++++++++++++++++++++++++++------- 4 files changed, 152 insertions(+), 35 deletions(-) diff --git a/dlls/iertutil/main.c b/dlls/iertutil/main.c index 0ad7e38ddfb..83f70be4a12 100644 --- a/dlls/iertutil/main.c +++ b/dlls/iertutil/main.c @@ -415,7 +415,7 @@ static HRESULT STDMETHODCALLTYPE uri_factory_CreateUri(IUriRuntimeClassFactory * 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); + hr = create_uri(raw_buffer, Uri_CREATE_ALLOW_IMPLICIT_FILE_SCHEME | Uri_CREATE_NO_DECODE_EXTRA_INFO | WINE_URI_RUNTIME_CLASS, 0, &uri); if (FAILED(hr)) { *instance = NULL; diff --git a/dlls/iertutil/private.h b/dlls/iertutil/private.h index 5c416aba80a..bf2e363f6cc 100644 --- a/dlls/iertutil/private.h +++ b/dlls/iertutil/private.h @@ -25,6 +25,7 @@ #include "windef.h" #include "winbase.h" #include "winstring.h" +#include "urlmon.h" #include "wine/debug.h" #include "activation.h" @@ -71,6 +72,9 @@ DEFINE_IINSPECTABLE_(pfx, iface_type, impl_type, impl_from_##iface_type, iface_type##_iface, \ &impl->base_iface) +#define WINE_URI_RUNTIME_CLASS 0x80000000 /* IUriRuntimeClass URI parser */ + +extern HRESULT create_uri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IUri **ppURI); extern HRESULT Uri_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); #endif /* __WINE_IERTUTIL_PRIVATE_H */ diff --git a/dlls/iertutil/tests/iertutil.c b/dlls/iertutil/tests/iertutil.c index 13d45654b42..1ed87363cc9 100644 --- a/dlls/iertutil/tests/iertutil.c +++ b/dlls/iertutil/tests/iertutil.c @@ -232,6 +232,8 @@ static void test_IUriRuntimeClass(void) {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 */ diff --git a/dlls/iertutil/uri.c b/dlls/iertutil/uri.c index 7a32b6f0f4e..e15b4d7f25a 100644 --- a/dlls/iertutil/uri.c +++ b/dlls/iertutil/uri.c @@ -691,6 +691,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. @@ -2004,6 +2064,51 @@ static BOOL canonicalize_reg_name(const parse_data *data, Uri *uri, } } + if(flags & WINE_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] = '\\'; @@ -5294,37 +5399,8 @@ HRESULT Uri_Construct(IUnknown *pUnkOuter, LPVOID *ppobj) return S_OK; } -/*********************************************************************** - * CreateUri (iertutil.@) - * - * Creates a new IUri object using the URI represented by pwzURI. This function - * parses and validates the components of pwzURI and then canonicalizes the - * parsed components. - * - * PARAMS - * pwzURI [I] The URI to parse, validate, and canonicalize. - * dwFlags [I] Flags which can affect how the parsing/canonicalization is performed. - * dwReserved [I] Reserved (not used). - * ppURI [O] The resulting IUri after parsing/canonicalization occurs. - * - * RETURNS - * Success: Returns S_OK. ppURI contains the pointer to the newly allocated IUri. - * Failure: E_INVALIDARG if there are invalid flag combinations in dwFlags, or an - * invalid parameter, or pwzURI doesn't represent a valid URI. - * E_OUTOFMEMORY if any memory allocation fails. - * - * NOTES - * Default flags: - * Uri_CREATE_CANONICALIZE, Uri_CREATE_DECODE_EXTRA_INFO, Uri_CREATE_CRACK_UNKNOWN_SCHEMES, - * Uri_CREATE_PRE_PROCESS_HTML_URI, Uri_CREATE_NO_IE_SETTINGS. - */ -HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IUri **ppURI) +HRESULT create_uri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IUri **ppURI) { - const DWORD supported_flags = Uri_CREATE_ALLOW_RELATIVE|Uri_CREATE_ALLOW_IMPLICIT_WILDCARD_SCHEME| - 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 *ret; HRESULT hr; parse_data data; @@ -5345,10 +5421,6 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU return E_INVALIDARG; } - /* Currently unsupported. */ - if(dwFlags & ~supported_flags) - FIXME("Ignoring unsupported flag(s) %lx\n", dwFlags & ~supported_flags); - hr = Uri_Construct(NULL, (void**)&ret); if(FAILED(hr)) { *ppURI = NULL; @@ -5394,6 +5466,45 @@ HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IU return S_OK; } +/*********************************************************************** + * CreateUri (iertutil.@) + * + * Creates a new IUri object using the URI represented by pwzURI. This function + * parses and validates the components of pwzURI and then canonicalizes the + * parsed components. + * + * PARAMS + * pwzURI [I] The URI to parse, validate, and canonicalize. + * dwFlags [I] Flags which can affect how the parsing/canonicalization is performed. + * dwReserved [I] Reserved (not used). + * ppURI [O] The resulting IUri after parsing/canonicalization occurs. + * + * RETURNS + * Success: Returns S_OK. ppURI contains the pointer to the newly allocated IUri. + * Failure: E_INVALIDARG if there are invalid flag combinations in dwFlags, or an + * invalid parameter, or pwzURI doesn't represent a valid URI. + * E_OUTOFMEMORY if any memory allocation fails. + * + * NOTES + * Default flags: + * Uri_CREATE_CANONICALIZE, Uri_CREATE_DECODE_EXTRA_INFO, Uri_CREATE_CRACK_UNKNOWN_SCHEMES, + * Uri_CREATE_PRE_PROCESS_HTML_URI, Uri_CREATE_NO_IE_SETTINGS. + */ +HRESULT WINAPI CreateUri(LPCWSTR pwzURI, DWORD dwFlags, DWORD_PTR dwReserved, IUri **ppURI) +{ + const DWORD supported_flags = Uri_CREATE_ALLOW_RELATIVE|Uri_CREATE_ALLOW_IMPLICIT_WILDCARD_SCHEME| + 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; + + /* Currently unsupported. */ + if(dwFlags & ~supported_flags) + FIXME("Ignoring unsupported flag(s) %lx\n", dwFlags & ~supported_flags); + + return create_uri(pwzURI, dwFlags, dwReserved, ppURI); +} + /*********************************************************************** * CreateUriWithFragment (iertutil.@) * -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10542
v3: Fix CI test failures on Win10 21H2. Some of the malformed URIs are parsed differently on Win10 22H2. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_135464
Jacek Caban (@jacek) commented about dlls/iertutil/uri.c:
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 = *ptr - data->host;
I think it would be a bit more idiomatic to use `data->host_len++` here. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_135691
Jacek Caban (@jacek) commented about dlls/iertutil/uri.c:
} }
- if(!is_file && *path && *path != '/') { + if(!is_file && *path && *path != '/' && *path != '\\') {
We have `is_slash` for that. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_135692
Jacek Caban (@jacek) commented about dlls/iertutil/tests/iertutil.c:
{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},
A quick test on Windows suggests that IUri should be changed as well. What is the exact reason to keep it different? BTW, since the other comments are mostly cosmetic, feel free to move the last commit out of this MR to allow merging the rest while we figure this one out. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10542#note_135693
participants (3)
-
Jacek Caban (@jacek) -
Zhiyi Zhang -
Zhiyi Zhang (@zhiyi)