tool_cb_hdr.c (14128B)
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_UNISTD_H 27 #include <unistd.h> 28 #endif 29 30 #include "tool_cfgable.h" 31 #include "tool_doswin.h" 32 #include "tool_msgs.h" 33 #include "tool_cb_hdr.h" 34 #include "tool_cb_wrt.h" 35 #include "tool_operate.h" 36 #include "tool_libinfo.h" 37 38 #include "memdebug.h" /* keep this as LAST include */ 39 40 static char *parse_filename(const char *ptr, size_t len); 41 42 #ifdef _WIN32 43 #define BOLD "\x1b[1m" 44 #define BOLDOFF "\x1b[22m" 45 #else 46 #define BOLD "\x1b[1m" 47 /* Switch off bold by setting "all attributes off" since the explicit 48 bold-off code (21) is not supported everywhere - like in the mac 49 Terminal. */ 50 #define BOLDOFF "\x1b[0m" 51 /* OSC 8 hyperlink escape sequence */ 52 #define LINK "\x1b]8;;" 53 #define LINKST "\x1b\\" 54 #define LINKOFF LINK LINKST 55 #endif 56 57 #ifdef LINK 58 static void write_linked_location(CURL *curl, const char *location, 59 size_t loclen, FILE *stream); 60 #endif 61 62 int tool_write_headers(struct HdrCbData *hdrcbdata, FILE *stream) 63 { 64 struct curl_slist *h = hdrcbdata->headlist; 65 int rc = 1; 66 while(h) { 67 /* not "handled", just show it */ 68 size_t len = strlen(h->data); 69 if(len != fwrite(h->data, 1, len, stream)) 70 goto fail; 71 h = h->next; 72 } 73 rc = 0; /* success */ 74 fail: 75 curl_slist_free_all(hdrcbdata->headlist); 76 hdrcbdata->headlist = NULL; 77 return rc; 78 } 79 80 81 /* 82 ** callback for CURLOPT_HEADERFUNCTION 83 */ 84 85 size_t tool_header_cb(char *ptr, size_t size, size_t nmemb, void *userdata) 86 { 87 struct per_transfer *per = userdata; 88 struct HdrCbData *hdrcbdata = &per->hdrcbdata; 89 struct OutStruct *outs = &per->outs; 90 struct OutStruct *heads = &per->heads; 91 struct OutStruct *etag_save = &per->etag_save; 92 const char *str = ptr; 93 const size_t cb = size * nmemb; 94 const char *end = (char *)ptr + cb; 95 const char *scheme = NULL; 96 97 if(!per->config) 98 return CURL_WRITEFUNC_ERROR; 99 100 #ifdef DEBUGBUILD 101 if(size * nmemb > (size_t)CURL_MAX_HTTP_HEADER) { 102 warnf(per->config->global, "Header data exceeds write limit"); 103 return CURL_WRITEFUNC_ERROR; 104 } 105 #endif 106 107 #ifdef _WIN32 108 /* Discard incomplete UTF-8 sequence buffered from body */ 109 if(outs->utf8seq[0]) 110 memset(outs->utf8seq, 0, sizeof(outs->utf8seq)); 111 #endif 112 113 /* 114 * Write header data when curl option --dump-header (-D) is given. 115 */ 116 117 if(per->config->headerfile && heads->stream) { 118 size_t rc = fwrite(ptr, size, nmemb, heads->stream); 119 if(rc != cb) 120 return rc; 121 /* flush the stream to send off what we got earlier */ 122 if(fflush(heads->stream)) { 123 errorf(per->config->global, "Failed writing headers to %s", 124 per->config->headerfile); 125 return CURL_WRITEFUNC_ERROR; 126 } 127 } 128 129 curl_easy_getinfo(per->curl, CURLINFO_SCHEME, &scheme); 130 scheme = proto_token(scheme); 131 if((scheme == proto_http || scheme == proto_https)) { 132 long response = 0; 133 curl_easy_getinfo(per->curl, CURLINFO_RESPONSE_CODE, &response); 134 135 if((response/100 != 2) && (response/100 != 3)) 136 /* only care about etag and content-disposition headers in 2xx and 3xx 137 responses */ 138 ; 139 /* 140 * Write etag to file when --etag-save option is given. 141 */ 142 else if(per->config->etag_save_file && etag_save->stream && 143 /* match only header that start with etag (case insensitive) */ 144 checkprefix("etag:", str)) { 145 const char *etag_h = &str[5]; 146 const char *eot = end - 1; 147 if(*eot == '\n') { 148 while(ISBLANK(*etag_h) && (etag_h < eot)) 149 etag_h++; 150 while(ISSPACE(*eot)) 151 eot--; 152 153 if(eot >= etag_h) { 154 size_t etag_length = eot - etag_h + 1; 155 /* 156 * Truncate the etag save stream, it can have an existing etag value. 157 */ 158 #if defined(HAVE_FTRUNCATE) && !defined(__MINGW32CE__) 159 if(ftruncate(fileno(etag_save->stream), 0)) { 160 return CURL_WRITEFUNC_ERROR; 161 } 162 #else 163 if(fseek(etag_save->stream, 0, SEEK_SET)) { 164 return CURL_WRITEFUNC_ERROR; 165 } 166 #endif 167 168 fwrite(etag_h, size, etag_length, etag_save->stream); 169 /* terminate with newline */ 170 fputc('\n', etag_save->stream); 171 (void)fflush(etag_save->stream); 172 } 173 } 174 } 175 176 /* 177 * This callback sets the filename where output shall be written when 178 * curl options --remote-name (-O) and --remote-header-name (-J) have 179 * been simultaneously given and additionally server returns an HTTP 180 * Content-Disposition header specifying a filename property. 181 */ 182 183 else if(hdrcbdata->honor_cd_filename) { 184 if((cb > 20) && checkprefix("Content-disposition:", str)) { 185 const char *p = str + 20; 186 187 /* look for the 'filename=' parameter 188 (encoded filenames (*=) are not supported) */ 189 for(;;) { 190 char *filename; 191 size_t len; 192 193 while((p < end) && *p && !ISALPHA(*p)) 194 p++; 195 if(p > end - 9) 196 break; 197 198 if(memcmp(p, "filename=", 9)) { 199 /* no match, find next parameter */ 200 while((p < end) && *p && (*p != ';')) 201 p++; 202 if((p < end) && *p) 203 continue; 204 else 205 break; 206 } 207 p += 9; 208 209 len = cb - (size_t)(p - str); 210 filename = parse_filename(p, len); 211 if(filename) { 212 if(outs->stream) { 213 /* indication of problem, get out! */ 214 free(filename); 215 return CURL_WRITEFUNC_ERROR; 216 } 217 218 if(per->config->output_dir) { 219 outs->filename = aprintf("%s/%s", per->config->output_dir, 220 filename); 221 free(filename); 222 if(!outs->filename) 223 return CURL_WRITEFUNC_ERROR; 224 } 225 else 226 outs->filename = filename; 227 228 outs->is_cd_filename = TRUE; 229 outs->s_isreg = TRUE; 230 outs->fopened = FALSE; 231 outs->alloc_filename = TRUE; 232 hdrcbdata->honor_cd_filename = FALSE; /* done now! */ 233 if(!tool_create_output_file(outs, per->config)) 234 return CURL_WRITEFUNC_ERROR; 235 if(tool_write_headers(&per->hdrcbdata, outs->stream)) 236 return CURL_WRITEFUNC_ERROR; 237 } 238 break; 239 } 240 if(!outs->stream && !tool_create_output_file(outs, per->config)) 241 return CURL_WRITEFUNC_ERROR; 242 if(tool_write_headers(&per->hdrcbdata, outs->stream)) 243 return CURL_WRITEFUNC_ERROR; 244 } /* content-disposition handling */ 245 246 if(hdrcbdata->honor_cd_filename && 247 hdrcbdata->config->show_headers) { 248 /* still awaiting the Content-Disposition header, store the header in 249 memory. Since it is not null-terminated, we need an extra dance. */ 250 char *clone = aprintf("%.*s", (int)cb, str); 251 if(clone) { 252 struct curl_slist *old = hdrcbdata->headlist; 253 hdrcbdata->headlist = curl_slist_append(old, clone); 254 free(clone); 255 if(!hdrcbdata->headlist) { 256 curl_slist_free_all(old); 257 return CURL_WRITEFUNC_ERROR; 258 } 259 } 260 else { 261 curl_slist_free_all(hdrcbdata->headlist); 262 hdrcbdata->headlist = NULL; 263 return CURL_WRITEFUNC_ERROR; 264 } 265 return cb; /* done for now */ 266 } 267 } 268 } 269 if(hdrcbdata->config->writeout) { 270 char *value = memchr(ptr, ':', cb); 271 if(value) { 272 if(per->was_last_header_empty) 273 per->num_headers = 0; 274 per->was_last_header_empty = FALSE; 275 per->num_headers++; 276 } 277 else if(ptr[0] == '\r' || ptr[0] == '\n') 278 per->was_last_header_empty = TRUE; 279 } 280 if(hdrcbdata->config->show_headers && 281 (scheme == proto_http || scheme == proto_https || 282 scheme == proto_rtsp || scheme == proto_file)) { 283 /* bold headers only for selected protocols */ 284 char *value = NULL; 285 286 if(!outs->stream && !tool_create_output_file(outs, per->config)) 287 return CURL_WRITEFUNC_ERROR; 288 289 if(hdrcbdata->global->isatty && 290 #ifdef _WIN32 291 tool_term_has_bold && 292 #endif 293 hdrcbdata->global->styled_output) 294 value = memchr(ptr, ':', cb); 295 if(value) { 296 size_t namelen = value - ptr; 297 fprintf(outs->stream, BOLD "%.*s" BOLDOFF ":", (int)namelen, ptr); 298 #ifndef LINK 299 fwrite(&value[1], cb - namelen - 1, 1, outs->stream); 300 #else 301 if(curl_strnequal("Location", ptr, namelen)) { 302 write_linked_location(per->curl, &value[1], cb - namelen - 1, 303 outs->stream); 304 } 305 else 306 fwrite(&value[1], cb - namelen - 1, 1, outs->stream); 307 #endif 308 } 309 else 310 /* not "handled", just show it */ 311 fwrite(ptr, cb, 1, outs->stream); 312 } 313 return cb; 314 } 315 316 /* 317 * Copies a filename part and returns an ALLOCATED data buffer. 318 */ 319 static char *parse_filename(const char *ptr, size_t len) 320 { 321 char *copy; 322 char *p; 323 char *q; 324 char stop = '\0'; 325 326 /* simple implementation of strndup() */ 327 copy = malloc(len + 1); 328 if(!copy) 329 return NULL; 330 memcpy(copy, ptr, len); 331 copy[len] = '\0'; 332 333 p = copy; 334 if(*p == '\'' || *p == '"') { 335 /* store the starting quote */ 336 stop = *p; 337 p++; 338 } 339 else 340 stop = ';'; 341 342 /* scan for the end letter and stop there */ 343 q = strchr(p, stop); 344 if(q) 345 *q = '\0'; 346 347 /* if the filename contains a path, only use filename portion */ 348 q = strrchr(p, '/'); 349 if(q) { 350 p = q + 1; 351 if(!*p) { 352 tool_safefree(copy); 353 return NULL; 354 } 355 } 356 357 /* If the filename contains a backslash, only use filename portion. The idea 358 is that even systems that do not handle backslashes as path separators 359 probably want the path removed for convenience. */ 360 q = strrchr(p, '\\'); 361 if(q) { 362 p = q + 1; 363 if(!*p) { 364 tool_safefree(copy); 365 return NULL; 366 } 367 } 368 369 /* make sure the filename does not end in \r or \n */ 370 q = strchr(p, '\r'); 371 if(q) 372 *q = '\0'; 373 374 q = strchr(p, '\n'); 375 if(q) 376 *q = '\0'; 377 378 if(copy != p) 379 memmove(copy, p, strlen(p) + 1); 380 381 #if defined(_WIN32) || defined(MSDOS) 382 { 383 char *sanitized; 384 SANITIZEcode sc = sanitize_file_name(&sanitized, copy, 0); 385 tool_safefree(copy); 386 if(sc) 387 return NULL; 388 copy = sanitized; 389 } 390 #endif /* _WIN32 || MSDOS */ 391 392 return copy; 393 } 394 395 #ifdef LINK 396 /* 397 * Treat the Location: header specially, by writing a special escape 398 * sequence that adds a hyperlink to the displayed text. This makes 399 * the absolute URL of the redirect clickable in supported terminals, 400 * which could not happen otherwise for relative URLs. The Location: 401 * header is supposed to always be absolute so this theoretically 402 * should not be needed but the real world returns plenty of relative 403 * URLs here. 404 */ 405 static 406 void write_linked_location(CURL *curl, const char *location, size_t loclen, 407 FILE *stream) { 408 /* This would so simple if CURLINFO_REDIRECT_URL were available here */ 409 CURLU *u = NULL; 410 char *copyloc = NULL, *locurl = NULL, *scheme = NULL, *finalurl = NULL; 411 const char *loc = location; 412 size_t llen = loclen; 413 int space_skipped = 0; 414 const char *vver = getenv("VTE_VERSION"); 415 416 if(vver) { 417 curl_off_t num; 418 if(curlx_str_number(&vver, &num, CURL_OFF_T_MAX) || 419 /* Skip formatting for old versions of VTE <= 0.48.1 (Mar 2017) since 420 some of those versions have formatting bugs. (#10428) */ 421 (num <= 4801)) 422 goto locout; 423 } 424 425 /* Strip leading whitespace of the redirect URL */ 426 while(llen && (*loc == ' ' || *loc == '\t')) { 427 ++loc; 428 --llen; 429 ++space_skipped; 430 } 431 432 /* Strip the trailing end-of-line characters, normally "\r\n" */ 433 while(llen && (loc[llen-1] == '\n' || loc[llen-1] == '\r')) 434 --llen; 435 436 /* CURLU makes it easy to handle the relative URL case */ 437 u = curl_url(); 438 if(!u) 439 goto locout; 440 441 /* Create a null-terminated and whitespace-stripped copy of Location: */ 442 copyloc = malloc(llen + 1); 443 if(!copyloc) 444 goto locout; 445 memcpy(copyloc, loc, llen); 446 copyloc[llen] = 0; 447 448 /* The original URL to use as a base for a relative redirect URL */ 449 if(curl_easy_getinfo(curl, CURLINFO_EFFECTIVE_URL, &locurl)) 450 goto locout; 451 if(curl_url_set(u, CURLUPART_URL, locurl, 0)) 452 goto locout; 453 454 /* Redirected location. This can be either absolute or relative. */ 455 if(curl_url_set(u, CURLUPART_URL, copyloc, 0)) 456 goto locout; 457 458 if(curl_url_get(u, CURLUPART_URL, &finalurl, CURLU_NO_DEFAULT_PORT)) 459 goto locout; 460 461 if(curl_url_get(u, CURLUPART_SCHEME, &scheme, 0)) 462 goto locout; 463 464 if(!strcmp("http", scheme) || 465 !strcmp("https", scheme) || 466 !strcmp("ftp", scheme) || 467 !strcmp("ftps", scheme)) { 468 fprintf(stream, "%.*s" LINK "%s" LINKST "%.*s" LINKOFF, 469 space_skipped, location, 470 finalurl, 471 (int)loclen - space_skipped, loc); 472 goto locdone; 473 } 474 475 /* Not a "safe" URL: do not linkify it */ 476 477 locout: 478 /* Write the normal output in case of error or unsafe */ 479 fwrite(location, loclen, 1, stream); 480 481 locdone: 482 if(u) { 483 curl_free(finalurl); 484 curl_free(scheme); 485 curl_url_cleanup(u); 486 free(copyloc); 487 } 488 } 489 #endif