xref: /curl/src/tool_cb_wrt.c (revision 9126b141)
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 */
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   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 
tool_write_cb(char * buffer,size_t sz,size_t nmemb,void * userdata)140 size_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 = curl_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