diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md
index b365eeb22d..8676091f36 100644
--- a/docs/cmdline-opts/write-out.md
+++ b/docs/cmdline-opts/write-out.md
@@ -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, this 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
diff --git a/src/tool_writeout.c b/src/tool_writeout.c
index 225cf91fd4..06c8a42ffd 100644
--- a/src/tool_writeout.c
+++ b/src/tool_writeout.c
@@ -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 && (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);
diff --git a/tests/data/Makefile.am b/tests/data/Makefile.am
index daed381e47..d2c9e0a709 100644
--- a/tests/data/Makefile.am
+++ b/tests/data/Makefile.am
@@ -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 test795 test796 test797 \
diff --git a/tests/data/test764 b/tests/data/test764
new file mode 100644
index 0000000000..dd138e1084
--- /dev/null
+++ b/tests/data/test764
@@ -0,0 +1,67 @@
+
+
+
+HTTP
+HTTP GET
+-w
+%header
+
+
+
+#
+# Server-side
+
+
+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-
+
+
+HTTP/1.1 200 Not a redirect
+Accept-Ranges: bytes
+This: three
+This: four
+Content-Length: 6
+Funny-head: yesyes
+
+-foo-
+
+
+
+
+#
+# Client-side
+
+
+headers-api
+
+
+http
+
+
+-w with multiple header output
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -L -w '%header{this:all:***}\n' -o %LOGDIR/%TESTNUMBER.out
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+one***two***three***four
+
+
+
diff --git a/tests/data/test765 b/tests/data/test765
new file mode 100644
index 0000000000..2a868620c8
--- /dev/null
+++ b/tests/data/test765
@@ -0,0 +1,67 @@
+
+
+
+HTTP
+HTTP GET
+-w
+%header
+
+
+
+#
+# Server-side
+
+
+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-
+
+
+HTTP/1.1 200 Not a redirect
+Accept-Ranges: bytes
+This: three
+This: four
+Content-Length: 6
+Funny-head: yesyes
+
+-foo-
+
+
+
+
+#
+# Client-side
+
+
+headers-api
+
+
+http
+
+
+-w with multiple header output using } in separator
+
+
+http://%HOSTIP:%HTTPPORT/%TESTNUMBER -L -w '%header{this:all:-{\}-}\n' -o %LOGDIR/%TESTNUMBER.out
+
+
+
+#
+# Verify data after the test has been "shot"
+
+
+one-{}-two-{}-three-{}-four
+
+
+