1 /***************************************************************************
2 * _ _ ____ _
3 * Project ___| | | | _ \| |
4 * / __| | | | |_) | |
5 * | (__| |_| | _ <| |___
6 * \___|\___/|_| \_\_____|
7 *
8 * Copyright (C) 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 * SPDX-License-Identifier: curl
22 *
23 ***************************************************************************/
24 #include "tool_setup.h"
25 #define ENABLE_CURLX_PRINTF
26 /* use our own printf() functions */
27 #include "curlx.h"
28 #include "tool_cfgable.h"
29 #include "tool_writeout.h"
30 #include "tool_writeout_json.h"
31 #include "dynbuf.h"
32
33 #include "memdebug.h" /* keep this as LAST include */
34
35 static int writeTime(FILE *stream, const struct writeoutvar *wovar,
36 struct per_transfer *per, CURLcode per_result,
37 bool use_json);
38
39 static int writeString(FILE *stream, const struct writeoutvar *wovar,
40 struct per_transfer *per, CURLcode per_result,
41 bool use_json);
42
43 static int writeLong(FILE *stream, const struct writeoutvar *wovar,
44 struct per_transfer *per, CURLcode per_result,
45 bool use_json);
46
47 static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
48 struct per_transfer *per, CURLcode per_result,
49 bool use_json);
50
51 struct httpmap {
52 const char *str;
53 int num;
54 };
55
56 static const struct httpmap http_version[] = {
57 { "0", CURL_HTTP_VERSION_NONE},
58 { "1", CURL_HTTP_VERSION_1_0},
59 { "1.1", CURL_HTTP_VERSION_1_1},
60 { "2", CURL_HTTP_VERSION_2},
61 { "3", CURL_HTTP_VERSION_3},
62 { NULL, 0} /* end of list */
63 };
64
65 /* The designated write function should be the same as the CURLINFO return type
66 with exceptions special cased in the respective function. For example,
67 http_version uses CURLINFO_HTTP_VERSION which returns the version as a long,
68 however it is output as a string and therefore is handled in writeString.
69
70 Yes: "http_version": "1.1"
71 No: "http_version": 1.1
72
73 Variable names should be in alphabetical order.
74 */
75 static const struct writeoutvar variables[] = {
76 {"certs", VAR_CERT, CURLINFO_NONE, writeString},
77 {"content_type", VAR_CONTENT_TYPE, CURLINFO_CONTENT_TYPE, writeString},
78 {"errormsg", VAR_ERRORMSG, CURLINFO_NONE, writeString},
79 {"exitcode", VAR_EXITCODE, CURLINFO_NONE, writeLong},
80 {"filename_effective", VAR_EFFECTIVE_FILENAME, CURLINFO_NONE, writeString},
81 {"ftp_entry_path", VAR_FTP_ENTRY_PATH, CURLINFO_FTP_ENTRY_PATH, writeString},
82 {"header_json", VAR_HEADER_JSON, CURLINFO_NONE, NULL},
83 {"http_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
84 {"http_connect", VAR_HTTP_CODE_PROXY, CURLINFO_HTTP_CONNECTCODE, writeLong},
85 {"http_version", VAR_HTTP_VERSION, CURLINFO_HTTP_VERSION, writeString},
86 {"json", VAR_JSON, CURLINFO_NONE, NULL},
87 {"local_ip", VAR_LOCAL_IP, CURLINFO_LOCAL_IP, writeString},
88 {"local_port", VAR_LOCAL_PORT, CURLINFO_LOCAL_PORT, writeLong},
89 {"method", VAR_EFFECTIVE_METHOD, CURLINFO_EFFECTIVE_METHOD, writeString},
90 {"num_certs", VAR_NUM_CERTS, CURLINFO_NONE, writeLong},
91 {"num_connects", VAR_NUM_CONNECTS, CURLINFO_NUM_CONNECTS, writeLong},
92 {"num_headers", VAR_NUM_HEADERS, CURLINFO_NONE, writeLong},
93 {"num_redirects", VAR_REDIRECT_COUNT, CURLINFO_REDIRECT_COUNT, writeLong},
94 {"onerror", VAR_ONERROR, CURLINFO_NONE, NULL},
95 {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT,
96 CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong},
97 {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString},
98 {"referer", VAR_REFERER, CURLINFO_REFERER, writeString},
99 {"remote_ip", VAR_PRIMARY_IP, CURLINFO_PRIMARY_IP, writeString},
100 {"remote_port", VAR_PRIMARY_PORT, CURLINFO_PRIMARY_PORT, writeLong},
101 {"response_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
102 {"scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString},
103 {"size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset},
104 {"size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong},
105 {"size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong},
106 {"size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset},
107 {"speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T,
108 writeOffset},
109 {"speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset},
110 {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, CURLINFO_SSL_VERIFYRESULT,
111 writeLong},
112 {"stderr", VAR_STDERR, CURLINFO_NONE, NULL},
113 {"stdout", VAR_STDOUT, CURLINFO_NONE, NULL},
114 {"time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T,
115 writeTime},
116 {"time_connect", VAR_CONNECT_TIME, CURLINFO_CONNECT_TIME_T, writeTime},
117 {"time_namelookup", VAR_NAMELOOKUP_TIME, CURLINFO_NAMELOOKUP_TIME_T,
118 writeTime},
119 {"time_pretransfer", VAR_PRETRANSFER_TIME, CURLINFO_PRETRANSFER_TIME_T,
120 writeTime},
121 {"time_redirect", VAR_REDIRECT_TIME, CURLINFO_REDIRECT_TIME_T, writeTime},
122 {"time_starttransfer", VAR_STARTTRANSFER_TIME, CURLINFO_STARTTRANSFER_TIME_T,
123 writeTime},
124 {"time_total", VAR_TOTAL_TIME, CURLINFO_TOTAL_TIME_T, writeTime},
125 {"url", VAR_INPUT_URL, CURLINFO_NONE, writeString},
126 {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString},
127 {"urlnum", VAR_URLNUM, CURLINFO_NONE, writeLong},
128 {NULL, VAR_NONE, CURLINFO_NONE, NULL}
129 };
130
writeTime(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)131 static int writeTime(FILE *stream, const struct writeoutvar *wovar,
132 struct per_transfer *per, CURLcode per_result,
133 bool use_json)
134 {
135 bool valid = false;
136 curl_off_t us = 0;
137
138 (void)per;
139 (void)per_result;
140 DEBUGASSERT(wovar->writefunc == writeTime);
141
142 if(wovar->ci) {
143 if(!curl_easy_getinfo(per->curl, wovar->ci, &us))
144 valid = true;
145 }
146 else {
147 DEBUGASSERT(0);
148 }
149
150 if(valid) {
151 curl_off_t secs = us / 1000000;
152 us %= 1000000;
153
154 if(use_json)
155 fprintf(stream, "\"%s\":", wovar->name);
156
157 fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU
158 ".%06" CURL_FORMAT_CURL_OFF_TU, secs, us);
159 }
160 else {
161 if(use_json)
162 fprintf(stream, "\"%s\":null", wovar->name);
163 }
164
165 return 1; /* return 1 if anything was written */
166 }
167
writeString(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)168 static int writeString(FILE *stream, const struct writeoutvar *wovar,
169 struct per_transfer *per, CURLcode per_result,
170 bool use_json)
171 {
172 bool valid = false;
173 const char *strinfo = NULL;
174 struct dynbuf buf;
175 curlx_dyn_init(&buf, 256*1024);
176
177 DEBUGASSERT(wovar->writefunc == writeString);
178
179 if(wovar->ci) {
180 if(wovar->ci == CURLINFO_HTTP_VERSION) {
181 long version = 0;
182 if(!curl_easy_getinfo(per->curl, CURLINFO_HTTP_VERSION, &version)) {
183 const struct httpmap *m = &http_version[0];
184 while(m->str) {
185 if(m->num == version) {
186 strinfo = m->str;
187 valid = true;
188 break;
189 }
190 m++;
191 }
192 }
193 }
194 else {
195 if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo)
196 valid = true;
197 }
198 }
199 else {
200 switch(wovar->id) {
201 case VAR_CERT:
202 if(per->certinfo) {
203 int i;
204 bool error = FALSE;
205 for(i = 0; (i < per->certinfo->num_of_certs) && !error; i++) {
206 struct curl_slist *slist;
207
208 for(slist = per->certinfo->certinfo[i]; slist; slist = slist->next) {
209 size_t len;
210 if(curl_strnequal(slist->data, "cert:", 5)) {
211 if(curlx_dyn_add(&buf, &slist->data[5])) {
212 error = TRUE;
213 break;
214 }
215 }
216 else {
217 if(curlx_dyn_add(&buf, slist->data)) {
218 error = TRUE;
219 break;
220 }
221 }
222 len = curlx_dyn_len(&buf);
223 if(len) {
224 char *ptr = curlx_dyn_ptr(&buf);
225 if(ptr[len -1] != '\n') {
226 /* add a newline to make things look better */
227 if(curlx_dyn_addn(&buf, "\n", 1)) {
228 error = TRUE;
229 break;
230 }
231 }
232 }
233 }
234 }
235 if(!error) {
236 strinfo = curlx_dyn_ptr(&buf);
237 if(!strinfo)
238 /* maybe not a TLS protocol */
239 strinfo = "";
240 valid = true;
241 }
242 }
243 else
244 strinfo = ""; /* no cert info */
245 break;
246 case VAR_ERRORMSG:
247 if(per_result) {
248 strinfo = (per->errorbuffer && per->errorbuffer[0]) ?
249 per->errorbuffer : curl_easy_strerror(per_result);
250 valid = true;
251 }
252 break;
253 case VAR_EFFECTIVE_FILENAME:
254 if(per->outs.filename) {
255 strinfo = per->outs.filename;
256 valid = true;
257 }
258 break;
259 case VAR_INPUT_URL:
260 if(per->this_url) {
261 strinfo = per->this_url;
262 valid = true;
263 }
264 break;
265 default:
266 DEBUGASSERT(0);
267 break;
268 }
269 }
270
271 if(valid) {
272 DEBUGASSERT(strinfo);
273 if(use_json) {
274 fprintf(stream, "\"%s\":", wovar->name);
275 jsonWriteString(stream, strinfo, FALSE);
276 }
277 else
278 fputs(strinfo, stream);
279 }
280 else {
281 if(use_json)
282 fprintf(stream, "\"%s\":null", wovar->name);
283 }
284
285 curlx_dyn_free(&buf);
286 return 1; /* return 1 if anything was written */
287 }
288
writeLong(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)289 static int writeLong(FILE *stream, const struct writeoutvar *wovar,
290 struct per_transfer *per, CURLcode per_result,
291 bool use_json)
292 {
293 bool valid = false;
294 long longinfo = 0;
295
296 DEBUGASSERT(wovar->writefunc == writeLong);
297
298 if(wovar->ci) {
299 if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo))
300 valid = true;
301 }
302 else {
303 switch(wovar->id) {
304 case VAR_NUM_CERTS:
305 longinfo = per->certinfo ? per->certinfo->num_of_certs : 0;
306 valid = true;
307 break;
308 case VAR_NUM_HEADERS:
309 longinfo = per->num_headers;
310 valid = true;
311 break;
312 case VAR_EXITCODE:
313 longinfo = per_result;
314 valid = true;
315 break;
316 case VAR_URLNUM:
317 if(per->urlnum <= INT_MAX) {
318 longinfo = (long)per->urlnum;
319 valid = true;
320 }
321 break;
322 default:
323 DEBUGASSERT(0);
324 break;
325 }
326 }
327
328 if(valid) {
329 if(use_json)
330 fprintf(stream, "\"%s\":%ld", wovar->name, longinfo);
331 else {
332 if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY)
333 fprintf(stream, "%03ld", longinfo);
334 else
335 fprintf(stream, "%ld", longinfo);
336 }
337 }
338 else {
339 if(use_json)
340 fprintf(stream, "\"%s\":null", wovar->name);
341 }
342
343 return 1; /* return 1 if anything was written */
344 }
345
writeOffset(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)346 static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
347 struct per_transfer *per, CURLcode per_result,
348 bool use_json)
349 {
350 bool valid = false;
351 curl_off_t offinfo = 0;
352
353 (void)per;
354 (void)per_result;
355 DEBUGASSERT(wovar->writefunc == writeOffset);
356
357 if(wovar->ci) {
358 if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo))
359 valid = true;
360 }
361 else {
362 DEBUGASSERT(0);
363 }
364
365 if(valid) {
366 if(use_json)
367 fprintf(stream, "\"%s\":", wovar->name);
368
369 fprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo);
370 }
371 else {
372 if(use_json)
373 fprintf(stream, "\"%s\":null", wovar->name);
374 }
375
376 return 1; /* return 1 if anything was written */
377 }
378
ourWriteOut(struct OperationConfig * config,struct per_transfer * per,CURLcode per_result)379 void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
380 CURLcode per_result)
381 {
382 FILE *stream = stdout;
383 const char *writeinfo = config->writeout;
384 const char *ptr = writeinfo;
385 bool done = FALSE;
386 struct curl_certinfo *certinfo;
387 CURLcode res = curl_easy_getinfo(per->curl, CURLINFO_CERTINFO, &certinfo);
388
389 if(!writeinfo)
390 return;
391
392 if(!res && certinfo)
393 per->certinfo = certinfo;
394
395 while(ptr && *ptr && !done) {
396 if('%' == *ptr && ptr[1]) {
397 if('%' == ptr[1]) {
398 /* an escaped %-letter */
399 fputc('%', stream);
400 ptr += 2;
401 }
402 else {
403 /* this is meant as a variable to output */
404 char *end;
405 size_t vlen;
406 if('{' == ptr[1]) {
407 int i;
408 bool match = FALSE;
409 end = strchr(ptr, '}');
410 ptr += 2; /* pass the % and the { */
411 if(!end) {
412 fputs("%{", stream);
413 continue;
414 }
415 vlen = end - ptr;
416 for(i = 0; variables[i].name; i++) {
417 if((strlen(variables[i].name) == vlen) &&
418 curl_strnequal(ptr, variables[i].name, vlen)) {
419 match = TRUE;
420 switch(variables[i].id) {
421 case VAR_ONERROR:
422 if(per_result == CURLE_OK)
423 /* this isn't error so skip the rest */
424 done = TRUE;
425 break;
426 case VAR_STDOUT:
427 stream = stdout;
428 break;
429 case VAR_STDERR:
430 stream = stderr;
431 break;
432 case VAR_JSON:
433 ourWriteOutJSON(stream, variables, per, per_result);
434 break;
435 case VAR_HEADER_JSON:
436 headerJSON(stream, per);
437 break;
438 default:
439 (void)variables[i].writefunc(stream, &variables[i],
440 per, per_result, false);
441 break;
442 }
443 break;
444 }
445 }
446 if(!match) {
447 fprintf(stderr, "curl: unknown --write-out variable: '%.*s'\n",
448 (int)vlen, ptr);
449 }
450 ptr = end + 1; /* pass the end */
451 }
452 else if(!strncmp("header{", &ptr[1], 7)) {
453 ptr += 8;
454 end = strchr(ptr, '}');
455 if(end) {
456 char hname[256]; /* holds the longest header field name */
457 struct curl_header *header;
458 vlen = end - ptr;
459 if(vlen < sizeof(hname)) {
460 memcpy(hname, ptr, vlen);
461 hname[vlen] = 0;
462 if(CURLHE_OK == curl_easy_header(per->curl, hname, 0,
463 CURLH_HEADER, -1, &header))
464 fputs(header->value, stream);
465 }
466 ptr = end + 1;
467 }
468 else
469 fputs("%header{", stream);
470 }
471 else {
472 /* illegal syntax, then just output the characters that are used */
473 fputc('%', stream);
474 fputc(ptr[1], stream);
475 ptr += 2;
476 }
477 }
478 }
479 else if('\\' == *ptr && ptr[1]) {
480 switch(ptr[1]) {
481 case 'r':
482 fputc('\r', stream);
483 break;
484 case 'n':
485 fputc('\n', stream);
486 break;
487 case 't':
488 fputc('\t', stream);
489 break;
490 default:
491 /* unknown, just output this */
492 fputc(*ptr, stream);
493 fputc(ptr[1], stream);
494 break;
495 }
496 ptr += 2;
497 }
498 else {
499 fputc(*ptr, stream);
500 ptr++;
501 }
502 }
503 }
504