]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: handle user-defined connection headers
authorStefan Eissing <stefan@eissing.org>
Mon, 22 Sep 2025 13:48:07 +0000 (15:48 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 26 Sep 2025 07:27:50 +0000 (09:27 +0200)
When there is more than one user-supplied 'Connection: ' header, add
values that curl needs internally to the first one and emit all
subsequent ones thereafter.

Fixes #18662
Reported-by: Evgeny Grin (Karlson2k)
Closes #18686

docs/libcurl/opts/CURLOPT_HTTPHEADER.md
lib/http.c
tests/data/Makefile.am
tests/data/test1617 [new file with mode: 0644]

index 4440b4ec4b61492da5de45f7fe08ade8be635547..c7a705bedc82e0cd40c758973655b4836ecc204f 100644 (file)
@@ -55,6 +55,10 @@ without content (no data on the right side of the colon) as in `Accept:`, the
 internally used header is removed. To forcibly add a header without content
 (nothing after the colon), use the form `name;` (using a trailing semicolon).
 
+There are exceptions when suppressing headers. The `Connection:` header in
+HTTP/1.1 cannot be overridden. You can provide values for it, but should a
+request require specific ones, they are always added to your own.
+
 The headers included in the linked list **must not** be CRLF-terminated, since
 libcurl adds CRLF after each header item itself. Failure to comply with this
 might result in strange behavior. libcurl passes on the verbatim strings you
index ce31e6dff005335071fb7021e439c5331611a8e2..3479bb4ec976f9fa3203bb3da34dd7b314936397 100644 (file)
@@ -263,6 +263,19 @@ char *Curl_checkProxyheaders(struct Curl_easy *data,
 #define Curl_checkProxyheaders(x,y,z,a) NULL
 #endif
 
+static bool http_header_is_empty(const char *header)
+{
+  struct Curl_str out;
+
+  if(!curlx_str_cspn(&header, &out, ";:") &&
+     (!curlx_str_single(&header, ':') || !curlx_str_single(&header, ';'))) {
+    curlx_str_untilnl(&header, &out, MAX_HTTP_RESP_HEADER_SIZE);
+    curlx_str_trimblanks(&out);
+    return curlx_strlen(&out) == 0;
+  }
+  return TRUE; /* invalid head format, treat as empty */
+}
+
 /*
  * Strip off leading and trailing whitespace from the value in the given HTTP
  * header line and return a strdup()ed copy. Returns NULL in case of
@@ -1702,7 +1715,7 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data,
               curlx_str_casecompare(&name, "Content-Length"))
         ;
       else if(curlx_str_casecompare(&name, "Connection"))
-        /* Normal Connection: header generation takes care of this */
+        /* Connection headers are handled specially */
         ;
       else if((httpversion >= 20) &&
               curlx_str_casecompare(&name, "Transfer-Encoding"))
@@ -2636,17 +2649,29 @@ static CURLcode http_check_new_conn(struct Curl_easy *data)
 static CURLcode http_add_connection_hd(struct Curl_easy *data,
                                        struct dynbuf *req)
 {
-  char *custom = Curl_checkheaders(data, STRCONST("Connection"));
-  char *custom_val = custom ? Curl_copy_header_value(custom) : NULL;
-  const char *sep = (custom_val && *custom_val) ? ", " : "Connection: ";
+  struct curl_slist *head;
+  const char *sep = "Connection: ";
   CURLcode result = CURLE_OK;
   size_t rlen = curlx_dyn_len(req);
+  char *value;
+  bool skip;
+
+  /* Add the 1st custom "Connection: " header, if there is one */
+  for(head = data->set.headers; head; head = head->next) {
+    if(curl_strnequal(head->data, "Connection", 10) &&
+       Curl_headersep(head->data[10]) &&
+       !http_header_is_empty(head->data)) {
+      value = Curl_copy_header_value(head->data);
+      if(!value)
+        return CURLE_OUT_OF_MEMORY;
+      result = curlx_dyn_addf(req, "%s%s", sep, value);
+      sep = ", ";
+      free(value);
+      break; /* leave, having added 1st one */
+    }
+  }
 
-  if(custom && !custom_val)
-    return CURLE_OUT_OF_MEMORY;
-
-  if(custom_val && *custom_val)
-    result = curlx_dyn_addf(req, "Connection: %s", custom_val);
+  /* add our internal Connection: header values, if we have any */
   if(!result && data->state.http_hd_te) {
     result = curlx_dyn_addf(req, "%s%s", sep, "TE");
     sep = ", ";
@@ -2660,9 +2685,26 @@ static CURLcode http_add_connection_hd(struct Curl_easy *data,
   }
   if(!result && (rlen < curlx_dyn_len(req)))
     result = curlx_dyn_addn(req, STRCONST("\r\n"));
+  if(result)
+    return result;
 
-  free(custom_val);
-  return result;
+  /* Add all user-defined Connection: headers after the first */
+  skip = TRUE;
+  for(head = data->set.headers; head; head = head->next) {
+    if(curl_strnequal(head->data, "Connection", 10) &&
+       Curl_headersep(head->data[10]) &&
+       !http_header_is_empty(head->data)) {
+      if(skip) {
+        skip = FALSE;
+        continue;
+      }
+      result = curlx_dyn_addf(req, "%s\r\n", head->data);
+      if(result)
+        return result;
+    }
+  }
+
+  return CURLE_OK;
 }
 
 /* Header identifier in order we send them by default */
index 854791fba0e930d92c0099290c5c78ac17258f39..5063e0210f42604db970947afbab52ae2212ac19 100644 (file)
@@ -214,7 +214,7 @@ test1580 test1581 \
 test1590 test1591 test1592 test1593 test1594 test1595 test1596 test1597 \
 test1598 test1599 test1600 test1601 test1602 test1603 test1604 test1605 \
 test1606 test1607 test1608 test1609 test1610 test1611 test1612 test1613 \
-test1614 test1615 test1616 \
+test1614 test1615 test1616 test1617 \
 test1620 test1621 \
 \
 test1630 test1631 test1632 test1633 test1634 test1635 \
diff --git a/tests/data/test1617 b/tests/data/test1617
new file mode 100644 (file)
index 0000000..0fe967b
--- /dev/null
@@ -0,0 +1,75 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+compressed
+Transfer-Encoding
+</keywords>
+</info>
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK\r
+Date: Mon, 29 Nov 2004 21:56:53 GMT\r
+Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29\r
+Vary: Accept-Encoding\r
+Content-Type: text/html; charset=ISO-8859-1\r
+Transfer-Encoding: gzip, chunked\r
+\r
+2c\r
+%hex[%1f%8b%08%08%79%9e%ab%41%00%03%6c%61%6c%61%6c%61%00%cb%c9%cc%4b%55%30%e4%52%c8%01%d1%46%5c]hex%
+%hex[%10%86%31%17%00]hex%
+%hex[%02%71%60%18%00%00%00]hex%\r
+0\r
+\r
+</data>
+
+<datacheck>
+HTTP/1.1 200 OK\r
+Date: Mon, 29 Nov 2004 21:56:53 GMT\r
+Server: Apache/1.3.31 (Debian GNU/Linux) mod_gzip/1.3.26.1a PHP/4.3.9-1 mod_ssl/2.8.20 OpenSSL/0.9.7d mod_perl/1.29\r
+Vary: Accept-Encoding\r
+Content-Type: text/html; charset=ISO-8859-1\r
+Transfer-Encoding: gzip, chunked\r
+\r
+line 1
+ line 2
+  line 3
+</datacheck>
+
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+libz
+</features>
+<server>
+http
+</server>
+<name>
+HTTP GET transfer-encoding with two user Connection: headers
+</name>
+<command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER --tr-encoding -H "Connection: this" -H "Connection: that"
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<protocol>
+GET /%TESTNUMBER HTTP/1.1\r
+Host: %HOSTIP:%HTTPPORT\r
+User-Agent: curl/%VERSION\r
+Accept: */*\r
+TE: gzip\r
+Connection: this, TE\r
+Connection: that\r
+\r
+</protocol>
+</verify>
+</testcase>