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 */ 54bool 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 140size_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