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