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