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
42 static 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
60 static 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
tool_header_cb(char * ptr,size_t size,size_t nmemb,void * userdata)68 size_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 */
parse_filename(const char * ptr,size_t len)253 static 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 */
359 static
write_linked_location(CURL * curl,const char * location,size_t loclen,FILE * stream)360 void 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
430 locout:
431 /* Write the normal output in case of error or unsafe */
432 fwrite(location, loclen, 1, stream);
433
434 locdone:
435 if(u) {
436 curl_free(finalurl);
437 curl_free(scheme);
438 curl_url_cleanup(u);
439 free(copyloc);
440 }
441 }
442 #endif
443