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"}