]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
write-out: make %header{} able to output *all* occurances of a header
authorDaniel Stenberg <daniel@haxx.se>
Sun, 7 Sep 2025 22:18:52 +0000 (00:18 +0200)
committerDaniel Stenberg <daniel@haxx.se>
Sat, 20 Sep 2025 22:00:29 +0000 (00:00 +0200)
By appending `:all:[separator]` to the header name. The `[separator]` string
is output between each header value if there are more than one to output.

Test 764 and 765 verify

Idea-by: kapsiR on github
Ref: #18449
Closes #18491

docs/cmdline-opts/write-out.md
src/tool_writeout.c
tests/data/Makefile.am
tests/data/test764 [new file with mode: 0644]
tests/data/test765 [new file with mode: 0644]

index b365eeb22de3bf9be3d84d6963ea4f98f696c1a0..9946349c1552f279a762489f1ac4c5c5184bbe73 100644 (file)
@@ -93,6 +93,14 @@ The value of header `name` from the transfer's most recent server response.
 Unlike other variables, the variable name `header` is not in braces. For
 example `%header{date}`. Refer to --write-out remarks. (Added in 7.84.0)
 
+Starting with 8.17.0, output the contents of *all* header fields using a
+specific name - even for a whole redirect "chain" by appending
+`:all:[separator]` to the header name. The `[separator]` string (if not blank)
+is output between the headers if there are more than one. When more than one
+header is shown, they are output in the chronological order of appearance over
+the wire. To include a close brace (`}`) in the separator, escape it with a
+backslash: `\}`.
+
 ## `header_json`
 A JSON object with all HTTP response headers from the recent transfer. Values
 are provided as arrays, since in the case of multiple headers there can be
index 225cf91fd4e6d961998b0752fb9a3f86566c58e4..cdde28c9095073708c59fa96443d2e3b8587e923 100644 (file)
@@ -615,6 +615,111 @@ static const char *outtime(const char *ptr, /* %time{ ... */
   return ptr;
 }
 
