]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: unfold response headers earlier
authorDaniel Stenberg <daniel@haxx.se>
Fri, 12 Dec 2025 15:36:08 +0000 (16:36 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 13 Dec 2025 22:02:18 +0000 (23:02 +0100)
Make the low-level HTTP header "builder" unfold headers so that
everything else can keep pretending folding does not exist.

This code no longer tries to reduce repeated leading whitespace (in the
continued folded header) to a single one. To avoid having to have a
special state for that.

Adjusted two test cases accordingly

Closes #19949

lib/headers.c
lib/http.c
lib/urldata.h
tests/data/test1274
tests/data/test1940

index 46de5d107bc981636fe57ad9553c6d06c29b37aa..0cafcaa2708a94c257c1984c2adc977b785c527b 100644 (file)
@@ -215,55 +215,6 @@ static CURLcode namevalue(char *header, size_t hlen, unsigned int type,
   return CURLE_OK;
 }
 
-static CURLcode unfold_value(struct Curl_easy *data, const char *value,
-                             size_t vlen)  /* length of the incoming header */
-{
-  struct Curl_header_store *hs;
-  struct Curl_header_store *newhs;
-  size_t olen; /* length of the old value */
-  size_t oalloc; /* length of the old name + value + separator */
-  size_t offset;
-  DEBUGASSERT(data->state.prevhead);
-  hs = data->state.prevhead;
-  olen = strlen(hs->value);
-  offset = hs->value - hs->buffer;
-  oalloc = olen + offset + 1;
-
-  /* skip all trailing space letters */
-  while(vlen && ISBLANK(value[vlen - 1]))
-    vlen--;
-
-  /* save only one leading space */
-  while((vlen > 1) && ISBLANK(value[0]) && ISBLANK(value[1])) {
-    vlen--;
-    value++;
-  }
-
-  /* since this header block might move in the realloc below, it needs to
-     first be unlinked from the list and then re-added again after the
-     realloc */
-  Curl_node_remove(&hs->node);
-
-  /* new size = struct + new value length + old name+value length */
-  newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + oalloc + 1);
-  if(!newhs)
-    return CURLE_OUT_OF_MEMORY;
-  /* ->name and ->value point into ->buffer (to keep the header allocation
-     in a single memory block), which now potentially have moved. Adjust
-     them. */
-  newhs->name = newhs->buffer;
-  newhs->value = &newhs->buffer[offset];
-
-  /* put the data at the end of the previous data, not the newline */
-  memcpy(&newhs->value[olen], value, vlen);
-  newhs->value[olen + vlen] = 0; /* null-terminate at newline */
-
-  /* insert this node into the list of headers */
-  Curl_llist_append(&data->state.httphdrs, newhs, &newhs->node);
-  data->state.prevhead = newhs;
-  return CURLE_OK;
-}
-
 /*
  * Curl_headers_push() gets passed a full HTTP header to store. It gets called
  * immediately before the header callback. The header is CRLF, CR or LF
@@ -292,20 +243,14 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
     /* neither CR nor LF as terminator is not a valid header */
     return CURLE_WEIRD_SERVER_REPLY;
 
