]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
hsts: when a dupe host adds subdomains, use that
authorDaniel Stenberg <daniel@haxx.se>
Thu, 26 Mar 2026 16:28:34 +0000 (17:28 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Thu, 26 Mar 2026 22:27:24 +0000 (23:27 +0100)
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
lib/curlx/strparse.c
lib/curlx/strparse.h
lib/hsts.c
tests/data/Makefile.am
tests/data/test1638 [new file with mode: 0644]

index 28c4c1d8f85802a991d6441686b5fc31deacdaa1..20718a96181e2db0e2978e318a2f4030a860112d 100644 (file)
@@ -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.
 
-### `<file name="%LOGDIR/filename" [nonewline="yes"][crlf="yes"]>`
+### `<file name="%LOGDIR/filename" [options]>`
 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.
+
 ### `<file1>`
 1 to 4 can be appended to 'file' to create more files.
 
index 138b3df1255328b71f6f352d6aa78ae3195c2f6f..7866b2087ab782bb74814253d7be071955560137 100644 (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,
index 514203dc92fad6a5f39c9428d243e06a37ff249b..c7801b2cb3461d6fd9dc827dccaca6322dfe78bf 100644 (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)
index 3585af422af055823dfa404dddd08d00c8b03be4..9e4710f1b13aeb3dd57a1a7a2002e74602c526ab 100644 (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;
index f85d0b072326e0546fad20883b98c5ed521714f3..830ac2fada40baec793d1219e0fecc9281ab089d 100644 (file)
@@ -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 (file)
index 0000000..043111e
--- /dev/null
@@ -0,0 +1,79 @@
+<?xml version="1.0" encoding="US-ASCII"?>
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP proxy
+HSTS
+</keywords>
+</info>
+
+<reply>
+
+# we use this as response to a CONNECT
+<connect crlf="headers" nocheck="yes">
+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
+
+</connect>
+
+<data crlf="headers" nocheck="yes">
+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-
+</data>
+</reply>
+
+<client>
+<server>
+http
+</server>
+<features>
+HSTS
+proxy
+https
+large-time
+</features>
+
+<file name="%LOGDIR/hsts%TESTNUMBER">
+# comment in input file
+foo.example. "20391001 04:47:41"
+.foo.example. "20291001 04:47:41"
+</file>
+
+<name>
+HSTS duplicate domains where the update adds subdomains
+</name>
+<command>
+-x http://%HOSTIP:%HTTPPORT http://this.hsts.example/%TESTNUMBER --hsts %LOGDIR/hsts%TESTNUMBER
+</command>
+<disable>
+test-duphandle
+</disable>
+</client>
+
+<verify>
+<protocol crlf="headers">
+GET http://this.hsts.example/%TESTNUMBER HTTP/1.1
+Host: this.hsts.example
+User-Agent: curl/%VERSION
+Accept: */*
+Proxy-Connection: Keep-Alive
+
+</protocol>
+<file name="%LOGDIR/hsts%TESTNUMBER" mode="text">
+# 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"
+</file>
+
+</verify>
+</testcase>