urlapi: do not keep an internal port string

Do not allocated an internal port string, just keep and number and a bit
if it is present. Format the port string in a local buffer when someone
asks for it and copy that.

This avoid the format/alloc penalty when parsing URLs and no one ever
asking for the port string.

(And there will be a method to get the uint16_t value directly in
 another PR)

Closes #22167
This commit is contained in:
Stefan Eissing 2026-06-25 11:18:27 +02:00 committed by Daniel Stenberg
parent d2fe26cfe2
commit 26c6a4d898
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
2 changed files with 49 additions and 45 deletions

View file

@ -34,11 +34,11 @@ struct Curl_URL {
char *options; /* IMAP only? */
char *host;
char *zoneid; /* for numerical IPv6 addresses */
char *port;
char *path;
char *query;
char *fragment;
unsigned short portnum; /* the numerical version (if 'port' is set) */
uint16_t portnum; /* the numerical port if present */
BIT(port_present); /* to support missing port */
BIT(query_present); /* to support blank */
BIT(fragment_present); /* to support blank */
BIT(guessed_scheme); /* when a URL without scheme is parsed */

View file

@ -73,7 +73,6 @@ static void free_urlhandle(struct Curl_URL *u)
curlx_free(u->options);
curlx_free(u->host);
curlx_free(u->zoneid);
curlx_free(u->port);
curlx_free(u->path);
curlx_free(u->query);
curlx_free(u->fragment);
@ -358,6 +357,8 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
/*
* Find the end of an IPv6 address on the ']' ending bracket.
*/
u->portnum = 0;
u->port_present = FALSE;
if(hostname[0] == '[') {
portptr = strchr(hostname, ']');
if(!portptr)
@ -393,12 +394,8 @@ UNITTEST CURLUcode parse_port(struct Curl_URL *u, struct dynbuf *host,
if(curlx_str_number(&portptr, &port, 0xffff) || *portptr)
return CURLUE_BAD_PORT_NUMBER;
u->portnum = (unsigned short)port;
/* generate a new port number string to get rid of leading zeroes etc */
curlx_free(u->port);
u->port = curl_maprintf("%" CURL_FORMAT_CURL_OFF_T, port);
if(!u->port)
return CURLUE_OUT_OF_MEMORY;
u->portnum = (uint16_t)port;
u->port_present = TRUE;
}
return CURLUE_OK;
@ -1344,12 +1341,12 @@ CURLU *curl_url_dup(const CURLU *in)
DUP(u, in, password);
DUP(u, in, options);
DUP(u, in, host);
DUP(u, in, port);
DUP(u, in, path);
DUP(u, in, query);
DUP(u, in, fragment);
DUP(u, in, zoneid);
u->portnum = in->portnum;
u->port_present = in->port_present;
u->fragment_present = in->fragment_present;
u->query_present = in->query_present;
}
@ -1484,7 +1481,7 @@ static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
else {
const char *scheme;
char *options = u->options;
char *port = u->port;
char *port = NULL;
const struct Curl_scheme *h = NULL;
char schemebuf[MAX_SCHEME_LEN + 5];
if(u->scheme)
@ -1494,19 +1491,26 @@ static CURLUcode urlget_url(const CURLU *u, char **part, unsigned int flags)
else
return CURLUE_NO_SCHEME;
if(u->port_present) {
curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
port = portbuf;
}
h = Curl_get_scheme(scheme);
if(h) {
if(!port && (flags & CURLU_DEFAULT_PORT)) {
if(!u->port_present && (flags & CURLU_DEFAULT_PORT)) {
/* there is no stored port number, but asked to deliver a default one
for the scheme */
curl_msnprintf(portbuf, sizeof(portbuf), "%u", h->defport);
port = portbuf;
}
else if(port && (h->defport == u->portnum) &&
(flags & CURLU_NO_DEFAULT_PORT))
else if(u->port_present && (h->defport == u->portnum) &&
(flags & CURLU_NO_DEFAULT_PORT)) {
/* there is a stored port number, but asked to inhibit if it matches
the default port for the scheme */
port = NULL;
}
if(!(h->flags & PROTOPT_URLOPTIONS))
options = NULL;
}
@ -1614,10 +1618,24 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
ifmissing = CURLUE_NO_ZONEID;
break;
case CURLUPART_PORT:
ptr = u->port;
ptr = NULL;
ifmissing = CURLUE_NO_PORT;
flags &= ~U_CURLU_URLDECODE; /* never for port */
if(!ptr && (flags & CURLU_DEFAULT_PORT) && u->scheme) {
if(u->port_present) {
const struct Curl_scheme *h = u->scheme ?
Curl_get_scheme(u->scheme) : NULL;
/* there is a stored port number, but ask to inhibit if
it matches the default one for the scheme */
if(h && (h->defport == u->portnum) &&
(flags & CURLU_NO_DEFAULT_PORT)) {
ptr = NULL;
}
else {
curl_msnprintf(portbuf, sizeof(portbuf), "%u", u->portnum);
ptr = portbuf;
}
}
else if((flags & CURLU_DEFAULT_PORT) && u->scheme) {
/* there is no stored port number, but asked to deliver
a default one for the scheme */
const struct Curl_scheme *h = Curl_get_scheme(u->scheme);
@ -1626,14 +1644,6 @@ CURLUcode curl_url_get(const CURLU *u, CURLUPart what,
ptr = portbuf;
}
}
else if(ptr && u->scheme) {
/* there is a stored port number, but ask to inhibit if
it matches the default one for the scheme */
const struct Curl_scheme *h = Curl_get_scheme(u->scheme);
if(h && (h->defport == u->portnum) &&
(flags & CURLU_NO_DEFAULT_PORT))
ptr = NULL;
}
break;
case CURLUPART_PATH:
ptr = u->path;
@ -1700,7 +1710,6 @@ static CURLUcode set_url_scheme(CURLU *u, const char *scheme,
static CURLUcode set_url_port(CURLU *u, const char *provided_port)
{
char *tmp;
curl_off_t port;
if(!ISDIGIT(provided_port[0]))
/* not a number */
@ -1708,12 +1717,8 @@ static CURLUcode set_url_port(CURLU *u, const char *provided_port)
if(curlx_str_number(&provided_port, &port, 0xffff) || *provided_port)
/* weirdly provided number, not good! */
return CURLUE_BAD_PORT_NUMBER;
tmp = curl_maprintf("%" CURL_FORMAT_CURL_OFF_T, port);
if(!tmp)
return CURLUE_OUT_OF_MEMORY;
curlx_free(u->port);
u->port = tmp;
u->portnum = (unsigned short)port;
u->portnum = (uint16_t)port;
u->port_present = TRUE;
return CURLUE_OK;
}
@ -1796,7 +1801,7 @@ static CURLUcode urlset_clear(CURLU *u, CURLUPart what)
break;
case CURLUPART_PORT:
u->portnum = 0;
curlx_safefree(u->port);
u->port_present = FALSE;
break;
case CURLUPART_PATH:
curlx_safefree(u->path);
@ -2077,26 +2082,25 @@ bool Curl_url_same_origin(CURLU *base, CURLU *href)
if(href->host) {
if(!curl_strequal(base->host, href->host))
return FALSE;
if(!curl_strequal(base->zoneid ? base->zoneid : "",
href->zoneid ? href->zoneid : ""))
return FALSE;
if(!curl_strequal(base->port, href->port)) {
/* This may still match if only one has an explicit port
* and it is the default for the scheme. */
if(base->port && href->port)
return FALSE;
if(base->port_present != href->port_present) {
/* one is present, one is not */
s = Curl_get_scheme(base->scheme);
if(!s) /* Cannot match default port for unknown scheme */
return FALSE;
/* The port which is set must be the default one */
if((base->port && (base->portnum != s->defport)) ||
(href->port && (href->portnum != s->defport)))
/* to match, the present one must be the default port */
if((base->port_present && (base->portnum != s->defport)) ||
(href->port_present && (href->portnum != s->defport)))
return FALSE;
}
else if(base->portnum != href->portnum) /* both present or missing */
return FALSE;
if(!curl_strequal(base->zoneid ? base->zoneid : "",
href->zoneid ? href->zoneid : ""))
return FALSE;
}
else if(href->port) /* no host in href, then there must be no port */
else if(href->port_present) /* no host in href, then there must be no port */
return FALSE;
return TRUE;
}