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