xref: /third_party/curl/src/tool_cb_hdr.c (revision 13498266)
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