Blob Blame History Raw
From fcd651bcfe1982f55e591d47521f509a2ee98975 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Thu, 1 Dec 2022 09:21:04 +0100
Subject: [PATCH 1/6] runtests: do CRLF replacements per section only

The `crlf="yes"` attribute and "hyper mode" are now only applied on a
subset of dedicated sections: data, datacheck, stdout and protocol.

Updated test 2500 accordingly.

Also made test1 use crlf="yes" for <protocol>, mostly because it is
often used as a template test case. Going forward, using this attribute
we should be able to write test cases using linefeeds only and avoid
mixed line ending encodings.

Follow-up to ca15b7512e8d11

Fixes #10009
Closes #10010

Upstream-commit: 2f34a7347f315513bfda9ef14770d287fb246bcd
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 tests/FILEFORMAT.md | 22 ++++++++++++++------
 tests/data/test1    | 14 ++++++-------
 tests/runtests.pl   | 49 +++++++++++++++++++++++++++++++++++++++++----
 3 files changed, 68 insertions(+), 17 deletions(-)

diff --git a/tests/FILEFORMAT.md b/tests/FILEFORMAT.md
index dc1092b..159a3cf 100644
--- a/tests/FILEFORMAT.md
+++ b/tests/FILEFORMAT.md
@@ -195,7 +195,7 @@ When using curl built with Hyper, the keywords must include HTTP or HTTPS for
 'hyper mode' to kick in and make line ending checks work for tests.
 ## `<reply>`
 
-### `<data [nocheck="yes"] [sendzero="yes"] [base64="yes"] [hex="yes"] [nonewline="yes"]>`
+### `<data [nocheck="yes"] [sendzero="yes"] [base64="yes"] [hex="yes"] [nonewline="yes"] [crlf="yes"]>`
 
 data to be sent to the client on its request and later verified that it
 arrived safely. Set `nocheck="yes"` to prevent the test script from verifying
@@ -224,12 +224,16 @@ and used as "raw" data.
 `nonewline=yes` means that the last byte (the trailing newline character)
 should be cut off from the data before sending or comparing it.
 
+`crlf=yes` forces *header* newlines to become CRLF even if not written so in
+the source file. Note that this makes runtests.pl parse and "guess" what is a
+header and what is not in order to apply the CRLF line endings appropriately.
+
 For FTP file listings, the `<data>` section will be used *only* if you make
 sure that there has been a CWD done first to a directory named `test-[num]`
 where [num] is the test case number. Otherwise the ftp server can't know from
 which test file to load the list content.
 
-### `<dataNUM>`
+### `<dataNUM [crlf="yes"]>`
 
 Send back this contents instead of the <data> one. The num is set by:
 
@@ -256,7 +260,7 @@ a connect prefix.
 ### `<socks>`
 Address type and address details as logged by the SOCKS proxy.
 
-### `<datacheck [mode="text"] [nonewline="yes"]>`
+### `<datacheck [mode="text"] [nonewline="yes"] [crlf="yes"]>`
 if the data is sent but this is what should be checked afterwards. If
 `nonewline=yes` is set, runtests will cut off the trailing newline from the
 data before comparing with the one actually received by the client.
@@ -264,7 +268,7 @@ data before comparing with the one actually received by the client.
 Use the `mode="text"` attribute if the output is in text mode on platforms
 that have a text/binary difference.
 
-### `<datacheckNUM [nonewline="yes"] [mode="text"]>`
+### `<datacheckNUM [nonewline="yes"] [mode="text"] [crlf="yes"]>`
 The contents of numbered datacheck sections are appended to the non-numbered
 one.
 
@@ -549,13 +553,16 @@ changing protocol data such as port numbers or user-agent strings.
 One perl op per line that operates on the protocol dump. This is pretty
 advanced. Example: `s/^EPRT .*/EPRT stripped/`.
 
