]> git.feebdaed.xyz Git - 0xmirror/curl.git/commitdiff
cf-h1-proxy: support folded headers in CONNECT responses
authorDaniel Stenberg <daniel@haxx.se>
Tue, 23 Dec 2025 12:54:12 +0000 (13:54 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 23 Dec 2025 16:12:14 +0000 (17:12 +0100)
Update test 1941 to verify this

Remove unused code from dynhds for handling folded headers, and the
associated unit tests of those functions in test 2602 and 2603.

Closes #20080

lib/cf-h1-proxy.c
lib/dynhds.c
lib/http.c
lib/http.h
tests/data/test1941
tests/unit/unit2602.c
tests/unit/unit2603.c

index 83b8239d45b7d52f45c591b63e221ba7abbe2da9..5c764bb59f3004c05d79dfc4a461ccb05fbbc96b 100644 (file)
@@ -69,6 +69,8 @@ struct h1_tunnel_state {
   h1_tunnel_state tunnel_state;
   BIT(chunked_encoding);
   BIT(close_connection);
+  BIT(maybe_folded);
+  BIT(leading_unfold);
 };
 
 static bool tunnel_is_established(struct h1_tunnel_state *ts)
@@ -94,6 +96,8 @@ static CURLcode tunnel_reinit(struct Curl_cfilter *cf,
   ts->keepon = KEEPON_CONNECT;
   ts->cl = 0;
   ts->close_connection = FALSE;
+  ts->maybe_folded = FALSE;
+  ts->leading_unfold = FALSE;
   return CURLE_OK;
 }
 
@@ -345,16 +349,82 @@ static CURLcode on_resp_header(struct Curl_cfilter *cf,
   return result;
 }
 
+static CURLcode single_header(struct Curl_cfilter *cf,
+                              struct Curl_easy *data,
+                              struct h1_tunnel_state *ts)
+{
+  CURLcode result = CURLE_OK;
+  char *linep = curlx_dyn_ptr(&ts->rcvbuf);
+  size_t line_len = curlx_dyn_len(&ts->rcvbuf); /* bytes in this line */
+  struct SingleRequest *k = &data->req;
+  int writetype;
+  ts->headerlines++;
+
+  /* output debug if that is requested */
+  Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
+
+  /* send the header to the callback */
+  writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
+    (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
+  result = Curl_client_write(data, writetype, linep, line_len);
+  if(result)
+    return result;
+
+  result = Curl_bump_headersize(data, line_len, TRUE);
+  if(result)
+    return result;
+
+  /* Newlines are CRLF, so the CR is ignored as the line is not
+     really terminated until the LF comes. Treat a following CR
+     as end-of-headers as well.*/
+
+  if(ISNEWLINE(linep[0])) {
+    /* end of response-headers from the proxy */
+
+    if((407 == k->httpcode) && !data->state.authproblem) {
+      /* If we get a 407 response code with content length
+         when we have no auth problem, we must ignore the
+         whole response-body */
+      ts->keepon = KEEPON_IGNORE;
+
+      if(ts->cl) {
+        infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl);
+      }
+      else if(ts->chunked_encoding) {
+        infof(data, "Ignore chunked response-body");
+      }
+      else {
+        /* without content-length or chunked encoding, we
+           cannot keep the connection alive since the close is
+           the end signal so we bail out at once instead */
+        CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
+        ts->keepon = KEEPON_DONE;
+      }
+    }
+    else {
+      ts->keepon = KEEPON_DONE;
+    }
+
+    DEBUGASSERT(ts->keepon == KEEPON_IGNORE ||
+                ts->keepon == KEEPON_DONE);
+    return result;
+  }
+
+  result = on_resp_header(cf, data, ts, linep);
+  if(result)
+    return result;
+
+  curlx_dyn_reset(&ts->rcvbuf);
+  return result;
+}
+
 static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
                                   struct Curl_easy *data,
                                   struct h1_tunnel_state *ts,
                                   bool *done)
 {
   CURLcode result = CURLE_OK;
-  struct SingleRequest *k = &data->req;
-  char *linep;
-  size_t line_len;
-  int error, writetype;
+  int error;
 
 #define SELECT_OK      0
 #define SELECT_ERROR   1
@@ -428,6 +498,31 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
       continue;
     }
 
