xref: /curl/src/tool_writeout.c (revision b06b3515)
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 
26 #include "curlx.h"
27 #include "tool_cfgable.h"
28 #include "tool_writeout.h"
29 #include "tool_writeout_json.h"
30 #include "dynbuf.h"
31 
32 #include "memdebug.h" /* keep this as LAST include */
33 
34 static int writeTime(FILE *stream, const struct writeoutvar *wovar,
35                      struct per_transfer *per, CURLcode per_result,
36                      bool use_json);
37 
38 static int writeString(FILE *stream, const struct writeoutvar *wovar,
39                        struct per_transfer *per, CURLcode per_result,
40                        bool use_json);
41 
42 static int writeLong(FILE *stream, const struct writeoutvar *wovar,
43                      struct per_transfer *per, CURLcode per_result,
44                      bool use_json);
45 
46 static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
47                        struct per_transfer *per, CURLcode per_result,
48                        bool use_json);
49 
50 struct httpmap {
51   const char *str;
52   int num;
53 };
54 
55 static const struct httpmap http_version[] = {
56   { "0",   CURL_HTTP_VERSION_NONE},
57   { "1",   CURL_HTTP_VERSION_1_0},
58   { "1.1", CURL_HTTP_VERSION_1_1},
59   { "2",   CURL_HTTP_VERSION_2},
60   { "3",   CURL_HTTP_VERSION_3},
61   { NULL, 0} /* end of list */
62 };
63 
64 /* The designated write function should be the same as the CURLINFO return type
65    with exceptions special cased in the respective function. For example,
66    http_version uses CURLINFO_HTTP_VERSION which returns the version as a long,
67    however it is output as a string and therefore is handled in writeString.
68 
69    Yes: "http_version": "1.1"
70    No:  "http_version": 1.1
71 
72    Variable names MUST be in alphabetical order.
73    */
74 static const struct writeoutvar variables[] = {
75   {"certs", VAR_CERT, CURLINFO_NONE, writeString},
76   {"conn_id", VAR_CONN_ID, CURLINFO_CONN_ID, writeOffset},
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   {"num_retries", VAR_NUM_RETRY, CURLINFO_NONE, writeLong},
95   {"onerror", VAR_ONERROR, CURLINFO_NONE, NULL},
96   {"proxy_ssl_verify_result", VAR_PROXY_SSL_VERIFY_RESULT,
97    CURLINFO_PROXY_SSL_VERIFYRESULT, writeLong},
98   {"proxy_used", VAR_PROXY_USED, CURLINFO_USED_PROXY, writeLong},
99   {"redirect_url", VAR_REDIRECT_URL, CURLINFO_REDIRECT_URL, writeString},
100   {"referer", VAR_REFERER, CURLINFO_REFERER, writeString},
101   {"remote_ip", VAR_PRIMARY_IP, CURLINFO_PRIMARY_IP, writeString},
102   {"remote_port", VAR_PRIMARY_PORT, CURLINFO_PRIMARY_PORT, writeLong},
103   {"response_code", VAR_HTTP_CODE, CURLINFO_RESPONSE_CODE, writeLong},
104   {"scheme", VAR_SCHEME, CURLINFO_SCHEME, writeString},
105   {"size_download", VAR_SIZE_DOWNLOAD, CURLINFO_SIZE_DOWNLOAD_T, writeOffset},
106   {"size_header", VAR_HEADER_SIZE, CURLINFO_HEADER_SIZE, writeLong},
107   {"size_request", VAR_REQUEST_SIZE, CURLINFO_REQUEST_SIZE, writeLong},
108   {"size_upload", VAR_SIZE_UPLOAD, CURLINFO_SIZE_UPLOAD_T, writeOffset},
109   {"speed_download", VAR_SPEED_DOWNLOAD, CURLINFO_SPEED_DOWNLOAD_T,
110    writeOffset},
111   {"speed_upload", VAR_SPEED_UPLOAD, CURLINFO_SPEED_UPLOAD_T, writeOffset},
112   {"ssl_verify_result", VAR_SSL_VERIFY_RESULT, CURLINFO_SSL_VERIFYRESULT,
113    writeLong},
114   {"stderr", VAR_STDERR, CURLINFO_NONE, NULL},
115   {"stdout", VAR_STDOUT, CURLINFO_NONE, NULL},
116   {"time_appconnect", VAR_APPCONNECT_TIME, CURLINFO_APPCONNECT_TIME_T,
117    writeTime},
118   {"time_connect", VAR_CONNECT_TIME, CURLINFO_CONNECT_TIME_T, writeTime},
119   {"time_namelookup", VAR_NAMELOOKUP_TIME, CURLINFO_NAMELOOKUP_TIME_T,
120    writeTime},
121   {"time_posttransfer", VAR_POSTTRANSFER_TIME, CURLINFO_POSTTRANSFER_TIME_T,
122    writeTime},
123   {"time_pretransfer", VAR_PRETRANSFER_TIME, CURLINFO_PRETRANSFER_TIME_T,
124    writeTime},
125   {"time_queue", VAR_QUEUE_TIME, CURLINFO_QUEUE_TIME_T, writeTime},
126   {"time_redirect", VAR_REDIRECT_TIME, CURLINFO_REDIRECT_TIME_T, writeTime},
127   {"time_starttransfer", VAR_STARTTRANSFER_TIME, CURLINFO_STARTTRANSFER_TIME_T,
128    writeTime},
129   {"time_total", VAR_TOTAL_TIME, CURLINFO_TOTAL_TIME_T, writeTime},
130   {"url", VAR_INPUT_URL, CURLINFO_NONE, writeString},
131   {"url.fragment", VAR_INPUT_URLFRAGMENT, CURLINFO_NONE, writeString},
132   {"url.host", VAR_INPUT_URLHOST, CURLINFO_NONE, writeString},
133   {"url.options", VAR_INPUT_URLOPTIONS, CURLINFO_NONE, writeString},
134   {"url.password", VAR_INPUT_URLPASSWORD, CURLINFO_NONE, writeString},
135   {"url.path", VAR_INPUT_URLPATH, CURLINFO_NONE, writeString},
136   {"url.port", VAR_INPUT_URLPORT, CURLINFO_NONE, writeString},
137   {"url.query", VAR_INPUT_URLQUERY, CURLINFO_NONE, writeString},
138   {"url.scheme", VAR_INPUT_URLSCHEME, CURLINFO_NONE, writeString},
139   {"url.user", VAR_INPUT_URLUSER, CURLINFO_NONE, writeString},
140   {"url.zoneid", VAR_INPUT_URLZONEID, CURLINFO_NONE, writeString},
141   {"url_effective", VAR_EFFECTIVE_URL, CURLINFO_EFFECTIVE_URL, writeString},
142   {"urle.fragment", VAR_INPUT_URLEFRAGMENT, CURLINFO_NONE, writeString},
143   {"urle.host", VAR_INPUT_URLEHOST, CURLINFO_NONE, writeString},
144   {"urle.options", VAR_INPUT_URLEOPTIONS, CURLINFO_NONE, writeString},
145   {"urle.password", VAR_INPUT_URLEPASSWORD, CURLINFO_NONE, writeString},
146   {"urle.path", VAR_INPUT_URLEPATH, CURLINFO_NONE, writeString},
147   {"urle.port", VAR_INPUT_URLEPORT, CURLINFO_NONE, writeString},
148   {"urle.query", VAR_INPUT_URLEQUERY, CURLINFO_NONE, writeString},
149   {"urle.scheme", VAR_INPUT_URLESCHEME, CURLINFO_NONE, writeString},
150   {"urle.user", VAR_INPUT_URLEUSER, CURLINFO_NONE, writeString},
151   {"urle.zoneid", VAR_INPUT_URLEZONEID, CURLINFO_NONE, writeString},
152   {"urlnum", VAR_URLNUM, CURLINFO_NONE, writeLong},
153   {"xfer_id", VAR_EASY_ID, CURLINFO_XFER_ID, writeOffset}
154 };
155 
writeTime(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)156 static int writeTime(FILE *stream, const struct writeoutvar *wovar,
157                      struct per_transfer *per, CURLcode per_result,
158                      bool use_json)
159 {
160   bool valid = false;
161   curl_off_t us = 0;
162 
163   (void)per;
164   (void)per_result;
165   DEBUGASSERT(wovar->writefunc == writeTime);
166 
167   if(wovar->ci) {
168     if(!curl_easy_getinfo(per->curl, wovar->ci, &us))
169       valid = true;
170   }
171   else {
172     DEBUGASSERT(0);
173   }
174 
175   if(valid) {
176     curl_off_t secs = us / 1000000;
177     us %= 1000000;
178 
179     if(use_json)
180       fprintf(stream, "\"%s\":", wovar->name);
181 
182     fprintf(stream, "%" CURL_FORMAT_CURL_OFF_TU
183             ".%06" CURL_FORMAT_CURL_OFF_TU, secs, us);
184   }
185   else {
186     if(use_json)
187       fprintf(stream, "\"%s\":null", wovar->name);
188   }
189 
190   return 1; /* return 1 if anything was written */
191 }
192 
urlpart(struct per_transfer * per,writeoutid vid,const char ** contentp)193 static int urlpart(struct per_transfer *per, writeoutid vid,
194                    const char **contentp)
195 {
196   CURLU *uh = curl_url();
197   int rc = 0;
198   if(uh) {
199     CURLUPart cpart = CURLUPART_HOST;
200     char *part = NULL;
201     const char *url = NULL;
202 
203     if(vid >= VAR_INPUT_URLESCHEME) {
204       if(curl_easy_getinfo(per->curl, CURLINFO_EFFECTIVE_URL, &url))
205         rc = 5;
206     }
207     else
208       url = per->url;
209 
210     if(!rc) {
211       switch(vid) {
212       case VAR_INPUT_URLSCHEME:
213       case VAR_INPUT_URLESCHEME:
214         cpart = CURLUPART_SCHEME;
215         break;
216       case VAR_INPUT_URLUSER:
217       case VAR_INPUT_URLEUSER:
218         cpart = CURLUPART_USER;
219         break;
220       case VAR_INPUT_URLPASSWORD:
221       case VAR_INPUT_URLEPASSWORD:
222         cpart = CURLUPART_PASSWORD;
223         break;
224       case VAR_INPUT_URLOPTIONS:
225       case VAR_INPUT_URLEOPTIONS:
226         cpart = CURLUPART_OPTIONS;
227         break;
228       case VAR_INPUT_URLHOST:
229       case VAR_INPUT_URLEHOST:
230         cpart = CURLUPART_HOST;
231         break;
232       case VAR_INPUT_URLPORT:
233       case VAR_INPUT_URLEPORT:
234         cpart = CURLUPART_PORT;
235         break;
236       case VAR_INPUT_URLPATH:
237       case VAR_INPUT_URLEPATH:
238         cpart = CURLUPART_PATH;
239         break;
240       case VAR_INPUT_URLQUERY:
241       case VAR_INPUT_URLEQUERY:
242         cpart = CURLUPART_QUERY;
243         break;
244       case VAR_INPUT_URLFRAGMENT:
245       case VAR_INPUT_URLEFRAGMENT:
246         cpart = CURLUPART_FRAGMENT;
247         break;
248       case VAR_INPUT_URLZONEID:
249       case VAR_INPUT_URLEZONEID:
250         cpart = CURLUPART_ZONEID;
251         break;
252       default:
253         /* not implemented */
254         rc = 4;
255         break;
256       }
257     }
258     if(!rc && curl_url_set(uh, CURLUPART_URL, url,
259                            CURLU_GUESS_SCHEME|CURLU_NON_SUPPORT_SCHEME))
260       rc = 2;
261 
262     if(!rc && curl_url_get(uh, cpart, &part, CURLU_DEFAULT_PORT))
263       rc = 3;
264 
265     if(!rc && part)
266       *contentp = part;
267     curl_url_cleanup(uh);
268   }
269   else
270     return 1;
271   return rc;
272 }
273 
certinfo(struct per_transfer * per)274 static void certinfo(struct per_transfer *per)
275 {
276   if(!per->certinfo) {
277     struct curl_certinfo *certinfo;
278     CURLcode res = curl_easy_getinfo(per->curl, CURLINFO_CERTINFO, &certinfo);
279     per->certinfo = (!res && certinfo) ? certinfo : NULL;
280   }
281 }
282 
writeString(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)283 static int writeString(FILE *stream, const struct writeoutvar *wovar,
284                        struct per_transfer *per, CURLcode per_result,
285                        bool use_json)
286 {
287   bool valid = false;
288   const char *strinfo = NULL;
289   const char *freestr = NULL;
290   struct dynbuf buf;
291   curlx_dyn_init(&buf, 256*1024);
292 
293   DEBUGASSERT(wovar->writefunc == writeString);
294 
295   if(wovar->ci) {
296     if(wovar->ci == CURLINFO_HTTP_VERSION) {
297       long version = 0;
298       if(!curl_easy_getinfo(per->curl, CURLINFO_HTTP_VERSION, &version)) {
299         const struct httpmap *m = &http_version[0];
300         while(m->str) {
301           if(m->num == version) {
302             strinfo = m->str;
303             valid = true;
304             break;
305           }
306           m++;
307         }
308       }
309     }
310     else {
311       if(!curl_easy_getinfo(per->curl, wovar->ci, &strinfo) && strinfo)
312         valid = true;
313     }
314   }
315   else {
316     switch(wovar->id) {
317     case VAR_CERT:
318       certinfo(per);
319       if(per->certinfo) {
320         int i;
321         bool error = FALSE;
322         for(i = 0; (i < per->certinfo->num_of_certs) && !error; i++) {
323           struct curl_slist *slist;
324 
325           for(slist = per->certinfo->certinfo[i]; slist; slist = slist->next) {
326             size_t len;
327             if(curl_strnequal(slist->data, "cert:", 5)) {
328               if(curlx_dyn_add(&buf, &slist->data[5])) {
329                 error = TRUE;
330                 break;
331               }
332             }
333             else {
334               if(curlx_dyn_add(&buf, slist->data)) {
335                 error = TRUE;
336                 break;
337               }
338             }
339             len = curlx_dyn_len(&buf);
340             if(len) {
341               char *ptr = curlx_dyn_ptr(&buf);
342               if(ptr[len -1] != '\n') {
343                 /* add a newline to make things look better */
344                 if(curlx_dyn_addn(&buf, "\n", 1)) {
345                   error = TRUE;
346                   break;
347                 }
348               }
349             }
350           }
351         }
352         if(!error) {
353           strinfo = curlx_dyn_ptr(&buf);
354           if(!strinfo)
355             /* maybe not a TLS protocol */
356             strinfo = "";
357           valid = true;
358         }
359       }
360       else
361         strinfo = ""; /* no cert info */
362       break;
363     case VAR_ERRORMSG:
364       if(per_result) {
365         strinfo = (per->errorbuffer && per->errorbuffer[0]) ?
366           per->errorbuffer : curl_easy_strerror(per_result);
367         valid = true;
368       }
369       break;
370     case VAR_EFFECTIVE_FILENAME:
371       if(per->outs.filename) {
372         strinfo = per->outs.filename;
373         valid = true;
374       }
375       break;
376     case VAR_INPUT_URL:
377       if(per->url) {
378         strinfo = per->url;
379         valid = true;
380       }
381       break;
382     case VAR_INPUT_URLSCHEME:
383     case VAR_INPUT_URLUSER:
384     case VAR_INPUT_URLPASSWORD:
385     case VAR_INPUT_URLOPTIONS:
386     case VAR_INPUT_URLHOST:
387     case VAR_INPUT_URLPORT:
388     case VAR_INPUT_URLPATH:
389     case VAR_INPUT_URLQUERY:
390     case VAR_INPUT_URLFRAGMENT:
391     case VAR_INPUT_URLZONEID:
392     case VAR_INPUT_URLESCHEME:
393     case VAR_INPUT_URLEUSER:
394     case VAR_INPUT_URLEPASSWORD:
395     case VAR_INPUT_URLEOPTIONS:
396     case VAR_INPUT_URLEHOST:
397     case VAR_INPUT_URLEPORT:
398     case VAR_INPUT_URLEPATH:
399     case VAR_INPUT_URLEQUERY:
400     case VAR_INPUT_URLEFRAGMENT:
401     case VAR_INPUT_URLEZONEID:
402       if(per->url) {
403         if(!urlpart(per, wovar->id, &strinfo)) {
404           freestr = strinfo;
405           valid = true;
406         }
407       }
408       break;
409     default:
410       DEBUGASSERT(0);
411       break;
412     }
413   }
414 
415   if(valid) {
416     DEBUGASSERT(strinfo);
417     if(use_json) {
418       fprintf(stream, "\"%s\":", wovar->name);
419       jsonWriteString(stream, strinfo, FALSE);
420     }
421     else
422       fputs(strinfo, stream);
423   }
424   else {
425     if(use_json)
426       fprintf(stream, "\"%s\":null", wovar->name);
427   }
428   curl_free((char *)freestr);
429 
430   curlx_dyn_free(&buf);
431   return 1; /* return 1 if anything was written */
432 }
433 
writeLong(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)434 static int writeLong(FILE *stream, const struct writeoutvar *wovar,
435                      struct per_transfer *per, CURLcode per_result,
436                      bool use_json)
437 {
438   bool valid = false;
439   long longinfo = 0;
440 
441   DEBUGASSERT(wovar->writefunc == writeLong);
442 
443   if(wovar->ci) {
444     if(!curl_easy_getinfo(per->curl, wovar->ci, &longinfo))
445       valid = true;
446   }
447   else {
448     switch(wovar->id) {
449     case VAR_NUM_RETRY:
450       longinfo = per->num_retries;
451       valid = true;
452       break;
453     case VAR_NUM_CERTS:
454       certinfo(per);
455       longinfo = per->certinfo ? per->certinfo->num_of_certs : 0;
456       valid = true;
457       break;
458     case VAR_NUM_HEADERS:
459       longinfo = per->num_headers;
460       valid = true;
461       break;
462     case VAR_EXITCODE:
463       longinfo = (long)per_result;
464       valid = true;
465       break;
466     case VAR_URLNUM:
467       if(per->urlnum <= INT_MAX) {
468         longinfo = (long)per->urlnum;
469         valid = true;
470       }
471       break;
472     default:
473       DEBUGASSERT(0);
474       break;
475     }
476   }
477 
478   if(valid) {
479     if(use_json)
480       fprintf(stream, "\"%s\":%ld", wovar->name, longinfo);
481     else {
482       if(wovar->id == VAR_HTTP_CODE || wovar->id == VAR_HTTP_CODE_PROXY)
483         fprintf(stream, "%03ld", longinfo);
484       else
485         fprintf(stream, "%ld", longinfo);
486     }
487   }
488   else {
489     if(use_json)
490       fprintf(stream, "\"%s\":null", wovar->name);
491   }
492 
493   return 1; /* return 1 if anything was written */
494 }
495 
writeOffset(FILE * stream,const struct writeoutvar * wovar,struct per_transfer * per,CURLcode per_result,bool use_json)496 static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
497                        struct per_transfer *per, CURLcode per_result,
498                        bool use_json)
499 {
500   bool valid = false;
501   curl_off_t offinfo = 0;
502 
503   (void)per;
504   (void)per_result;
505   DEBUGASSERT(wovar->writefunc == writeOffset);
506 
507   if(wovar->ci) {
508     if(!curl_easy_getinfo(per->curl, wovar->ci, &offinfo))
509       valid = true;
510   }
511   else {
512     DEBUGASSERT(0);
513   }
514 
515   if(valid) {
516     if(use_json)
517       fprintf(stream, "\"%s\":", wovar->name);
518 
519     fprintf(stream, "%" CURL_FORMAT_CURL_OFF_T, offinfo);
520   }
521   else {
522     if(use_json)
523       fprintf(stream, "\"%s\":null", wovar->name);
524   }
525 
526   return 1; /* return 1 if anything was written */
527 }
528 
529 static int
matchvar(const void * m1,const void * m2)530 matchvar(const void *m1, const void *m2)
531 {
532   const struct writeoutvar *v1 = m1;
533   const struct writeoutvar *v2 = m2;
534 
535   return strcmp(v1->name, v2->name);
536 }
537 
538 #define MAX_WRITEOUT_NAME_LENGTH 24
539 
ourWriteOut(struct OperationConfig * config,struct per_transfer * per,CURLcode per_result)540 void ourWriteOut(struct OperationConfig *config, struct per_transfer *per,
541                  CURLcode per_result)
542 {
543   FILE *stream = stdout;
544   const char *writeinfo = config->writeout;
545   const char *ptr = writeinfo;
546   bool done = FALSE;
547   bool fclose_stream = FALSE;
548   struct dynbuf name;
549 
550   if(!writeinfo)
551     return;
552 
553   curlx_dyn_init(&name, MAX_WRITEOUT_NAME_LENGTH);
554   while(ptr && *ptr && !done) {
555     if('%' == *ptr && ptr[1]) {
556       if('%' == ptr[1]) {
557         /* an escaped %-letter */
558         fputc('%', stream);
559         ptr += 2;
560       }
561       else {
562         /* this is meant as a variable to output */
563         char *end;
564         size_t vlen;
565         if('{' == ptr[1]) {
566           struct writeoutvar *wv = NULL;
567           struct writeoutvar find = { 0 };
568           end = strchr(ptr, '}');
569           ptr += 2; /* pass the % and the { */
570           if(!end) {
571             fputs("%{", stream);
572             continue;
573           }
574           vlen = end - ptr;
575 
576           curlx_dyn_reset(&name);
577           if(!curlx_dyn_addn(&name, ptr, vlen)) {
578             find.name = curlx_dyn_ptr(&name);
579             wv = bsearch(&find,
580                          variables, sizeof(variables)/sizeof(variables[0]),
581                          sizeof(variables[0]), matchvar);
582           }
583           if(wv) {
584             switch(wv->id) {
585             case VAR_ONERROR:
586               if(per_result == CURLE_OK)
587                 /* this is not error so skip the rest */
588                 done = TRUE;
589               break;
590             case VAR_STDOUT:
591               if(fclose_stream)
592                 fclose(stream);
593               fclose_stream = FALSE;
594               stream = stdout;
595               break;
596             case VAR_STDERR:
597               if(fclose_stream)
598                 fclose(stream);
599               fclose_stream = FALSE;
600               stream = tool_stderr;
601               break;
602             case VAR_JSON:
603               ourWriteOutJSON(stream, variables,
604                               sizeof(variables)/sizeof(variables[0]),
605                               per, per_result);
606               break;
607             case VAR_HEADER_JSON:
608               headerJSON(stream, per);
609               break;
610             default:
611               (void)wv->writefunc(stream, wv, per, per_result, false);
612               break;
613             }
614           }
615           else {
616             fprintf(tool_stderr,
617                     "curl: unknown --write-out variable: '%.*s'\n",
618                     (int)vlen, ptr);
619           }
620           ptr = end + 1; /* pass the end */
621         }
622         else if(!strncmp("header{", &ptr[1], 7)) {
623           ptr += 8;
624           end = strchr(ptr, '}');
625           if(end) {
626             char hname[256]; /* holds the longest header field name */
627             struct curl_header *header;
628             vlen = end - ptr;
629             if(vlen < sizeof(hname)) {
630               memcpy(hname, ptr, vlen);
631               hname[vlen] = 0;
632               if(CURLHE_OK == curl_easy_header(per->curl, hname, 0,
633                                                CURLH_HEADER, -1, &header))
634                 fputs(header->value, stream);
635             }
636             ptr = end + 1;
637           }
638           else
639             fputs("%header{", stream);
640         }
641         else if(!strncmp("output{", &ptr[1], 7)) {
642           bool append = FALSE;
643           ptr += 8;
644           if((ptr[0] == '>') && (ptr[1] == '>')) {
645             append = TRUE;
646             ptr += 2;
647           }
648           end = strchr(ptr, '}');
649           if(end) {
650             char fname[512]; /* holds the longest filename */
651             size_t flen = end - ptr;
652             if(flen < sizeof(fname)) {
653               FILE *stream2;
654               memcpy(fname, ptr, flen);
655               fname[flen] = 0;
656               stream2 = fopen(fname, append ? FOPEN_APPENDTEXT :
657                               FOPEN_WRITETEXT);
658               if(stream2) {
659                 /* only change if the open worked */
660                 if(fclose_stream)
661                   fclose(stream);
662                 stream = stream2;
663                 fclose_stream = TRUE;
664               }
665             }
666             ptr = end + 1;
667           }
668           else
669             fputs("%output{", stream);
670         }
671         else {
672           /* illegal syntax, then just output the characters that are used */
673           fputc('%', stream);
674           fputc(ptr[1], stream);
675           ptr += 2;
676         }
677       }
678     }
679     else if('\\' == *ptr && ptr[1]) {
680       switch(ptr[1]) {
681       case 'r':
682         fputc('\r', stream);
683         break;
684       case 'n':
685         fputc('\n', stream);
686         break;
687       case 't':
688         fputc('\t', stream);
689         break;
690       default:
691         /* unknown, just output this */
692         fputc(*ptr, stream);
693         fputc(ptr[1], stream);
694         break;
695       }
696       ptr += 2;
697     }
698     else {
699       fputc(*ptr, stream);
700       ptr++;
701     }
702   }
703   if(fclose_stream)
704     fclose(stream);
705   curlx_dyn_free(&name);
706 }
707