From 04afd160767d22c9e8b95a8113564c2a8f1d3d29 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Tue, 9 Jun 2026 08:18:18 +0200 Subject: [PATCH] urlapi: URL decode hostname before IP address normalization With this, IPv6 addresses that end with '%25' with no following zone id are considered invalid. Extend test 1560 to verify Reported-by: Hem Parekh Closes #21918 --- lib/urlapi.c | 17 +++++++++-------- tests/libtest/lib1560.c | 11 +++++++++-- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/lib/urlapi.c b/lib/urlapi.c index 1799625ffd..22b9304ed0 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -658,20 +658,24 @@ static CURLUcode parse_authority(struct Curl_URL *u, */ uc = parse_hostname_login(u, auth, authlen, flags, &offset); if(uc) - goto out; + return uc; result = curlx_dyn_addn(host, auth + offset, authlen - offset); if(result) { uc = cc2cu(result); - goto out; + return uc; } uc = parse_port(u, host, has_scheme); if(uc) - goto out; + return uc; if(!curlx_dyn_len(host)) - return CURLUE_NO_HOST; + uc = CURLUE_NO_HOST; + else + uc = urldecode_host(host); + if(uc) + return uc; switch(ipv4_normalize(host)) { case HOST_IPV4: @@ -680,9 +684,7 @@ static CURLUcode parse_authority(struct Curl_URL *u, uc = ipv6_parse(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_NAME: - uc = urldecode_host(host); - if(!uc) - uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); + uc = hostname_check(u, curlx_dyn_ptr(host), curlx_dyn_len(host)); break; case HOST_ERROR: uc = CURLUE_OUT_OF_MEMORY; @@ -692,7 +694,6 @@ static CURLUcode parse_authority(struct Curl_URL *u, break; } -out: return uc; } diff --git a/tests/libtest/lib1560.c b/tests/libtest/lib1560.c index e66ae84b56..62c163c7cb 100644 --- a/tests/libtest/lib1560.c +++ b/tests/libtest/lib1560.c @@ -626,6 +626,13 @@ static const struct testcase get_parts_list[] = { }; static const struct urltestcase get_url_list[] = { + /* percent-encoded IP addresses */ + {"https://127.0.0.%31.", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://127.0.0.0%78f%46.", "https://127.0.0.255/", 0, 0, CURLUE_OK}, + {"https://%30%31%37%37%2e%31", "https://127.0.0.1/", 0, 0, CURLUE_OK}, + {"https://[fe80%3A%3A20c%3A29ff%3Afe9c%3A409b]/", + "https://[fe80::20c:29ff:fe9c:409b]/", 0, 0, CURLUE_OK }, + /* IPvFuture format */ {"http://[v1.fe80::abcd]/", "", 0, 0, CURLUE_BAD_IPV6}, @@ -842,8 +849,8 @@ static const struct urltestcase get_url_list[] = { "", 0, 0, CURLUE_BAD_IPV6}, {"https://[fe80::20c:29ff:fe9c:409b%25]:1234", - "https://[fe80::20c:29ff:fe9c:409b%2525]:1234/", - 0, 0, CURLUE_OK}, + "", + 0, 0, CURLUE_BAD_IPV6}, {"https://[fe80::20c:29ff:fe9c:409b%eth0]:1234", "https://[fe80::20c:29ff:fe9c:409b%25eth0]:1234/", 0, 0, CURLUE_OK},