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