|  | /*************************************************************************** | 
|  | *                                  _   _ ____  _ | 
|  | *  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++; | 
|  | } | 
|  | } | 
|  | } |