-### `<protocol [nonewline="yes"]>`
+### `<protocol [nonewline="yes"] crlf="yes">`
 
 the protocol dump curl should transmit, if 'nonewline' is set, we will cut off
 the trailing newline of this given data before comparing with the one actually
 sent by the client The `<strip>` and `<strippart>` rules are applied before
 comparisons are made.
 
+`crlf=yes` forces the newlines to become CRLF even if not written so in the
+test.
+
 ### `<proxy [nonewline="yes"]>`
 
 The protocol dump curl should transmit to a HTTP proxy (when the http-proxy
@@ -572,7 +579,7 @@ have a text/binary difference.
 If 'nonewline' is set, we will cut off the trailing newline of this given data
 before comparing with the one actually received by the client
 
-### `<stdout [mode="text"] [nonewline="yes"]>`
+### `<stdout [mode="text"] [nonewline="yes"] [crlf="yes"]>`
 This verifies that this data was passed to stdout.
 
 Use the mode="text" attribute if the output is in text mode on platforms that
@@ -581,6 +588,9 @@ have a text/binary difference.
 If 'nonewline' is set, we will cut off the trailing newline of this given data
 before comparing with the one actually received by the client
 
+`crlf=yes` forces the newlines to become CRLF even if not written so in the
+test.
+
 ### `<file name="log/filename" [mode="text"]>`
 The file's contents must be identical to this after the test is complete.  Use
 the mode="text" attribute if the output is in text mode on platforms that have
diff --git a/tests/data/test1 b/tests/data/test1
index f39a08b..700bed8 100644
--- a/tests/data/test1
+++ b/tests/data/test1
@@ -9,7 +9,7 @@ HTTP GET
 #
 # Server-side
 <reply>
-<data>
+<data crlf="yes">
 HTTP/1.1 200 OK
 Date: Tue, 09 Nov 2010 14:49:00 GMT
 Server: test-server/fake
@@ -42,12 +42,12 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER
 #
 # Verify data after the test has been "shot"
 <verify>
-<protocol>
-GET /%TESTNUMBER HTTP/1.1
-Host: %HOSTIP:%HTTPPORT
-User-Agent: curl/%VERSION
-Accept: */*
-
+<protocol crlf="yes">
+GET /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+User-Agent: curl/%VERSION
+Accept: */*
+
 </protocol>
 </verify>
 </testcase>
diff --git a/tests/runtests.pl b/tests/runtests.pl
index 7eeabfc..88fc799 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -3501,7 +3501,13 @@ sub subBase64 {
 
 my $prevupdate;
 sub subNewlines {
-    my ($thing) = @_;
+    my ($force, $thing) = @_;
+
+    if($force) {
+        # enforce CRLF newline
+        $$thing =~ s/\x0d*\x0a/\x0d\x0a/;
+        return;
+    }
 
     # When curl is built with Hyper, it gets all response headers delivered as
     # name/value pairs and curl "invents" the newlines when it saves the
@@ -3515,7 +3521,7 @@ sub subNewlines {
         # skip curl error messages
         ($$thing !~ /^curl: \(\d+\) /))) {
         # enforce CRLF newline
-        $$thing =~ s/\x0a/\x0d\x0a/;
+        $$thing =~ s/\x0d*\x0a/\x0d\x0a/;
         $prevupdate = 1;
     }
     else {
@@ -3587,6 +3593,7 @@ sub prepro {
     my (@entiretest) = @_;
     my $show = 1;
     my @out;
+    my $data_crlf;
     for my $s (@entiretest) {
         my $f = $s;
         if($s =~ /^ *%if (.*)/) {
@@ -3610,10 +3617,19 @@ sub prepro {
             next;
         }
         if($show) {
+            # The processor does CRLF replacements in the <data*> sections if
+            # necessary since those parts might be read by separate servers.
+            if($s =~ /^ *<data(.*)\>/) {
+                if($1 =~ /crlf="yes"/ || $has_hyper) {
+                    $data_crlf = 1;
+                }
+            }
+            elsif(($s =~ /^ *<\/data/) && $data_crlf) {
+                $data_crlf = 0;
+            }
             subVariables(\$s, $testnum, "%");
             subBase64(\$s);
-            subNewlines(\$s) if($has_hyper && ($keywords{"HTTP"} ||
-                                               $keywords{"HTTPS"}));
+            subNewlines(0, \$s) if($data_crlf);
             push @out, $s;
         }
     }
@@ -3929,6 +3945,11 @@ sub singletest {
                     # of the datacheck
                     chomp($replycheckpart[$#replycheckpart]);
                 }
+                if($replycheckpartattr{'crlf'} ||
+                   ($has_hyper && ($keywords{"HTTP"}
+                                   || $keywords{"HTTPS"}))) {
+                    map subNewlines(0, \$_), @replycheckpart;
+                }
                 push(@reply, @replycheckpart);
             }
         }
@@ -3950,6 +3971,11 @@ sub singletest {
             map s/\r\n/\n/g, @reply;
             map s/\n/\r\n/g, @reply;
         }
+        if($replyattr{'crlf'} ||
+           ($has_hyper && ($keywords{"HTTP"}
+                           || $keywords{"HTTPS"}))) {
+            map subNewlines(0, \$_), @reply;
+        }
     }
 
     # this is the valid protocol blurb curl should generate
@@ -4406,6 +4432,12 @@ sub singletest {
             chomp($validstdout[$#validstdout]);
         }
 
+        if($hash{'crlf'} ||
+           ($has_hyper && ($keywords{"HTTP"}
+                           || $keywords{"HTTPS"}))) {
+            map subNewlines(0, \$_), @validstdout;
+        }
+
         $res = compare($testnum, $testname, "stdout", \@actual, \@validstdout);
         if($res) {
             return $errorreturncode;
@@ -4506,6 +4538,10 @@ sub singletest {
             }
         }
 
+        if($hash{'crlf'}) {
+            map subNewlines(1, \$_), @protstrip;
+        }
+
         if((!$out[0] || ($out[0] eq "")) && $protstrip[0]) {
             logmsg "\n $testnum: protocol FAILED!\n".
                 " There was no content at all in the file $SERVERIN.\n".
@@ -4637,6 +4673,11 @@ sub singletest {
                 map s/\r\n/\n/g, @outfile;
                 map s/\n/\r\n/g, @outfile;
             }
+            if($hash{'crlf'} ||
+               ($has_hyper && ($keywords{"HTTP"}
+                               || $keywords{"HTTPS"}))) {
+                map subNewlines(0, \$_), @outfile;
+            }
 
             my $strip;
             for $strip (@stripfile) {
-- 
2.39.1


From 117fce3d4fe11c36a20403cd4d6850e5b8771b41 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 27 Dec 2022 11:50:20 +0100
Subject: [PATCH 2/6] share: add sharing of HSTS cache among handles

Closes #10138

Upstream-commit: 076a2f629119222aeeb50f5a03bf9f9052fabb9a
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 docs/libcurl/opts/CURLSHOPT_SHARE.3 |  4 +++
 docs/libcurl/symbols-in-versions    |  1 +
 include/curl/curl.h                 |  1 +
 lib/hsts.c                          | 15 +++++++++
 lib/hsts.h                          |  2 ++
 lib/setopt.c                        | 48 ++++++++++++++++++++++++-----
 lib/share.c                         | 32 +++++++++++++++++--
 lib/share.h                         |  6 +++-
 lib/transfer.c                      |  3 ++
 lib/url.c                           |  6 +++-
 lib/urldata.h                       |  2 ++
 11 files changed, 109 insertions(+), 11 deletions(-)

diff --git a/docs/libcurl/opts/CURLSHOPT_SHARE.3 b/docs/libcurl/opts/CURLSHOPT_SHARE.3
index 92783b6..b15af82 100644
--- a/docs/libcurl/opts/CURLSHOPT_SHARE.3
+++ b/docs/libcurl/opts/CURLSHOPT_SHARE.3
@@ -79,6 +79,10 @@ Added in 7.61.0.
 
 Note that when you use the multi interface, all easy handles added to the same
 multi handle will share PSL cache by default without using this option.
+.IP CURL_LOCK_DATA_HSTS
+The in-memory HSTS cache.
+
+Added in 7.88.0
 .SH PROTOCOLS
 All
 .SH EXAMPLE
diff --git a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
index 5ee245d..41fffc3 100644
--- a/docs/libcurl/symbols-in-versions
+++ b/docs/libcurl/symbols-in-versions
@@ -67,6 +67,7 @@ CURL_LOCK_ACCESS_SINGLE         7.10.3
 CURL_LOCK_DATA_CONNECT          7.10.3
 CURL_LOCK_DATA_COOKIE           7.10.3
 CURL_LOCK_DATA_DNS              7.10.3
+CURL_LOCK_DATA_HSTS             7.88.0
 CURL_LOCK_DATA_NONE             7.10.3
 CURL_LOCK_DATA_PSL              7.61.0
 CURL_LOCK_DATA_SHARE            7.10.4
diff --git a/include/curl/curl.h b/include/curl/curl.h
index 139df99..5758e3b 100644
--- a/include/curl/curl.h
+++ b/include/curl/curl.h
@@ -2882,6 +2882,7 @@ typedef enum {
   CURL_LOCK_DATA_SSL_SESSION,
   CURL_LOCK_DATA_CONNECT,
   CURL_LOCK_DATA_PSL,
+  CURL_LOCK_DATA_HSTS,
   CURL_LOCK_DATA_LAST
 } curl_lock_data;
 
diff --git a/lib/hsts.c b/lib/hsts.c
index c449120..339237b 100644
--- a/lib/hsts.c
+++ b/lib/hsts.c
@@ -39,6 +39,7 @@
 #include "parsedate.h"
 #include "fopen.h"
 #include "rename.h"
+#include "share.h"
 #include "strtoofft.h"
 
 /* The last 3 #include files should be in this order */
@@ -552,4 +553,18 @@ CURLcode Curl_hsts_loadcb(struct Curl_easy *data, struct hsts *h)
   return CURLE_OK;
 }
 
+void Curl_hsts_loadfiles(struct Curl_easy *data)
+{
+  struct curl_slist *l = data->set.hstslist;
+  if(l) {
+    Curl_share_lock(data, CURL_LOCK_DATA_HSTS, CURL_LOCK_ACCESS_SINGLE);
+
+    while(l) {
+      (void)Curl_hsts_loadfile(data, data->hsts, l->data);
+      l = l->next;
+    }
+    Curl_share_unlock(data, CURL_LOCK_DATA_HSTS);
+  }
+}
+
 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
diff --git a/lib/hsts.h b/lib/hsts.h
index 0e36a77..3da7574 100644
--- a/lib/hsts.h
+++ b/lib/hsts.h
@@ -59,9 +59,11 @@ CURLcode Curl_hsts_loadfile(struct Curl_easy *data,
                             struct hsts *h, const char *file);
 CURLcode Curl_hsts_loadcb(struct Curl_easy *data,
                           struct hsts *h);
+void Curl_hsts_loadfiles(struct Curl_easy *data);
 #else
 #define Curl_hsts_cleanup(x)
 #define Curl_hsts_loadcb(x,y) CURLE_OK
 #define Curl_hsts_save(x,y,z)
+#define Curl_hsts_loadfiles(x)
 #endif /* CURL_DISABLE_HTTP || CURL_DISABLE_HSTS */
 #endif /* HEADER_CURL_HSTS_H */
diff --git a/lib/setopt.c b/lib/setopt.c
index b77e95b..f71a606 100644
--- a/lib/setopt.c
+++ b/lib/setopt.c
@@ -2283,9 +2283,14 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
         data->cookies = NULL;
 #endif
 
+#ifndef CURL_DISABLE_HSTS
+      if(data->share->hsts == data->hsts)
+        data->hsts = NULL;
+#endif
+#ifdef USE_SSL
       if(data->share->sslsession == data->state.session)
         data->state.session = NULL;
-
+#endif
 #ifdef USE_LIBPSL
       if(data->psl == &data->share->psl)
         data->psl = data->multi? &data->multi->psl: NULL;
@@ -2319,10 +2324,19 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
         data->cookies = data->share->cookies;
       }
 #endif   /* CURL_DISABLE_HTTP */
+#ifndef CURL_DISABLE_HSTS
+      if(data->share->hsts) {
+        /* first free the private one if any */
+        Curl_hsts_cleanup(&data->hsts);
+        data->hsts = data->share->hsts;
+      }
+#endif   /* CURL_DISABLE_HTTP */
+#ifdef USE_SSL
       if(data->share->sslsession) {
         data->set.general_ssl.max_ssl_sessions = data->share->max_ssl_sessions;
         data->state.session = data->share->sslsession;
       }
+#endif
 #ifdef USE_LIBPSL
       if(data->share->specifier & (1 << CURL_LOCK_DATA_PSL))
         data->psl = &data->share->psl;
@@ -3071,19 +3085,39 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
   case CURLOPT_HSTSWRITEDATA:
     data->set.hsts_write_userp = va_arg(param, void *);
     break;
-  case CURLOPT_HSTS:
+  case CURLOPT_HSTS: {
+    struct curl_slist *h;
     if(!data->hsts) {
       data->hsts = Curl_hsts_init();
       if(!data->hsts)
         return CURLE_OUT_OF_MEMORY;
     }
     argptr = va_arg(param, char *);
-    result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr);
-    if(result)
-      return result;
-    if(argptr)
-      (void)Curl_hsts_loadfile(data, data->hsts, argptr);
+    if(argptr) {
+      result = Curl_setstropt(&data->set.str[STRING_HSTS], argptr);
+      if(result)
+        return result;
+      /* this needs to build a list of file names to read from, so that it can
+         read them later, as we might get a shared HSTS handle to load them
+         into */
+      h = curl_slist_append(data->set.hstslist, argptr);
+      if(!h) {
+        curl_slist_free_all(data->set.hstslist);
+        data->set.hstslist = NULL;
+        return CURLE_OUT_OF_MEMORY;
+      }
+      data->set.hstslist = h; /* store the list for later use */
+    }
+    else {
+      /* clear the list of HSTS files */
+      curl_slist_free_all(data->set.hstslist);
+      data->set.hstslist = NULL;
+      if(!data->share || !data->share->hsts)
+        /* throw away the HSTS cache unless shared */
+        Curl_hsts_cleanup(&data->hsts);
+    }
     break;
+  }
   case CURLOPT_HSTS_CTRL:
     arg = va_arg(param, long);
     if(arg & CURLHSTS_ENABLE) {
diff --git a/lib/share.c b/lib/share.c
index 1a083e7..69ee00b 100644
--- a/lib/share.c
+++ b/lib/share.c
@@ -29,9 +29,11 @@
 #include "share.h"
 #include "psl.h"
 #include "vtls/vtls.h"
-#include "curl_memory.h"
+#include "hsts.h"
 
-/* The last #include file should be: */
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
 #include "memdebug.h"
 
 struct Curl_share *
@@ -89,6 +91,18 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...)
 #endif
       break;
 
+    case CURL_LOCK_DATA_HSTS:
+#ifndef CURL_DISABLE_HSTS
+      if(!share->hsts) {
+        share->hsts = Curl_hsts_init();
+        if(!share->hsts)
+          res = CURLSHE_NOMEM;
+      }
+#else   /* CURL_DISABLE_HSTS */
+      res = CURLSHE_NOT_BUILT_IN;
+#endif
+      break;
+
     case CURL_LOCK_DATA_SSL_SESSION:
 #ifdef USE_SSL
       if(!share->sslsession) {
@@ -141,6 +155,16 @@ curl_share_setopt(struct Curl_share *share, CURLSHoption option, ...)
 #endif
       break;
 
+    case CURL_LOCK_DATA_HSTS:
+#ifndef CURL_DISABLE_HSTS
+      if(share->hsts) {
+        Curl_hsts_cleanup(&share->hsts);
+      }
+#else   /* CURL_DISABLE_HSTS */
+      res = CURLSHE_NOT_BUILT_IN;
+#endif
+      break;
+
     case CURL_LOCK_DATA_SSL_SESSION:
 #ifdef USE_SSL
       Curl_safefree(share->sslsession);
@@ -207,6 +231,10 @@ curl_share_cleanup(struct Curl_share *share)
   Curl_cookie_cleanup(share->cookies);
 #endif
 
+#ifndef CURL_DISABLE_HSTS
+  Curl_hsts_cleanup(&share->hsts);
+#endif
+
 #ifdef USE_SSL
   if(share->sslsession) {
     size_t i;
diff --git a/lib/share.h b/lib/share.h
index 32be416..2449730 100644
--- a/lib/share.h
+++ b/lib/share.h
@@ -59,10 +59,14 @@ struct Curl_share {
 #ifdef USE_LIBPSL
   struct PslCache psl;
 #endif
-
+#ifndef CURL_DISABLE_HSTS
+  struct hsts *hsts;
+#endif
+#ifdef USE_SSL
   struct Curl_ssl_session *sslsession;
   size_t max_ssl_sessions;
   long sessionage;
+#endif
 };
 
 CURLSHcode Curl_share_lock(struct Curl_easy *, curl_lock_data,
diff --git a/lib/transfer.c b/lib/transfer.c
index ba0410f..d433117 100644
--- a/lib/transfer.c
+++ b/lib/transfer.c
@@ -1469,6 +1469,9 @@ CURLcode Curl_pretransfer(struct Curl_easy *data)
   if(data->state.resolve)
     result = Curl_loadhostpairs(data);
 
+  /* If there is a list of hsts files to read */
+  Curl_hsts_loadfiles(data);
+
   if(!result) {
     /* Allow data->set.use_port to set which port to use. This needs to be
      * disabled for example when we follow Location: headers to URLs using
diff --git a/lib/url.c b/lib/url.c
index 3ab63a0..831ae06 100644
--- a/lib/url.c
+++ b/lib/url.c
@@ -435,7 +435,11 @@ CURLcode Curl_close(struct Curl_easy **datap)
   Curl_altsvc_save(data, data->asi, data->set.str[STRING_ALTSVC]);
   Curl_altsvc_cleanup(&data->asi);
   Curl_hsts_save(data, data->hsts, data->set.str[STRING_HSTS]);
-  Curl_hsts_cleanup(&data->hsts);
+#ifndef CURL_DISABLE_HSTS
+  if(!data->share || !data->share->hsts)
+    Curl_hsts_cleanup(&data->hsts);
+  curl_slist_free_all(data->set.hstslist); /* clean up list */
+#endif
 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
   Curl_http_auth_cleanup_digest(data);
 #endif
diff --git a/lib/urldata.h b/lib/urldata.h
index 3d7545c..5b4b34f 100644
--- a/lib/urldata.h
+++ b/lib/urldata.h
@@ -1672,6 +1672,8 @@ struct UserDefined {
 
   void *seek_client;    /* pointer to pass to the seek callback */
 #ifndef CURL_DISABLE_HSTS
+  struct curl_slist *hstslist; /* list of HSTS files set by
+                                  curl_easy_setopt(HSTS) calls */
   curl_hstsread_callback hsts_read;
   void *hsts_read_userp;
   curl_hstswrite_callback hsts_write;
-- 
2.39.1


From 32066a5fa8f649da2aa7a4e4e86bc0b73d32212f Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 27 Dec 2022 11:50:23 +0100
Subject: [PATCH 3/6] tool_operate: share HSTS between handles

Upstream-commit: 0bf8b796a0ea98395b390c7807187982215f5c11
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 src/tool_operate.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/tool_operate.c b/src/tool_operate.c
index 79db063..a5b024e 100644
--- a/src/tool_operate.c
+++ b/src/tool_operate.c
@@ -2698,6 +2698,7 @@ CURLcode operate(struct GlobalConfig *global, int argc, argv_item_t argv[])
         curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_SSL_SESSION);
         curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_CONNECT);
         curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_PSL);
+        curl_share_setopt(share, CURLSHOPT_SHARE, CURL_LOCK_DATA_HSTS);
 
         /* Get the required arguments for each operation */
         do {
-- 
2.39.1


From fe6b64ac33a0994e5f50ef8b3d0916b3a248a7e8 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 27 Dec 2022 11:50:23 +0100
Subject: [PATCH 4/6] hsts: handle adding the same host name again

It will then use the largest expire time of the two entries.

Upstream-commit: ca02a77f05bd5cef20618c8f741aa48b7be0a648
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 lib/hsts.c | 13 +++++++++++--
 1 file changed, 11 insertions(+), 2 deletions(-)

diff --git a/lib/hsts.c b/lib/hsts.c
index 339237b..8d6723e 100644
--- a/lib/hsts.c
+++ b/lib/hsts.c
@@ -427,14 +427,23 @@ static CURLcode hsts_add(struct hsts *h, char *line)
   if(2 == rc) {
     time_t expires = strcmp(date, UNLIMITED) ? Curl_getdate_capped(date) :
       TIME_T_MAX;
-    CURLcode result;
+    CURLcode result = CURLE_OK;
     char *p = host;
     bool subdomain = FALSE;
+    struct stsentry *e;
     if(p[0] == '.') {
       p++;
       subdomain = TRUE;
     }
-    result = hsts_create(h, p, subdomain, expires);
+    /* only add it if not already present */
+    e = Curl_hsts(h, p, subdomain);
+    if(!e)
+      result = hsts_create(h, p, subdomain, expires);
+    else {
+      /* the same host name, use the largest expire time */
+      if(expires > e->expires)
+        e->expires = expires;
+    }
     if(result)
       return result;
   }
-- 
2.39.1


From c52b93434c65ec8a44193a6f2b833a1efec8f643 Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 27 Dec 2022 11:50:23 +0100
Subject: [PATCH 5/6] runtests: support crlf="yes" for verify/proxy

Upstream-commit: dc0725244a3163f1e2d5f51165db3a1a430f3ba0
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 tests/FILEFORMAT.md | 4 ++--
 tests/runtests.pl   | 5 +++++
 2 files changed, 7 insertions(+), 2 deletions(-)

diff --git a/tests/FILEFORMAT.md b/tests/FILEFORMAT.md
index 8143967..be11167 100644
--- a/tests/FILEFORMAT.md
+++ b/tests/FILEFORMAT.md
@@ -553,7 +553,7 @@ changing protocol data such as port numbers or user-agent strings.
 One perl op per line that operates on the protocol dump. This is pretty
 advanced. Example: `s/^EPRT .*/EPRT stripped/`.
 
-### `<protocol [nonewline="yes"] crlf="yes">`
+### `<protocol [nonewline="yes"][crlf="yes"]>`
 
 the protocol dump curl should transmit, if 'nonewline' is set, we will cut off
 the trailing newline of this given data before comparing with the one actually
@@ -563,7 +563,7 @@ comparisons are made.
 `crlf=yes` forces the newlines to become CRLF even if not written so in the
 test.
 
-### `<proxy [nonewline="yes"]>`
+### `<proxy [nonewline="yes"][crlf="yes"]>`
 
 The protocol dump curl should transmit to a HTTP proxy (when the http-proxy
 server is used), if 'nonewline' is set, we will cut off the trailing newline
diff --git a/tests/runtests.pl b/tests/runtests.pl
index c6a739e..f49e385 100755
--- a/tests/runtests.pl
+++ b/tests/runtests.pl
@@ -4634,6 +4634,11 @@ sub singletest {
             }
         }
 
+        if($hash{'crlf'} ||
+           ($has_hyper && ($keywords{"HTTP"} || $keywords{"HTTPS"}))) {
+            map subNewlines(0, \$_), @protstrip;
+        }
+
         $res = compare($testnum, $testname, "proxy", \@out, \@protstrip);
         if($res) {
             return $errorreturncode;
-- 
2.39.1


From e428f66157caedc1f58ff5206915842937b0950e Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Tue, 27 Dec 2022 11:50:23 +0100
Subject: [PATCH 6/6] test446: verify hsts with two URLs

Upstream-commit: ea5aaaa5ede53819f8bc7ae767fc2d13d3704d37
Signed-off-by: Kamil Dudka <kdudka@redhat.com>
---
 tests/data/Makefile.inc |  2 +-
 tests/data/test446      | 84 +++++++++++++++++++++++++++++++++++++++++
 2 files changed, 85 insertions(+), 1 deletion(-)
 create mode 100644 tests/data/test446

diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc
index 3e0221a..fb51cd6 100644
--- a/tests/data/Makefile.inc
+++ b/tests/data/Makefile.inc
@@ -71,7 +71,7 @@ test408 test409 test410 test411 test412 test413 test414 \
 \
 test430 test431 test432 test433 test434 test435 test436 \
 \
-test440 test441 test442 test443 test444 test445 \
+test440 test441 test442 test443 test444 test445 test446 \
 \
 test490 test491 test492 test493 test494 \
 \
diff --git a/tests/data/test446 b/tests/data/test446
new file mode 100644
index 0000000..0e2dfdc
--- /dev/null
+++ b/tests/data/test446
@@ -0,0 +1,84 @@
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP proxy
+HSTS
+trailing-dot
+</keywords>
+</info>
+
+<reply>
+
+# we use this as response to a CONNECT
+<connect nocheck="yes">
+HTTP/1.1 200 OK
+
+</connect>
+<data crlf="yes">
+HTTP/1.1 200 OK
+Content-Length: 6
+Strict-Transport-Security: max-age=604800
+
+-foo-
+</data>
+<data2 crlf="yes">
+HTTP/1.1 200 OK
+Content-Length: 6
+Strict-Transport-Security: max-age=6048000
+
+-baa-
+</data2>
+</reply>
+
+<client>
+<server>
+https
+http-proxy
+</server>
+<features>
+HSTS
+proxy
+https
+debug
+</features>
+<setenv>
+CURL_HSTS_HTTP=yes
+CURL_TIME=2000000000
+</setenv>
+
+<name>
+HSTS with two URLs
+</name>
+<command>
+-x http://%HOSTIP:%PROXYPORT --hsts log/hsts%TESTNUMBER http://this.hsts.example./%TESTNUMBER http://another.example.com/%TESTNUMBER0002
+</command>
+</client>
+
+<verify>
+# we let it CONNECT to the server to confirm HSTS but deny from there
+<proxy crlf="yes">
+GET http://this.hsts.example./%TESTNUMBER HTTP/1.1
+Host: this.hsts.example.
+User-Agent: curl/%VERSION
+Accept: */*
+Proxy-Connection: Keep-Alive
+
+GET http://another.example.com/%TESTNUMBER0002 HTTP/1.1
+Host: another.example.com
+User-Agent: curl/%VERSION
+Accept: */*
+Proxy-Connection: Keep-Alive
+
+</proxy>
+
+<file name="log/hsts%TESTNUMBER" mode="text">
+# Your HSTS cache. https://curl.se/docs/hsts.html
+# This file was generated by libcurl! Edit at your own risk.
+this.hsts.example "20330525 03:33:20"
+another.example.com "20330727 03:33:20"
+</file>
+
+</verify>
+</testcase>
-- 
2.39.1