quickjs-tart

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

tool_cb_wrt.c (11584B)


      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 #ifdef HAVE_FCNTL_H
     27 /* for open() */
     28 #include <fcntl.h>
     29 #endif
     30 
     31 #include "tool_cfgable.h"
     32 #include "tool_msgs.h"
     33 #include "tool_cb_wrt.h"
     34 #include "tool_operate.h"
     35 
     36 #include "memdebug.h" /* keep this as LAST include */
     37 
     38 #ifdef _WIN32
     39 #define OPENMODE S_IREAD | S_IWRITE
     40 #else
     41 #define OPENMODE S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH
     42 #endif
     43 
     44 /* create/open a local file for writing, return TRUE on success */
     45 bool tool_create_output_file(struct OutStruct *outs,
     46                              struct OperationConfig *config)
     47 {
     48   struct GlobalConfig *global;
     49   FILE *file = NULL;
     50   const char *fname = outs->filename;
     51   DEBUGASSERT(outs);
     52   DEBUGASSERT(config);
     53   global = config->global;
     54   DEBUGASSERT(fname && *fname);
     55 
     56   if(config->file_clobber_mode == CLOBBER_ALWAYS ||
     57      (config->file_clobber_mode == CLOBBER_DEFAULT &&
     58       !outs->is_cd_filename)) {
     59     /* open file for writing */
     60     file = fopen(fname, "wb");
     61   }
     62   else {
     63     int fd;
     64     do {
     65       fd = open(fname, O_CREAT | O_WRONLY | O_EXCL | CURL_O_BINARY, OPENMODE);
     66       /* Keep retrying in the hope that it is not interrupted sometime */
     67       /* !checksrc! disable ERRNOVAR 1 */
     68     } while(fd == -1 && errno == EINTR);
     69     if(config->file_clobber_mode == CLOBBER_NEVER && fd == -1) {
     70       int next_num = 1;
     71       size_t len = strlen(fname);
     72       size_t newlen = len + 13; /* nul + 1-11 digits + dot */
     73       char *newname;
     74       /* Guard against wraparound in new filename */
     75       if(newlen < len) {
     76         errorf(global, "overflow in filename generation");
     77         return FALSE;
     78       }
     79       newname = malloc(newlen);
     80       if(!newname) {
     81         errorf(global, "out of memory");
     82         return FALSE;
     83       }
     84       memcpy(newname, fname, len);
     85       newname[len] = '.';
     86       /* !checksrc! disable ERRNOVAR 1 */
     87       while(fd == -1 && /* have not successfully opened a file */
     88             (errno == EEXIST || errno == EISDIR) &&
     89             /* because we keep having files that already exist */
     90             next_num < 100 /* and we have not reached the retry limit */ ) {
     91         msnprintf(newname + len + 1, 12, "%d", next_num);
     92         next_num++;
     93         do {
     94           fd = open(newname, O_CREAT | O_WRONLY | O_EXCL | CURL_O_BINARY,
     95                              OPENMODE);
     96           /* Keep retrying in the hope that it is not interrupted sometime */
     97         } while(fd == -1 && errno == EINTR);
     98       }
     99       outs->filename = newname; /* remember the new one */
    100       outs->alloc_filename = TRUE;
    101     }
    102     /* An else statement to not overwrite existing files and not retry with
    103        new numbered names (which would cover
    104        config->file_clobber_mode == CLOBBER_DEFAULT && outs->is_cd_filename)
    105        is not needed because we would have failed earlier, in the while loop
    106        and `fd` would now be -1 */
    107     if(fd != -1) {
    108       file = fdopen(fd, "wb");
    109       if(!file)
    110         close(fd);
    111     }
    112   }
    113 
    114   if(!file) {
    115     warnf(global, "Failed to open the file %s: %s", fname,
    116           strerror(errno));
    117     return FALSE;
    118   }
    119   outs->s_isreg = TRUE;
    120   outs->fopened = TRUE;
    121   outs->stream = file;
    122   outs->bytes = 0;
    123   outs->init = 0;
    124   return TRUE;
    125 }
    126 
    127 /*
    128 ** callback for CURLOPT_WRITEFUNCTION
    129 */
    130 
    131 size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
    132 {
    133   size_t rc;
    134   struct per_transfer *per = userdata;
    135   struct OutStruct *outs = &per->outs;
    136   struct OperationConfig *config = per->config;
    137   size_t bytes = sz * nmemb;
    138   bool is_tty = config->global->isatty;
    139 #if defined(_WIN32) && !defined(UNDER_CE)
    140   CONSOLE_SCREEN_BUFFER_INFO console_info;
    141   intptr_t fhnd;
    142 #endif
    143 
    144 #ifdef DEBUGBUILD
    145   {
    146     char *tty = curl_getenv("CURL_ISATTY");
    147     if(tty) {
    148       is_tty = TRUE;
    149       curl_free(tty);
    150     }
    151   }
    152 
    153   if(config->show_headers) {
    154     if(bytes > (size_t)CURL_MAX_HTTP_HEADER) {
    155       warnf(config->global, "Header data size exceeds write limit");
    156       return CURL_WRITEFUNC_ERROR;
    157     }
    158   }
    159   else {
    160     if(bytes > (size_t)CURL_MAX_WRITE_SIZE) {
    161       warnf(config->global, "Data size exceeds write limit");
    162       return CURL_WRITEFUNC_ERROR;
    163     }
    164   }
    165 
    166   {
    167     /* Some internal congruency checks on received OutStruct */
    168     bool check_fails = FALSE;
    169     if(outs->filename) {
    170       /* regular file */
    171       if(!*outs->filename)
    172         check_fails = TRUE;
    173       if(!outs->s_isreg)
    174         check_fails = TRUE;
    175       if(outs->fopened && !outs->stream)
    176         check_fails = TRUE;
    177       if(!outs->fopened && outs->stream)
    178         check_fails = TRUE;
    179       if(!outs->fopened && outs->bytes)
    180         check_fails = TRUE;
    181     }
    182     else {
    183       /* standard stream */
    184       if(!outs->stream || outs->s_isreg || outs->fopened)
    185         check_fails = TRUE;
    186       if(outs->alloc_filename || outs->is_cd_filename || outs->init)
    187         check_fails = TRUE;
    188     }
    189     if(check_fails) {
    190       warnf(config->global, "Invalid output struct data for write callback");
    191       return CURL_WRITEFUNC_ERROR;
    192     }
    193   }
    194 #endif
    195 
    196   if(!outs->stream && !tool_create_output_file(outs, per->config))
    197     return CURL_WRITEFUNC_ERROR;
    198 
    199   if(is_tty && (outs->bytes < 2000) && !config->terminal_binary_ok) {
    200     /* binary output to terminal? */
    201     if(memchr(buffer, 0, bytes)) {
    202       warnf(config->global, "Binary output can mess up your terminal. "
    203             "Use \"--output -\" to tell curl to output it to your terminal "
    204             "anyway, or consider \"--output <FILE>\" to save to a file.");
    205       config->synthetic_error = TRUE;
    206       return CURL_WRITEFUNC_ERROR;
    207     }
    208   }
    209 
    210 #if defined(_WIN32) && !defined(UNDER_CE)
    211   fhnd = _get_osfhandle(fileno(outs->stream));
    212   /* if Windows console then UTF-8 must be converted to UTF-16 */
    213   if(isatty(fileno(outs->stream)) &&
    214      GetConsoleScreenBufferInfo((HANDLE)fhnd, &console_info)) {
    215     wchar_t *wc_buf;
    216     DWORD wc_len, chars_written;
    217     unsigned char *rbuf = (unsigned char *)buffer;
    218     DWORD rlen = (DWORD)bytes;
    219 
    220 #define IS_TRAILING_BYTE(x) (0x80 <= (x) && (x) < 0xC0)
    221 
    222     /* attempt to complete an incomplete UTF-8 sequence from previous call.
    223        the sequence does not have to be well-formed. */
    224     if(outs->utf8seq[0] && rlen) {
    225       bool complete = false;
    226       /* two byte sequence (lead byte 110yyyyy) */
    227       if(0xC0 <= outs->utf8seq[0] && outs->utf8seq[0] < 0xE0) {
    228         outs->utf8seq[1] = *rbuf++;
    229         --rlen;
    230         complete = true;
    231       }
    232       /* three byte sequence (lead byte 1110zzzz) */
    233       else if(0xE0 <= outs->utf8seq[0] && outs->utf8seq[0] < 0xF0) {
    234         if(!outs->utf8seq[1]) {
    235           outs->utf8seq[1] = *rbuf++;
    236           --rlen;
    237         }
    238         if(rlen && !outs->utf8seq[2]) {
    239           outs->utf8seq[2] = *rbuf++;
    240           --rlen;
    241           complete = true;
    242         }
    243       }
    244       /* four byte sequence (lead byte 11110uuu) */
    245       else if(0xF0 <= outs->utf8seq[0] && outs->utf8seq[0] < 0xF8) {
    246         if(!outs->utf8seq[1]) {
    247           outs->utf8seq[1] = *rbuf++;
    248           --rlen;
    249         }
    250         if(rlen && !outs->utf8seq[2]) {
    251           outs->utf8seq[2] = *rbuf++;
    252           --rlen;
    253         }
    254         if(rlen && !outs->utf8seq[3]) {
    255           outs->utf8seq[3] = *rbuf++;
    256           --rlen;
    257           complete = true;
    258         }
    259       }
    260 
    261       if(complete) {
    262         WCHAR prefix[3] = {0};  /* UTF-16 (1-2 WCHARs) + NUL */
    263 
    264         if(MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)outs->utf8seq, -1,
    265                                prefix, CURL_ARRAYSIZE(prefix))) {
    266           DEBUGASSERT(prefix[2] == L'\0');
    267           if(!WriteConsoleW(
    268               (HANDLE) fhnd,
    269               prefix,
    270               prefix[1] ? 2 : 1,
    271               &chars_written,
    272               NULL)) {
    273             return CURL_WRITEFUNC_ERROR;
    274           }
    275         }
    276         /* else: UTF-8 input was not well formed and OS is pre-Vista which
    277            drops invalid characters instead of writing U+FFFD to output.  */
    278 
    279         memset(outs->utf8seq, 0, sizeof(outs->utf8seq));
    280       }
    281     }
    282 
    283     /* suppress an incomplete utf-8 sequence at end of rbuf */
    284     if(!outs->utf8seq[0] && rlen && (rbuf[rlen - 1] & 0x80)) {
    285       /* check for lead byte from a two, three or four byte sequence */
    286       if(0xC0 <= rbuf[rlen - 1] && rbuf[rlen - 1] < 0xF8) {
    287         outs->utf8seq[0] = rbuf[rlen - 1];
    288         rlen -= 1;
    289       }
    290       else if(rlen >= 2 && IS_TRAILING_BYTE(rbuf[rlen - 1])) {
    291         /* check for lead byte from a three or four byte sequence */
    292         if(0xE0 <= rbuf[rlen - 2] && rbuf[rlen - 2] < 0xF8) {
    293           outs->utf8seq[0] = rbuf[rlen - 2];
    294           outs->utf8seq[1] = rbuf[rlen - 1];
    295           rlen -= 2;
    296         }
    297         else if(rlen >= 3 && IS_TRAILING_BYTE(rbuf[rlen - 2])) {
    298           /* check for lead byte from a four byte sequence */
    299           if(0xF0 <= rbuf[rlen - 3] && rbuf[rlen - 3] < 0xF8) {
    300             outs->utf8seq[0] = rbuf[rlen - 3];
    301             outs->utf8seq[1] = rbuf[rlen - 2];
    302             outs->utf8seq[2] = rbuf[rlen - 1];
    303             rlen -= 3;
    304           }
    305         }
    306       }
    307     }
    308 
    309     if(rlen) {
    310       /* calculate buffer size for wide characters */
    311       wc_len = (DWORD)MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)rbuf, (int)rlen,
    312                                           NULL, 0);
    313       if(!wc_len)
    314         return CURL_WRITEFUNC_ERROR;
    315 
    316       wc_buf = (wchar_t*) malloc(wc_len * sizeof(wchar_t));
    317       if(!wc_buf)
    318         return CURL_WRITEFUNC_ERROR;
    319 
    320       wc_len = (DWORD)MultiByteToWideChar(CP_UTF8, 0, (LPCSTR)rbuf, (int)rlen,
    321                                           wc_buf, (int)wc_len);
    322       if(!wc_len) {
    323         free(wc_buf);
    324         return CURL_WRITEFUNC_ERROR;
    325       }
    326 
    327       if(!WriteConsoleW(
    328           (HANDLE) fhnd,
    329           wc_buf,
    330           wc_len,
    331           &chars_written,
    332           NULL)) {
    333         free(wc_buf);
    334         return CURL_WRITEFUNC_ERROR;
    335       }
    336       free(wc_buf);
    337     }
    338 
    339     rc = bytes;
    340   }
    341   else
    342 #endif
    343   {
    344     if(per->hdrcbdata.headlist) {
    345       if(tool_write_headers(&per->hdrcbdata, outs->stream))
    346         return CURL_WRITEFUNC_ERROR;
    347     }
    348     rc = fwrite(buffer, sz, nmemb, outs->stream);
    349   }
    350 
    351   if(bytes == rc)
    352     /* we added this amount of data to the output */
    353     outs->bytes += bytes;
    354 
    355   if(config->readbusy) {
    356     config->readbusy = FALSE;
    357     curl_easy_pause(per->curl, CURLPAUSE_CONT);
    358   }
    359 
    360   if(config->nobuffer) {
    361     /* output buffering disabled */
    362     int res;
    363     do {
    364       res = fflush(outs->stream);
    365       /* Keep retrying in the hope that it is not interrupted sometime */
    366       /* !checksrc! disable ERRNOVAR 1 */
    367     } while(res && errno == EINTR);
    368     if(res)
    369       return CURL_WRITEFUNC_ERROR;
    370   }
    371 
    372   return rc;
    373 }