Browse Source

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
Jay Satiro 3 years ago
parent
commit
65ca229461
7 changed files with 305 additions and 416 deletions
  1. 1 1
      docs/cmdline-opts/write-out.d
  2. 4 3
      src/tool_operate.c
  3. 278 271
      src/tool_writeout.c
  4. 6 15
      src/tool_writeout.h
  5. 11 123
      src/tool_writeout_json.c
  6. 4 2
      src/tool_writeout_json.h
  7. 1 1
      tests/data/test970

+ 1 - 1
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

+ 4 - 3
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);

+ 278 - 271
src/tool_writeout.c

@@ -29,77 +29,288 @@
 
 #include "memdebug.h" /* keep this as LAST include */
 
+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 */
+};
+
+/* 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, 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}
+  {"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 void us2sec(FILE *stream, curl_off_t us)
+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;

+ 6 - 15
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 */

+ 11 - 123
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) {
+  for(i = 0; mappings[i].name != NULL; i++) {
+    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, "\"}");
 }

+ 4 - 2
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 */

+ 1 - 1
tests/data/test970

@@ -59,7 +59,7 @@ Accept: */*
 
 </protocol>
 <stdout nonewline="yes">
-{"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"}
 </stdout>
 </verify>
 </testcase>