From 79a24161ab40baf9a287210af58337b68b145c53 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Sun, 14 Jun 2026 22:56:14 +0200 Subject: [PATCH] urlapi: refactor and simplify curl_url_set Create and make use of: - url_encode_part() - url_uppercasehex_part() - url_append_query() - url_sethost() Closes #22012 --- lib/urlapi.c | 238 +++++++++++++++++++++++++++++---------------------- 1 file changed, 134 insertions(+), 104 deletions(-) diff --git a/lib/urlapi.c b/lib/urlapi.c index 60e5c7f7e3..05c79dfb0d 100644 --- a/lib/urlapi.c +++ b/lib/urlapi.c @@ -1841,6 +1841,126 @@ static bool allowed_in_path(unsigned char x) return FALSE; } +static CURLUcode url_encode_part(struct dynbuf *encp, + const char *part, + bool plusencode, + bool pathmode, + bool equalsencode) +{ + const unsigned char *i; + + for(i = (const unsigned char *)part; *i; i++) { + CURLcode result; + if((*i == ' ') && plusencode) + result = curlx_dyn_addn(encp, "+", 1); + else if(ISUNRESERVED(*i) || + (pathmode && allowed_in_path(*i)) || + ((*i == '=') && equalsencode)) { + if((*i == '=') && equalsencode) + /* only skip the first equals sign */ + equalsencode = FALSE; + result = curlx_dyn_addn(encp, i, 1); + } + else { + unsigned char out[3] = { '%' }; + Curl_hexbyte(&out[1], *i); + result = curlx_dyn_addn(encp, out, 3); + } + if(result) + return cc2cu(result); + } + return CURLUE_OK; +} + +static CURLUcode url_uppercasehex_part(struct dynbuf *encp, + const char *part) +{ + char *p; + CURLcode result = curlx_dyn_add(encp, part); + if(result) + return cc2cu(result); + p = curlx_dyn_ptr(encp); + while(*p) { + /* make sure percent encoded are upper case */ + if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) && + (ISLOWER(p[1]) || ISLOWER(p[2]))) { + p[1] = Curl_raw_toupper(p[1]); + p[2] = Curl_raw_toupper(p[2]); + p += 3; + } + else + p++; + } + return CURLUE_OK; +} + +static CURLUcode url_append_query(CURLU *u, struct dynbuf *encp) +{ + /* Append the 'encp' string onto the old query. Add a '&' separator if none + is already present at the end of the existing query */ + + size_t querylen = u->query ? strlen(u->query) : 0; + bool addamperand = querylen && (u->query[querylen - 1] != '&'); + if(querylen) { + struct dynbuf qbuf; + CURLcode result; + const char *newp = curlx_dyn_ptr(encp); + curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); + + /* add original query */ + result = curlx_dyn_addn(&qbuf, u->query, querylen); + if(!result && addamperand) + /* add ampersand */ + result = curlx_dyn_addn(&qbuf, "&", 1); + if(!result) + /* add new query part */ + result = curlx_dyn_add(&qbuf, newp); + if(result) + goto nomem; + curlx_dyn_free(encp); + curlx_free(u->query); + u->query = curlx_dyn_ptr(&qbuf); + return CURLUE_OK; +nomem: + curlx_dyn_free(encp); + return cc2cu(result); + } + else { + curlx_free(u->query); + u->query = curlx_dyn_ptr(encp); + } + return CURLUE_OK; +} + +static CURLUcode url_sethost(CURLU *u, struct dynbuf *encp, + bool urlencode, + unsigned int flags) +{ + size_t n = curlx_dyn_len(encp); + bool bad = FALSE; + char *newp = curlx_dyn_ptr(encp); + if(!n) + /* an empty hostname is okay if told so */ + bad = (flags & CURLU_NO_AUTHORITY) ? FALSE : TRUE; + else if(!urlencode) { + /* if the hostname part was not URL encoded here, it was set already URL + encoded so we need to decode it to check */ + size_t dlen; + char *decoded = NULL; + CURLcode result = Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); + if(result || hostname_check(u, decoded, dlen)) + bad = TRUE; + curlx_free(decoded); + } + else if(hostname_check(u, (char *)CURL_UNCONST(newp), n)) + bad = TRUE; + if(bad) { + curlx_dyn_free(encp); + return CURLUE_BAD_HOSTNAME; + } + return CURLUE_OK; +} + CURLUcode curl_url_set(CURLU *u, CURLUPart what, const char *part, unsigned int flags) { @@ -1914,8 +2034,9 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, } DEBUGASSERT(storep); { - const char *newp; + const char *newp = NULL; struct dynbuf enc; + CURLUcode status; curlx_dyn_init(&enc, (nalloc * 3) + 1 + leadingslash); if(leadingslash && (part[0] != '/')) { @@ -1923,111 +2044,20 @@ CURLUcode curl_url_set(CURLU *u, CURLUPart what, if(result) return cc2cu(result); } - if(urlencode) { - const unsigned char *i; + if(urlencode) + status = url_encode_part(&enc, part, plusencode, pathmode, equalsencode); + else + status = url_uppercasehex_part(&enc, part); + if(!status) { + newp = curlx_dyn_ptr(&enc); - for(i = (const unsigned char *)part; *i; i++) { - CURLcode result; - if((*i == ' ') && plusencode) { - result = curlx_dyn_addn(&enc, "+", 1); - if(result) - return CURLUE_OUT_OF_MEMORY; - } - else if(ISUNRESERVED(*i) || - (pathmode && allowed_in_path(*i)) || - ((*i == '=') && equalsencode)) { - if((*i == '=') && equalsencode) - /* only skip the first equals sign */ - equalsencode = FALSE; - result = curlx_dyn_addn(&enc, i, 1); - if(result) - return cc2cu(result); - } - else { - unsigned char out[3] = { '%' }; - Curl_hexbyte(&out[1], *i); - result = curlx_dyn_addn(&enc, out, 3); - if(result) - return cc2cu(result); - } - } - } - else { - char *p; - CURLcode result = curlx_dyn_add(&enc, part); - if(result) - return cc2cu(result); - p = curlx_dyn_ptr(&enc); - while(*p) { - /* make sure percent encoded are upper case */ - if((*p == '%') && ISXDIGIT(p[1]) && ISXDIGIT(p[2]) && - (ISLOWER(p[1]) || ISLOWER(p[2]))) { - p[1] = Curl_raw_toupper(p[1]); - p[2] = Curl_raw_toupper(p[2]); - p += 3; - } - else - p++; - } - } - newp = curlx_dyn_ptr(&enc); - - if(appendquery && newp) { - /* Append the 'newp' string onto the old query. Add a '&' separator if - none is present at the end of the existing query already */ - - size_t querylen = u->query ? strlen(u->query) : 0; - bool addamperand = querylen && (u->query[querylen - 1] != '&'); - if(querylen) { - struct dynbuf qbuf; - curlx_dyn_init(&qbuf, CURL_MAX_INPUT_LENGTH); - - if(curlx_dyn_addn(&qbuf, u->query, querylen)) /* add original query */ - goto nomem; - - if(addamperand) { - if(curlx_dyn_addn(&qbuf, "&", 1)) - goto nomem; - } - if(curlx_dyn_add(&qbuf, newp)) - goto nomem; - curlx_dyn_free(&enc); - curlx_free(*storep); - *storep = curlx_dyn_ptr(&qbuf); - return CURLUE_OK; -nomem: - curlx_dyn_free(&enc); - return CURLUE_OUT_OF_MEMORY; - } - } - else if(what == CURLUPART_HOST) { - size_t n = curlx_dyn_len(&enc); - if(!n && (flags & CURLU_NO_AUTHORITY)) { - /* Skip hostname check, it is allowed to be empty. */ - } - else { - bool bad = FALSE; - if(!n) - bad = TRUE; /* empty hostname is not okay */ - else if(!urlencode) { - /* if the hostname part was not URL encoded here, it was set ready - URL encoded so we need to decode it to check */ - size_t dlen; - char *decoded = NULL; - CURLcode result = - Curl_urldecode(newp, n, &decoded, &dlen, REJECT_CTRL); - if(result || hostname_check(u, decoded, dlen)) - bad = TRUE; - curlx_free(decoded); - } - else if(hostname_check(u, (char *)CURL_UNCONST(newp), n)) - bad = TRUE; - if(bad) { - curlx_dyn_free(&enc); - return CURLUE_BAD_HOSTNAME; - } - } + if(appendquery && newp) + return url_append_query(u, &enc); + else if(what == CURLUPART_HOST) + status = url_sethost(u, &enc, urlencode, flags); } + if(status) + return status; curlx_free(*storep); *storep = (char *)CURL_UNCONST(newp);