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