| /*************************************************************************** | 
 |  *                                  _   _ ____  _ | 
 |  *  Project                     ___| | | |  _ \| | | 
 |  *                             / __| | | | |_) | | | 
 |  *                            | (__| |_| |  _ <| |___ | 
 |  *                             \___|\___/|_| \_\_____| | 
 |  * | 
 |  * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al. | 
 |  * | 
 |  * This software is licensed as described in the file COPYING, which | 
 |  * you should have received as part of this distribution. The terms | 
 |  * are also available at https://curl.se/docs/copyright.html. | 
 |  * | 
 |  * You may opt to use, copy, modify, merge, publish, distribute and/or sell | 
 |  * copies of the Software, and permit persons to whom the Software is | 
 |  * furnished to do so, under the terms of the COPYING file. | 
 |  * | 
 |  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | 
 |  * KIND, either express or implied. | 
 |  * | 
 |  * SPDX-License-Identifier: curl | 
 |  * | 
 |  ***************************************************************************/ | 
 | #include "tool_setup.h" | 
 | #define ENABLE_CURLX_PRINTF | 
 | /* use our own printf() functions */ | 
 | #include "curlx.h" | 
 | #include "tool_cfgable.h" | 
 | #include "tool_writeout.h" | 
 | #include "tool_writeout_json.h" | 
 |  | 
 | #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); | 
 |  | 
 | struct httpmap { | 
 |   const char *str; | 
 |   int num; | 
 | }; | 
 |  | 
 | static const struct httpmap 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}, | 
 |   { NULL, 0} /* end of list */ | 
 | }; | 
 |  | 
 | /* 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, CURLINFO_NONE, writeString}, | 
 |   {"exitcode", VAR_EXITCODE, CURLINFO_NONE, writeLong}, | 
 |   {"filename_effective", VAR_EFFECTIVE_FILENAME, CURLINFO_NONE, writeString}, | 
 |   {"ftp_entry_path", VAR_FTP_ENTRY_PATH, CURLINFO_FTP_ENTRY_PATH, writeString}, | 
 |   {"header_json", VAR_HEADER_JSON, CURLINFO_NONE, NULL}, | 
 |   {"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, CURLINFO_NONE, 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, CURLINFO_NONE, writeLong}, | 
 |   {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong}, | 
 |   {"onerror", VAR_ONERROR, CURLINFO_NONE, NULL}, | 
 |   {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT, | 
 |    CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong}, | 
 |   {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString}, | 
 |   {"referer", VAR_REFERER, CURLINFO_REFERER, 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, CURLINFO_NONE, NULL}, | 
 |   {"stdout", VAR_STDOUT, CURLINFO_NONE, 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, CURLINFO_NONE, writeString}, | 
 |   {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString}, | 
 |   {"urlnum", VAR_URLNUM, CURLINFO_NONE, writeLong}, | 
 |   {NULL, VAR_NONE, CURLINFO_NONE, NULL} | 
 | }; | 
 |  | 
 | static int writeTime(FILE *stream, const struct writeoutvar *wovar, | 
 |                      struct per_transfer *per, CURLcode per_result, | 
 |                      bool use_json) | 
 | { | 
 |   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 */ | 
 | } | 
 |  | 
 | 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)) { | 
 |         const struct httpmap *m = &http_version[0]; | 
 |         while(m->str) { | 
 |           if(m->num == version) { | 
 |             strinfo = m->str; | 
 |             valid = true; | 
 |             break; | 
 |           } | 
 |           m++; | 
 |         } | 
 |       } | 
 |     } | 
 |     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 && 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, FALSE); | 
 |     } | 
 |     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\":%ld", wovar->name, longinfo); | 
 |     else { | 
 |       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; | 
 |   bool done = FALSE; | 
 |  | 
 |   while(ptr && *ptr && !done) { | 
 |     if('%' == *ptr && ptr[1]) { | 
 |       if('%' == ptr[1]) { | 
 |         /* an escaped %-letter */ | 
 |         fputc('%', stream); | 
 |         ptr += 2; | 
 |       } | 
 |       else { | 
 |         /* this is meant as a variable to output */ | 
 |         char *end; | 
 |         size_t vlen; | 
 |         if('{' == ptr[1]) { | 
 |           int i; | 
 |           bool match = FALSE; | 
 |           end = strchr(ptr, '}'); | 
 |           ptr += 2; /* pass the % and the { */ | 
 |           if(!end) { | 
 |             fputs("%{", stream); | 
 |             continue; | 
 |           } | 
 |           vlen = end - ptr; | 
 |           for(i = 0; variables[i].name; i++) { | 
 |             if((strlen(variables[i].name) == vlen) && | 
 |                curl_strnequal(ptr, variables[i].name, vlen)) { | 
 |               match = TRUE; | 
 |               switch(variables[i].id) { | 
 |               case VAR_ONERROR: | 
 |                 if(per_result == CURLE_OK) | 
 |                   /* this isn't error so skip the rest */ | 
 |                   done = TRUE; | 
 |                 break; | 
 |               case VAR_STDOUT: | 
 |                 stream = stdout; | 
 |                 break; | 
 |               case VAR_STDERR: | 
 |                 stream = stderr; | 
 |                 break; | 
 |               case VAR_JSON: | 
 |                 ourWriteOutJSON(stream, variables, per, per_result); | 
 |                 break; | 
 |               case VAR_HEADER_JSON: | 
 |                 headerJSON(stream, per); | 
 |                 break; | 
 |               default: | 
 |                 (void)variables[i].writefunc(stream, &variables[i], | 
 |                                              per, per_result, false); | 
 |                 break; | 
 |               } | 
 |               break; | 
 |             } | 
 |           } | 
 |           if(!match) { | 
 |             fprintf(stderr, "curl: unknown --write-out variable: '%.*s'\n", | 
 |                     (int)vlen, ptr); | 
 |           } | 
 |           ptr = end + 1; /* pass the end */ | 
 |         } | 
 |         else if(!strncmp("header{", &ptr[1], 7)) { | 
 |           ptr += 8; | 
 |           end = strchr(ptr, '}'); | 
 |           if(end) { | 
 |             char hname[256]; /* holds the longest header field name */ | 
 |             struct curl_header *header; | 
 |             vlen = end - ptr; | 
 |             if(vlen < sizeof(hname)) { | 
 |               memcpy(hname, ptr, vlen); | 
 |               hname[vlen] = 0; | 
 |               if(CURLHE_OK == curl_easy_header(per->curl, hname, 0, | 
 |                                                CURLH_HEADER, -1, &header)) | 
 |                 fputs(header->value, stream); | 
 |             } | 
 |             ptr = end + 1; | 
 |           } | 
 |           else | 
 |             fputs("%header{", stream); | 
 |         } | 
 |         else { | 
 |           /* illegal syntax, then just output the characters that are used */ | 
 |           fputc('%', stream); | 
 |           fputc(ptr[1], stream); | 
 |           ptr += 2; | 
 |         } | 
 |       } | 
 |     } | 
 |     else if('\\' == *ptr && ptr[1]) { | 
 |       switch(ptr[1]) { | 
 |       case 'r': | 
 |         fputc('\r', stream); | 
 |         break; | 
 |       case 'n': | 
 |         fputc('\n', stream); | 
 |         break; | 
 |       case 't': | 
 |         fputc('\t', stream); | 
 |         break; | 
 |       default: | 
 |         /* unknown, just output this */ | 
 |         fputc(*ptr, stream); | 
 |         fputc(ptr[1], stream); | 
 |         break; | 
 |       } | 
 |       ptr += 2; | 
 |     } | 
 |     else { | 
 |       fputc(*ptr, stream); | 
 |       ptr++; | 
 |     } | 
 |   } | 
 | } |