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