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