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