From 65ca2294619f865362332ceddc622db92d0f50a6 Mon Sep 17 00:00:00 2001 From: Jay Satiro Date: Thu, 28 Jan 2021 18:56:50 -0500 Subject: [PATCH] tool_writeout: refactor write-out and write-out json - Deduplicate the logic used by write-out and write-out json. Rather than have separate writeLong, writeString, etc, logic for each of write-out and write-out json instead have respective shared functions that can output either format and a 'use_json' parameter to indicate whether it is json that is output. This will make it easier to maintain. Rather than have to go through two sets of logic now we only have to go through one. - Support write-out %{errormsg} and %{exitcode} in json. - Clarify in the doc that %{exitcode} is the exit code of the transfer. Prior to this change it just said "The numerical exitcode" which implies it's the exit code of the tool, and it's not necessarily that. Closes https://github.com/curl/curl/pull/6544 --- docs/cmdline-opts/write-out.d | 2 +- src/tool_operate.c | 7 +- src/tool_writeout.c | 551 +++++++++++++++++----------------- src/tool_writeout.h | 21 +- src/tool_writeout_json.c | 134 +-------- src/tool_writeout_json.h | 6 +- tests/data/test970 | 2 +- 7 files changed, 306 insertions(+), 417 deletions(-) diff --git a/docs/cmdline-opts/write-out.d b/docs/cmdline-opts/write-out.d index af5d0cf22..be8f75ad5 100644 --- a/docs/cmdline-opts/write-out.d +++ b/docs/cmdline-opts/write-out.d @@ -33,7 +33,7 @@ The Content-Type of the requested document, if there was any. The error message. (Added in 7.75.0) .TP .B exitcode -The numerical exitcode. (Added in 7.75.0) +The numerical exitcode of the transfer. (Added in 7.75.0) .TP .B filename_effective The ultimate filename that curl writes out to. This is only meaningful if curl diff --git a/src/tool_operate.c b/src/tool_operate.c index 140142a32..2b23680f2 100644 --- a/src/tool_operate.c +++ b/src/tool_operate.c @@ -625,9 +625,6 @@ static CURLcode post_per_transfer(struct GlobalConfig *global, newline here */ fputs("\n", per->progressbar.out); - if(config->writeout) - ourWriteOut(per->curl, per, config->writeout, result); - /* Close the outs file */ if(outs->fopened && outs->stream) { int rc = fclose(outs->stream); @@ -647,6 +644,10 @@ static CURLcode post_per_transfer(struct GlobalConfig *global, setfiletime(filetime, outs->filename, global); } + /* Write the --write-out data before cleanup but after result is final */ + if(config->writeout) + ourWriteOut(config->writeout, per, result); + /* Close function-local opened file descriptors */ if(per->heads.fopened && per->heads.stream) fclose(per->heads.stream); diff --git a/src/tool_writeout.c b/src/tool_writeout.c index c548bd8f2..e914dfa7b 100644 --- a/src/tool_writeout.c +++ b/src/tool_writeout.c @@ -29,77 +29,288 @@ #include "memdebug.h" /* keep this as LAST include */ -static const struct writeoutvar variables[] = { - {"content_type", VAR_CONTENT_TYPE, 0, CURLINFO_CONTENT_TYPE, JSON_STRING}, - {"filename_effective", VAR_EFFECTIVE_FILENAME, 0, 0, JSON_FILENAME}, - {"exitcode", VAR_EXITCODE, 0, 0, JSON_LONG}, - {"errormsg", VAR_ERRORMSG, 0, 0, JSON_STRING}, - {"ftp_entry_path", VAR_FTP_ENTRY_PATH, 0, CURLINFO_FTP_ENTRY_PATH, - JSON_STRING}, - {"http_code", VAR_HTTP_CODE, 0, CURLINFO_RESPONSE_CODE, JSON_LONG}, - {"http_connect", VAR_HTTP_CODE_PROXY, 0, CURLINFO_HTTP_CONNECTCODE, - JSON_LONG}, - {"http_version", VAR_HTTP_VERSION, 0, CURLINFO_HTTP_VERSION, JSON_VERSION}, - {"json", VAR_JSON, 1, 0, JSON_NONE}, - {"local_ip", VAR_LOCAL_IP, 0, CURLINFO_LOCAL_IP, JSON_STRING}, - {"local_port", VAR_LOCAL_PORT, 0, CURLINFO_LOCAL_PORT, JSON_LONG}, - {"method", VAR_EFFECTIVE_METHOD, 0, CURLINFO_EFFECTIVE_METHOD, JSON_STRING}, - {"num_connects", VAR_NUM_CONNECTS, 0, CURLINFO_NUM_CONNECTS, JSON_LONG}, - {"num_headers", VAR_NUM_HEADERS, 0, 0, JSON_LONG}, - {"num_redirects", VAR_REDIRECT_COUNT, 0, CURLINFO_REDIRECT_COUNT, JSON_LONG}, - {"onerror", VAR_ONERROR, 1, 0, JSON_NONE}, - {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT, 0, - CURLINFO_PROXY_SSL_VERIFYRESULT, JSON_LONG}, - {"redirect_url", VAR_REDIRECT_URL, 0, CURLINFO_REDIRECT_URL, JSON_STRING}, - {"remote_ip", VAR_PRIMARY_IP, 0, CURLINFO_PRIMARY_IP, JSON_STRING}, - {"remote_port", VAR_PRIMARY_PORT, 0, CURLINFO_PRIMARY_PORT, JSON_LONG}, - {"response_code", VAR_HTTP_CODE, 0, CURLINFO_RESPONSE_CODE, JSON_LONG}, - {"scheme", VAR_SCHEME, 0, CURLINFO_SCHEME, JSON_STRING}, - {"size_download", VAR_SIZE_DOWNLOAD, 0, CURLINFO_SIZE_DOWNLOAD_T, - JSON_OFFSET}, - {"size_header", VAR_HEADER_SIZE, 0, CURLINFO_HEADER_SIZE, JSON_LONG}, - {"size_request", VAR_REQUEST_SIZE, 0, CURLINFO_REQUEST_SIZE, JSON_LONG}, - {"size_upload", VAR_SIZE_UPLOAD, 0, CURLINFO_SIZE_UPLOAD_T, JSON_OFFSET}, - {"speed_download", VAR_SPEED_DOWNLOAD, 0, CURLINFO_SPEED_DOWNLOAD_T, - JSON_OFFSET}, - {"speed_upload", VAR_SPEED_UPLOAD, 0, CURLINFO_SPEED_UPLOAD_T, JSON_OFFSET}, - {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, 0, CURLINFO_SSL_VERIFYRESULT, - JSON_LONG}, - {"stderr", VAR_STDERR, 1, 0, JSON_NONE}, - {"stdout", VAR_STDOUT, 1, 0, JSON_NONE}, - {"time_appconnect", VAR_APPCONNECT_TIME, 0, CURLINFO_APPCONNECT_TIME_T, - JSON_TIME}, - {"time_connect", VAR_CONNECT_TIME, 0, CURLINFO_CONNECT_TIME_T, JSON_TIME}, - {"time_namelookup", VAR_NAMELOOKUP_TIME, 0, CURLINFO_NAMELOOKUP_TIME_T, - JSON_TIME}, - {"time_pretransfer", VAR_PRETRANSFER_TIME, 0, CURLINFO_PRETRANSFER_TIME_T, - JSON_TIME}, - {"time_redirect", VAR_REDIRECT_TIME, 0, CURLINFO_REDIRECT_TIME_T, JSON_TIME}, - {"time_starttransfer", VAR_STARTTRANSFER_TIME, 0, - CURLINFO_STARTTRANSFER_TIME_T, JSON_TIME}, - {"time_total", VAR_TOTAL_TIME, 0, CURLINFO_TOTAL_TIME_T, JSON_TIME}, - {"url", VAR_INPUT_URL, 0, 0, JSON_STRING}, - {"url_effective", VAR_EFFECTIVE_URL, 0, CURLINFO_EFFECTIVE_URL, JSON_STRING}, - {"urlnum", VAR_URLNUM, 0, 0, JSON_LONG}, - {NULL, VAR_NONE, 1, 0, JSON_NONE} +static int writeTime(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json); + +static int writeString(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json); + +static int writeLong(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json); + +static int writeOffset(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json); + +static const char *http_version[] = { + "0", /* CURL_HTTP_VERSION_NONE */ + "1", /* CURL_HTTP_VERSION_1_0 */ + "1.1", /* CURL_HTTP_VERSION_1_1 */ + "2", /* CURL_HTTP_VERSION_2 */ + "3" /* CURL_HTTP_VERSION_3 */ }; -static void us2sec(FILE *stream, curl_off_t us) +/* 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, + however it is output as a string and therefore is handled in writeString. + + Yes: "http_version": "1.1" + No: "http_version": 1.1 + + Variable names should be in alphabetical order. + */ +static const struct writeoutvar variables[] = { + {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString}, + {"errormsg", VAR_ERRORMSG, 0, writeString}, + {"exitcode", VAR_EXITCODE, 0, writeLong}, + {"filename_effective", VAR_EFFECTIVE_FILENAME, 0, writeString}, + {"ftp_entry_path", VAR_FTP_ENTRY_PATH, CURLINFO_FTP_ENTRY_PATH, writeString}, + {"http_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong}, + {"http_connect", VAR_HTTP_CODE_PROXY, CURLINFO_HTTP_CONNECTCODE, writeLong}, + {"http_version", VAR_HTTP_VERSION, CURLINFO_HTTP_VERSION, writeString}, + {"json", VAR_JSON, 0, NULL}, + {"local_ip", VAR_LOCAL_IP, CURLINFO_LOCAL_IP, writeString}, + {"local_port", VAR_LOCAL_PORT, CURLINFO_LOCAL_PORT, writeLong}, + {"method", VAR_EFFECTIVE_METHOD, CURLINFO_EFFECTIVE_METHOD, writeString}, + {"num_connects", VAR_NUM_CONNECTS, CURLINFO_NUM_CONNECTS, writeLong}, + {"num_headers", VAR_NUM_HEADERS, 0, writeLong}, + {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong}, + {"onerror", VAR_ONERROR, 0, NULL}, + {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT, + CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong}, + {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString}, + {"remote_ip", VAR_PRIMARY_IP, CURLINFO_PRIMARY_IP, writeString}, + {"remote_port", VAR_PRIMARY_PORT, CURLINFO_PRIMARY_PORT, writeLong}, + {"response_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong}, + {"scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString}, + {"size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset}, + {"size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong}, + {"size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong}, + {"size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset}, + {"speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T, + writeOffset}, + {"speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset}, + {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, CURLINFO_SSL_VERIFYRESULT, + writeLong}, + {"stderr", VAR_STDERR, 0, NULL}, + {"stdout", VAR_STDOUT, 0, NULL}, + {"time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T, + writeTime}, + {"time_connect", VAR_CONNECT_TIME, CURLINFO_CONNECT_TIME_T, writeTime}, + {"time_namelookup", VAR_NAMELOOKUP_TIME, CURLINFO_NAMELOOKUP_TIME_T, + writeTime}, + {"time_pretransfer", VAR_PRETRANSFER_TIME, CURLINFO_PRETRANSFER_TIME_T, + writeTime}, + {"time_redirect", VAR_REDIRECT_TIME, CURLINFO_REDIRECT_TIME_T, writeTime}, + {"time_starttransfer", VAR_STARTTRANSFER_TIME, CURLINFO_STARTTRANSFER_TIME_T, + writeTime}, + {"time_total", VAR_TOTAL_TIME, CURLINFO_TOTAL_TIME_T, writeTime}, + {"url", VAR_INPUT_URL, 0, writeString}, + {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString}, + {"urlnum", VAR_URLNUM, 0, writeLong}, + {NULL, VAR_NONE, 0, NULL} +}; + +static int writeTime(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json) { - curl_off_t secs = us / 1000000; - us %= 1000000; - fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU ".%06" CURL_FORMAT_CURL_OFF_TU, - secs, us); + bool valid = false; + curl_off_t us = 0; + + (void)per; + (void)per_result; + DEBUGASSERT(wovar->writefunc == writeTime); + + if(wovar->ci) { + if(!curl_easy_getinfo(per->curl, wovar->ci, &us)) + valid = true; + } + else { + DEBUGASSERT(0); + } + + if(valid) { + curl_off_t secs = us / 1000000; + us %= 1000000; + + if(use_json) + fprintf(stream, "\"%s\":", wovar->name); + + fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU + ".%06" CURL_FORMAT_CURL_OFF_TU, secs, us); + } + else { + if(use_json) + fprintf(stream, "\"%s\":null", wovar->name); + } + + return 1; /* return 1 if anything was written */ } -void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo, - CURLcode result) +static int writeString(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json) +{ + bool valid = false; + const char *strinfo = NULL; + + DEBUGASSERT(wovar->writefunc == writeString); + + if(wovar->ci) { + if(wovar->ci == CURLINFO_HTTP_VERSION) { + long version = 0; + if(!curl_easy_getinfo(per->curl, CURLINFO_HTTP_VERSION, &version) && + (version >= 0) && + (version < (long)(sizeof(http_version)/sizeof(http_version[0])))) { + strinfo = http_version[version]; + valid = true; + } + } + else { + if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo) + valid = true; + } + } + else { + switch(wovar->id) { + case VAR_ERRORMSG: + if(per_result) { + strinfo = per->errorbuffer[0] ? per->errorbuffer : + curl_easy_strerror(per_result); + valid = true; + } + break; + case VAR_EFFECTIVE_FILENAME: + if(per->outs.filename) { + strinfo = per->outs.filename; + valid = true; + } + break; + case VAR_INPUT_URL: + if(per->this_url) { + strinfo = per->this_url; + valid = true; + } + break; + default: + DEBUGASSERT(0); + break; + } + } + + if(valid) { + DEBUGASSERT(strinfo); + if(use_json) { + fprintf(stream, "\"%s\":\"", wovar->name); + jsonWriteString(stream, strinfo); + fputs("\"", stream); + } + else + fputs(strinfo, stream); + } + else { + if(use_json) + fprintf(stream, "\"%s\":null", wovar->name); + } + + return 1; /* return 1 if anything was written */ +} + +static int writeLong(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json) +{ + bool valid = false; + long longinfo = 0; + + DEBUGASSERT(wovar->writefunc == writeLong); + + if(wovar->ci) { + if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo)) + valid = true; + } + else { + switch(wovar->id) { + case VAR_NUM_HEADERS: + longinfo = per->num_headers; + valid = true; + break; + case VAR_EXITCODE: + longinfo = per_result; + valid = true; + break; + case VAR_URLNUM: + if(per->urlnum <= INT_MAX) { + longinfo = (long)per->urlnum; + valid = true; + } + break; + default: + DEBUGASSERT(0); + break; + } + } + + if(valid) { + if(use_json) + fprintf(stream, "\"%s\":", wovar->name); + + if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY) + fprintf(stream, "%03ld", longinfo); + else + fprintf(stream, "%ld", longinfo); + } + else { + if(use_json) + fprintf(stream, "\"%s\":null", wovar->name); + } + + return 1; /* return 1 if anything was written */ +} + +static int writeOffset(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json) +{ + bool valid = false; + curl_off_t offinfo = 0; + + (void)per; + (void)per_result; + DEBUGASSERT(wovar->writefunc == writeOffset); + + if(wovar->ci) { + if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo)) + valid = true; + } + else { + DEBUGASSERT(0); + } + + if(valid) { + if(use_json) + fprintf(stream, "\"%s\":", wovar->name); + + fprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo); + } + else { + if(use_json) + fprintf(stream, "\"%s\":null", wovar->name); + } + + return 1; /* return 1 if anything was written */ +} + +void ourWriteOut(const char *writeinfo, struct per_transfer *per, + CURLcode per_result) { FILE *stream = stdout; const char *ptr = writeinfo; - char *stringp = NULL; - long longinfo; - curl_off_t offinfo; bool done = FALSE; while(ptr && *ptr && !done) { @@ -129,217 +340,10 @@ void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo, match = TRUE; switch(variables[i].id) { case VAR_ONERROR: - if(result == CURLE_OK) + if(per_result == CURLE_OK) /* this isn't error so skip the rest */ done = TRUE; break; - case VAR_EXITCODE: - fprintf(stream, "%d", (int)result); - break; - case VAR_ERRORMSG: - if(result) - fputs(per->errorbuffer[0] ? per->errorbuffer : - curl_easy_strerror(result), stream); - break; - case VAR_INPUT_URL: - if(per->this_url) - fputs(per->this_url, stream); - break; - case VAR_URLNUM: - fprintf(stream, "%u", per->urlnum); - break; - case VAR_EFFECTIVE_URL: - if((CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &stringp)) - && stringp) - fputs(stringp, stream); - break; - case VAR_EFFECTIVE_METHOD: - if((CURLE_OK == curl_easy_getinfo(curl, - CURLINFO_EFFECTIVE_METHOD, - &stringp)) - && stringp) - fputs(stringp, stream); - break; - case VAR_HTTP_CODE: - if(CURLE_OK == - 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, - &longinfo)) - fprintf(stream, "%03ld", longinfo); - break; - case VAR_HEADER_SIZE: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_HEADER_SIZE, &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_REQUEST_SIZE: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_REQUEST_SIZE, &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_NUM_CONNECTS: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_NUM_CONNECTS, &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_REDIRECT_COUNT: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_REDIRECT_COUNT, &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_REDIRECT_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_REDIRECT_TIME_T, &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_TOTAL_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_TOTAL_TIME_T, &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_NAMELOOKUP_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_NAMELOOKUP_TIME_T, - &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_CONNECT_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_CONNECT_TIME_T, &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_APPCONNECT_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_APPCONNECT_TIME_T, - &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_PRETRANSFER_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_PRETRANSFER_TIME_T, - &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_STARTTRANSFER_TIME: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_STARTTRANSFER_TIME_T, - &offinfo)) - us2sec(stream, offinfo); - break; - case VAR_SIZE_UPLOAD: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_SIZE_UPLOAD_T, &offinfo)) - fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU, offinfo); - break; - case VAR_SIZE_DOWNLOAD: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_SIZE_DOWNLOAD_T, - &offinfo)) - fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU, offinfo); - break; - case VAR_SPEED_DOWNLOAD: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_SPEED_DOWNLOAD_T, - &offinfo)) - fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU, offinfo); - break; - case VAR_SPEED_UPLOAD: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_SPEED_UPLOAD_T, &offinfo)) - fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU, offinfo); - break; - case VAR_CONTENT_TYPE: - if((CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &stringp)) - && stringp) - fputs(stringp, stream); - break; - case VAR_FTP_ENTRY_PATH: - if((CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_FTP_ENTRY_PATH, &stringp)) - && stringp) - fputs(stringp, stream); - break; - case VAR_REDIRECT_URL: - if((CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_REDIRECT_URL, &stringp)) - && stringp) - fputs(stringp, stream); - break; - case VAR_SSL_VERIFY_RESULT: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_SSL_VERIFYRESULT, - &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_PROXY_SSL_VERIFY_RESULT: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_PROXY_SSL_VERIFYRESULT, - &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_EFFECTIVE_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)) && stringp) - fputs(stringp, stream); - break; - case VAR_PRIMARY_PORT: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_PRIMARY_PORT, - &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_LOCAL_IP: - if((CURLE_OK == curl_easy_getinfo(curl, CURLINFO_LOCAL_IP, - &stringp)) && stringp) - fputs(stringp, stream); - break; - case VAR_LOCAL_PORT: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_LOCAL_PORT, - &longinfo)) - fprintf(stream, "%ld", longinfo); - break; - case VAR_HTTP_VERSION: - if(CURLE_OK == - curl_easy_getinfo(curl, CURLINFO_HTTP_VERSION, - &longinfo)) { - const char *version = "0"; - switch(longinfo) { - case CURL_HTTP_VERSION_1_0: - version = "1.0"; - break; - case CURL_HTTP_VERSION_1_1: - version = "1.1"; - break; - case CURL_HTTP_VERSION_2_0: - version = "2"; - break; - case CURL_HTTP_VERSION_3: - version = "3"; - break; - } - - fprintf(stream, version); - } - break; - case VAR_SCHEME: - if((CURLE_OK == curl_easy_getinfo(curl, CURLINFO_SCHEME, - &stringp)) && stringp) - fputs(stringp, stream); - break; case VAR_STDOUT: stream = stdout; break; @@ -347,8 +351,11 @@ void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo, stream = stderr; break; case VAR_JSON: - ourWriteOutJSON(variables, curl, per, stream); + ourWriteOutJSON(stream, variables, per, per_result); + break; default: + (void)variables[i].writefunc(stream, &variables[i], + per, per_result, false); break; } break; diff --git a/src/tool_writeout.h b/src/tool_writeout.h index 02858d8c1..2cf7d012a 100644 --- a/src/tool_writeout.h +++ b/src/tool_writeout.h @@ -69,25 +69,16 @@ typedef enum { VAR_NUM_OF_VARS /* must be the last */ } writeoutid; -typedef enum { - JSON_NONE, - JSON_STRING, - JSON_LONG, - JSON_OFFSET, - JSON_TIME, - JSON_VERSION, - JSON_FILENAME -} jsontype; - struct writeoutvar { const char *name; writeoutid id; - int is_ctrl; - CURLINFO cinfo; - jsontype jsontype; + CURLINFO ci; + int (*writefunc)(FILE *stream, const struct writeoutvar *wovar, + struct per_transfer *per, CURLcode per_result, + bool use_json); }; -void ourWriteOut(CURL *curl, struct per_transfer *per, const char *writeinfo, - CURLcode exitcode); +void ourWriteOut(const char *writeinfo, struct per_transfer *per, + CURLcode per_result); #endif /* HEADER_CURL_TOOL_WRITEOUT_H */ diff --git a/src/tool_writeout_json.c b/src/tool_writeout_json.c index 186ed6851..ca1e70868 100644 --- a/src/tool_writeout_json.c +++ b/src/tool_writeout_json.c @@ -30,15 +30,7 @@ #include "tool_writeout.h" -static const char *http_version[] = { - "0", /* CURL_HTTP_VERSION_NONE */ - "1", /* CURL_HTTP_VERSION_1_0 */ - "1.1", /* CURL_HTTP_VERSION_1_1 */ - "2", /* CURL_HTTP_VERSION_2 */ - "3" /* CURL_HTTP_VERSION_3 */ -}; - -static void jsonEscape(FILE *stream, const char *in) +void jsonWriteString(FILE *stream, const char *in) { const char *i = in; const char *in_end = in + strlen(in); @@ -78,126 +70,22 @@ static void jsonEscape(FILE *stream, const char *in) } } -static int writeTime(FILE *str, CURL *curl, const char *key, CURLINFO ci) -{ - curl_off_t val = 0; - if(CURLE_OK == curl_easy_getinfo(curl, ci, &val)) { - curl_off_t s = val / 1000000l; - curl_off_t ms = val % 1000000l; - fprintf(str, "\"%s\":%" CURL_FORMAT_CURL_OFF_T - ".%06" CURL_FORMAT_CURL_OFF_T, key, s, ms); - return 1; - } - return 0; -} - -static int writeString(FILE *str, CURL *curl, const char *key, CURLINFO ci) -{ - char *valp = NULL; - if((CURLE_OK == curl_easy_getinfo(curl, ci, &valp)) && valp) { - fprintf(str, "\"%s\":\"", key); - jsonEscape(str, valp); - fprintf(str, "\""); - return 1; - } - return 0; -} - -static int writeLong(FILE *str, CURL *curl, const char *key, CURLINFO ci, - struct per_transfer *per, const struct writeoutvar *wovar) -{ - 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; -} - -static int writeOffset(FILE *str, CURL *curl, const char *key, CURLINFO ci) -{ - curl_off_t val = 0; - if(CURLE_OK == curl_easy_getinfo(curl, ci, &val)) { - fprintf(str, "\"%s\":%" CURL_FORMAT_CURL_OFF_T, key, val); - return 1; - } - return 0; -} - -static int writeFilename(FILE *str, const char *key, const char *filename) -{ - if(filename) { - fprintf(str, "\"%s\":\"", key); - jsonEscape(str, filename); - fprintf(str, "\""); - } - else { - fprintf(str, "\"%s\":null", key); - } - return 1; -} - -static int writeVersion(FILE *str, CURL *curl, const char *key, CURLINFO ci) -{ - long version = 0; - if(CURLE_OK == curl_easy_getinfo(curl, ci, &version) && - (version >= 0) && - (version < (long)(sizeof(http_version)/sizeof(char *)))) { - fprintf(str, "\"%s\":\"%s\"", key, http_version[version]); - return 1; - } - return 0; -} - -void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, - struct per_transfer *per, FILE *stream) +void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], + struct per_transfer *per, CURLcode per_result) { 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; - - if(mappings[i].is_ctrl == 1) { - continue; - } - - switch(mappings[i].jsontype) { - case JSON_STRING: - ok = writeString(stream, curl, name, cinfo); - break; - case JSON_LONG: - ok = writeLong(stream, curl, name, cinfo, per, wovar); - break; - case JSON_OFFSET: - ok = writeOffset(stream, curl, name, cinfo); - break; - case JSON_TIME: - ok = writeTime(stream, curl, name, cinfo); - break; - case JSON_FILENAME: - ok = writeFilename(stream, name, per->outs.filename); - break; - case JSON_VERSION: - ok = writeVersion(stream, curl, name, cinfo); - break; - default: - break; - } - - if(ok) { + if(mappings[i].writefunc && + mappings[i].writefunc(stream, &mappings[i], per, per_result, true)) fputs(",", stream); - } } - fprintf(stream, "\"curl_version\":\"%s\"}", curl_version()); + /* The variables are sorted in alphabetical order but as a special case + curl_version (which is not actually a --write-out variable) is last. */ + fprintf(stream, "\"curl_version\":\""); + jsonWriteString(stream, curl_version()); + fprintf(stream, "\"}"); } diff --git a/src/tool_writeout_json.h b/src/tool_writeout_json.h index cc8c4e585..3eb75243c 100644 --- a/src/tool_writeout_json.h +++ b/src/tool_writeout_json.h @@ -24,7 +24,9 @@ #include "tool_setup.h" #include "tool_writeout.h" -void ourWriteOutJSON(const struct writeoutvar mappings[], CURL *curl, - struct per_transfer *per, FILE *stream); +void ourWriteOutJSON(FILE *stream, const struct writeoutvar mappings[], + struct per_transfer *per, CURLcode per_result); + +void jsonWriteString(FILE *stream, const char *in); #endif /* HEADER_CURL_TOOL_WRITEOUT_H */ diff --git a/tests/data/test970 b/tests/data/test970 index d2f9aa7b0..6a89a5455 100644 --- a/tests/data/test970 +++ b/tests/data/test970 @@ -59,7 +59,7 @@ Accept: */* -{"content_type":"text/html","filename_effective":"log/out970","http_code":200,"http_connect":0,"http_version":"1.1","local_ip":"127.0.0.1","local_port":13,"method":"GET","num_connects":1,"num_headers":9,"num_redirects":0,"proxy_ssl_verify_result":0,"remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"response_code":200,"scheme":"HTTP","size_download":445,"size_header":4019,"size_request":4019,"size_upload":0,"speed_download":13,"speed_upload":13,"ssl_verify_result":0,"time_appconnect":0.000013,"time_connect":0.000013,"time_namelookup":0.000013,"time_pretransfer":0.000013,"time_redirect":0.000013,"time_starttransfer":0.000013,"time_total":0.000013,"url_effective":"http://%HOSTIP:%HTTPPORT/970","curl_version":"curl-unit-test-fake-version"} +{"content_type":"text/html","errormsg":null,"exitcode":0,"filename_effective":"log/out970","ftp_entry_path":null,"http_code":200,"http_connect":000,"http_version":"1.1","local_ip":"127.0.0.1","local_port":13,"method":"GET","num_connects":1,"num_headers":9,"num_redirects":0,"proxy_ssl_verify_result":0,"redirect_url":null,"remote_ip":"%HOSTIP","remote_port":%HTTPPORT,"response_code":200,"scheme":"HTTP","size_download":445,"size_header":4019,"size_request":4019,"size_upload":0,"speed_download":13,"speed_upload":13,"ssl_verify_result":0,"time_appconnect":0.000013,"time_connect":0.000013,"time_namelookup":0.000013,"time_pretransfer":0.000013,"time_redirect":0.000013,"time_starttransfer":0.000013,"time_total":0.000013,"url":"http://%HOSTIP:%HTTPPORT/970","url_effective":"http://%HOSTIP:%HTTPPORT/970","urlnum":0,"curl_version":"curl-unit-test-fake-version"}