-  if((header[0] == ' ') || (header[0] == '\t')) {
-    if(data->state.prevhead)
-      /* line folding, append value to the previous header's value */
-      return unfold_value(data, header, hlen);
-    else {
-      /* cannot unfold without a previous header. Instead of erroring, just
-         pass the leading blanks. */
-      while(hlen && ISBLANK(*header)) {
-        header++;
-        hlen--;
-      }
-      if(!hlen)
-        return CURLE_WEIRD_SERVER_REPLY;
+  if(ISBLANK(header[0])) {
+    /* pass leading blanks */
+    while(hlen && ISBLANK(*header)) {
+      header++;
+      hlen--;
     }
+    if(!hlen)
+      return CURLE_WEIRD_SERVER_REPLY;
   }
   if(Curl_llist_count(&data->state.httphdrs) >= MAX_HTTP_RESP_HEADER_COUNT) {
     failf(data, "Too many response headers, %d is max",
index d5aca0e8005b6b9e40e68f8183309c8959cdbb67..c80deb319eab2301423442fb3bab985bfe8546d7 100644 (file)
@@ -111,6 +111,8 @@ static CURLcode http_statusline(struct Curl_easy *data,
                                 struct connectdata *conn);
 static CURLcode http_target(struct Curl_easy *data, struct dynbuf *req);
 static CURLcode http_useragent(struct Curl_easy *data);
+static CURLcode http_write_header(struct Curl_easy *data,
+                                  const char *hd, size_t hdlen);
 
 /*
  * HTTP handler interface.
@@ -1575,6 +1577,10 @@ CURLcode Curl_http_done(struct Curl_easy *data,
   data->state.authhost.multipass = FALSE;
   data->state.authproxy.multipass = FALSE;
 
+  if(curlx_dyn_len(&data->state.headerb)) {
+    (void)http_write_header(data, curlx_dyn_ptr(&data->state.headerb),
+                            curlx_dyn_len(&data->state.headerb));
+  }
   curlx_dyn_reset(&data->state.headerb);
 
   if(status)
@@ -2947,6 +2953,7 @@ CURLcode Curl_http(struct Curl_easy *data, bool *done)
   /* make sure the header buffer is reset - if there are leftovers from a
      previous transfer */
   curlx_dyn_reset(&data->state.headerb);
+  data->state.maybe_folded = FALSE;
 
   if(!data->conn->bits.reuse) {
     result = http_check_new_conn(data);
@@ -4283,6 +4290,18 @@ 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)
+{
+  size_t len = curlx_dyn_len(&data->state.headerb);
+  char *hd = curlx_dyn_ptr(&data->state.headerb);
+  if(len && (hd[len -1] == '\n'))
+    len--;
+  if(len && (hd[len -1] == '\r'))
+    len--;
+  curlx_dyn_setlen(&data->state.headerb, len);
+}
+
 /*
  * Read any HTTP header lines from the server and pass them to the client app.
  */
@@ -4296,10 +4315,32 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
   char *end_ptr;
   bool leftover_body = FALSE;
 
+  /* we have bytes for the next header, make sure it is not a folded header
+     before passing it on */
+  if(data->state.maybe_folded && blen) {
+    if(ISBLANK(buf[0])) {
+      /* folded, remove the trailing newlines and append the next header */
+      unfold_header(data);
+    }
+    else {
+      /* the header data we hold is a complete header, pass it on */
+      size_t ignore_this;
+      result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb),
+                          curlx_dyn_len(&data->state.headerb),
+                          NULL, 0, &ignore_this);
+      curlx_dyn_reset(&data->state.headerb);
+      if(result)
+        return result;
+    }
+    data->state.maybe_folded = FALSE;
+  }
+
   /* header line within buffer loop */
   *pconsumed = 0;
   while(blen && k->header) {
     size_t consumed;
+    size_t hlen;
+    char *hd;
 
     end_ptr = memchr(buf, '\n', blen);
     if(!end_ptr) {
@@ -4350,11 +4391,12 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
      * We now have a FULL header line in 'headerb'.
      *****/
 
+    hlen = curlx_dyn_len(&data->state.headerb);
+    hd = curlx_dyn_ptr(&data->state.headerb);
+
     if(!k->headerline) {
-      /* the first read header */
-      statusline st = checkprotoprefix(data, conn,
-                                       curlx_dyn_ptr(&data->state.headerb),
-                                       curlx_dyn_len(&data->state.headerb));
+      /* the first read "header", the status line */
+      statusline st = checkprotoprefix(data, conn, hd, hlen);
       if(st == STATUS_BAD) {
         streamclose(conn, "bad HTTP: No end-of-message indicator");
         /* this is not the beginning of a protocol first header line.
@@ -4372,10 +4414,25 @@ static CURLcode http_parse_headers(struct Curl_easy *data,
         goto out;
       }
     }
+    else {
+      if(hlen && !ISNEWLINE(hd[0])) {
+        /* this is NOT the header separator */
+
+        /* if we have bytes for the next header, check for folding */
+        if(blen && ISBLANK(buf[0])) {
+          /* remove the trailing CRLF and append the next header */
+          unfold_header(data);
+          continue;
+        }
+        else if(!blen) {
+          /* this might be a folded header so deal with it in next invoke */
+          data->state.maybe_folded = TRUE;
+          break;
+        }
+      }
+    }
 
-    result = http_rw_hd(data, curlx_dyn_ptr(&data->state.headerb),
-                        curlx_dyn_len(&data->state.headerb),
-                        buf, blen, &consumed);
+    result = http_rw_hd(data, hd, hlen, buf, blen, &consumed);
     /* We are done with this line. We reset because response
      * processing might switch to HTTP/2 and that might call us
      * directly again. */
index 7d5166e8e06fccb5af45c88c72f8abd582a615f2..20a0a1cd5d26561da330af53080e72b748a7dae5 100644 (file)
@@ -1133,6 +1133,7 @@ struct UrlState {
   BIT(http_hd_te); /* Added HTTP header TE: */
   BIT(http_hd_upgrade); /* Added HTTP header Upgrade: */
   BIT(http_hd_h2_settings); /* Added HTTP header H2Settings: */
+  BIT(maybe_folded);
 #endif
 };
 
index 0cb75e9b08a6163c943f615fc9d1e14b8ddb3f28..99d23ad8db213b1a67b2db897e50a0346935537c 100644 (file)
@@ -51,14 +51,11 @@ Accept: */*
 <file name="%LOGDIR/out%TESTNUMBER" crlf="yes">
 HTTP/1.1 200 OK
 Date: Tue, 09 Nov 2010 14:49:00 GMT
-Server: test-server/
- fake
- folded
+Server: test-server/ fake folded
 Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
 ETag: "21025-dc7-39462498"
 Content-Length: 6
-Connection:%repeat[46 x  ]%
-   close
+Connection:%repeat[46 x  ]%   close
 
 </file>
 </verify>
index 597adf851ba7510a8638a4139d3e05b4f1bb6bf8..8f2f73437de1f99783e106f663b7cfb2aafdb1f7 100644 (file)
@@ -59,7 +59,7 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER
 - Set-Cookie == onecookie=data; (0/3)
 - Set-Cookie == secondcookie=2data; (1/3)
 - Set-Cookie == cookie3=data3; (2/3)
- Fold == is folding a line
+ Fold == is folding a        line
  Blank ==%SP
  Blank2 ==%SP
 </stdout>