/*************************************************************************** * _ _ ____ _ * Project ___| | | | _ \| | * / __| | | | |_) | | * | (__| |_| | _ <| |___ * \___|\___/|_| \_\_____| * * Copyright (C) Daniel Stenberg, , 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 "dynbuf.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[] = { {"certs", VAR_CERT, CURLINFO_NONE, writeString}, {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString}, {"conn_id", VAR_CONN_ID, CURLINFO_CONN_ID, writeOffset}, {"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_certs", VAR_NUM_CERTS, CURLINFO_NONE, writeLong}, {"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}, {"proxy_used", VAR_PROXY_USED, CURLINFO_USED_PROXY, 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.scheme", VAR_INPUT_URLSCHEME, CURLINFO_NONE, writeString}, {"url.user", VAR_INPUT_URLUSER, CURLINFO_NONE, writeString}, {"url.password", VAR_INPUT_URLPASSWORD, CURLINFO_NONE, writeString}, {"url.options", VAR_INPUT_URLOPTIONS, CURLINFO_NONE, writeString}, {"url.host", VAR_INPUT_URLHOST, CURLINFO_NONE, writeString}, {"url.port", VAR_INPUT_URLPORT, CURLINFO_NONE, writeString}, {"url.path", VAR_INPUT_URLPATH, CURLINFO_NONE, writeString}, {"url.query", VAR_INPUT_URLQUERY, CURLINFO_NONE, writeString}, {"url.fragment", VAR_INPUT_URLFRAGMENT, CURLINFO_NONE, writeString}, {"url.zoneid", VAR_INPUT_URLZONEID, CURLINFO_NONE, writeString}, {"urle.scheme", VAR_INPUT_URLESCHEME, CURLINFO_NONE, writeString}, {"urle.user", VAR_INPUT_URLEUSER, CURLINFO_NONE, writeString}, {"urle.password", VAR_INPUT_URLEPASSWORD, CURLINFO_NONE, writeString}, {"urle.options", VAR_INPUT_URLEOPTIONS, CURLINFO_NONE, writeString}, {"urle.host", VAR_INPUT_URLEHOST, CURLINFO_NONE, writeString}, {"urle.port", VAR_INPUT_URLEPORT, CURLINFO_NONE, writeString}, {"urle.path", VAR_INPUT_URLEPATH, CURLINFO_NONE, writeString}, {"urle.query", VAR_INPUT_URLEQUERY, CURLINFO_NONE, writeString}, {"urle.fragment", VAR_INPUT_URLEFRAGMENT, CURLINFO_NONE, writeString}, {"urle.zoneid", VAR_INPUT_URLEZONEID, CURLINFO_NONE, writeString}, {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString}, {"urlnum", VAR_URLNUM, CURLINFO_NONE, writeLong}, {"xfer_id", VAR_EASY_ID, CURLINFO_XFER_ID, writeOffset}, {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 urlpart(struct per_transfer *per, writeoutid vid, const char **contentp) { CURLU *uh = curl_url(); int rc = 0; if(uh) { CURLUPart cpart = CURLUPART_HOST; char *part = NULL; const char *url = NULL; if(vid >= VAR_INPUT_URLEHOST) { if(curl_easy_getinfo(per->curl, CURLINFO_EFFECTIVE_URL, &url)) rc = 5; } else url = per->this_url; if(!rc) { switch(vid) { case VAR_INPUT_URLSCHEME: case VAR_INPUT_URLESCHEME: cpart = CURLUPART_SCHEME; break; case VAR_INPUT_URLUSER: case VAR_INPUT_URLEUSER: cpart = CURLUPART_USER; break; case VAR_INPUT_URLPASSWORD: case VAR_INPUT_URLEPASSWORD: cpart = CURLUPART_PASSWORD; break; case VAR_INPUT_URLOPTIONS: case VAR_INPUT_URLEOPTIONS: cpart = CURLUPART_OPTIONS; break; case VAR_INPUT_URLHOST: case VAR_INPUT_URLEHOST: cpart = CURLUPART_HOST; break; case VAR_INPUT_URLPORT: case VAR_INPUT_URLEPORT: cpart = CURLUPART_PORT; break; case VAR_INPUT_URLPATH: case VAR_INPUT_URLEPATH: cpart = CURLUPART_PATH; break; case VAR_INPUT_URLQUERY: case VAR_INPUT_URLEQUERY: cpart = CURLUPART_QUERY; break; case VAR_INPUT_URLFRAGMENT: case VAR_INPUT_URLEFRAGMENT: cpart = CURLUPART_FRAGMENT; break; case VAR_INPUT_URLZONEID: case VAR_INPUT_URLEZONEID: cpart = CURLUPART_ZONEID; break; default: /* not implemented */ rc = 4; break; } } if(!rc && curl_url_set(uh, CURLUPART_URL, url, CURLU_GUESS_SCHEME|CURLU_NON_SUPPORT_SCHEME)) rc = 2; if(!rc && curl_url_get(uh, cpart, &part, CURLU_DEFAULT_PORT)) rc = 3; if(!rc && part) *contentp = part; curl_url_cleanup(uh); } else return 1; return rc; } 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; const char *freestr = NULL; struct dynbuf buf; curlx_dyn_init(&buf, 256*1024); 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_CERT: if(per->certinfo) { int i; bool error = FALSE; for(i = 0; (i < per->certinfo->num_of_certs) && !error; i++) { struct curl_slist *slist; for(slist = per->certinfo->certinfo[i]; slist; slist = slist->next) { size_t len; if(curl_strnequal(slist->data, "cert:", 5)) { if(curlx_dyn_add(&buf, &slist->data[5])) { error = TRUE; break; } } else { if(curlx_dyn_add(&buf, slist->data)) { error = TRUE; break; } } len = curlx_dyn_len(&buf); if(len) { char *ptr = curlx_dyn_ptr(&buf); if(ptr[len -1] != '\n') { /* add a newline to make things look better */ if(curlx_dyn_addn(&buf, "\n", 1)) { error = TRUE; break; } } } } } if(!error) { strinfo = curlx_dyn_ptr(&buf); if(!strinfo) /* maybe not a TLS protocol */ strinfo = ""; valid = true; } } else strinfo = ""; /* no cert info */ break; 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; case VAR_INPUT_URLSCHEME: case VAR_INPUT_URLUSER: case VAR_INPUT_URLPASSWORD: case VAR_INPUT_URLOPTIONS: case VAR_INPUT_URLHOST: case VAR_INPUT_URLPORT: case VAR_INPUT_URLPATH: case VAR_INPUT_URLQUERY: case VAR_INPUT_URLFRAGMENT: case VAR_INPUT_URLZONEID: case VAR_INPUT_URLESCHEME: case VAR_INPUT_URLEUSER: case VAR_INPUT_URLEPASSWORD: case VAR_INPUT_URLEOPTIONS: case VAR_INPUT_URLEHOST: case VAR_INPUT_URLEPORT: case VAR_INPUT_URLEPATH: case VAR_INPUT_URLEQUERY: case VAR_INPUT_URLEFRAGMENT: case VAR_INPUT_URLEZONEID: if(per->this_url) { if(!urlpart(per, wovar->id, &strinfo)) { freestr = strinfo; 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); } curl_free((char *)freestr); curlx_dyn_free(&buf); 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_CERTS: longinfo = per->certinfo ? per->certinfo->num_of_certs : 0; valid = true; break; case VAR_NUM_HEADERS: longinfo = per->num_headers; valid = true; break; case VAR_EXITCODE: longinfo = (long)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(struct OperationConfig *config, struct per_transfer *per, CURLcode per_result) { FILE *stream = stdout; const char *writeinfo = config->writeout; const char *ptr = writeinfo; bool done = FALSE; struct curl_certinfo *certinfo; CURLcode res = curl_easy_getinfo(per->curl, CURLINFO_CERTINFO, &certinfo); bool fclose_stream = FALSE; if(!writeinfo) return; if(!res && certinfo) per->certinfo = certinfo; 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: if(fclose_stream) fclose(stream); fclose_stream = FALSE; stream = stdout; break; case VAR_STDERR: if(fclose_stream) fclose(stream); fclose_stream = FALSE; stream = tool_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(tool_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 if(!strncmp("output{", &ptr[1], 7)) { bool append = FALSE; ptr += 8; if((ptr[0] == '>') && (ptr[1] == '>')) { append = TRUE; ptr += 2; } end = strchr(ptr, '}'); if(end) { char fname[512]; /* holds the longest file name */ size_t flen = end - ptr; if(flen < sizeof(fname)) { FILE *stream2; memcpy(fname, ptr, flen); fname[flen] = 0; stream2 = fopen(fname, append? FOPEN_APPENDTEXT : FOPEN_WRITETEXT); if(stream2) { /* only change if the open worked */ if(fclose_stream) fclose(stream); stream = stream2; fclose_stream = TRUE; } } ptr = end + 1; } else fputs("%output{", 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++; } } if(fclose_stream) fclose(stream); }