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 = curlx_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