diff --git a/docs/INTERNALS.md b/docs/INTERNALS.md index a15703463..863da70af 100644 --- a/docs/INTERNALS.md +++ b/docs/INTERNALS.md @@ -503,7 +503,7 @@ Client status and exits. When the operation is done, the `ourWriteOut()` function in `src/writeout.c` - may be called to report about the operation. That function is using the + may be called to report about the operation. That function is mostly using the `curl_easy_getinfo()` function to extract useful information from the curl session. diff --git a/docs/cmdline-opts/write-out.d b/docs/cmdline-opts/write-out.d index 013319cc2..28b9a13a9 100644 --- a/docs/cmdline-opts/write-out.d +++ b/docs/cmdline-opts/write-out.d @@ -67,6 +67,10 @@ The http method used in the most recent HTTP request (Added in 7.72.0) .B num_connects Number of new connects made in the recent transfer. (Added in 7.12.3) .TP +.B num_headers +The number of response headers in the most recent request (restarted at each + redirect). Note that the status line IS NOT a header. (Added in 7.73.0) +.TP .B num_redirects Number of redirects that were followed in the request. (Added in 7.12.3) .TP diff --git a/src/tool_cb_hdr.c b/src/tool_cb_hdr.c index ee3ef7390..6c6c095c7 100644 --- a/src/tool_cb_hdr.c +++ b/src/tool_cb_hdr.c @@ -179,7 +179,17 @@ size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) if(!outs->stream && !tool_create_output_file(outs, per->config)) return failure; } - + if(hdrcbdata->config->writeout) { + char *value = memchr(ptr, ':', cb); + if(value) { + if(per->was_last_header_empty) + per->num_headers = 0; + per->was_last_header_empty = FALSE; + per->num_headers++; + } + else if(ptr[0] == '\r' || ptr[0] == '\n') + per->was_last_header_empty = TRUE; + } if(hdrcbdata->config->show_headers && (protocol & (CURLPROTO_HTTP|CURLPROTO_HTTPS|CURLPROTO_RTSP|CURLPROTO_FILE))) { diff --git a/src/tool_operate.c b/src/tool_operate.c index 991adfa2b..e03bbedb8 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -622,7 +622,7 @@ static CURLcode post_per_transfer(struct GlobalConfig *global, fputs("\n", per->progressbar.out); if(config->writeout) - ourWriteOut(per->curl, &per->outs, config->writeout); + ourWriteOut(per->curl, per, config->writeout); /* Close the outs file */ if(outs->fopened && outs->stream) { diff --git a/src/tool_operate.h b/src/tool_operate.h index 42b1861d0..6cb3bd036 100644 --- a/src/tool_operate.h +++ b/src/tool_operate.h @@ -51,6 +51,8 @@ struct per_transfer { struct OutStruct etag_save; struct InStruct input; struct HdrCbData hdrcbdata; + long num_headers; + bool was_last_header_empty; char errorbuffer[CURL_ERROR_SIZE]; bool added; /* set TRUE when added to the multi handle */ diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 41441ff30..63d2cd003 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -38,6 +38,8 @@ static const struct writeoutvar variables[] = { CURLINFO_RESPONSE_CODE, JSON_LONG}, {"response_code", VAR_HTTP_CODE, 0, CURLINFO_RESPONSE_CODE, JSON_LONG}, + {"num_headers", VAR_NUM_HEADERS, 0, + 0, JSON_LONG}, {"http_connect", VAR_HTTP_CODE_PROXY, 0, CURLINFO_HTTP_CONNECTCODE, JSON_LONG}, {"time_total", VAR_TOTAL_TIME, 0, @@ -104,7 +106,7 @@ static const struct writeoutvar variables[] = { 0, JSON_NONE} }; -void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) +void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo) { FILE *stream = stdout; const char *ptr = writeinfo; @@ -156,6 +158,9 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &longinfo)) fprintf(stream, "%03ld", longinfo); break; + case VAR_NUM_HEADERS: + fprintf(stream, "%ld", per->num_headers); + break; case VAR_HTTP_CODE_PROXY: if(CURLE_OK == curl_easy_getinfo(curl, CURLINFO_HTTP_CONNECTCODE, @@ -275,14 +280,14 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) fprintf(stream, "%ld", longinfo); break; case VAR_EFFECTIVE_FILENAME: - if(outs->filename) - fprintf(stream, "%s", outs->filename); + if(per->outs.filename) + fputs(per->outs.filename, stream); break; case VAR_PRIMARY_IP: if(CURLE_OK == curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &stringp)) - fprintf(stream, "%s", stringp); + fputs(stringp, stream); break; case VAR_PRIMARY_PORT: if(CURLE_OK == @@ -294,7 +299,7 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) if(CURLE_OK == curl_easy_getinfo(curl, CURLINFO_LOCAL_IP, &stringp)) - fprintf(stream, "%s", stringp); + fputs(stringp, stream); break; case VAR_LOCAL_PORT: if(CURLE_OK == @@ -329,7 +334,7 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) if(CURLE_OK == curl_easy_getinfo(curl, CURLINFO_SCHEME, &stringp)) - fprintf(stream, "%s", stringp); + fputs(stringp, stream); break; case VAR_STDOUT: stream = stdout; @@ -338,7 +343,7 @@ void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo) stream = stderr; break; case VAR_JSON: - ourWriteOutJSON(variables, curl, outs, stream); + ourWriteOutJSON(variables, curl, per, stream); default: break; } diff --git a/src/tool_writeout.h b/src/tool_writeout.h index 68bacb9d4..377f0b2b3 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -22,6 +22,7 @@ * ***************************************************************************/ #include "tool_setup.h" +#include "tool_operate.h" typedef enum { VAR_NONE, /* must be the first */ @@ -38,6 +39,7 @@ typedef enum { VAR_HTTP_CODE, VAR_HTTP_CODE_PROXY, VAR_HEADER_SIZE, + VAR_NUM_HEADERS, VAR_REQUEST_SIZE, VAR_EFFECTIVE_METHOD, VAR_EFFECTIVE_URL, @@ -80,6 +82,6 @@ struct writeoutvar { jsontype jsontype; }; -void ourWriteOut(CURL *curl, struct OutStruct *outs, const char *writeinfo); +void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo); #endif /* HEADER_CURL_TOOL_WRITEOUT_H */ diff --git a/src/tool_writeout_json.c b/src/tool_writeout_json.c index dfe51b9ff..bae7f5c15 100644 --- a/src/tool_writeout_json.c +++ b/src/tool_writeout_json.c @@ -103,13 +103,20 @@ static int writeString(FILE *str, CURL *curl, const char *key, CURLINFO ci) return 0; } -static int writeLong(FILE *str, CURL *curl, const char *key, CURLINFO ci) +static int writeLong(FILE *str, CURL *curl, const char *key, CURLINFO ci, + struct per_transfer *per, const struct writeoutvar *wovar) { - long val = 0; - if(CURLE_OK == curl_easy_getinfo(curl, ci, &val)) { - fprintf(str, "\"%s\":%ld", key, val); + if(wovar->id == VAR_NUM_HEADERS) { + fprintf(str, "\"%s\":%ld", key, per->num_headers); return 1; } + else { + long val = 0; + if(CURLE_OK == curl_easy_getinfo(curl, ci, &val)) { + fprintf(str, "\"%s\":%ld", key, val); + return 1; + } + } return 0; } @@ -149,12 +156,13 @@ static int writeVersion(FILE *str, CURL *curl, const char *key, CURLINFO ci) } void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, - struct OutStruct *outs, FILE *stream) + struct per_transfer *per, FILE *stream) { int i; fputs("{", stream); for(i = 0; mappings[i].name != NULL; i++) { + const struct writeoutvar *wovar = &mappings[i]; const char *name = mappings[i].name; CURLINFO cinfo = mappings[i].cinfo; int ok = 0; @@ -168,7 +176,7 @@ void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, ok = writeString(stream, curl, name, cinfo); break; case JSON_LONG: - ok = writeLong(stream, curl, name, cinfo); + ok = writeLong(stream, curl, name, cinfo, per, wovar); break; case JSON_OFFSET: ok = writeOffset(stream, curl, name, cinfo); @@ -177,7 +185,7 @@ void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, ok = writeTime(stream, curl, name, cinfo); break; case JSON_FILENAME: - ok = writeFilename(stream, name, outs->filename); + ok = writeFilename(stream, name, per->outs.filename); break; case JSON_VERSION: ok = writeVersion(stream, curl, name, cinfo); diff --git a/src/tool_writeout_json.h b/src/tool_writeout_json.h index d3988d521..0390ae5c8 100644 --- a/src/tool_writeout_json.h +++ b/src/tool_writeout_json.h @@ -24,7 +24,7 @@ #include "tool_setup.h" #include "tool_writeout.h" -void ourWriteOutJSON(const struct writeoutvar mappings[], - CURL *curl, struct OutStruct *outs, FILE *stream); +void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, + struct per_transfer *per, FILE *stream); #endif /* HEADER_CURL_TOOL_WRITEOUT_H */ diff --git a/tests/data/Makefile.inc b/tests/data/Makefile.inc index 606a6031a..42f427eea 100644 --- a/tests/data/Makefile.inc +++ b/tests/data/Makefile.inc @@ -225,4 +225,4 @@ test2080 \ test2100 \ \ test3000 test3001 test3002 test3003 test3004 test3005 test3006 test3007 \ -test3008 test3009 test3010 test3011 test3012 test3013 +test3008 test3009 test3010 test3011 test3012 test3013 test3014 test3015 diff --git a/tests/data/test1439 b/tests/data/test1439 index cb906adbc..91edd2248 100644 --- a/tests/data/test1439 +++ b/tests/data/test1439 @@ -26,7 +26,7 @@ http -Check if %{scheme} returns HTTP +Check if %{http_version} returns 1.1 http://%HOSTIP:%HTTPPORT/1439 --write-out '%{http_version}' diff --git a/tests/data/test3014 b/tests/data/test3014 new file mode 100644 index 000000000..7fab42fcd --- /dev/null +++ b/tests/data/test3014 @@ -0,0 +1,58 @@ + + + +HTTP +--write-out + + +# Server-side + + +HTTP/1.1 200 OK +Date: Thu, 09 Nov 2010 14:49:00 GMT +Content-Length: 9 +Connection: close +Content-Type: text/plain + +testdata + + + + +# Client-side + + +http + + + +Check if %{num_headers} returns correct number of headers + + +http://%HOSTIP:%HTTPPORT/1439 --write-out '%{num_headers}' + + + +# Verify data + + +HTTP/1.1 200 OK +Date: Thu, 09 Nov 2010 14:49:00 GMT +Content-Length: 9 +Connection: close +Content-Type: text/plain + +testdata +4 + + +GET /1439 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + +^User-Agent:.* + + + diff --git a/tests/data/test3015 b/tests/data/test3015 new file mode 100644 index 000000000..fbc6eadb8 --- /dev/null +++ b/tests/data/test3015 @@ -0,0 +1,79 @@ + + + +HTTP +HTTP GET +followlocation +chunked Transfer-Encoding +--write-out + + + +# +# Server-side + + +HTTP/1.1 302 OK +Date: Sun, 13 Sep 2020 15:00 GMT +Content-Length: 8 +Connection: close +Content-Type: text/plain +Location: ./30150001 + +monster + + +HTTP/1.1 200 OK +Date: Sun, 13 Sep 2020 15:00 GMT +Transfer-Encoding: chunked +Connection: close +Content-Type: text/plain; charset=us-ascii + +0007 +bigger +0008 +monster + +0 + + + + + +# +# Client-side + + +http + + +HTTP GET -w num_headers with redirected fetch (2 connects) + + +http://%HOSTIP:%HTTPPORT/3015 -w "%{num_headers}\n" -L -o/dev/null + + + +# +# Verify data after the test has been "shot" + + +^User-Agent:.* + + +GET /3015 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + +GET /30150001 HTTP/1.1 +Host: %HOSTIP:%HTTPPORT +Accept: */* + + + + +4 + + + + diff --git a/tests/data/test970 b/tests/data/test970 index c0a88a798..bc5af5ba7 100644 --- a/tests/data/test970 +++ b/tests/data/test970 @@ -61,7 +61,7 @@ Accept: */* -{"url_effective":"http://%HOSTIP:%HTTPPORT/970","method":"GET","http_code":200,"response_code":200,"http_connect":0,"time_total":0.000013,"time_namelookup":0.000013,"time_connect":0.000013,"time_appconnect":0.000013,"time_pretransfer":0.000013,"time_starttransfer":0.000013,"size_header":4019,"size_request":4019,"size_download":445,"size_upload":0,"speed_download":13,"speed_upload":13,"content_type":"text/html","num_connects":1,"time_redirect":0.000013,"num_redirects":0,"ssl_verify_result":0,"proxy_ssl_verify_result":0,"filename_effective":"log/out970","remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"local_ip":"127.0.0.1","local_port":13,"http_version":"1.1","scheme":"HTTP","curl_version":"curl-unit-test-fake-version"} +{"url_effective":"http://%HOSTIP:%HTTPPORT/970","method":"GET","http_code":200,"response_code":200,"num_headers":9,"http_connect":0,"time_total":0.000013,"time_namelookup":0.000013,"time_connect":0.000013,"time_appconnect":0.000013,"time_pretransfer":0.000013,"time_starttransfer":0.000013,"size_header":4019,"size_request":4019,"size_download":445,"size_upload":0,"speed_download":13,"speed_upload":13,"content_type":"text/html","num_connects":1,"time_redirect":0.000013,"num_redirects":0,"ssl_verify_result":0,"proxy_ssl_verify_result":0,"filename_effective":"log/out970","remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"local_ip":"127.0.0.1","local_port":13,"http_version":"1.1","scheme":"HTTP","curl_version":"curl-unit-test-fake-version"}