+static void separator(const char *sep, size_t seplen, FILE *stream)
+{
+  while(seplen) {
+    if(*sep == '\\') {
+      switch(sep[1]) {
+      case 'r':
+        fputc('\r', stream);
+        break;
+      case 'n':
+        fputc('\n', stream);
+        break;
+      case 't':
+        fputc('\t', stream);
+        break;
+      case '}':
+        fputc('}', stream);
+        break;
+      case '\0':
+        break;
+      default:
+        /* unknown, just output this */
+        fputc(sep[0], stream);
+        fputc(sep[1], stream);
+        break;
+      }
+      sep += 2;
+      seplen -= 2;
+    }
+    else {
+      fputc(*sep, stream);
+      sep++;
+      seplen--;
+    }
+  }
+}
+
+static void output_header(struct per_transfer *per,
+                          FILE *stream,
+                          const char **pptr)
+{
+  const char *ptr = *pptr;
+  const char *end;
+  end = strchr(ptr, '}');
+  do {
+    if(!end || (end[-1] != '\\'))
+      break;
+    end = strchr(&end[1], '}');
+  } while(end);
+  if(end) {
+    char hname[256]; /* holds the longest header field name */
+    struct curl_header *header;
+    const char *instr;
+    const char *sep = NULL;
+    size_t seplen = 0;
+    size_t vlen = end - ptr;
+    instr = memchr(ptr, ':', vlen);
+    if(instr) {
+      /* instructions follow */
+      if(!strncmp(&instr[1], "all:", 4)) {
+        sep = &instr[5];
+        seplen = end - sep;
+        vlen -= (seplen + 5);
+      }
+    }
+    if(vlen < sizeof(hname)) {
+      memcpy(hname, ptr, vlen);
+      hname[vlen] = 0;
+      if(sep) {
+        /* get headers from all requests */
+        int reqno = 0;
+        size_t indno = 0;
+        bool output = FALSE;
+        do {
+          if(CURLHE_OK == curl_easy_header(per->curl, hname, indno,
+                                           CURLH_HEADER, reqno,
+                                           &header)) {
+            if(output)
+              /* output separator */
+              separator(sep, seplen, stream);
+            fputs(header->value, stream);
+            output = TRUE;
+          }
+          else
+            break;
+          if((header->index + 1) < header->amount)
+            indno++;
+          else {
+            ++reqno;
+            indno = 0;
+          }
+        } while(1);
+      }
+      else {
+        if(CURLHE_OK == curl_easy_header(per->curl, hname, 0,
+                                         CURLH_HEADER, -1, &header))
+          fputs(header->value, stream);
+      }
+    }
+    ptr = end + 1;
+  }
+  else
+    fputs("%header{", stream);
+  *pptr = ptr;
+}
+
 void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
                  CURLcode per_result)
 {
@@ -699,22 +804,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
         }
         else if(!strncmp("header{", &ptr[1], 7)) {
           ptr += 8;
-          end = strchr(ptr, '}');
-          if(end) {
-            char hname[256]; /* holds the longest header field name */
-            struct curl_header *header;
-            vlen = end - ptr;
-            if(vlen < sizeof(hname)) {
-              memcpy(hname, ptr, vlen);
-              hname[vlen] = 0;
-              if(CURLHE_OK == curl_easy_header(per->curl, hname, 0,
-                                               CURLH_HEADER, -1, &header))
-                fputs(header->value, stream);
-            }
-            ptr = end + 1;
-          }
-          else
-            fputs("%header{", stream);
+          output_header(per, stream, &ptr);
         }
         else if(!strncmp("time{", &ptr[1], 5)) {
           ptr = outtime(ptr, stream);
index dfff0122575cf449197196c6454ed160685e2aa7..854791fba0e930d92c0099290c5c78ac17258f39 100644 (file)
@@ -109,7 +109,7 @@ test727 test728 test729 test730 test731 test732 test733 test734 test735 \
 test736 test737 test738 test739 test740 test741 test742 test743 test744 \
 test745 test746 test747 test748 test749 test750 test751 test752 test753 \
 test754 test755 test756 test757 test758 test759 test760 test761 test762 \
-test763 \
+test763 test764 test765 \
 \
 test780 test781 test782 test783 test784 test785 test786 test787 test788 \
 test789 test790 test791 test792 test793 test794         test796 test797 \
diff --git a/tests/data/test764 b/tests/data/test764
new file mode 100644 (file)
index 0000000..dd138e1
--- /dev/null
@@ -0,0 +1,67 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+-w
+%header
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 301 Redirect
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+This: one
+This: two
+Content-Length: 6
+Location: %TESTNUMBER0002
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+<data2 nocheck="yes">
+HTTP/1.1 200 Not a redirect
+Accept-Ranges: bytes
+This: three
+This: four
+Content-Length: 6
+Funny-head: yesyes
+
+-foo-
+</data2>
+
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+headers-api
+</features>
+<server>
+http
+</server>
+<name>
+-w with multiple header output
+</name>
+<command option="no-output">
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -L -w '%header{this:all:***}\n' -o %LOGDIR/%TESTNUMBER.out
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<stdout mode="text">
+one***two***three***four
+</stdout>
+</verify>
+</testcase>
diff --git a/tests/data/test765 b/tests/data/test765
new file mode 100644 (file)
index 0000000..2a86862
--- /dev/null
@@ -0,0 +1,67 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+HTTP GET
+-w
+%header
+</keywords>
+</info>
+
+#
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 301 Redirect
+Date: Tue, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT
+ETag: "21025-dc7-39462498"
+Accept-Ranges: bytes
+This: one
+This: two
+Content-Length: 6
+Location: %TESTNUMBER0002
+Content-Type: text/html
+Funny-head: yesyes
+
+-foo-
+</data>
+<data2 nocheck="yes">
+HTTP/1.1 200 Not a redirect
+Accept-Ranges: bytes
+This: three
+This: four
+Content-Length: 6
+Funny-head: yesyes
+
+-foo-
+</data2>
+
+</reply>
+
+#
+# Client-side
+<client>
+<features>
+headers-api
+</features>
+<server>
+http
+</server>
+<name>
+-w with multiple header output using } in separator
+</name>
+<command option="no-output">
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -L -w '%header{this:all:-{\}-}\n' -o %LOGDIR/%TESTNUMBER.out
+</command>
+</client>
+
+#
+# Verify data after the test has been "shot"
+<verify>
+<stdout mode="text">
+one-{}-two-{}-three-{}-four
+</stdout>
+</verify>
+</testcase>