From e1fdbdd16f269da33938d39725a074b4684fd132 Mon Sep 17 00:00:00 2001 From: Daniel Stenberg Date: Thu, 26 Mar 2026 17:28:34 +0100 Subject: [PATCH] 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 --- docs/tests/FILEFORMAT.md | 5 ++- lib/curlx/strparse.c | 9 +++++ lib/curlx/strparse.h | 1 + lib/hsts.c | 12 +++++- tests/data/Makefile.am | 2 +- tests/data/test1638 | 79 ++++++++++++++++++++++++++++++++++++++++ 6 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 tests/data/test1638 diff --git a/docs/tests/FILEFORMAT.md b/docs/tests/FILEFORMAT.md index 28c4c1d8f8..20718a9618 100644 --- a/docs/tests/FILEFORMAT.md +++ b/docs/tests/FILEFORMAT.md @@ -636,7 +636,7 @@ parameter is the not negative integer number of seconds for the delay. This 'delay' attribute is intended for specific test cases, and normally not needed. -### `` +### `` This creates the named file with this content before the test case is run, which is useful if the test case needs a file to act on. @@ -646,6 +646,9 @@ off. `crlf=yes` forces the newlines to become CRLF even if not written so in the test. +`mode="text"` normalizes the line endings to make them compare as text on all +platforms. + ### `` 1 to 4 can be appended to 'file' to create more files. diff --git a/lib/curlx/strparse.c b/lib/curlx/strparse.c index 138b3df125..7866b2087a 100644 --- a/lib/curlx/strparse.c +++ b/lib/curlx/strparse.c @@ -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, diff --git a/lib/curlx/strparse.h b/lib/curlx/strparse.h index 514203dc92..c7801b2cb3 100644 --- a/lib/curlx/strparse.h +++ b/lib/curlx/strparse.h @@ -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) diff --git a/lib/hsts.c b/lib/hsts.c index 3585af422a..9e4710f1b1 100644 --- a/lib/hsts.c +++ b/lib/hsts.c @@ -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; diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am index f85d0b0723..830ac2fada 100644 --- a/tests/data/Makefile.am +++ b/tests/data/Makefile.am @@ -217,7 +217,7 @@ test1614 test1615 test1616 test1617 test1618 \ test1620 test1621 test1622 test1623 test1624 test1625 test1626 test1627 \ \ test1630 test1631 test1632 test1633 test1634 test1635 test1636 test1637 \ -\ +test1638 \ test1640 test1641 test1642 test1643 \ \ test1650 test1651 test1652 test1653 test1654 test1655 test1656 test1657 \ diff --git a/tests/data/test1638 b/tests/data/test1638 new file mode 100644 index 0000000000..043111ec35 --- /dev/null +++ b/tests/data/test1638 @@ -0,0 +1,79 @@ + + + + +HTTP +HTTP proxy +HSTS + + + + + +# we use this as response to a CONNECT + +HTTP/1.1 200 not OK at all +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Connection: close +Funny-head: yesyes + + + + +HTTP/1.1 200 not OK at all +Date: Tue, 09 Nov 2010 14:49:00 GMT +Server: test-server/fake +Content-Length: 6 +Connection: close +Funny-head: yesyes + +-foo- + + + + + +http + + +HSTS +proxy +https +large-time + + + +# comment in input file +foo.example. "20391001 04:47:41" +.foo.example. "20291001 04:47:41" + + + +HSTS duplicate domains where the update adds subdomains + + +-x http://%HOSTIP:%HTTPPORT http://this.hsts.example/%TESTNUMBER --hsts %LOGDIR/hsts%TESTNUMBER + + +test-duphandle + + + + + +GET http://this.hsts.example/%TESTNUMBER HTTP/1.1 +Host: this.hsts.example +User-Agent: curl/%VERSION +Accept: */* +Proxy-Connection: Keep-Alive + + + +# Your HSTS cache. https://curl.se/docs/hsts.html +# This file was generated by libcurl! Edit at your own risk. +.foo.example "20391001 04:47:41" + + + +