]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
http: restore header folding behavior
authorDaniel Stenberg <daniel@haxx.se>
Tue, 24 May 2022 21:33:35 +0000 (23:33 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 24 May 2022 21:33:47 +0000 (23:33 +0200)
Folded header lines will now get passed through like before. The headers
API is adapted and will provide the content unfolded.

Added test 1274 and extended test 1940 to verify.

Reported-by: Petr Pisar
Fixes #8844
Closes #8899

docs/libcurl/curl_easy_header.3
lib/headers.c
lib/http.c
lib/urldata.h
tests/data/Makefile.inc
tests/data/test1274 [new file with mode: 0644]
tests/data/test1940
tests/libtest/lib1940.c

index e4ce0dd31f9c90c1bd1e4b24c7bae8f00ea6ac92..9aaa9d767cbd6970b1f5adb274560ac9f603bb31 100644 (file)
@@ -94,7 +94,9 @@ but it might have a different case.
 
 The data \fBvalue\fP field points to, comes exactly as delivered over the
 network but with leading and trailing whitespace and newlines stripped
-off. The `value` data is nul-terminated.
+off. The `value` data is nul-terminated. For legacy HTTP/1 "folded headers",
+this API provides the full single value in an unfolded manner with a single
+whitespace between the lines.
 
 \fBamount\fP is how many headers using this name that exist, within the origin
 and request scope asked for.
index 226c696be6b7186e538ee8bbae5ccd63dbf24f2b..b83557d77cc1a54cab33e21d762c21494baef091 100644 (file)
@@ -216,6 +216,54 @@ static CURLcode namevalue(char *header, size_t hlen, unsigned int type,
   return CURLE_OK;
 }
 
+static CURLcode append_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 offset;
+  DEBUGASSERT(data->state.prevhead);
+  hs = data->state.prevhead;
+  olen = strlen(hs->value);
+  offset = hs->value - hs->buffer;
+
+  /* skip all trailing space letters */
+  while(vlen && ISSPACE(value[vlen - 1]))
+    vlen--;
+
+  /* save only one leading space */
+  while((vlen > 1) && ISSPACE(value[0]) && ISSPACE(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_llist_remove(&data->state.httphdrs, &hs->node, NULL);
+
+  newhs = Curl_saferealloc(hs, sizeof(*hs) + vlen + olen + 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; /* zero terminate at newline */
+
+  /* insert this node into the list of headers */
+  Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail,
+                         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 terminated.
@@ -242,6 +290,10 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
   }
   hlen = end - header + 1;
 
+  if((header[0] == ' ') || (header[0] == '\t'))
+    /* line folding, append value to the previous header's value */
+    return append_value(data, header, hlen);
+
   hs = calloc(1, sizeof(*hs) + hlen);
   if(!hs)
     return CURLE_OUT_OF_MEMORY;
@@ -260,7 +312,7 @@ CURLcode Curl_headers_push(struct Curl_easy *data, const char *header,
   /* insert this node into the list of headers */
   Curl_llist_insert_next(&data->state.httphdrs, data->state.httphdrs.tail,
                          hs, &hs->node);
-
+  data->state.prevhead = hs;
   return CURLE_OK;
   fail:
   free(hs);
index b215307dcaaa0dcd5213341f09024169d096523b..765fe0bd313fe4f07558b6cbdec7cc32a0485c1c 100644 (file)
@@ -3799,11 +3799,16 @@ static CURLcode verify_header(struct Curl_easy *data)
   if(k->headerline < 2)
     /* the first "header" is the status-line and it has no colon */
     return CURLE_OK;
-  ptr = memchr(header, ':', hlen);
-  if(!ptr) {
-    /* this is bad, bail out */
-    failf(data, "Header without colon");
-    return CURLE_WEIRD_SERVER_REPLY;
+  if(((header[0] == ' ') || (header[0] == '\t')) && k->headerline > 2)
+    /* line folding, can't happen on line 2 */
+    ;
+  else {
+    ptr = memchr(header, ':', hlen);
+    if(!ptr) {
+      /* this is bad, bail out */
+      failf(data, "Header without colon");
+      return CURLE_WEIRD_SERVER_REPLY;
+    }
   }
   return CURLE_OK;
 }
index 14770574d83f95a5364e1689cbd8aed952244a89..145836f57c4d83aae3f7be7317afd3f3f9caac65 100644 (file)
@@ -1423,6 +1423,7 @@ struct UrlState {
                                  headers */
   struct Curl_llist httphdrs; /* received headers */
   struct curl_header headerout; /* for external purposes */
+  struct Curl_header_store *prevhead; /* the latest added header */
 #endif
   trailers_state trailers_state; /* whether we are sending trailers
                                        and what stage are we at */
index cc486a5c9e2e0f4885402f0a7c7b19c599356eee..9718bb978e63ea2bb45c874000379f91cc27132a 100644 (file)
@@ -160,7 +160,7 @@ test1240 test1241 test1242 test1243 test1244 test1245 test1246 test1247 \
 test1248 test1249 test1250 test1251 test1252 test1253 test1254 test1255 \
 test1256 test1257 test1258 test1259 test1260 test1261 test1262 test1263 \
 test1264 test1265 test1266 test1267 test1268 test1269 test1270 test1271 \
-test1272 test1273 \
+test1272 test1273 test1274 \
 \
 test1280 test1281 test1282 test1283 test1284 test1285 test1286 test1287 \
 test1288 test1289 test1290 test1291 test1292 test1293 test1294 test1295 \
diff --git a/tests/data/test1274 b/tests/data/test1274
new file mode 100644 (file)
index 0000000..ae29f48
--- /dev/null
@@ -0,0 +1,65 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+header line folding
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK\r
+Date: Tue, 09 Nov 2010 14:49:00 GMT\r
+Server: test-server/\r
+ fake\r
+ folded\r
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT\r
+ETag: "21025-dc7-39462498"\r
+Content-Length: 6\r
+Connection: close\r
+\r
+-foo-
+</data>
+</reply>
+
+#
+# Client-side
+<client>
+<server>
+http
+</server>
+ <name>
+HTTP header line folding
+ </name>
+ <command>
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -D log/out%TESTNUMBER
+</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
+\r
+</protocol>
+<file name="log/out%TESTNUMBER">
+HTTP/1.1 200 OK\r
+Date: Tue, 09 Nov 2010 14:49:00 GMT\r
+Server: test-server/\r
+ fake\r
+ folded\r
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT\r
+ETag: "21025-dc7-39462498"\r
+Content-Length: 6\r
+Connection: close\r
+\r
+</file>
+</verify>
+</testcase>
index 682916b709d3bf478cfa6c4c9c2da6a6e6795842..8e05538b5c6b6fbd4bc3b0d450c17a48b0a52e2d 100644 (file)
@@ -12,6 +12,9 @@ HTTP/1.1 200 OK
 Date: Thu, 09 Nov 2010 14:49:00 GMT
 Server:       test with trailing space     
 Content-Type: text/html
+Fold: is
+ folding a     
+   line
 Content-Length: 0
 Set-Cookie: onecookie=data;
 Set-Cookie: secondcookie=2data;
@@ -44,7 +47,7 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER
 
 # Verify data after the test has been "shot"
 <verify>
-<stdout>
+<stdout mode="text">
  Date == Thu, 09 Nov 2010 14:49:00 GMT
  Server == test with trailing space
  Content-Type == text/html
@@ -53,6 +56,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
 </stdout>
 </verify>
 </testcase>
index 922cd425a0e4a32f6a31d945a6d4875862b13b12..93bf0a2da7e753d8bf96d5184450edc5e7330112 100644 (file)
@@ -32,6 +32,7 @@ static const char *show[]={
   "location",
   "set-cookie",
   "silly-thing",
+  "fold",
   NULL
 };