From: Daniel Stenberg Date: Sun, 7 Sep 2025 22:18:52 +0000 (+0200) Subject: write-out: make %header{} able to output *all* occurances of a header X-Git-Tag: rc-8_17_0-1~306 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=3dad0cfd779413866a2a0a8353ee9a98212673f8;p=thirdparty%2Fcurl.git write-out: make %header{} able to output *all* occurances of a header 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 --- diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index b365eeb22d..9946349c15 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, 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..cdde28c909 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[-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 dfff012257..854791fba0 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 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 + + +