xref: /third_party/curl/src/tool_writeout.c (revision 13498266)
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
35static int writeTime(FILE *stream, const struct writeoutvar *wovar,
36                     struct per_transfer *per, CURLcode per_result,
37                     bool use_json);
38
39static int writeString(FILE *stream, const struct writeoutvar *wovar,
40                       struct per_transfer *per, CURLcode per_result,
41                       bool use_json);
42
43static int writeLong(FILE *stream, const struct writeoutvar *wovar,
44                     struct per_transfer *per, CURLcode per_result,
45                     bool use_json);
46
47static int writeOffset(FILE *stream, const struct writeoutvar *wovar,
48                       struct per_transfer *per, CURLcode per_result,
49                       bool use_json);
50
51struct httpmap {
52  const char *str;
53  int num;
54};
55
56static 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   */
75static 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
153static 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
190static 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
271static 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
421static 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
478static 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
511void 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