write-out: make %header{} able to output *all* occurances of a header
No binary (build failed)
—
(log yok)
| @@ -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 | |
| @@ -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); | |
| @@ -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 \ | |
| @@ -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> |
| @@ -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> |