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
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)
{
}
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);
--- /dev/null
+<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>
--- /dev/null
+<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>