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