• 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 #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   char *aname = NULL;
61   DEBUGASSERT(outs);
62   DEBUGASSERT(config);
63   global = config->global;
64   if(!fname || !*fname) {
65     warnf(global, "Remote filename has no length!\n");
66     return FALSE;
67   }
68 
69   if(config->output_dir && outs->is_cd_filename) {
70     aname = aprintf("%s/%s", config->output_dir, fname);
71     if(!aname) {
72       errorf(global, "out of memory\n");
73       return FALSE;
74     }
75     fname = aname;
76   }
77 
78   if(config->file_clobber_mode == CLOBBER_ALWAYS ||
79      (config->file_clobber_mode == CLOBBER_DEFAULT &&
80       !outs->is_cd_filename)) {
81     /* open file for writing */
82     file = fopen(fname, "wb");
83   }
84   else {
85     int fd;
86     do {
87       fd = open(fname, O_CREAT | O_WRONLY | O_EXCL | O_BINARY, OPENMODE);
88       /* Keep retrying in the hope that it isn't interrupted sometime */
89     } while(fd == -1 && errno == EINTR);
90     if(config->file_clobber_mode == CLOBBER_NEVER && fd == -1) {
91       int next_num = 1;
92       size_t len = strlen(fname);
93       size_t newlen = len + 13; /* nul + 1-11 digits + dot */
94       char *newname;
95       /* Guard against wraparound in new filename */
96       if(newlen < len) {
97         free(aname);
98         errorf(global, "overflow in filename generation\n");
99         return FALSE;
100       }
101       newname = malloc(newlen);
102       if(!newname) {
103         errorf(global, "out of memory\n");
104         free(aname);
105         return FALSE;
106       }
107       memcpy(newname, fname, len);
108       newname[len] = '.';
109       while(fd == -1 && /* haven't successfully opened a file */
110             (errno == EEXIST || errno == EISDIR) &&
111             /* because we keep having files that already exist */
112             next_num < 100 /* and we haven't reached the retry limit */ ) {
113         curlx_msnprintf(newname + len + 1, 12, "%d", next_num);
114         next_num++;
115         do {
116           fd = open(newname, O_CREAT | O_WRONLY | O_EXCL | O_BINARY, OPENMODE);
117           /* Keep retrying in the hope that it isn't interrupted sometime */
118         } while(fd == -1 && errno == EINTR);
119       }
120       outs->filename = newname; /* remember the new one */
121       outs->alloc_filename = TRUE;
122     }
123     /* An else statement to not overwrite existing files and not retry with
124        new numbered names (which would cover
125        config->file_clobber_mode == CLOBBER_DEFAULT && outs->is_cd_filename)
126        is not needed because we would have failed earlier, in the while loop
127        and `fd` would now be -1 */
128     if(fd != -1) {
129       file = fdopen(fd, "wb");
130       if(!file)
131         close(fd);
132     }
133   }
134 
135   if(!file) {
136     warnf(global, "Failed to open the file %s: %s\n", fname,
137           strerror(errno));
138     free(aname);
139     return FALSE;
140   }
141   free(aname);
142   outs->s_isreg = TRUE;
143   outs->fopened = TRUE;
144   outs->stream = file;
145   outs->bytes = 0;
146   outs->init = 0;
147   return TRUE;
148 }
149 
150 /*
151 ** callback for CURLOPT_WRITEFUNCTION
152 */
153 
tool_write_cb(char * buffer,size_t sz,size_t nmemb,void * userdata)154 size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
155 {
156   size_t rc;
157   struct per_transfer *per = userdata;
158   struct OutStruct *outs = &per->outs;
159   struct OperationConfig *config = per->config;
160   size_t bytes = sz * nmemb;
161   bool is_tty = config->global->isatty;
162 #ifdef WIN32
163   CONSOLE_SCREEN_BUFFER_INFO console_info;
164   intptr_t fhnd;
165 #endif
166 
167 #ifdef DEBUGBUILD
168   {
169     char *tty = curlx_getenv("CURL_ISATTY");
170     if(tty) {
171       is_tty = TRUE;
172       curl_free(tty);
173     }
174   }
175 
176   if(config->show_headers) {
177     if(bytes > (size_t)CURL_MAX_HTTP_HEADER) {
178       warnf(config->global, "Header data size exceeds single call write "
179             "limit!\n");
180       return CURL_WRITEFUNC_ERROR;
181     }
182   }
183   else {
184     if(bytes > (size_t)CURL_MAX_WRITE_SIZE) {
185       warnf(config->global, "Data size exceeds single call write limit!\n");
186       return CURL_WRITEFUNC_ERROR;
187     }
188   }
189 
190   {
191     /* Some internal congruency checks on received OutStruct */
192     bool check_fails = FALSE;
193     if(outs->filename) {
194       /* regular file */
195       if(!*outs->filename)
196         check_fails = TRUE;
197       if(!outs->s_isreg)
198         check_fails = TRUE;
199       if(outs->fopened && !outs->stream)
200         check_fails = TRUE;
201       if(!outs->fopened && outs->stream)
202         check_fails = TRUE;
203       if(!outs->fopened && outs->bytes)
204         check_fails = TRUE;
205     }
206     else {
207       /* standard stream */
208       if(!outs->stream || outs->s_isreg || outs->fopened)
209         check_fails = TRUE;
210       if(outs->alloc_filename || outs->is_cd_filename || outs->init)
211         check_fails = TRUE;
212     }
213     if(check_fails) {
214       warnf(config->global, "Invalid output struct data for write callback\n");
215       return CURL_WRITEFUNC_ERROR;
216     }
217   }
218 #endif
219 
220   if(!outs->stream && !tool_create_output_file(outs, per->config))
221     return CURL_WRITEFUNC_ERROR;
222 
223   if(is_tty && (outs->bytes < 2000) && !config->terminal_binary_ok) {
224     /* binary output to terminal? */
225     if(memchr(buffer, 0, bytes)) {
226       warnf(config->global, "Binary output can mess up your terminal. "
227             "Use \"--output -\" to tell curl to output it to your terminal "
228             "anyway, or consider \"--output <FILE>\" to save to a file.\n");
229       config->synthetic_error = TRUE;
230       return CURL_WRITEFUNC_ERROR;
231     }
232   }
233 
234 #ifdef WIN32
235   fhnd = _get_osfhandle(fileno(outs->stream));
236   if(isatty(fileno(outs->stream)) &&
237      GetConsoleScreenBufferInfo((HANDLE)fhnd, &console_info)) {
238     DWORD in_len = (DWORD)(sz * nmemb);
239     wchar_t* wc_buf;
240     DWORD wc_len;
241 
242     /* calculate buffer size for wide characters */
243     wc_len = MultiByteToWideChar(CP_UTF8, 0, buffer, in_len,  NULL, 0);
244     wc_buf = (wchar_t*) malloc(wc_len * sizeof(wchar_t));
245     if(!wc_buf)
246       return CURL_WRITEFUNC_ERROR;
247 
248     /* calculate buffer size for multi-byte characters */
249     wc_len = MultiByteToWideChar(CP_UTF8, 0, buffer, in_len, wc_buf, wc_len);
250     if(!wc_len) {
251       free(wc_buf);
252       return CURL_WRITEFUNC_ERROR;
253     }
254 
255     if(!WriteConsoleW(
256         (HANDLE) fhnd,
257         wc_buf,
258         wc_len,
259         &wc_len,
260         NULL)) {
261       free(wc_buf);
262       return CURL_WRITEFUNC_ERROR;
263     }
264     free(wc_buf);
265     rc = bytes;
266   }
267   else
268 #endif
269     rc = fwrite(buffer, sz, nmemb, outs->stream);
270 
271   if(bytes == rc)
272     /* we added this amount of data to the output */
273     outs->bytes += bytes;
274 
275   if(config->readbusy) {
276     config->readbusy = FALSE;
277     curl_easy_pause(per->curl, CURLPAUSE_CONT);
278   }
279 
280   if(config->nobuffer) {
281     /* output buffering disabled */
282     int res = fflush(outs->stream);
283     if(res)
284       return CURL_WRITEFUNC_ERROR;
285   }
286 
287   return rc;
288 }
289