• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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