From 8063e4d9709b7c24ec00b61864451249d92e8126 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Thu, 9 Jul 2026 19:03:42 +0000 Subject: [PATCH 01/15] Add write out format pretty option For all the size option to the write out format specifiers, add a pretty option to have a human readable format --- src/tool_helpers.c | 30 +++++++++++++++++++++++++ src/tool_helpers.h | 8 +++++++ src/tool_progress.h | 1 - src/tool_writeout.c | 51 ++++++++++++++++++++++++++++++++++++++++++ tests/tunit/tool1622.c | 1 + 5 files changed, 90 insertions(+), 1 deletion(-) diff --git a/src/tool_helpers.c b/src/tool_helpers.c index b5e9b54121..986dc683e8 100644 --- a/src/tool_helpers.c +++ b/src/tool_helpers.c @@ -121,3 +121,33 @@ void customrequest_helper(HttpReq req, const char *method) "the way you want. Consider using -I/--head instead."); } } + +char *max5data(curl_off_t bytes, char *max5, size_t mlen) { + /* a signed 64-bit value is 8192 petabytes maximum */ const char unit[] = {'k', 'M', 'G', 'T', 'P', 'E', 0}; + int k = 0; + if (bytes < 100000) { + curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); + return max5; + } + + do { + curl_off_t nbytes = bytes / 1024; + if (nbytes < 100) { + /* display with a decimal */ + curl_msnprintf(max5, mlen, + "%2" CURL_FORMAT_CURL_OFF_T ".%" CURL_FORMAT_CURL_OFF_T + "%c", + bytes / 1024, (bytes % 1024) * 10 / 1024, unit[k]); + break; + } else if (nbytes < 10000) { + /* no decimals */ + curl_msnprintf(max5, mlen, "%4" CURL_FORMAT_CURL_OFF_T "%c", nbytes, + unit[k]); + break; + } + bytes = nbytes; + k++; + DEBUGASSERT(unit[k]); + } while (unit[k]); + return max5; +} diff --git a/src/tool_helpers.h b/src/tool_helpers.h index cbfa168523..5af121d2b1 100644 --- a/src/tool_helpers.h +++ b/src/tool_helpers.h @@ -29,4 +29,12 @@ const char *param2text(ParameterError error); int SetHTTPrequest(HttpReq req, HttpReq *store); void customrequest_helper(HttpReq req, const char *method); +/* The point of this function would be to return a string of the input data, + but never longer than 5 columns (+ one zero byte). + Add suffix k, M, G when suitable... + + Unit test @1622 +*/ +char *max5data(curl_off_t bytes, char *max5, size_t mlen); + #endif /* HEADER_CURL_TOOL_HELPERS_H */ diff --git a/src/tool_progress.h b/src/tool_progress.h index e07b5fd8d1..e15c2e9514 100644 --- a/src/tool_progress.h +++ b/src/tool_progress.h @@ -38,7 +38,6 @@ struct per_transfer; void progress_finalize(struct per_transfer *per); #ifdef UNITTESTS -UNITTEST char *max5data(curl_off_t bytes, char *max5, size_t mlen); UNITTEST void timebuf(char *r, size_t rlen, curl_off_t seconds); #endif diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 63e93ff52d..411f5a6c78 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -26,6 +26,7 @@ #include "tool_cfgable.h" #include "tool_writeout.h" #include "tool_writeout_json.h" +#include "tool_helpers.h" struct httpmap { const char *str; @@ -418,6 +419,49 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, return 1; /* return 1 if anything was written */ } + +static int writePretty(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json) { + bool valid = FALSE; + curl_off_t offinfo = 0; + char prettyOff[6]; + + (void)per; + (void)per_result; + DEBUGASSERT(wovar->writefunc == writeOffset); + + if (wovar->ci) { + if (!curl_easy_getinfo(per->curl, wovar->ci, &offinfo)) + valid = TRUE; + } else { + switch (wovar->id) { + case VAR_URLNUM: + if (per->urlnum <= INT_MAX) { + offinfo = per->urlnum; + valid = TRUE; + } + break; + default: + DEBUGASSERT(0); + } + } + + if (valid) { + if (use_json) + curl_mfprintf(stream, "\"%s\":", wovar->name); + + + curl_mfprintf(stream, "%s", + max5data(offinfo, prettyOff, sizeof(prettyOff))); + } else { + if (use_json) + curl_mfprintf(stream, "\"%s\":null", wovar->name); + } + + return 1; +} + /* The designated write function should be the same as the CURLINFO return type with exceptions special cased in the respective function. For example, http_version uses CURLINFO_HTTP_VERSION which returns the version as a long, @@ -463,11 +507,18 @@ static const struct writeoutvar variables[] = { { "scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString }, { "size_delivered", VAR_SIZE_DELIVERED, CURLINFO_SIZE_DELIVERED, writeOffset }, + { "size_delivered_pretty", VAR_SIZE_DELIVERED, CURLINFO_SIZE_DELIVERED, + writePretty }, { "size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset }, + { "size_download_pretty", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, + writePretty }, { "size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong }, + { "size_header_pretty", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writePretty }, { "size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong }, + { "size_request_pretty", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writePretty }, { "size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset }, + { "size_upload_pretty", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writePretty }, { "speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T, writeOffset }, { "speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset }, diff --git a/tests/tunit/tool1622.c b/tests/tunit/tool1622.c index 6844cf0ec5..44559c2bf8 100644 --- a/tests/tunit/tool1622.c +++ b/tests/tunit/tool1622.c @@ -23,6 +23,7 @@ ***************************************************************************/ #include "unitcheck.h" #include "tool_progress.h" +#include "tool_helpers.h" static CURLcode test_tool1622(const char *arg) { From 644325acdf9bb6be752788340e3d5c76ef01f7e0 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Fri, 10 Jul 2026 15:17:11 +0200 Subject: [PATCH 02/15] Fix tests compilations --- src/tool_helpers.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/tool_helpers.h b/src/tool_helpers.h index 5af121d2b1..a812db2728 100644 --- a/src/tool_helpers.h +++ b/src/tool_helpers.h @@ -23,6 +23,8 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "tool_getparam.h" +#include "tool_sdecls.h" #include "tool_setup.h" const char *param2text(ParameterError error); From bd55cda4ce21d12f96e3946a73e0a023f6eccafc Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 13 Jul 2026 18:05:16 +0200 Subject: [PATCH 03/15] Add a filter logic for write-out The filter can be appended to the variable name, the writeout function is in charge of handling (or not) the filter depending of what's the type of data it's handling --- src/tool_helpers.c | 15 ++-- src/tool_writeout.c | 172 +++++++++++++++++++++++---------------- src/tool_writeout.h | 14 +++- src/tool_writeout_json.c | 9 +- src/tool_writeout_json.h | 4 +- 5 files changed, 129 insertions(+), 85 deletions(-) diff --git a/src/tool_helpers.c b/src/tool_helpers.c index 986dc683e8..9d23b98470 100644 --- a/src/tool_helpers.c +++ b/src/tool_helpers.c @@ -122,24 +122,27 @@ void customrequest_helper(HttpReq req, const char *method) } } -char *max5data(curl_off_t bytes, char *max5, size_t mlen) { - /* a signed 64-bit value is 8192 petabytes maximum */ const char unit[] = {'k', 'M', 'G', 'T', 'P', 'E', 0}; +char *max5data(curl_off_t bytes, char *max5, size_t mlen) +{ + /* a signed 64-bit value is 8192 petabytes maximum */ + const char unit[] = {'k', 'M', 'G', 'T', 'P', 'E', 0}; int k = 0; - if (bytes < 100000) { + if(bytes < 100000) { curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); return max5; } do { curl_off_t nbytes = bytes / 1024; - if (nbytes < 100) { + if(nbytes < 100) { /* display with a decimal */ curl_msnprintf(max5, mlen, "%2" CURL_FORMAT_CURL_OFF_T ".%" CURL_FORMAT_CURL_OFF_T "%c", bytes / 1024, (bytes % 1024) * 10 / 1024, unit[k]); break; - } else if (nbytes < 10000) { + } + else if(nbytes < 10000) { /* no decimals */ curl_msnprintf(max5, mlen, "%4" CURL_FORMAT_CURL_OFF_T "%c", nbytes, unit[k]); @@ -148,6 +151,6 @@ char *max5data(curl_off_t bytes, char *max5, size_t mlen) { bytes = nbytes; k++; DEBUGASSERT(unit[k]); - } while (unit[k]); + } while(unit[k]); return max5; } diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 411f5a6c78..791e59f41c 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -21,12 +21,15 @@ * SPDX-License-Identifier: curl * ***************************************************************************/ +#include "curl_setup.h" +#include "curlx/dynbuf.h" #include "tool_setup.h" #include "tool_cfgable.h" +#include "tool_helpers.h" #include "tool_writeout.h" #include "tool_writeout_json.h" -#include "tool_helpers.h" +#include struct httpmap { const char *str; @@ -44,8 +47,7 @@ static const struct httpmap http_version[] = { static int writeTime(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; curl_off_t us = 0; @@ -70,6 +72,14 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T ".%06" CURL_FORMAT_CURL_OFF_T, secs, us); + switch(filter->id) { + case FILTER_NONE: + break; + default: + curl_mfprintf(stream, ":%s", filter->name); + break; + } + } else { if(use_json) @@ -80,8 +90,7 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, } static int urlpart(struct per_transfer *per, writeoutid vid, - const char **contentp) -{ + const char **contentp) { CURLU *uh = curl_url(); int rc = 0; if(uh) { @@ -172,8 +181,7 @@ static void certinfo(struct per_transfer *per) static int writeString(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; const char *strinfo = NULL; const char *freestr = NULL; @@ -308,8 +316,16 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, curl_mfprintf(stream, "\"%s\":", wovar->name); jsonWriteString(stream, strinfo, FALSE); } - else + else { fputs(strinfo, stream); + switch(filter->id) { + case FILTER_NONE: + break; + default: + curl_mfprintf(stream, ":%s", filter->name); + break; + } + } } else { if(use_json) @@ -323,8 +339,7 @@ static int writeString(FILE *stream, const struct writeoutvar *wovar, static int writeLong(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; long longinfo = 0; @@ -365,8 +380,16 @@ static int writeLong(FILE *stream, const struct writeoutvar *wovar, else { if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY) curl_mfprintf(stream, "%03ld", longinfo); - else + else { curl_mfprintf(stream, "%ld", longinfo); + switch(filter->id) { + case FILTER_NONE: + break; + default: + curl_mfprintf(stream, ":%s", filter->name); + break; + } + } } } else { @@ -379,10 +402,10 @@ static int writeLong(FILE *stream, const struct writeoutvar *wovar, static int writeOffset(FILE *stream, const struct writeoutvar *wovar, struct per_transfer *per, CURLcode per_result, - bool use_json) -{ + bool use_json, const struct writeoutfilter *filter) { bool valid = FALSE; curl_off_t offinfo = 0; + char prettyOff[6]; (void)per; (void)per_result; @@ -409,7 +432,19 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, if(use_json) curl_mfprintf(stream, "\"%s\":", wovar->name); - curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); + switch(filter->id) { + case FILTER_NONE: + + curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); + break; + case FILTER_BYTES_PRETTY: + curl_mfprintf(stream, "%s", + max5data(offinfo, prettyOff, sizeof(prettyOff))); + break; + default: + curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T ":%s", offinfo, + filter->name); + } } else { if(use_json) @@ -419,49 +454,6 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, return 1; /* return 1 if anything was written */ } - -static int writePretty(FILE *stream, const struct writeoutvar *wovar, - struct per_transfer *per, CURLcode per_result, - bool use_json) { - bool valid = FALSE; - curl_off_t offinfo = 0; - char prettyOff[6]; - - (void)per; - (void)per_result; - DEBUGASSERT(wovar->writefunc == writeOffset); - - if (wovar->ci) { - if (!curl_easy_getinfo(per->curl, wovar->ci, &offinfo)) - valid = TRUE; - } else { - switch (wovar->id) { - case VAR_URLNUM: - if (per->urlnum <= INT_MAX) { - offinfo = per->urlnum; - valid = TRUE; - } - break; - default: - DEBUGASSERT(0); - } - } - - if (valid) { - if (use_json) - curl_mfprintf(stream, "\"%s\":", wovar->name); - - - curl_mfprintf(stream, "%s", - max5data(offinfo, prettyOff, sizeof(prettyOff))); - } else { - if (use_json) - curl_mfprintf(stream, "\"%s\":null", wovar->name); - } - - return 1; -} - /* The designated write function should be the same as the CURLINFO return type with exceptions special cased in the respective function. For example, http_version uses CURLINFO_HTTP_VERSION which returns the version as a long, @@ -507,18 +499,11 @@ static const struct writeoutvar variables[] = { { "scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString }, { "size_delivered", VAR_SIZE_DELIVERED, CURLINFO_SIZE_DELIVERED, writeOffset }, - { "size_delivered_pretty", VAR_SIZE_DELIVERED, CURLINFO_SIZE_DELIVERED, - writePretty }, { "size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset }, - { "size_download_pretty", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, - writePretty }, { "size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong }, - { "size_header_pretty", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writePretty }, { "size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong }, - { "size_request_pretty", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writePretty }, { "size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset }, - { "size_upload_pretty", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writePretty }, { "speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T, writeOffset }, { "speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset }, @@ -570,6 +555,13 @@ static const struct writeoutvar variables[] = { #define MAX_WRITEOUT_NAME_LENGTH 24 +static const struct writeoutfilter filters[] = { + {"none", FILTER_NONE}, + {"pretty", FILTER_BYTES_PRETTY}, +}; + +#define MAX_WRITEOUT_FILTER_LENGTH 7 + /* return the position after %time{} */ static const char *outtime(const char *ptr, /* %time{ ... */ FILE *stream) @@ -810,17 +802,55 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, else { /* this is meant as a variable to output */ const char *end; - size_t vlen; + const char *filter; + size_t vlen, filen; if('{' == ptr[1]) { + struct dynbuf fil_name; const struct writeoutvar *wv = NULL; - struct writeoutvar find = { 0 }; + const struct writeoutfilter *cur_fil = NULL; + struct writeoutvar find = {0}; + struct writeoutfilter filter_find = {0}; + const char *none = "none"; + filter = strchr(ptr, ':'); end = strchr(ptr, '}'); ptr += 2; /* pass the % and the { */ if(!end) { fputs("%{", stream); continue; } - vlen = end - ptr; + curlx_dyn_init(&fil_name, MAX_WRITEOUT_FILTER_LENGTH); + if(filter) { + + vlen = filter - ptr; + filen = end - filter; + + if(curlx_dyn_addn(&fil_name, filter + 1, filen - 1)) { + break; + } + + } + else { + vlen = end - ptr; + if(curlx_dyn_addn(&fil_name, none, strlen(none))) { + break; + } + } + filter_find.name = curlx_dyn_ptr(&fil_name); + cur_fil = bsearch(&filter_find, filters, CURL_ARRAYSIZE(filters), + sizeof(filters[0]), matchvar); + if(!cur_fil) { + curl_mfprintf(tool_stderr, + "curl: unknown --write-out filter: '%.*s, skipping'\n", + (int)filen, filter); + curlx_dyn_reset(&fil_name); + if(curlx_dyn_addn(&fil_name, none, strlen(none))) { + break; + } + filter_find.name = curlx_dyn_ptr(&fil_name); + cur_fil = bsearch(&filter_find, filters, CURL_ARRAYSIZE(filters), + sizeof(filters[0]), matchvar); + } + curlx_dyn_reset(&name); if(!curlx_dyn_addn(&name, ptr, vlen)) { @@ -851,15 +881,14 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, stream = tool_stderr; break; case VAR_JSON: - ourWriteOutJSON(stream, variables, - CURL_ARRAYSIZE(variables), - per, per_result); + ourWriteOutJSON(stream, variables, CURL_ARRAYSIZE(variables), per, + per_result, cur_fil); break; case VAR_HEADER_JSON: headerJSON(stream, per); break; default: - (void)wv->writefunc(stream, wv, per, per_result, FALSE); + (void)wv->writefunc(stream, wv, per, per_result, FALSE, cur_fil); break; } } @@ -869,6 +898,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, (int)vlen, ptr); } ptr = end + 1; /* pass the end */ + curlx_dyn_free(&fil_name); } else if(!strncmp("header{", &ptr[1], 7)) { ptr += 8; diff --git a/src/tool_writeout.h b/src/tool_writeout.h index 7bc54aeb4d..eb05ad3875 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -105,13 +105,23 @@ typedef enum { VAR_NUM_OF_VARS /* must be the last */ } writeoutid; +typedef enum { + FILTER_NONE, + FILTER_BYTES_PRETTY, +} writeoutfilterid; + +struct writeoutfilter { + const char *name; + writeoutfilterid id; +}; + struct writeoutvar { const char *name; writeoutid id; CURLINFO ci; int (*writefunc)(FILE *stream, const struct writeoutvar *wovar, - struct per_transfer *per, CURLcode per_result, - bool use_json); + struct per_transfer *per, CURLcode per_result, bool use_json, + const struct writeoutfilter *filter); }; void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, diff --git a/src/tool_writeout_json.c b/src/tool_writeout_json.c index 3b6f7df73f..500bde4b31 100644 --- a/src/tool_writeout_json.c +++ b/src/tool_writeout_json.c @@ -24,8 +24,8 @@ #include "tool_setup.h" #include "tool_cfgable.h" -#include "tool_writeout_json.h" #include "tool_writeout.h" +#include "tool_writeout_json.h" #define MAX_JSON_STRING 100000 @@ -95,8 +95,8 @@ void jsonWriteString(FILE *stream, const char *in, bool lowercase) } void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], - size_t nentries, - struct per_transfer *per, CURLcode per_result) + size_t nentries, struct per_transfer *per, + CURLcode per_result, const struct writeoutfilter *filter) { size_t i; @@ -104,7 +104,8 @@ void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], for(i = 0; i < nentries; i++) { if(mappings[i].writefunc && - mappings[i].writefunc(stream, &mappings[i], per, per_result, TRUE)) + mappings[i].writefunc(stream, &mappings[i], per, per_result, TRUE, + filter)) fputs(",", stream); } diff --git a/src/tool_writeout_json.h b/src/tool_writeout_json.h index 7318367c17..e1bc00f405 100644 --- a/src/tool_writeout_json.h +++ b/src/tool_writeout_json.h @@ -30,8 +30,8 @@ int jsonquoted(const char *in, size_t len, struct dynbuf *out, bool lowercase); void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], - size_t nentries, - struct per_transfer *per, CURLcode per_result); + size_t nentries, struct per_transfer *per, + CURLcode per_result, const struct writeoutfilter *filter); void headerJSON(FILE *stream, struct per_transfer *per); void jsonWriteString(FILE *stream, const char *in, bool lowercase); From 25746bd6f5fc3c638eaaf11d11d604e4802e1c67 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Tue, 14 Jul 2026 10:09:50 +0200 Subject: [PATCH 04/15] Initialize variable in all path The variable won't be used if we are in the fallback path of the if, but it make the analyzer happy. --- src/tool_writeout.c | 8 +++++--- src/tool_writeout.h | 4 ++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 791e59f41c..2cce4cdaac 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -831,6 +831,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, } else { vlen = end - ptr; + filen = 0; if(curlx_dyn_addn(&fil_name, none, strlen(none))) { break; } @@ -840,7 +841,8 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, sizeof(filters[0]), matchvar); if(!cur_fil) { curl_mfprintf(tool_stderr, - "curl: unknown --write-out filter: '%.*s, skipping'\n", + "curl: unknown --write-out filter: '%.*s, " + "skipping'\n", (int)filen, filter); curlx_dyn_reset(&fil_name); if(curlx_dyn_addn(&fil_name, none, strlen(none))) { @@ -881,8 +883,8 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, stream = tool_stderr; break; case VAR_JSON: - ourWriteOutJSON(stream, variables, CURL_ARRAYSIZE(variables), per, - per_result, cur_fil); + ourWriteOutJSON(stream, variables, CURL_ARRAYSIZE(variables), + per, per_result, cur_fil); break; case VAR_HEADER_JSON: headerJSON(stream, per); diff --git a/src/tool_writeout.h b/src/tool_writeout.h index eb05ad3875..6f969a0f6a 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -120,8 +120,8 @@ struct writeoutvar { writeoutid id; CURLINFO ci; int (*writefunc)(FILE *stream, const struct writeoutvar *wovar, - struct per_transfer *per, CURLcode per_result, bool use_json, - const struct writeoutfilter *filter); + struct per_transfer *per, CURLcode per_result, + bool use_json, const struct writeoutfilter *filter); }; void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, From fb98921e4de77e41e8b2369d668f17b748d13257 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 27 Jul 2026 21:12:35 +0200 Subject: [PATCH 05/15] Remove search logic for filter --- src/tool_writeout.c | 52 +++++++++++++++------------------------------ src/tool_writeout.h | 2 +- 2 files changed, 18 insertions(+), 36 deletions(-) diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 2cce4cdaac..b9fa86a6ec 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -23,6 +23,7 @@ ***************************************************************************/ #include "curl_setup.h" #include "curlx/dynbuf.h" +#include "tool_msgs.h" #include "tool_setup.h" #include "tool_cfgable.h" @@ -30,6 +31,7 @@ #include "tool_writeout.h" #include "tool_writeout_json.h" #include +#include struct httpmap { const char *str; @@ -555,12 +557,6 @@ static const struct writeoutvar variables[] = { #define MAX_WRITEOUT_NAME_LENGTH 24 -static const struct writeoutfilter filters[] = { - {"none", FILTER_NONE}, - {"pretty", FILTER_BYTES_PRETTY}, -}; - -#define MAX_WRITEOUT_FILTER_LENGTH 7 /* return the position after %time{} */ static const char *outtime(const char *ptr, /* %time{ ... */ @@ -805,12 +801,9 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, const char *filter; size_t vlen, filen; if('{' == ptr[1]) { - struct dynbuf fil_name; const struct writeoutvar *wv = NULL; - const struct writeoutfilter *cur_fil = NULL; + struct writeoutfilter cur_fil; struct writeoutvar find = {0}; - struct writeoutfilter filter_find = {0}; - const char *none = "none"; filter = strchr(ptr, ':'); end = strchr(ptr, '}'); ptr += 2; /* pass the % and the { */ @@ -818,39 +811,28 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, fputs("%{", stream); continue; } - curlx_dyn_init(&fil_name, MAX_WRITEOUT_FILTER_LENGTH); if(filter) { vlen = filter - ptr; + + filter += 1; /* remove the : */ filen = end - filter; - if(curlx_dyn_addn(&fil_name, filter + 1, filen - 1)) { + if(strncmp("pretty}", filter, filen) == 0) { + cur_fil.name = "pretty"; + cur_fil.id = FILTER_BYTES_PRETTY; + } + else { + errorf("Unknown --write-out filter: %s", filter); break; } + } else { vlen = end - ptr; - filen = 0; - if(curlx_dyn_addn(&fil_name, none, strlen(none))) { - break; - } - } - filter_find.name = curlx_dyn_ptr(&fil_name); - cur_fil = bsearch(&filter_find, filters, CURL_ARRAYSIZE(filters), - sizeof(filters[0]), matchvar); - if(!cur_fil) { - curl_mfprintf(tool_stderr, - "curl: unknown --write-out filter: '%.*s, " - "skipping'\n", - (int)filen, filter); - curlx_dyn_reset(&fil_name); - if(curlx_dyn_addn(&fil_name, none, strlen(none))) { - break; - } - filter_find.name = curlx_dyn_ptr(&fil_name); - cur_fil = bsearch(&filter_find, filters, CURL_ARRAYSIZE(filters), - sizeof(filters[0]), matchvar); + cur_fil.name = "none"; + cur_fil.id = FILTER_NONE; } @@ -884,13 +866,14 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, break; case VAR_JSON: ourWriteOutJSON(stream, variables, CURL_ARRAYSIZE(variables), - per, per_result, cur_fil); + per, per_result, &cur_fil); break; case VAR_HEADER_JSON: headerJSON(stream, per); break; default: - (void)wv->writefunc(stream, wv, per, per_result, FALSE, cur_fil); + (void)wv->writefunc(stream, wv, per, per_result, FALSE, + &cur_fil); break; } } @@ -900,7 +883,6 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, (int)vlen, ptr); } ptr = end + 1; /* pass the end */ - curlx_dyn_free(&fil_name); } else if(!strncmp("header{", &ptr[1], 7)) { ptr += 8; diff --git a/src/tool_writeout.h b/src/tool_writeout.h index 6f969a0f6a..3227330411 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -107,7 +107,7 @@ typedef enum { typedef enum { FILTER_NONE, - FILTER_BYTES_PRETTY, + FILTER_BYTES_PRETTY } writeoutfilterid; struct writeoutfilter { From e1c4398ef2c9a55a46f70beb29036883f9952c07 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 27 Jul 2026 21:12:48 +0200 Subject: [PATCH 06/15] Add minimal doc --- docs/cmdline-opts/write-out.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index 5cf9d91f87..d1c5014358 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -365,6 +365,11 @@ started yet for the handle. The transfer id is unique among all transfers performed using the same connection cache. (Added in 8.2.0) +The option allow a filter to be added to the variables that express a size. +This allow to convert the size using the standard suffixes K, M, G... like +what's printed by default on the CLI. To use it, add the `:pretty` suffix to +the variable. For example, `--write-out '%{size_download:pretty}'`. + ## TIME OUTPUT FORMAT From 151f747654576a1928935128330232e4082ccfc6 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 27 Jul 2026 21:34:45 +0200 Subject: [PATCH 07/15] Remove mx5data duplicate definition --- src/tool_progress.c | 38 -------------------------------------- src/tool_progress.h | 1 + 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/src/tool_progress.c b/src/tool_progress.c index c30644c0e3..e5347af3dc 100644 --- a/src/tool_progress.c +++ b/src/tool_progress.c @@ -26,44 +26,6 @@ #include "tool_operate.h" #include "tool_progress.h" -/* The point of this function would be to return a string of the input data, - but never longer than 5 columns (+ one zero byte). - Add suffix k, M, G when suitable... - - Unit test 1622 - */ -UNITTEST char *max5data(curl_off_t bytes, char *max5, size_t mlen) -{ - /* a signed 64-bit value is 8192 petabytes maximum */ - const char unit[] = { 'k', 'M', 'G', 'T', 'P', 'E', 0 }; - int k = 0; - if(bytes < 100000) { - curl_msnprintf(max5, mlen, "%5" CURL_FORMAT_CURL_OFF_T, bytes); - return max5; - } - - do { - curl_off_t nbytes = bytes / 1024; - if(nbytes < 100) { - /* display with a decimal */ - curl_msnprintf(max5, mlen, "%2" CURL_FORMAT_CURL_OFF_T ".%" - CURL_FORMAT_CURL_OFF_T "%c", bytes / 1024, - (bytes % 1024) * 10 / 1024, unit[k]); - break; - } - else if(nbytes < 10000) { - /* no decimals */ - curl_msnprintf(max5, mlen, "%4" CURL_FORMAT_CURL_OFF_T "%c", nbytes, - unit[k]); - break; - } - bytes = nbytes; - k++; - DEBUGASSERT(unit[k]); - } while(unit[k]); - return max5; -} - int xferinfo_cb(void *clientp, curl_off_t dltotal, curl_off_t dlnow, diff --git a/src/tool_progress.h b/src/tool_progress.h index e15c2e9514..c6ea8d9efb 100644 --- a/src/tool_progress.h +++ b/src/tool_progress.h @@ -24,6 +24,7 @@ * ***************************************************************************/ #include "tool_setup.h" +#include "tool_helpers.h" int xferinfo_cb(void *clientp, curl_off_t dltotal, From 60ba96ea83663f58c90a49c267d52b6d3a36e721 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 27 Jul 2026 21:36:39 +0200 Subject: [PATCH 08/15] Remove empty lines --- src/tool_writeout.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tool_writeout.c b/src/tool_writeout.c index b9fa86a6ec..2724eaf249 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -81,7 +81,6 @@ static int writeTime(FILE *stream, const struct writeoutvar *wovar, curl_mfprintf(stream, ":%s", filter->name); break; } - } else { if(use_json) @@ -826,8 +825,6 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, errorf("Unknown --write-out filter: %s", filter); break; } - - } else { vlen = end - ptr; From b5d5771a5a5ccfbd184045eaadda9f403587f503 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 27 Jul 2026 21:51:49 +0200 Subject: [PATCH 09/15] Don't search filter outside of variable --- src/tool_writeout.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 2724eaf249..127ff64661 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -810,8 +810,7 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, fputs("%{", stream); continue; } - if(filter) { - + if(filter && filter < end) { vlen = filter - ptr; filter += 1; /* remove the : */ From 40834d22e132fffa3912c9703a17085f0e9b9439 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Mon, 27 Jul 2026 21:53:31 +0200 Subject: [PATCH 10/15] Remove empty lines --- src/tool_writeout.c | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/tool_writeout.c b/src/tool_writeout.c index 127ff64661..bd6776782f 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -556,7 +556,6 @@ static const struct writeoutvar variables[] = { #define MAX_WRITEOUT_NAME_LENGTH 24 - /* return the position after %time{} */ static const char *outtime(const char *ptr, /* %time{ ... */ FILE *stream) @@ -830,8 +829,6 @@ void ourWriteOut(struct OperationConfig *config, struct per_transfer *per, cur_fil.name = "none"; cur_fil.id = FILTER_NONE; } - - curlx_dyn_reset(&name); if(!curlx_dyn_addn(&name, ptr, vlen)) { find.name = curlx_dyn_ptr(&name); From 64675a35c67c4819f559f1d0554dafee7485c5d0 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Tue, 28 Jul 2026 21:38:48 +0200 Subject: [PATCH 11/15] Better explain filter behaviour --- docs/cmdline-opts/write-out.md | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index d1c5014358..55a33b1326 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -57,6 +57,14 @@ when using this option to properly escape. If this option is used at the command prompt then the % cannot be escaped and unintended expansion is possible. +The option allow a filter to be added to the variables that express a size. +This allow to convert the size using the standard suffixes K, M, G... like +what's printed by default on the CLI. The output won't exceed 4 digits plus one +suffix character. To use it, add the `:pretty` suffix to the variable. For +example, `--write-out '%{size_download:pretty}'`. This only works with variable +that output a size like parameter + + The variables available are: ## `certs` @@ -365,11 +373,6 @@ started yet for the handle. The transfer id is unique among all transfers performed using the same connection cache. (Added in 8.2.0) -The option allow a filter to be added to the variables that express a size. -This allow to convert the size using the standard suffixes K, M, G... like -what's printed by default on the CLI. To use it, add the `:pretty` suffix to -the variable. For example, `--write-out '%{size_download:pretty}'`. - ## TIME OUTPUT FORMAT From 04dd62417f2eff36e39c3e4e9190e5d8627dc62c Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Tue, 28 Jul 2026 21:44:31 +0200 Subject: [PATCH 12/15] remove empty line --- docs/cmdline-opts/write-out.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index 55a33b1326..8d2a68a385 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -64,7 +64,6 @@ suffix character. To use it, add the `:pretty` suffix to the variable. For example, `--write-out '%{size_download:pretty}'`. This only works with variable that output a size like parameter - The variables available are: ## `certs` From e762d33304e6a3a1dfa30896d13430ad8fc7e4a3 Mon Sep 17 00:00:00 2001 From: Xavier Claude Date: Tue, 28 Jul 2026 21:49:01 +0200 Subject: [PATCH 13/15] reword the doc --- docs/cmdline-opts/write-out.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index 8d2a68a385..9df35658f8 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -59,8 +59,8 @@ possible. The option allow a filter to be added to the variables that express a size. This allow to convert the size using the standard suffixes K, M, G... like -what's printed by default on the CLI. The output won't exceed 4 digits plus one -suffix character. To use it, add the `:pretty` suffix to the variable. For +what's printed by default on the CLI. The filter allow maximum 4 digits plus +one suffix character. To use it, add the `:pretty` suffix to the variable. For example, `--write-out '%{size_download:pretty}'`. This only works with variable that output a size like parameter From d03d703868ea404d084d5d8f9ffa7befcd1a7f3e Mon Sep 17 00:00:00 2001 From: claudex Date: Wed, 29 Jul 2026 20:54:59 +0200 Subject: [PATCH 14/15] Update docs/cmdline-opts/write-out.md Co-authored-by: Daniel Stenberg --- docs/cmdline-opts/write-out.md | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/docs/cmdline-opts/write-out.md b/docs/cmdline-opts/write-out.md index 9df35658f8..6c82534739 100644 --- a/docs/cmdline-opts/write-out.md +++ b/docs/cmdline-opts/write-out.md @@ -57,12 +57,11 @@ when using this option to properly escape. If this option is used at the command prompt then the % cannot be escaped and unintended expansion is possible. -The option allow a filter to be added to the variables that express a size. -This allow to convert the size using the standard suffixes K, M, G... like -what's printed by default on the CLI. The filter allow maximum 4 digits plus -one suffix character. To use it, add the `:pretty` suffix to the variable. For -example, `--write-out '%{size_download:pretty}'`. This only works with variable -that output a size like parameter +To format numerical output for better human readability, append the `:pretty` +modifier to a numerical variable name (e.g., `--write-out '%{size_download:pretty}'`). +This modifier applies standard SI metric suffixes (K, M, G, etc.) and +constrains the output to a maximum width of 5 characters, including the +suffix. This works only with numerical variables. The variables available are: From 1c3f0c194ebf6d435548654f17c265d3a49b02f1 Mon Sep 17 00:00:00 2001 From: claudex Date: Wed, 29 Jul 2026 20:55:33 +0200 Subject: [PATCH 15/15] Update src/tool_writeout.c Co-authored-by: Daniel Stenberg --- src/tool_writeout.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/tool_writeout.c b/src/tool_writeout.c index bd6776782f..b21b8d898b 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -439,8 +439,7 @@ static int writeOffset(FILE *stream, const struct writeoutvar *wovar, curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); break; case FILTER_BYTES_PRETTY: - curl_mfprintf(stream, "%s", - max5data(offinfo, prettyOff, sizeof(prettyOff))); + fputs(max5data(offinfo, prettyOff, sizeof(prettyOff)), stream); break; default: curl_mfprintf(stream, "%" CURL_FORMAT_CURL_OFF_T ":%s", offinfo,