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