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