1 /***************************************************************************
2  *                                  _   _ ____  _
3  *  Project                     ___| | | |  _ \| |
4  *                             / __| | | | |_) | |
5  *                            | (__| |_| |  _ <| |___
6  *                             \___|\___/|_| \_\_____|
7  *
8  * Copyright (C) 1998 - 2020, 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.haxx.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  ***************************************************************************/
22 #include "tool_setup.h"
23 
24 #ifdef HAVE_FCNTL_H
25 /* for open() */
26 #include <fcntl.h>
27 #endif
28 
29 #include <sys/stat.h>
30 
31 #define ENABLE_CURLX_PRINTF
32 /* use our own printf() functions */
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 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   DEBUGASSERT(outs);
58   DEBUGASSERT(config);
59   global = config->global;
60   if(!outs->filename || !*outs->filename) {
61     warnf(global, "Remote filename has no length!\n");
62     return FALSE;
63   }
64 
65   if(outs->is_cd_filename) {
66     /* don't overwrite existing files */
67     int fd;
68     char *name = outs->filename;
69     char *aname = NULL;
70     if(config->output_dir) {
71       aname = aprintf("%s/%s", config->output_dir, name);
72       if(!aname) {
73         errorf(global, "out of memory\n");
74         return FALSE;
75       }
76       name = aname;
77     }
78     fd = open(name, O_CREAT | O_WRONLY | O_EXCL | O_BINARY, OPENMODE);
79     if(fd != -1) {
80       file = fdopen(fd, "wb");
81       if(!file)
82         close(fd);
83     }
84     free(aname);
85   }
86   else
87     /* open file for writing */
88     file = fopen(outs->filename, "wb");
89 
90   if(!file) {
91     warnf(global, "Failed to create the file %s: %s\n", outs->filename,
92           strerror(errno));
93     return FALSE;
94   }
95   outs->s_isreg = TRUE;
96   outs->fopened = TRUE;
97   outs->stream = file;
98   outs->bytes = 0;
99   outs->init = 0;
100   return TRUE;
101 }
102 
103 /*
104 ** callback for CURLOPT_WRITEFUNCTION
105 */
106 
tool_write_cb(char * buffer,size_t sz,size_t nmemb,void * userdata)107 size_t tool_write_cb(char *buffer, size_t sz, size_t nmemb, void *userdata)
108 {
109   size_t rc;
110   struct per_transfer *per = userdata;
111   struct OutStruct *outs = &per->outs;
112   struct OperationConfig *config = per->config;
113   size_t bytes = sz * nmemb;
114   bool is_tty = config->global->isatty;
115 #ifdef WIN32
116   CONSOLE_SCREEN_BUFFER_INFO console_info;
117   intptr_t fhnd;
118 #endif
119 
120   /*
121    * Once that libcurl has called back tool_write_cb() the returned value
122    * is checked against the amount that was intended to be written, if
123    * it does not match then it fails with CURLE_WRITE_ERROR. So at this
124    * point returning a value different from sz*nmemb indicates failure.
125    */
126   const size_t failure = bytes ? 0 : 1;
127 
128 #ifdef DEBUGBUILD
129   {
130     char *tty = curlx_getenv("CURL_ISATTY");
131     if(tty) {
132       is_tty = TRUE;
133       curl_free(tty);
134     }
135   }
136 
137   if(config->show_headers) {
138     if(bytes > (size_t)CURL_MAX_HTTP_HEADER) {
139       warnf(config->global, "Header data size exceeds single call write "
140             "limit!\n");
141       return failure;
142     }
143   }
144   else {
145     if(bytes > (size_t)CURL_MAX_WRITE_SIZE) {
146       warnf(config->global, "Data size exceeds single call write limit!\n");
147       return failure;
148     }
149   }
150 
151   {
152     /* Some internal congruency checks on received OutStruct */
153     bool check_fails = FALSE;
154     if(outs->filename) {
155       /* regular file */
156       if(!*outs->filename)
157         check_fails = TRUE;
158       if(!outs->s_isreg)
159         check_fails = TRUE;
160       if(outs->fopened && !outs->stream)
161         check_fails = TRUE;
162       if(!outs->fopened && outs->stream)
163         check_fails = TRUE;
164       if(!outs->fopened && outs->bytes)
165         check_fails = TRUE;
166     }
167     else {
168       /* standard stream */
169       if(!outs->stream || outs->s_isreg || outs->fopened)
170         check_fails = TRUE;
171       if(outs->alloc_filename || outs->is_cd_filename || outs->init)
172         check_fails = TRUE;
173     }
174     if(check_fails) {
175       warnf(config->global, "Invalid output struct data for write callback\n");
176       return failure;
177     }
178   }
179 #endif
180 
181   if(!outs->stream && !tool_create_output_file(outs, per->config))
182     return failure;
183 
184   if(is_tty && (outs->bytes < 2000) && !config->terminal_binary_ok) {
185     /* binary output to terminal? */
186     if(memchr(buffer, 0, bytes)) {
187       warnf(config->global, "Binary output can mess up your terminal. "
188             "Use \"--output -\" to tell curl to output it to your terminal "
189             "anyway, or consider \"--output <FILE>\" to save to a file.\n");
190       config->synthetic_error = ERR_BINARY_TERMINAL;
191       return failure;
192     }
193   }
194 
195 #ifdef WIN32
196   fhnd = _get_osfhandle(fileno(outs->stream));
197   if(isatty(fileno(outs->stream)) &&
198      GetConsoleScreenBufferInfo((HANDLE)fhnd, &console_info)) {
199     DWORD in_len = (DWORD)(sz * nmemb);
200     wchar_t* wc_buf;
201     DWORD wc_len;
202 
203     /* calculate buffer size for wide characters */
204     wc_len = MultiByteToWideChar(CP_UTF8, 0, buffer, in_len,  NULL, 0);
205     wc_buf = (wchar_t*) malloc(wc_len * sizeof(wchar_t));
206     if(!wc_buf)
207       return failure;
208 
209     /* calculate buffer size for multi-byte characters */
210     wc_len = MultiByteToWideChar(CP_UTF8, 0, buffer, in_len, wc_buf, wc_len);
211     if(!wc_len) {
212       free(wc_buf);
213       return failure;
214     }
215 
216     if(!WriteConsoleW(
217         (HANDLE) fhnd,
218         wc_buf,
219         wc_len,
220         &wc_len,
221         NULL)) {
222       free(wc_buf);
223       return failure;
224     }
225     free(wc_buf);
226     rc = bytes;
227   }
228   else
229 #endif
230     rc = fwrite(buffer, sz, nmemb, outs->stream);
231 
232   if(bytes == rc)
233     /* we added this amount of data to the output */
234     outs->bytes += bytes;
235 
236   if(config->readbusy) {
237     config->readbusy = FALSE;
238     curl_easy_pause(per->curl, CURLPAUSE_CONT);
239   }
240 
241   if(config->nobuffer) {
242     /* output buffering disabled */
243     int res = fflush(outs->stream);
244     if(res)
245       return failure;
246   }
247 
248   return rc;
249 }
250