+    if(ts->maybe_folded) {
+      if(ISBLANK(byte)) {
+        Curl_http_to_fold(&ts->rcvbuf);
+        ts->leading_unfold = TRUE;
+      }
+      else {
+        result = single_header(cf, data, ts);
+        if(result)
+          return result;
+        /* now handle the new byte */
+      }
+      ts->maybe_folded = FALSE;
+    }
+
+    if(ts->leading_unfold) {
+      if(ISBLANK(byte))
+        /* skip a bit brother */
+        continue;
+      /* non-blank, insert a space then continue the unfolding */
+      if(curlx_dyn_addn(&ts->rcvbuf, " ", 1)) {
+        failf(data, "CONNECT response too large");
+        return CURLE_RECV_ERROR;
+      }
+      ts->leading_unfold = FALSE;
+    }
     if(curlx_dyn_addn(&ts->rcvbuf, &byte, 1)) {
       failf(data, "CONNECT response too large");
       return CURLE_RECV_ERROR;
@@ -436,67 +531,19 @@ static CURLcode recv_CONNECT_resp(struct Curl_cfilter *cf,
     /* if this is not the end of a header line then continue */
     if(byte != 0x0a)
       continue;
-
-    ts->headerlines++;
-    linep = curlx_dyn_ptr(&ts->rcvbuf);
-    line_len = curlx_dyn_len(&ts->rcvbuf); /* amount of bytes in this line */
-
-    /* output debug if that is requested */
-    Curl_debug(data, CURLINFO_HEADER_IN, linep, line_len);
-
-    /* send the header to the callback */
-    writetype = CLIENTWRITE_HEADER | CLIENTWRITE_CONNECT |
-      (ts->headerlines == 1 ? CLIENTWRITE_STATUS : 0);
-    result = Curl_client_write(data, writetype, linep, line_len);
-    if(result)
-      return result;
-
-    result = Curl_bump_headersize(data, line_len, TRUE);
-    if(result)
-      return result;
-
-    /* Newlines are CRLF, so the CR is ignored as the line is not
-       really terminated until the LF comes. Treat a following CR
-       as end-of-headers as well.*/
-
-    if(('\r' == linep[0]) ||
-       ('\n' == linep[0])) {
-      /* end of response-headers from the proxy */
-
-      if((407 == k->httpcode) && !data->state.authproblem) {
-        /* If we get a 407 response code with content length
-           when we have no auth problem, we must ignore the
-           whole response-body */
-        ts->keepon = KEEPON_IGNORE;
-
-        if(ts->cl) {
-          infof(data, "Ignore %" FMT_OFF_T " bytes of response-body", ts->cl);
-        }
-        else if(ts->chunked_encoding) {
-          infof(data, "Ignore chunked response-body");
-        }
-        else {
-          /* without content-length or chunked encoding, we
-             cannot keep the connection alive since the close is
-             the end signal so we bail out at once instead */
-          CURL_TRC_CF(data, cf, "CONNECT: no content-length or chunked");
-          ts->keepon = KEEPON_DONE;
-        }
-      }
-      else {
-        ts->keepon = KEEPON_DONE;
+    else {
+      char *linep = curlx_dyn_ptr(&ts->rcvbuf);
+      size_t hlen = curlx_dyn_len(&ts->rcvbuf);
+      if(hlen && ISNEWLINE(linep[0])) {
+        /* end of headers */
+        result = single_header(cf, data, ts);
+        if(result)
+          return result;
       }
-
-      DEBUGASSERT(ts->keepon == KEEPON_IGNORE ||
-                  ts->keepon == KEEPON_DONE);
-      continue;
+      else
+        ts->maybe_folded = TRUE;
     }
 
-    result = on_resp_header(cf, data, ts, linep);
-    if(result)
-      return result;
-
-    curlx_dyn_reset(&ts->rcvbuf);
   } /* while there is buffer left and loop is requested */
 
   if(error)
index d0a3eb6a74c46ddbedee6bd38c8b87837711fadf..3a4c80de69b3767534610b90f24fa44d5310b494 100644 (file)
@@ -56,29 +56,6 @@ entry_new(const char *name, size_t namelen,
   return e;
 }
 
-static struct dynhds_entry *entry_append(struct dynhds_entry *e,
-                                         const char *value, size_t valuelen)
-{
-  struct dynhds_entry *e2;
-  size_t valuelen2 = e->valuelen + 1 + valuelen;
-  char *p;
-
-  DEBUGASSERT(value);
-  e2 = curlx_calloc(1, sizeof(*e) + e->namelen + valuelen2 + 2);
-  if(!e2)
-    return NULL;
-  e2->name = p = ((char *)e2) + sizeof(*e2);
-  memcpy(p, e->name, e->namelen);
-  e2->namelen = e->namelen;
-  e2->value = p += e->namelen + 1; /* leave a \0 at the end of name */
-  memcpy(p, e->value, e->valuelen);
-  p += e->valuelen;
-  p[0] = ' ';
-  memcpy(p + 1, value, valuelen);
-  e2->valuelen = valuelen2;
-  return e2;
-}
-
 static void entry_free(struct dynhds_entry *e)
 {
   curlx_free(e);
@@ -222,48 +199,26 @@ CURLcode Curl_dynhds_h1_add_line(struct dynhds *dynhds,
   if(!line || !line_len)
     return CURLE_OK;
 
-  if((line[0] == ' ') || (line[0] == '\t')) {
-    struct dynhds_entry *e, *e2;
-    /* header continuation, yikes! */
-    if(!dynhds->hds_len)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
-
-    while(line_len && ISBLANK(line[0])) {
-      ++line;
-      --line_len;
-    }
-    if(!line_len)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
-    e = dynhds->hds[dynhds->hds_len - 1];
-    e2 = entry_append(e, line, line_len);
-    if(!e2)
-      return CURLE_OUT_OF_MEMORY;
-    dynhds->hds[dynhds->hds_len - 1] = e2;
-    entry_free(e);
-    return CURLE_OK;
+  p = memchr(line, ':', line_len);
+  if(!p)
+    return CURLE_BAD_FUNCTION_ARGUMENT;
+  name = line;
+  namelen = p - line;
+  p++; /* move past the colon */
+  for(i = namelen + 1; i < line_len; ++i, ++p) {
+    if(!ISBLANK(*p))
+      break;
   }
-  else {
-    p = memchr(line, ':', line_len);
-    if(!p)
-      return CURLE_BAD_FUNCTION_ARGUMENT;
-    name = line;
-    namelen = p - line;
-    p++; /* move past the colon */
-    for(i = namelen + 1; i < line_len; ++i, ++p) {
-      if(!ISBLANK(*p))
-        break;
-    }
-    value = p;
-    valuelen = line_len - i;
+  value = p;
+  valuelen = line_len - i;
 
-    p = memchr(value, '\r', valuelen);
-    if(!p)
-      p = memchr(value, '\n', valuelen);
-    if(p)
-      valuelen = (size_t)(p - value);
+  p = memchr(value, '\r', valuelen);
+  if(!p)
+    p = memchr(value, '\n', valuelen);
+  if(p)
+    valuelen = (size_t)(p - value);
 
-    return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
-  }
+  return Curl_dynhds_add(dynhds, name, namelen, value, valuelen);
 }
 
 CURLcode Curl_dynhds_h1_cadd_line(struct dynhds *dynhds, const char *line)
index bf38d35dde2bed931ae8c3b40fbde569fbe0dad9..3aabd0f7be3675910abd74a81ae35295ecf7e4d5 100644 (file)
@@ -4288,18 +4288,23 @@ static CURLcode http_rw_hd(struct Curl_easy *data,
   return CURLE_OK;
 }
 
-/* cut off the newline characters */
-static void unfold_header(struct Curl_easy *data)
+/* remove trailing CRLF then all trailing whitespace */
+void Curl_http_to_fold(struct dynbuf *bf)
 {
-  size_t len = curlx_dyn_len(&data->state.headerb);
-  char *hd = curlx_dyn_ptr(&data->state.headerb);
+  size_t len = curlx_dyn_len(bf);
+  char *hd = curlx_dyn_ptr(bf);
   if(len && (hd[len - 1] == '\n'))
     len--;
   if(len && (hd[len - 1] == '\r'))
     len--;
   while(len && (ISBLANK(hd[len - 1]))) /* strip off trailing whitespace */
     len--;
-  curlx_dyn_setlen(&data->state.headerb, len);
+  curlx_dyn_setlen(bf, len);
+}
+
+static void unfold_header(struct Curl_easy *data)
+{
+  Curl_http_to_fold(&data->state.headerb);
   data->state.leading_unfold = TRUE;
 }
 
index 7f944ea33b6ab5575d5635084b2814bfcb6cd6d2..89810b1757e197bb1369694cc02a81b351bc906a 100644 (file)
@@ -104,6 +104,8 @@ CURLcode Curl_add_custom_headers(struct Curl_easy *data, bool is_connect,
 CURLcode Curl_dynhds_add_custom(struct Curl_easy *data, bool is_connect,
                                 struct dynhds *hds);
 
+void Curl_http_to_fold(struct dynbuf *bf);
+
 void Curl_http_method(struct Curl_easy *data,
                       const char **method, Curl_HttpReq *);
 
index 0542b7b9433b1e536de0f704a30af611bfed83cf..4532403bc9fd1f564392f485f3ce225c00c6a140 100644 (file)
@@ -13,7 +13,8 @@ CONNECT
 HTTP/1.1 200 OK
 Date: Thu, 09 Nov 2010 14:49:00 GMT
 Server:       test with trailing space%repeat[5 x  ]%
-Content-Type: text/html
+Content-Type:
+%SPtext/html
 Content-Length: 0
 Set-Cookie: onecookie=data;
 Set-Cookie: secondcookie=2data;
@@ -23,7 +24,9 @@ Location: /%TESTNUMBER0002
 </data>
 <connect crlf="headers">
 HTTP/1.1 200 Sure go ahead
-Server: from the connect
+Server: from%SP
+%SPthe%TAB
+%TAB%TABconnect
 Silly-thing: yes yes
 
 </connect>
index 684fa3db8332ad9bf4778b19c166887701888249..c33e8d63d1ae507ca9d68cd2d5c8d461f0ef65f9 100644 (file)
@@ -111,18 +111,6 @@ static CURLcode test_unit2602(const char *arg)
   }
 
   Curl_dynhds_free(&hds);
-  Curl_dynhds_init(&hds, 128, 4 * 1024);
-  /* continuation without previous header fails */
-  res = Curl_dynhds_h1_cadd_line(&hds, " indented value");
-  fail_unless(res, "add should have failed");
-
-  /* continuation with previous header must succeed */
-  fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti1: val1"), "add");
-  fail_if(Curl_dynhds_h1_cadd_line(&hds, " val2"), "add indent");
-  fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti2: val1"), "add");
-  fail_if(Curl_dynhds_h1_cadd_line(&hds, "\tval2"), "add indent");
-  fail_if(Curl_dynhds_h1_cadd_line(&hds, "ti3: val1"), "add");
-  fail_if(Curl_dynhds_h1_cadd_line(&hds, "     val2"), "add indent");
 
   curlx_dyn_init(&dbuf, 32 * 1024);
   fail_if(Curl_dynhds_h1_dprint(&hds, &dbuf), "h1 print failed");
index 1de774d26f8b413855e81019b189ba62df0ed706..b3ade82a620f2c0ef157e442c87bf1517a6fc5ab 100644 (file)
@@ -159,16 +159,6 @@ static CURLcode test_unit2603(const char *arg)
     T4_INPUT, NULL, NULL, "CONNECT", NULL, "ftp.curl.se:123", NULL, 3, 2
   };
 
-  static const char *T5_INPUT[] = {
-    "OPTIONS * HTTP/1.1\r\nContent-Length: 0\r\nBlabla: xxx.yyy\r",
-    "\n\tzzzzzz\r\n\r\n",
-    "123",
-    NULL,
-  };
-  static const struct tcase TEST5a = {
-    T5_INPUT, NULL, NULL, "OPTIONS", NULL, NULL, "*", 2, 3
-  };
-
   static const char *T6_INPUT[] = {
     "PUT /path HTTP/1.1\nHost: test.curl.se\n\n123",
     NULL,
@@ -194,7 +184,6 @@ static CURLcode test_unit2603(const char *arg)
   parse_success(&TEST2);
   parse_success(&TEST3a);
   parse_success(&TEST4a);
-  parse_success(&TEST5a);
   parse_success(&TEST6a);
   parse_success(&TEST7a);
   parse_success(&TEST7b);