hsts: when a dupe host adds subdomains, use that

Otherwise a weaker earlier entry is allowed to override a later more
restrictive one.

Add test 1638 to verify.

Closes #21108
This commit is contained in:
Daniel Stenberg 2026-03-26 17:28:34 +01:00
parent 5172ba5475
commit e1fdbdd16f
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
6 changed files with 105 additions and 3 deletions

View file

@ -35,6 +35,15 @@ void curlx_str_assign(struct Curl_str *out, const char *str, size_t len)
out->len = len;
}
/* remove bytes from the end of the string, never remove more bytes than what
the string holds! */
void curlx_str_trim(struct Curl_str *out, size_t len)
{
DEBUGASSERT(out);
DEBUGASSERT(out->len >= len);
out->len -= len;
}
/* Get a word until the first DELIM or end of string. At least one byte long.
return non-zero on error */
int curlx_str_until(const char **linep, struct Curl_str *out,

View file

@ -44,6 +44,7 @@ struct Curl_str {
void curlx_str_init(struct Curl_str *out);
void curlx_str_assign(struct Curl_str *out, const char *str, size_t len);
void curlx_str_trim(struct Curl_str *out, size_t len);
#define curlx_str(x) ((x)->str)
#define curlx_strlen(x) ((x)->len)

View file

@ -407,6 +407,7 @@ static CURLcode hsts_add(struct hsts *h, const char *line)
char dbuf[MAX_HSTS_DATELEN + 1];
time_t expires = 0;
const char *hp = curlx_str(&host);
size_t hlen;
/* The date parser works on a null-terminated string. The maximum length
is upheld by curlx_str_quotedword(). */
@ -420,17 +421,26 @@ static CURLcode hsts_add(struct hsts *h, const char *line)
if(hp[0] == '.') {
curlx_str_nudge(&host, 1);
hp = curlx_str(&host);
subdomain = TRUE;
}
hlen = curlx_strlen(&host);
if(hlen && (hp[hlen - 1] == '.'))
/* strip off any trailing dot */
curlx_str_trim(&host, 1);
/* only add it if not already present */
e = Curl_hsts(h, curlx_str(&host), curlx_strlen(&host), subdomain);
if(!e)
result = hsts_create(h, curlx_str(&host), curlx_strlen(&host),
subdomain, expires);
else if(curlx_str_casecompare(&host, e->host)) {
/* the same hostname, use the largest expire time */
/* the same hostname, use the largest expire time and keep the
strictest subdomain policy */
if(expires > e->expires)
e->expires = expires;
if(subdomain)
e->includeSubDomains = TRUE;
}
if(result)
return result;