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