tool_writeout.c 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. /***************************************************************************
  2. * _ _ ____ _
  3. * Project ___| | | | _ \| |
  4. * / __| | | | |_) | |
  5. * | (__| |_| | _ <| |___
  6. * \___|\___/|_| \_\_____|
  7. *
  8. * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
  9. *
  10. * This software is licensed as described in the file COPYING, which
  11. * you should have received as part of this distribution. The terms
  12. * are also available at https://curl.se/docs/copyright.html.
  13. *
  14. * You may opt to use, copy, modify, merge, publish, distribute and/or sell
  15. * copies of the Software, and permit persons to whom the Software is
  16. * furnished to do so, under the terms of the COPYING file.
  17. *
  18. * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
  19. * KIND, either express or implied.
  20. *
  21. ***************************************************************************/
  22. #include "tool_setup.h"
  23. #define ENABLE_CURLX_PRINTF
  24. /* use our own printf() functions */
  25. #include "curlx.h"
  26. #include "tool_cfgable.h"
  27. #include "tool_writeout.h"
  28. #include "tool_writeout_json.h"
  29. #include "memdebug.h" /* keep this as LAST include */
  30. static int writeTime(FILE *stream, const struct writeoutvar *wovar,
  31. struct per_transfer *per, CURLcode per_result,
  32. bool use_json);
  33. static int writeString(FILE *stream, const struct writeoutvar *wovar,
  34. struct per_transfer *per, CURLcode per_result,
  35. bool use_json);
  36. static int writeLong(FILE *stream, const struct writeoutvar *wovar,
  37. struct per_transfer *per, CURLcode per_result,
  38. bool use_json);
  39. static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
  40. struct per_transfer *per, CURLcode per_result,
  41. bool use_json);
  42. static const char *http_version[] = {
  43. "0", /* CURL_HTTP_VERSION_NONE */
  44. "1", /* CURL_HTTP_VERSION_1_0 */
  45. "1.1", /* CURL_HTTP_VERSION_1_1 */
  46. "2", /* CURL_HTTP_VERSION_2 */
  47. "3" /* CURL_HTTP_VERSION_3 */
  48. };
  49. /* The designated write function should be the same as the CURLINFO return type
  50. with exceptions special cased in the respective function. For example,
  51. http_version uses CURLINFO_HTTP_VERSION which returns the version as a long,
  52. however it is output as a string and therefore is handled in writeString.
  53. Yes: "http_version": "1.1"
  54. No: "http_version": 1.1
  55. Variable names should be in alphabetical order.
  56. */
  57. static const struct writeoutvar variables[] = {
  58. {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString},
  59. {"errormsg", VAR_ERRORMSG, 0, writeString},
  60. {"exitcode", VAR_EXITCODE, 0, writeLong},
  61. {"filename_effective", VAR_EFFECTIVE_FILENAME, 0, writeString},
  62. {"ftp_entry_path", VAR_FTP_ENTRY_PATH, CURLINFO_FTP_ENTRY_PATH, writeString},
  63. {"http_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
  64. {"http_connect", VAR_HTTP_CODE_PROXY, CURLINFO_HTTP_CONNECTCODE, writeLong},
  65. {"http_version", VAR_HTTP_VERSION, CURLINFO_HTTP_VERSION, writeString},
  66. {"json", VAR_JSON, 0, NULL},
  67. {"local_ip", VAR_LOCAL_IP, CURLINFO_LOCAL_IP, writeString},
  68. {"local_port", VAR_LOCAL_PORT, CURLINFO_LOCAL_PORT, writeLong},
  69. {"method", VAR_EFFECTIVE_METHOD, CURLINFO_EFFECTIVE_METHOD, writeString},
  70. {"num_connects", VAR_NUM_CONNECTS, CURLINFO_NUM_CONNECTS, writeLong},
  71. {"num_headers", VAR_NUM_HEADERS, 0, writeLong},
  72. {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong},
  73. {"onerror", VAR_ONERROR, 0, NULL},
  74. {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT,
  75. CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong},
  76. {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString},
  77. {"referer", VAR_REFERER, CURLINFO_REFERER, writeString},
  78. {"remote_ip", VAR_PRIMARY_IP, CURLINFO_PRIMARY_IP, writeString},
  79. {"remote_port", VAR_PRIMARY_PORT, CURLINFO_PRIMARY_PORT, writeLong},
  80. {"response_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
  81. {"scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString},
  82. {"size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset},
  83. {"size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong},
  84. {"size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong},
  85. {"size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset},
  86. {"speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T,
  87. writeOffset},
  88. {"speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset},
  89. {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, CURLINFO_SSL_VERIFYRESULT,
  90. writeLong},
  91. {"stderr", VAR_STDERR, 0, NULL},
  92. {"stdout", VAR_STDOUT, 0, NULL},
  93. {"time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T,
  94. writeTime},
  95. {"time_connect", VAR_CONNECT_TIME, CURLINFO_CONNECT_TIME_T, writeTime},
  96. {"time_namelookup", VAR_NAMELOOKUP_TIME, CURLINFO_NAMELOOKUP_TIME_T,
  97. writeTime},
  98. {"time_pretransfer", VAR_PRETRANSFER_TIME, CURLINFO_PRETRANSFER_TIME_T,
  99. writeTime},
  100. {"time_redirect", VAR_REDIRECT_TIME, CURLINFO_REDIRECT_TIME_T, writeTime},
  101. {"time_starttransfer", VAR_STARTTRANSFER_TIME, CURLINFO_STARTTRANSFER_TIME_T,
  102. writeTime},
  103. {"time_total", VAR_TOTAL_TIME, CURLINFO_TOTAL_TIME_T, writeTime},
  104. {"url", VAR_INPUT_URL, 0, writeString},
  105. {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString},
  106. {"urlnum", VAR_URLNUM, 0, writeLong},
  107. {NULL, VAR_NONE, 0, NULL}
  108. };
  109. static int writeTime(FILE *stream, const struct writeoutvar *wovar,
  110. struct per_transfer *per, CURLcode per_result,
  111. bool use_json)
  112. {
  113. bool valid = false;
  114. curl_off_t us = 0;
  115. (void)per;
  116. (void)per_result;
  117. DEBUGASSERT(wovar->writefunc == writeTime);
  118. if(wovar->ci) {
  119. if(!curl_easy_getinfo(per->curl, wovar->ci, &us))
  120. valid = true;
  121. }
  122. else {
  123. DEBUGASSERT(0);
  124. }
  125. if(valid) {
  126. curl_off_t secs = us / 1000000;
  127. us %= 1000000;
  128. if(use_json)
  129. fprintf(stream, "\"%s\":", wovar->name);
  130. fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU
  131. ".%06" CURL_FORMAT_CURL_OFF_TU, secs, us);
  132. }
  133. else {
  134. if(use_json)
  135. fprintf(stream, "\"%s\":null", wovar->name);
  136. }
  137. return 1; /* return 1 if anything was written */
  138. }
  139. static int writeString(FILE *stream, const struct writeoutvar *wovar,
  140. struct per_transfer *per, CURLcode per_result,
  141. bool use_json)
  142. {
  143. bool valid = false;
  144. const char *strinfo = NULL;
  145. DEBUGASSERT(wovar->writefunc == writeString);
  146. if(wovar->ci) {
  147. if(wovar->ci == CURLINFO_HTTP_VERSION) {
  148. long version = 0;
  149. if(!curl_easy_getinfo(per->curl, CURLINFO_HTTP_VERSION, &version) &&
  150. (version >= 0) &&
  151. (version < (long)(sizeof(http_version)/sizeof(http_version[0])))) {
  152. strinfo = http_version[version];
  153. valid = true;
  154. }
  155. }
  156. else {
  157. if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo)
  158. valid = true;
  159. }
  160. }
  161. else {
  162. switch(wovar->id) {
  163. case VAR_ERRORMSG:
  164. if(per_result) {
  165. strinfo = per->errorbuffer[0] ? per->errorbuffer :
  166. curl_easy_strerror(per_result);
  167. valid = true;
  168. }
  169. break;
  170. case VAR_EFFECTIVE_FILENAME:
  171. if(per->outs.filename) {
  172. strinfo = per->outs.filename;
  173. valid = true;
  174. }
  175. break;
  176. case VAR_INPUT_URL:
  177. if(per->this_url) {
  178. strinfo = per->this_url;
  179. valid = true;
  180. }
  181. break;
  182. default:
  183. DEBUGASSERT(0);
  184. break;
  185. }
  186. }
  187. if(valid) {
  188. DEBUGASSERT(strinfo);
  189. if(use_json) {
  190. fprintf(stream, "\"%s\":\"", wovar->name);
  191. jsonWriteString(stream, strinfo);
  192. fputs("\"", stream);
  193. }
  194. else
  195. fputs(strinfo, stream);
  196. }
  197. else {
  198. if(use_json)
  199. fprintf(stream, "\"%s\":null", wovar->name);
  200. }
  201. return 1; /* return 1 if anything was written */
  202. }
  203. static int writeLong(FILE *stream, const struct writeoutvar *wovar,
  204. struct per_transfer *per, CURLcode per_result,
  205. bool use_json)
  206. {
  207. bool valid = false;
  208. long longinfo = 0;
  209. DEBUGASSERT(wovar->writefunc == writeLong);
  210. if(wovar->ci) {
  211. if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo))
  212. valid = true;
  213. }
  214. else {
  215. switch(wovar->id) {
  216. case VAR_NUM_HEADERS:
  217. longinfo = per->num_headers;
  218. valid = true;
  219. break;
  220. case VAR_EXITCODE:
  221. longinfo = per_result;
  222. valid = true;
  223. break;
  224. case VAR_URLNUM:
  225. if(per->urlnum <= INT_MAX) {
  226. longinfo = (long)per->urlnum;
  227. valid = true;
  228. }
  229. break;
  230. default:
  231. DEBUGASSERT(0);
  232. break;
  233. }
  234. }
  235. if(valid) {
  236. if(use_json)
  237. fprintf(stream, "\"%s\":%ld", wovar->name, longinfo);
  238. else {
  239. if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY)
  240. fprintf(stream, "%03ld", longinfo);
  241. else
  242. fprintf(stream, "%ld", longinfo);
  243. }
  244. }
  245. else {
  246. if(use_json)
  247. fprintf(stream, "\"%s\":null", wovar->name);
  248. }
  249. return 1; /* return 1 if anything was written */
  250. }
  251. static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
  252. struct per_transfer *per, CURLcode per_result,
  253. bool use_json)
  254. {
  255. bool valid = false;
  256. curl_off_t offinfo = 0;
  257. (void)per;
  258. (void)per_result;
  259. DEBUGASSERT(wovar->writefunc == writeOffset);
  260. if(wovar->ci) {
  261. if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo))
  262. valid = true;
  263. }
  264. else {
  265. DEBUGASSERT(0);
  266. }
  267. if(valid) {
  268. if(use_json)
  269. fprintf(stream, "\"%s\":", wovar->name);
  270. fprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo);
  271. }
  272. else {
  273. if(use_json)
  274. fprintf(stream, "\"%s\":null", wovar->name);
  275. }
  276. return 1; /* return 1 if anything was written */
  277. }
  278. void ourWriteOut(const char *writeinfo, struct per_transfer *per,
  279. CURLcode per_result)
  280. {
  281. FILE *stream = stdout;
  282. const char *ptr = writeinfo;
  283. bool done = FALSE;
  284. while(ptr && *ptr && !done) {
  285. if('%' == *ptr && ptr[1]) {
  286. if('%' == ptr[1]) {
  287. /* an escaped %-letter */
  288. fputc('%', stream);
  289. ptr += 2;
  290. }
  291. else {
  292. /* this is meant as a variable to output */
  293. char *end;
  294. if('{' == ptr[1]) {
  295. char keepit;
  296. int i;
  297. bool match = FALSE;
  298. end = strchr(ptr, '}');
  299. ptr += 2; /* pass the % and the { */
  300. if(!end) {
  301. fputs("%{", stream);
  302. continue;
  303. }
  304. keepit = *end;
  305. *end = 0; /* null-terminate */
  306. for(i = 0; variables[i].name; i++) {
  307. if(curl_strequal(ptr, variables[i].name)) {
  308. match = TRUE;
  309. switch(variables[i].id) {
  310. case VAR_ONERROR:
  311. if(per_result == CURLE_OK)
  312. /* this isn't error so skip the rest */
  313. done = TRUE;
  314. break;
  315. case VAR_STDOUT:
  316. stream = stdout;
  317. break;
  318. case VAR_STDERR:
  319. stream = stderr;
  320. break;
  321. case VAR_JSON:
  322. ourWriteOutJSON(stream, variables, per, per_result);
  323. break;
  324. default:
  325. (void)variables[i].writefunc(stream, &variables[i],
  326. per, per_result, false);
  327. break;
  328. }
  329. break;
  330. }
  331. }
  332. if(!match) {
  333. fprintf(stderr, "curl: unknown --write-out variable: '%s'\n", ptr);
  334. }
  335. ptr = end + 1; /* pass the end */
  336. *end = keepit;
  337. }
  338. else {
  339. /* illegal syntax, then just output the characters that are used */
  340. fputc('%', stream);
  341. fputc(ptr[1], stream);
  342. ptr += 2;
  343. }
  344. }
  345. }
  346. else if('\\' == *ptr && ptr[1]) {
  347. switch(ptr[1]) {
  348. case 'r':
  349. fputc('\r', stream);
  350. break;
  351. case 'n':
  352. fputc('\n', stream);
  353. break;
  354. case 't':
  355. fputc('\t', stream);
  356. break;
  357. default:
  358. /* unknown, just output this */
  359. fputc(*ptr, stream);
  360. fputc(ptr[1], stream);
  361. break;
  362. }
  363. ptr += 2;
  364. }
  365. else {
  366. fputc(*ptr, stream);
  367. ptr++;
  368. }
  369. }
  370. }