urlapi: improved return codes

- add CURLUE_BACKSLASH that can be returned when a backslash was used
  where a forward one probably was intended.

- make CURLUE_NO_HOST higher priority than port number errors for URLs
  without hostname. Like in "http://::1"

- shortened some URL parser error strings

Extend test 1560 to verify.

Reported-by: kit-ty-kate on github
Fixes #22337
Closes #22408
This commit is contained in:
Daniel Stenberg 2026-07-27 16:05:38 +02:00
parent c7328740ec
commit 573a6ec16b
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
8 changed files with 54 additions and 27 deletions

View file

@ -724,6 +724,11 @@ libcurl lacks IDN support.
A value or data field is larger than allowed. A value or data field is larger than allowed.
## CURLUE_BACKSLASH (32)
Found a backslash character where a forward slash was expected. URL separators
are forward slashes (`/`), not backslashes (`\`).
# CURLHcode # CURLHcode
The header interface returns a *CURLHcode* to indicate when an error has The header interface returns a *CURLHcode* to indicate when an error has

View file

@ -1113,6 +1113,7 @@ CURLU_PUNY2IDN 8.3.0
CURLU_PUNYCODE 7.88.0 CURLU_PUNYCODE 7.88.0
CURLU_URLDECODE 7.62.0 CURLU_URLDECODE 7.62.0
CURLU_URLENCODE 7.62.0 CURLU_URLENCODE 7.62.0
CURLUE_BACKSLASH 8.22.0
CURLUE_BAD_FILE_URL 7.81.0 CURLUE_BAD_FILE_URL 7.81.0
CURLUE_BAD_FRAGMENT 7.81.0 CURLUE_BAD_FRAGMENT 7.81.0
CURLUE_BAD_HANDLE 7.62.0 CURLUE_BAD_HANDLE 7.62.0

View file

@ -64,6 +64,7 @@ typedef enum {
CURLUE_BAD_USER, /* 29 */ CURLUE_BAD_USER, /* 29 */
CURLUE_LACKS_IDN, /* 30 */ CURLUE_LACKS_IDN, /* 30 */
CURLUE_TOO_LARGE, /* 31 */ CURLUE_TOO_LARGE, /* 31 */
CURLUE_BACKSLASH, /* 32 */
CURLUE_LAST CURLUE_LAST
} CURLUcode; } CURLUcode;

View file

@ -452,34 +452,34 @@ const char *curl_url_strerror(CURLUcode error)
return "An unknown part ID was passed to a URL API function"; return "An unknown part ID was passed to a URL API function";
case CURLUE_NO_SCHEME: case CURLUE_NO_SCHEME:
return "No scheme part in the URL"; return "No scheme present";
case CURLUE_NO_USER: case CURLUE_NO_USER:
return "No user part in the URL"; return "No user present";
case CURLUE_NO_PASSWORD: case CURLUE_NO_PASSWORD:
return "No password part in the URL"; return "No password present";
case CURLUE_NO_OPTIONS: case CURLUE_NO_OPTIONS:
return "No options part in the URL"; return "No options present";
case CURLUE_NO_HOST: case CURLUE_NO_HOST:
return "No host part in the URL"; return "No host present";
case CURLUE_NO_PORT: case CURLUE_NO_PORT:
return "No port part in the URL"; return "No port number present";
case CURLUE_NO_QUERY: case CURLUE_NO_QUERY:
return "No query part in the URL"; return "No query present";
case CURLUE_NO_FRAGMENT: case CURLUE_NO_FRAGMENT:
return "No fragment part in the URL"; return "No fragment present";
case CURLUE_NO_ZONEID: case CURLUE_NO_ZONEID:
return "No zoneid part in the URL"; return "No zoneid present";
case CURLUE_BAD_LOGIN: case CURLUE_BAD_LOGIN:
return "Bad login part"; return "Bad login";
case CURLUE_BAD_IPV6: case CURLUE_BAD_IPV6:
return "Bad IPv6 address"; return "Bad IPv6 address";
@ -517,6 +517,9 @@ const char *curl_url_strerror(CURLUcode error)
case CURLUE_TOO_LARGE: case CURLUE_TOO_LARGE:
return "A value or data field is larger than allowed"; return "A value or data field is larger than allowed";
case CURLUE_BACKSLASH:
return "Found a backslash where a forward slash was expected";
case CURLUE_LAST: case CURLUE_LAST:
break; break;
} }

View file

@ -382,6 +382,7 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
if(portptr) { if(portptr) {
curl_off_t port; curl_off_t port;
size_t keep = portptr - hostname; size_t keep = portptr - hostname;
int rc;
/* Browser behavior adaptation. If there is a colon with no digits after, /* Browser behavior adaptation. If there is a colon with no digits after,
cut off the name there which makes us ignore the colon and use the cut off the name there which makes us ignore the colon and use the
@ -393,8 +394,14 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
portptr++; portptr++;
if(!*portptr) if(!*portptr)
return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER; return has_scheme ? CURLUE_OK : CURLUE_BAD_PORT_NUMBER;
if(*portptr == '\\')
if(curlx_str_number(&portptr, &port, 0xffff) || *portptr) return CURLUE_BACKSLASH;
rc = curlx_str_number(&portptr, &port, 0xffff);
if(rc)
return CURLUE_BAD_PORT_NUMBER;
else if(*portptr == '\\')
return CURLUE_BACKSLASH;
else if(*portptr)
return CURLUE_BAD_PORT_NUMBER; return CURLUE_BAD_PORT_NUMBER;
u->portnum = (uint16_t)port; u->portnum = (uint16_t)port;
@ -666,12 +673,11 @@ static CURLUcode parse_authority(struct Curl_URL *u,
} }
uc = parse_port(u, host, has_scheme); uc = parse_port(u, host, has_scheme);
if(uc)
return uc;
if(!curlx_dyn_len(host)) if(!curlx_dyn_len(host))
/* this makes no-host errors override port number problems */
uc = CURLUE_NO_HOST; uc = CURLUE_NO_HOST;
else if(!uc)
uc = urldecode_host(host); uc = urldecode_host(host);
if(uc) if(uc)
return uc; return uc;

View file

@ -2340,6 +2340,8 @@
d c 30 d c 30
d CURLUE_TOO_LARGE... d CURLUE_TOO_LARGE...
d c 31 d c 31
d CURLUE_BACKSLASH...
d c 32
* *
d CURLUPart s 10i 0 based(######ptr######) Enum d CURLUPart s 10i 0 based(######ptr######) Enum
d CURLUPART_URL c 0 d CURLUPART_URL c 0

View file

@ -163,20 +163,20 @@ u6: URL decode error, most likely because of rubbish in the input
u7: A memory function failed u7: A memory function failed
u8: Credentials was passed in the URL when prohibited u8: Credentials was passed in the URL when prohibited
u9: An unknown part ID was passed to a URL API function u9: An unknown part ID was passed to a URL API function
u10: No scheme part in the URL u10: No scheme present
u11: No user part in the URL u11: No user present
u12: No password part in the URL u12: No password present
u13: No options part in the URL u13: No options present
u14: No host part in the URL u14: No host present
u15: No port part in the URL u15: No port number present
u16: No query part in the URL u16: No query present
u17: No fragment part in the URL u17: No fragment present
u18: No zoneid part in the URL u18: No zoneid present
u19: Bad file:// URL u19: Bad file:// URL
u20: Bad fragment u20: Bad fragment
u21: Bad hostname u21: Bad hostname
u22: Bad IPv6 address u22: Bad IPv6 address
u23: Bad login part u23: Bad login
u24: Bad password u24: Bad password
u25: Bad path u25: Bad path
u26: Bad query u26: Bad query
@ -185,7 +185,8 @@ u28: Unsupported number of slashes following scheme
u29: Bad user u29: Bad user
u30: libcurl lacks IDN support u30: libcurl lacks IDN support
u31: A value or data field is larger than allowed u31: A value or data field is larger than allowed
u32: CURLUcode unknown u32: Found a backslash where a forward slash was expected
u33: CURLUcode unknown
</stdout> </stdout>
</verify> </verify>

View file

@ -153,6 +153,14 @@ struct clearurlcase {
}; };
static const struct testcase get_parts_list[] = { static const struct testcase get_parts_list[] = {
/* backslash mistakes */
{"http:\\\\hostname", "",
CURLU_GUESS_SCHEME, 0, CURLUE_BACKSLASH },
{"http:\\\\hostname:1234", "",
CURLU_GUESS_SCHEME, 0, CURLUE_BACKSLASH },
{"http://hostname:1111\\path", "",
0, 0, CURLUE_BACKSLASH },
/* non-supported URL without hostname */ /* non-supported URL without hostname */
{"weird:///path", {"weird:///path",
"weird | [11] | [12] | [13] | | [15] | /path | [16] | [17]", "weird | [11] | [12] | [13] | | [15] | /path | [16] | [17]",
@ -753,7 +761,7 @@ static const struct urltestcase get_url_list[] = {
/* malformed unbracketed IPv6 */ /* malformed unbracketed IPv6 */
{"https://fe80:8080::1/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, {"https://fe80:8080::1/", "", 0, 0, CURLUE_BAD_PORT_NUMBER},
{"https://::1/", "", 0, 0, CURLUE_BAD_PORT_NUMBER}, {"https://::1/", "", 0, 0, CURLUE_NO_HOST},
/* Empty host with standard schemes */ /* Empty host with standard schemes */
{"http:///", "", 0, 0, CURLUE_NO_HOST}, {"http:///", "", 0, 0, CURLUE_NO_HOST},