quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

tool_writeout.c (21930B)


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