lib: hostname related cleanups

* format `data->state.aptr.host` without crlf, so signatures do not need
  to strip it again. Add the crlf when adding the header to the request
  dynbuf
* check `connect-to` strings on normalized hostname and user supplied
  hostname (when those differ)
* libssh: always use the peer for setting ssh option SSH_OPTIONS_HOST,
  preserve ipv6 [] enclosure, use IDN converted hostname otherwise. This
  is the libssh documented expectation.
  Do NOT use strings from URL parsing.

Closes #22128
This commit is contained in:
Stefan Eissing 2026-06-22 10:45:42 +02:00 committed by Daniel Stenberg
parent 58771cf4f3
commit d786a85f19
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
4 changed files with 50 additions and 36 deletions

View file

@ -2000,17 +2000,17 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data)
{
struct connectdata *conn = data->conn;
struct dynamically_allocated_data *aptr = &data->state.aptr;
const char *ptr;
const char *ptr = NULL;
curlx_safefree(aptr->host);
#ifndef CURL_DISABLE_COOKIES
curlx_safefree(data->req.cookiehost);
#endif
ptr = Curl_checkheaders(data, STRCONST("Host"));
if(ptr &&
(!data->state.this_is_a_follow ||
Curl_peer_equal(data->state.initial_origin, data->state.origin))) {
if(Curl_peer_equal(data->state.initial_origin, data->state.origin))
ptr = Curl_checkheaders(data, STRCONST("Host"));
if(ptr) {
#ifndef CURL_DISABLE_COOKIES
/* If we have a given custom Host: header, we extract the hostname in
order to possibly use it for cookie reasons later on. We only allow the
@ -2047,30 +2047,40 @@ static CURLcode http_set_aptr_host(struct Curl_easy *data)
#endif
if(!curl_strequal("Host:", ptr)) {
aptr->host = curl_maprintf("Host:%s\r\n", &ptr[5]);
aptr->host = curl_maprintf("Host:%s", &ptr[5]);
if(!aptr->host)
return CURLE_OUT_OF_MEMORY;
}
}
else {
/* Use the hostname as present in the URL if it was IPv6. */
char *host = (data->state.origin->user_hostname[0] == '[') ?
data->state.origin->user_hostname : data->state.origin->hostname;
/* This is the HTTP Host: header, so we want
* - for ipv6 origins: "[ipv6-address]" where the ipv6 address is
* found in origin->hostname, stripped of zoneid/scopeid.
* - the (IDN converted) origin->hostname (DNS name or ipv4) otherwise.
* Note: zoneid/scopeid only applies to local routing and has no
* meaning on the remote HTTP server (eg. would confuse it). */
bool ipv6 = (bool)data->state.origin->ipv6;
struct dynbuf tmp;
size_t hlen;
CURLcode result;
if(((conn->given->protocol & (CURLPROTO_HTTPS | CURLPROTO_WSS)) &&
(data->state.origin->port == PORT_HTTPS)) ||
((conn->given->protocol & (CURLPROTO_HTTP | CURLPROTO_WS)) &&
(data->state.origin->port == PORT_HTTP)))
/* if(HTTPS on port 443) OR (HTTP on port 80) then do not include
the port number in the host string */
aptr->host = curl_maprintf("Host: %s\r\n", host);
else
aptr->host = curl_maprintf("Host: %s:%d\r\n",
host, data->state.origin->port);
curlx_dyn_init(&tmp, DYN_HTTP_REQUEST);
result = curlx_dyn_addn(&tmp, STRCONST("Host: "));
if(!result && ipv6)
result = curlx_dyn_addn(&tmp, STRCONST("["));
if(!result)
result = curlx_dyn_add(&tmp, data->state.origin->hostname);
if(!result && ipv6)
result = curlx_dyn_addn(&tmp, STRCONST("]"));
if(!result &&
((data->state.origin->port != data->state.origin->scheme->defport) ||
(data->state.origin->scheme->family != conn->scheme->family))) {
result = curlx_dyn_addf(&tmp, ":%u", data->state.origin->port);
}
if(!aptr->host)
/* without Host: we cannot make a nice request */
return CURLE_OUT_OF_MEMORY;
aptr->host = result ? NULL : curlx_dyn_take(&tmp, &hlen);
curlx_dyn_free(&tmp);
return result;
}
return CURLE_OK;
}
@ -2896,8 +2906,11 @@ static CURLcode http_add_hd(struct Curl_easy *data,
break;
case H1_HD_HOST:
if(data->state.aptr.host)
if(data->state.aptr.host) {
result = curlx_dyn_add(req, data->state.aptr.host);
if(!result)
result = curlx_dyn_addn(req, STRCONST("\r\n"));
}
break;
#ifndef CURL_DISABLE_PROXY

View file

@ -401,11 +401,8 @@ static CURLcode make_headers(struct Curl_easy *data,
if(!Curl_checkheaders(data, STRCONST("Host"))) {
char *fullhost;
if(data->state.aptr.host) {
/* remove /r/n as the separator for canonical request must be '\n' */
size_t pos = strcspn(data->state.aptr.host, "\n\r");
fullhost = curlx_memdup0(data->state.aptr.host, pos);
}
if(data->state.aptr.host)
fullhost = curlx_strdup(data->state.aptr.host);
else
fullhost = curl_maprintf("host:%s", hostname);

View file

@ -1856,14 +1856,16 @@ static CURLcode parse_connect_to_string(struct Curl_easy *data,
/* check whether the URL's hostname matches. Use the URL hostname
* when it was an IPv6 address. Otherwise use the connection's hostname
* that has IDN conversion. */
const char *hostname_to_match = (dest->user_hostname[0] == '[') ?
dest->user_hostname : dest->hostname;
size_t hlen = strlen(hostname_to_match);
host_match = curl_strnequal(ptr, hostname_to_match, hlen);
ptr += hlen;
host_match = host_match && *ptr == ':';
ptr++;
size_t hlen = strlen(dest->hostname);
host_match = curl_strnequal(ptr, dest->hostname, hlen);
if(!host_match && (dest->user_hostname != dest->hostname)) {
/* hostname was normalized, could be IPv6 or IDN */
hlen = strlen(dest->user_hostname);
host_match = curl_strnequal(ptr, dest->user_hostname, hlen);
}
host_match = host_match && ptr[hlen] == ':';
if(host_match)
ptr += hlen + 1;
}
if(host_match) {

View file

@ -2585,6 +2585,8 @@ static CURLcode myssh_connect(struct Curl_easy *data, bool *done)
return CURLE_FAILED_INIT;
}
/* For ipv6 origins, use the `user_hostname` that has the "[]" enclosure.
* Otherwise, use `hostname` that is IDN converted. */
rc = ssh_options_set(sshc->ssh_session, SSH_OPTIONS_HOST,
conn->origin->ipv6 ?
conn->origin->user_hostname : conn->origin->hostname);