wget: don't notify on download begin and end if quiet
[oweals/busybox.git] / networking / wget.c
1 /* vi: set sw=4 ts=4: */
2 /*
3  * wget - retrieve a file using HTTP or FTP
4  *
5  * Chip Rosenthal Covad Communications <chip@laserlink.net>
6  * Licensed under GPLv2, see file LICENSE in this source tree.
7  *
8  * Copyright (C) 2010 Bradley M. Kuhn <bkuhn@ebb.org>
9  * Kuhn's copyrights are licensed GPLv2-or-later.  File as a whole remains GPLv2.
10  */
11 //config:config WGET
12 //config:       bool "wget (38 kb)"
13 //config:       default y
14 //config:       help
15 //config:       wget is a utility for non-interactive download of files from HTTP
16 //config:       and FTP servers.
17 //config:
18 //config:config FEATURE_WGET_LONG_OPTIONS
19 //config:       bool "Enable long options"
20 //config:       default y
21 //config:       depends on WGET && LONG_OPTS
22 //config:
23 //config:config FEATURE_WGET_STATUSBAR
24 //config:       bool "Enable progress bar (+2k)"
25 //config:       default y
26 //config:       depends on WGET
27 //config:
28 //config:config FEATURE_WGET_AUTHENTICATION
29 //config:       bool "Enable HTTP authentication"
30 //config:       default y
31 //config:       depends on WGET
32 //config:       help
33 //config:       Support authenticated HTTP transfers.
34 //config:
35 //config:config FEATURE_WGET_TIMEOUT
36 //config:       bool "Enable timeout option -T SEC"
37 //config:       default y
38 //config:       depends on WGET
39 //config:       help
40 //config:       Supports network read and connect timeouts for wget,
41 //config:       so that wget will give up and timeout, through the -T
42 //config:       command line option.
43 //config:
44 //config:       Currently only connect and network data read timeout are
45 //config:       supported (i.e., timeout is not applied to the DNS query). When
46 //config:       FEATURE_WGET_LONG_OPTIONS is also enabled, the --timeout option
47 //config:       will work in addition to -T.
48 //config:
49 //config:config FEATURE_WGET_HTTPS
50 //config:       bool "Support HTTPS using internal TLS code"
51 //it also enables FTPS support, but it's not well tested yet
52 //config:       default y
53 //config:       depends on WGET
54 //config:       select TLS
55 //config:       help
56 //config:       wget will use internal TLS code to connect to https:// URLs.
57 //config:       Note:
58 //config:       On NOMMU machines, ssl_helper applet should be available
59 //config:       in the $PATH for this to work. Make sure to select that applet.
60 //config:
61 //config:       Note: currently, TLS code only makes TLS I/O work, it
62 //config:       does *not* check that the peer is who it claims to be, etc.
63 //config:       IOW: it uses peer-supplied public keys to establish encryption
64 //config:       and signing keys, then encrypts and signs outgoing data and
65 //config:       decrypts incoming data.
66 //config:       It does not check signature hashes on the incoming data:
67 //config:       this means that attackers manipulating TCP packets can
68 //config:       send altered data and we unknowingly receive garbage.
69 //config:       (This check might be relatively easy to add).
70 //config:       It does not check public key's certificate:
71 //config:       this means that the peer may be an attacker impersonating
72 //config:       the server we think we are talking to.
73 //config:
74 //config:       If you think this is unacceptable, consider this. As more and more
75 //config:       servers switch to HTTPS-only operation, without such "crippled"
76 //config:       TLS code it is *impossible* to simply download a kernel source
77 //config:       from kernel.org. Which can in real world translate into
78 //config:       "my small automatic tooling to build cross-compilers from sources
79 //config:       no longer works, I need to additionally keep a local copy
80 //config:       of ~4 megabyte source tarball of a SSL library and ~2 megabyte
81 //config:       source of wget, need to compile and built both before I can
82 //config:       download anything. All this despite the fact that the build
83 //config:       is done in a QEMU sandbox on a machine with absolutely nothing
84 //config:       worth stealing, so I don't care if someone would go to a lot
85 //config:       of trouble to intercept my HTTPS download to send me an altered
86 //config:       kernel tarball".
87 //config:
88 //config:       If you still think this is unacceptable, send patches.
89 //config:
90 //config:       If you still think this is unacceptable, do not want to send
91 //config:       patches, but do want to waste bandwidth expaining how wrong
92 //config:       it is, you will be ignored.
93 //config:
94 //config:config FEATURE_WGET_OPENSSL
95 //config:       bool "Try to connect to HTTPS using openssl"
96 //config:       default y
97 //config:       depends on WGET
98 //config:       help
99 //config:       Try to use openssl to handle HTTPS.
100 //config:
101 //config:       OpenSSL has a simple SSL client for debug purposes.
102 //config:       If you select this option, wget will effectively run:
103 //config:       "openssl s_client -quiet -connect hostname:443
104 //config:       -servername hostname 2>/dev/null" and pipe its data
105 //config:       through it. -servername is not used if hostname is numeric.
106 //config:       Note inconvenient API: host resolution is done twice,
107 //config:       and there is no guarantee openssl's idea of IPv6 address
108 //config:       format is the same as ours.
109 //config:       Another problem is that s_client prints debug information
110 //config:       to stderr, and it needs to be suppressed. This means
111 //config:       all error messages get suppressed too.
112 //config:       openssl is also a big binary, often dynamically linked
113 //config:       against ~15 libraries.
114 //config:
115 //config:       If openssl can't be executed, internal TLS code will be used
116 //config:       (if you enabled it); if openssl can be executed but fails later,
117 //config:       wget can't detect this, and download will fail.
118
119 //applet:IF_WGET(APPLET(wget, BB_DIR_USR_BIN, BB_SUID_DROP))
120
121 //kbuild:lib-$(CONFIG_WGET) += wget.o
122
123 //usage:#define wget_trivial_usage
124 //usage:        IF_FEATURE_WGET_LONG_OPTIONS(
125 //usage:       "[-c|--continue] [--spider] [-q|--quiet] [-O|--output-document FILE]\n"
126 //usage:       "        [-o|--output-file FILE] [--header 'header: value'] [-Y|--proxy on/off]\n"
127 /* Since we ignore these opts, we don't show them in --help */
128 /* //usage:    "        [--no-check-certificate] [--no-cache] [--passive-ftp] [-t TRIES]" */
129 /* //usage:    "        [-nv] [-nc] [-nH] [-np]" */
130 //usage:       "        [-P DIR] [-S|--server-response] [-U|--user-agent AGENT]" IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
131 //usage:        )
132 //usage:        IF_NOT_FEATURE_WGET_LONG_OPTIONS(
133 //usage:       "[-cq] [-O FILE] [-o FILE] [-Y on/off] [-P DIR] [-S] [-U AGENT]"
134 //usage:                        IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
135 //usage:        )
136 //usage:#define wget_full_usage "\n\n"
137 //usage:       "Retrieve files via HTTP or FTP\n"
138 //usage:        IF_FEATURE_WGET_LONG_OPTIONS(
139 //usage:     "\n        --spider        Only check URL existence: $? is 0 if exists"
140 ///////:     "\n        --no-check-certificate  Don't validate the server's certificate"
141 //usage:        )
142 //usage:     "\n        -c              Continue retrieval of aborted transfer"
143 //usage:     "\n        -q              Quiet"
144 //usage:     "\n        -P DIR          Save to DIR (default .)"
145 //usage:     "\n        -S              Show server response"
146 //usage:        IF_FEATURE_WGET_TIMEOUT(
147 //usage:     "\n        -T SEC          Network read timeout is SEC seconds"
148 //usage:        )
149 //usage:     "\n        -O FILE         Save to FILE ('-' for stdout)"
150 //usage:     "\n        -o FILE         Log messages to FILE"
151 //usage:     "\n        -U STR          Use STR for User-Agent header"
152 //usage:     "\n        -Y on/off       Use proxy"
153
154 #include "libbb.h"
155
156 #if 0
157 # define log_io(...) bb_error_msg(__VA_ARGS__)
158 # define SENDFMT(fp, fmt, ...) \
159         do { \
160                 log_io("> " fmt, ##__VA_ARGS__); \
161                 fprintf(fp, fmt, ##__VA_ARGS__); \
162         } while (0);
163 #else
164 # define log_io(...) ((void)0)
165 # define SENDFMT(fp, fmt, ...) fprintf(fp, fmt, ##__VA_ARGS__)
166 #endif
167
168
169 #define SSL_SUPPORTED (ENABLE_FEATURE_WGET_OPENSSL || ENABLE_FEATURE_WGET_HTTPS)
170
171 struct host_info {
172         char *allocated;
173         const char *path;
174         char       *user;
175         const char *protocol;
176         char       *host;
177         int         port;
178 };
179 static const char P_FTP[] ALIGN1 = "ftp";
180 static const char P_HTTP[] ALIGN1 = "http";
181 #if SSL_SUPPORTED
182 # if ENABLE_FEATURE_WGET_HTTPS
183 static const char P_FTPS[] ALIGN1 = "ftps";
184 # endif
185 static const char P_HTTPS[] ALIGN1 = "https";
186 #endif
187
188 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
189 /* User-specified headers prevent using our corresponding built-in headers.  */
190 enum {
191         HDR_HOST          = (1<<0),
192         HDR_USER_AGENT    = (1<<1),
193         HDR_RANGE         = (1<<2),
194         HDR_AUTH          = (1<<3) * ENABLE_FEATURE_WGET_AUTHENTICATION,
195         HDR_PROXY_AUTH    = (1<<4) * ENABLE_FEATURE_WGET_AUTHENTICATION,
196 };
197 static const char wget_user_headers[] ALIGN1 =
198         "Host:\0"
199         "User-Agent:\0"
200         "Range:\0"
201 # if ENABLE_FEATURE_WGET_AUTHENTICATION
202         "Authorization:\0"
203         "Proxy-Authorization:\0"
204 # endif
205         ;
206 # define USR_HEADER_HOST       (G.user_headers & HDR_HOST)
207 # define USR_HEADER_USER_AGENT (G.user_headers & HDR_USER_AGENT)
208 # define USR_HEADER_RANGE      (G.user_headers & HDR_RANGE)
209 # define USR_HEADER_AUTH       (G.user_headers & HDR_AUTH)
210 # define USR_HEADER_PROXY_AUTH (G.user_headers & HDR_PROXY_AUTH)
211 #else /* No long options, no user-headers :( */
212 # define USR_HEADER_HOST       0
213 # define USR_HEADER_USER_AGENT 0
214 # define USR_HEADER_RANGE      0
215 # define USR_HEADER_AUTH       0
216 # define USR_HEADER_PROXY_AUTH 0
217 #endif
218
219 /* Globals */
220 struct globals {
221         off_t content_len;        /* Content-length of the file */
222         off_t beg_range;          /* Range at which continue begins */
223 #if ENABLE_FEATURE_WGET_STATUSBAR
224         off_t transferred;        /* Number of bytes transferred so far */
225         const char *curfile;      /* Name of current file being transferred */
226         bb_progress_t pmt;
227 #endif
228         char *dir_prefix;
229 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
230         char *post_data;
231         char *extra_headers;
232         unsigned char user_headers; /* Headers mentioned by the user */
233 #endif
234         char *fname_out;        /* where to direct output (-O) */
235         char *fname_log;        /* where to direct log (-o) */
236         const char *proxy_flag; /* Use proxies if env vars are set */
237         const char *user_agent; /* "User-Agent" header field */
238         int output_fd;
239         int log_fd;
240         int o_flags;
241 #if ENABLE_FEATURE_WGET_TIMEOUT
242         unsigned timeout_seconds;
243         smallint die_if_timed_out;
244 #endif
245         smallint chunked;         /* chunked transfer encoding */
246         smallint got_clen;        /* got content-length: from server  */
247         /* Local downloads do benefit from big buffer.
248          * With 512 byte buffer, it was measured to be
249          * an order of magnitude slower than with big one.
250          */
251         char wget_buf[CONFIG_FEATURE_COPYBUF_KB*1024] ALIGNED(sizeof(long));
252 } FIX_ALIASING;
253 #define G (*ptr_to_globals)
254 #define INIT_G() do { \
255         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
256 } while (0)
257 #define FINI_G() do { \
258         FREE_PTR_TO_GLOBALS(); \
259 } while (0)
260
261
262 /* Must match option string! */
263 enum {
264         WGET_OPT_CONTINUE   = (1 << 0),
265         WGET_OPT_QUIET      = (1 << 1),
266         WGET_OPT_SERVER_RESPONSE = (1 << 2),
267         WGET_OPT_OUTNAME    = (1 << 3),
268         WGET_OPT_LOGNAME    = (1 << 4),
269         WGET_OPT_PREFIX     = (1 << 5),
270         WGET_OPT_PROXY      = (1 << 6),
271         WGET_OPT_USER_AGENT = (1 << 7),
272         WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
273         WGET_OPT_RETRIES    = (1 << 9),
274         WGET_OPT_nsomething = (1 << 10),
275         WGET_OPT_HEADER     = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
276         WGET_OPT_POST_DATA  = (1 << 12) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
277         WGET_OPT_SPIDER     = (1 << 13) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
278         WGET_OPT_NO_CHECK_CERT = (1 << 14) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
279 };
280
281 enum {
282         PROGRESS_START = -1,
283         PROGRESS_END   = 0,
284         PROGRESS_BUMP  = 1,
285 };
286 #if ENABLE_FEATURE_WGET_STATUSBAR
287 static void progress_meter(int flag)
288 {
289         int notty;
290
291         if (option_mask32 & WGET_OPT_QUIET)
292                 return;
293
294         /* Don't save progress to log file */
295         if (G.log_fd >= 0)
296                 return;
297
298         if (flag == PROGRESS_START)
299                 bb_progress_init(&G.pmt, G.curfile);
300
301         notty = bb_progress_update(&G.pmt,
302                         G.beg_range,
303                         G.transferred,
304                         (G.chunked || !G.got_clen) ? 0 : G.beg_range + G.transferred + G.content_len
305         );
306
307         if (flag == PROGRESS_END) {
308                 bb_progress_free(&G.pmt);
309                 if (notty == 0)
310                         bb_putchar_stderr('\n'); /* it's tty */
311                 G.transferred = 0;
312         }
313 }
314 #else
315 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
316 #endif
317
318
319 /* IPv6 knows scoped address types i.e. link and site local addresses. Link
320  * local addresses can have a scope identifier to specify the
321  * interface/link an address is valid on (e.g. fe80::1%eth0). This scope
322  * identifier is only valid on a single node.
323  *
324  * RFC 4007 says that the scope identifier MUST NOT be sent across the wire,
325  * unless all nodes agree on the semantic. Apache e.g. regards zone identifiers
326  * in the Host header as invalid requests, see
327  * https://issues.apache.org/bugzilla/show_bug.cgi?id=35122
328  */
329 static void strip_ipv6_scope_id(char *host)
330 {
331         char *scope, *cp;
332
333         /* bbox wget actually handles IPv6 addresses without [], like
334          * wget "http://::1/xxx", but this is not standard.
335          * To save code, _here_ we do not support it. */
336
337         if (host[0] != '[')
338                 return; /* not IPv6 */
339
340         scope = strchr(host, '%');
341         if (!scope)
342                 return;
343
344         /* Remove the IPv6 zone identifier from the host address */
345         cp = strchr(host, ']');
346         if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
347                 /* malformed address (not "[xx]:nn" or "[xx]") */
348                 return;
349         }
350
351         /* cp points to "]...", scope points to "%eth0]..." */
352         overlapping_strcpy(scope, cp);
353 }
354
355 #if ENABLE_FEATURE_WGET_AUTHENTICATION
356 /* Base64-encode character string. */
357 static char *base64enc(const char *str)
358 {
359         /* paranoia */
360         unsigned len = strnlen(str, sizeof(G.wget_buf)/4*3 - 10);
361         bb_uuencode(G.wget_buf, str, len, bb_uuenc_tbl_base64);
362         return G.wget_buf;
363 }
364 #endif
365
366 #if ENABLE_FEATURE_WGET_TIMEOUT
367 static void alarm_handler(int sig UNUSED_PARAM)
368 {
369         /* This is theoretically unsafe (uses stdio and malloc in signal handler) */
370         if (G.die_if_timed_out)
371                 bb_error_msg_and_die("download timed out");
372 }
373 static void set_alarm(void)
374 {
375         if (G.timeout_seconds) {
376                 alarm(G.timeout_seconds);
377                 G.die_if_timed_out = 1;
378         }
379 }
380 # define clear_alarm() ((void)(G.die_if_timed_out = 0))
381 #else
382 # define set_alarm()   ((void)0)
383 # define clear_alarm() ((void)0)
384 #endif
385
386 #if ENABLE_FEATURE_WGET_OPENSSL
387 /*
388  * is_ip_address() attempts to verify whether or not a string
389  * contains an IPv4 or IPv6 address (vs. an FQDN).  The result
390  * of inet_pton() can be used to determine this.
391  *
392  * TODO add proper error checking when inet_pton() returns -1
393  * (some form of system error has occurred, and errno is set)
394  */
395 static int is_ip_address(const char *string)
396 {
397         struct sockaddr_in sa;
398
399         int result = inet_pton(AF_INET, string, &(sa.sin_addr));
400 # if ENABLE_FEATURE_IPV6
401         if (result == 0) {
402                 struct sockaddr_in6 sa6;
403                 result = inet_pton(AF_INET6, string, &(sa6.sin6_addr));
404         }
405 # endif
406         return (result == 1);
407 }
408 #endif
409
410 static FILE *open_socket(len_and_sockaddr *lsa)
411 {
412         int fd;
413         FILE *fp;
414
415         set_alarm();
416         fd = xconnect_stream(lsa);
417         clear_alarm();
418
419         /* glibc 2.4 seems to try seeking on it - ??! */
420         /* hopefully it understands what ESPIPE means... */
421         fp = fdopen(fd, "r+");
422         if (!fp)
423                 bb_die_memory_exhausted();
424
425         return fp;
426 }
427
428 /* We balk at any control chars in other side's messages.
429  * This prevents nasty surprises (e.g. ESC sequences) in "Location:" URLs
430  * and error messages.
431  *
432  * The only exception is tabs, which are converted to (one) space:
433  * HTTP's "headers: <whitespace> values" may have those.
434  */
435 static char* sanitize_string(char *s)
436 {
437         unsigned char *p = (void *) s;
438         while (*p) {
439                 if (*p < ' ') {
440                         if (*p != '\t')
441                                 break;
442                         *p = ' ';
443                 }
444                 p++;
445         }
446         *p = '\0';
447         return s;
448 }
449
450 /* Returns '\n' if it was seen, else '\0'. Trims at first '\r' or '\n' */
451 static char fgets_trim_sanitize(FILE *fp, const char *fmt)
452 {
453         char c;
454         char *buf_ptr;
455
456         set_alarm();
457         if (fgets(G.wget_buf, sizeof(G.wget_buf), fp) == NULL)
458                 bb_perror_msg_and_die("error getting response");
459         clear_alarm();
460
461         buf_ptr = strchrnul(G.wget_buf, '\n');
462         c = *buf_ptr;
463 #if 1
464         /* Disallow any control chars: trim at first char < 0x20 */
465         sanitize_string(G.wget_buf);
466 #else
467         *buf_ptr = '\0';
468         buf_ptr = strchrnul(G.wget_buf, '\r');
469         *buf_ptr = '\0';
470 #endif
471
472         log_io("< %s", G.wget_buf);
473
474         if (fmt && (option_mask32 & WGET_OPT_SERVER_RESPONSE))
475                 fprintf(stderr, fmt, G.wget_buf);
476
477         return c;
478 }
479
480 static int ftpcmd(const char *s1, const char *s2, FILE *fp)
481 {
482         int result;
483         if (s1) {
484                 if (!s2)
485                         s2 = "";
486                 fprintf(fp, "%s%s\r\n", s1, s2);
487                 /* With --server-response, wget also shows its ftp commands */
488                 if (option_mask32 & WGET_OPT_SERVER_RESPONSE)
489                         fprintf(stderr, "--> %s%s\n\n", s1, s2);
490                 fflush(fp);
491                 log_io("> %s%s", s1, s2);
492         }
493
494         /* Read until "Nxx something" is received */
495         G.wget_buf[3] = 0;
496         do {
497                 fgets_trim_sanitize(fp, "%s\n");
498         } while (!isdigit(G.wget_buf[0]) || G.wget_buf[3] != ' ');
499
500         G.wget_buf[3] = '\0';
501         result = xatoi_positive(G.wget_buf);
502         G.wget_buf[3] = ' ';
503         return result;
504 }
505
506 static void parse_url(const char *src_url, struct host_info *h)
507 {
508         char *url, *p, *sp;
509
510         free(h->allocated);
511         h->allocated = url = xstrdup(src_url);
512
513         h->protocol = P_FTP;
514         p = strstr(url, "://");
515         if (p) {
516                 *p = '\0';
517                 h->host = p + 3;
518                 if (strcmp(url, P_FTP) == 0) {
519                         h->port = bb_lookup_std_port(P_FTP, "tcp", 21);
520                 } else
521 #if SSL_SUPPORTED
522 # if ENABLE_FEATURE_WGET_HTTPS
523                 if (strcmp(url, P_FTPS) == 0) {
524                         h->port = bb_lookup_std_port(P_FTPS, "tcp", 990);
525                         h->protocol = P_FTPS;
526                 } else
527 # endif
528                 if (strcmp(url, P_HTTPS) == 0) {
529                         h->port = bb_lookup_std_port(P_HTTPS, "tcp", 443);
530                         h->protocol = P_HTTPS;
531                 } else
532 #endif
533                 if (strcmp(url, P_HTTP) == 0) {
534  http:
535                         h->port = bb_lookup_std_port(P_HTTP, "tcp", 80);
536                         h->protocol = P_HTTP;
537                 } else {
538                         *p = ':';
539                         bb_error_msg_and_die("not an http or ftp url: %s", url);
540                 }
541         } else {
542                 // GNU wget is user-friendly and falls back to http://
543                 h->host = url;
544                 goto http;
545         }
546
547         // FYI:
548         // "Real" wget 'http://busybox.net?var=a/b' sends this request:
549         //   'GET /?var=a/b HTTP/1.0'
550         //   and saves 'index.html?var=a%2Fb' (we save 'b')
551         // wget 'http://busybox.net?login=john@doe':
552         //   request: 'GET /?login=john@doe HTTP/1.0'
553         //   saves: 'index.html?login=john@doe' (we save 'login=john@doe')
554         // wget 'http://busybox.net#test/test':
555         //   request: 'GET / HTTP/1.0'
556         //   saves: 'index.html' (we save 'test')
557         //
558         // We also don't add unique .N suffix if file exists...
559         sp = strchr(h->host, '/');
560         p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
561         p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
562         if (!sp) {
563                 h->path = "";
564         } else if (*sp == '/') {
565                 *sp = '\0';
566                 h->path = sp + 1;
567         } else {
568                 // sp points to '#' or '?'
569                 // Note:
570                 // http://busybox.net?login=john@doe is a valid URL
571                 // (without '/' between ".net" and "?"),
572                 // can't store NUL at sp[-1] - this destroys hostname.
573                 *sp++ = '\0';
574                 h->path = sp;
575         }
576
577         sp = strrchr(h->host, '@');
578         if (sp != NULL) {
579                 // URL-decode "user:password" string before base64-encoding:
580                 // wget http://test:my%20pass@example.com should send
581                 // Authorization: Basic dGVzdDpteSBwYXNz
582                 // which decodes to "test:my pass".
583                 // Standard wget and curl do this too.
584                 *sp = '\0';
585                 free(h->user);
586                 h->user = xstrdup(percent_decode_in_place(h->host, /*strict:*/ 0));
587                 h->host = sp + 1;
588         }
589         /* else: h->user remains NULL, or as set by original request
590          * before redirect (if we are here after a redirect).
591          */
592 }
593
594 static char *get_sanitized_hdr(FILE *fp)
595 {
596         char *s, *hdrval;
597         int c;
598
599         /* retrieve header line */
600         c = fgets_trim_sanitize(fp, "  %s\n");
601
602         /* end of the headers? */
603         if (G.wget_buf[0] == '\0')
604                 return NULL;
605
606         /* convert the header name to lower case */
607         for (s = G.wget_buf; isalnum(*s) || *s == '-' || *s == '.' || *s == '_'; ++s) {
608                 /*
609                  * No-op for 20-3f and 60-7f. "0-9a-z-." are in these ranges.
610                  * 40-5f range ("@A-Z[\]^_") maps to 60-7f.
611                  * "A-Z" maps to "a-z".
612                  * "@[\]" can't occur in header names.
613                  * "^_" maps to "~,DEL" (which is wrong).
614                  * "^" was never seen yet, "_" was seen from web.archive.org
615                  * (x-archive-orig-x_commoncrawl_Signature: HEXSTRING).
616                  */
617                 *s |= 0x20;
618         }
619
620         /* verify we are at the end of the header name */
621         if (*s != ':')
622                 bb_error_msg_and_die("bad header line: %s", G.wget_buf);
623
624         /* locate the start of the header value */
625         *s++ = '\0';
626         hdrval = skip_whitespace(s);
627
628         if (c != '\n') {
629                 /* Rats! The buffer isn't big enough to hold the entire header value */
630                 while (c = getc(fp), c != EOF && c != '\n')
631                         continue;
632         }
633
634         return hdrval;
635 }
636
637 static void reset_beg_range_to_zero(void)
638 {
639         bb_error_msg("restart failed");
640         G.beg_range = 0;
641         xlseek(G.output_fd, 0, SEEK_SET);
642         /* Done at the end instead: */
643         /* ftruncate(G.output_fd, 0); */
644 }
645
646 #if ENABLE_FEATURE_WGET_OPENSSL
647 static int spawn_https_helper_openssl(const char *host, unsigned port)
648 {
649         char *allocated = NULL;
650         char *servername;
651         int sp[2];
652         int pid;
653         IF_FEATURE_WGET_HTTPS(volatile int child_failed = 0;)
654
655         if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
656                 /* Kernel can have AF_UNIX support disabled */
657                 bb_perror_msg_and_die("socketpair");
658
659         if (!strchr(host, ':'))
660                 host = allocated = xasprintf("%s:%u", host, port);
661         servername = xstrdup(host);
662         strrchr(servername, ':')[0] = '\0';
663
664         fflush_all();
665         pid = xvfork();
666         if (pid == 0) {
667                 /* Child */
668                 char *argv[8];
669
670                 close(sp[0]);
671                 xmove_fd(sp[1], 0);
672                 xdup2(0, 1);
673                 /*
674                  * openssl s_client -quiet -connect www.kernel.org:443 2>/dev/null
675                  * It prints some debug stuff on stderr, don't know how to suppress it.
676                  * Work around by dev-nulling stderr. We lose all error messages :(
677                  */
678                 xmove_fd(2, 3);
679                 xopen("/dev/null", O_RDWR);
680                 memset(&argv, 0, sizeof(argv));
681                 argv[0] = (char*)"openssl";
682                 argv[1] = (char*)"s_client";
683                 argv[2] = (char*)"-quiet";
684                 argv[3] = (char*)"-connect";
685                 argv[4] = (char*)host;
686                 /*
687                  * Per RFC 6066 Section 3, the only permitted values in the
688                  * TLS server_name (SNI) field are FQDNs (DNS hostnames).
689                  * IPv4 and IPv6 addresses, port numbers are not allowed.
690                  */
691                 if (!is_ip_address(servername)) {
692                         argv[5] = (char*)"-servername";
693                         argv[6] = (char*)servername;
694                 }
695
696                 BB_EXECVP(argv[0], argv);
697                 xmove_fd(3, 2);
698 # if ENABLE_FEATURE_WGET_HTTPS
699                 child_failed = 1;
700                 xfunc_die();
701 # else
702                 bb_perror_msg_and_die("can't execute '%s'", argv[0]);
703 # endif
704                 /* notreached */
705         }
706
707         /* Parent */
708         free(servername);
709         free(allocated);
710         close(sp[1]);
711 # if ENABLE_FEATURE_WGET_HTTPS
712         if (child_failed) {
713                 close(sp[0]);
714                 return -1;
715         }
716 # endif
717         return sp[0];
718 }
719 #endif
720
721 #if ENABLE_FEATURE_WGET_HTTPS
722 static void spawn_ssl_client(const char *host, int network_fd, int flags)
723 {
724         int sp[2];
725         int pid;
726         char *servername, *p;
727
728         if (!(option_mask32 & WGET_OPT_NO_CHECK_CERT)) {
729                 option_mask32 |= WGET_OPT_NO_CHECK_CERT;
730                 bb_error_msg("note: TLS certificate validation not implemented");
731         }
732
733         servername = xstrdup(host);
734         p = strrchr(servername, ':');
735         if (p) *p = '\0';
736
737         if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
738                 /* Kernel can have AF_UNIX support disabled */
739                 bb_perror_msg_and_die("socketpair");
740
741         fflush_all();
742         pid = BB_MMU ? xfork() : xvfork();
743         if (pid == 0) {
744                 /* Child */
745                 close(sp[0]);
746                 xmove_fd(sp[1], 0);
747                 xdup2(0, 1);
748                 if (BB_MMU) {
749                         tls_state_t *tls = new_tls_state();
750                         tls->ifd = tls->ofd = network_fd;
751                         tls_handshake(tls, servername);
752                         tls_run_copy_loop(tls, flags);
753                         exit(0);
754                 } else {
755                         char *argv[6];
756
757                         xmove_fd(network_fd, 3);
758                         argv[0] = (char*)"ssl_client";
759                         argv[1] = (char*)"-s3";
760                         //TODO: if (!is_ip_address(servername))...
761                         argv[2] = (char*)"-n";
762                         argv[3] = servername;
763                         argv[4] = (flags & TLSLOOP_EXIT_ON_LOCAL_EOF ? (char*)"-e" : NULL);
764                         argv[5] = NULL;
765                         BB_EXECVP(argv[0], argv);
766                         bb_perror_msg_and_die("can't execute '%s'", argv[0]);
767                 }
768                 /* notreached */
769         }
770
771         /* Parent */
772         free(servername);
773         close(sp[1]);
774         xmove_fd(sp[0], network_fd);
775 }
776 #endif
777
778 static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
779 {
780         FILE *sfp;
781         char *pass;
782         int port;
783
784         sfp = open_socket(lsa);
785 #if ENABLE_FEATURE_WGET_HTTPS
786         if (target->protocol == P_FTPS)
787                 spawn_ssl_client(target->host, fileno(sfp), TLSLOOP_EXIT_ON_LOCAL_EOF);
788 #endif
789
790         if (ftpcmd(NULL, NULL, sfp) != 220)
791                 bb_error_msg_and_die("%s", G.wget_buf);
792                 /* note: ftpcmd() sanitizes G.wget_buf, ok to print */
793
794         /* Split username:password pair */
795         pass = (char*)"busybox"; /* password for "anonymous" */
796         if (target->user) {
797                 pass = strchr(target->user, ':');
798                 if (pass)
799                         *pass++ = '\0';
800         }
801
802         /* Log in */
803         switch (ftpcmd("USER ", target->user ?: "anonymous", sfp)) {
804         case 230:
805                 break;
806         case 331:
807                 if (ftpcmd("PASS ", pass, sfp) == 230)
808                         break;
809                 /* fall through (failed login) */
810         default:
811                 bb_error_msg_and_die("ftp login: %s", G.wget_buf);
812         }
813
814         ftpcmd("TYPE I", NULL, sfp);
815
816         /* Query file size */
817         if (ftpcmd("SIZE ", target->path, sfp) == 213) {
818                 G.content_len = BB_STRTOOFF(G.wget_buf + 4, NULL, 10);
819                 if (G.content_len < 0 || errno) {
820                         bb_error_msg_and_die("bad SIZE value '%s'", G.wget_buf + 4);
821                 }
822                 G.got_clen = 1;
823         }
824
825         /* Enter passive mode */
826         if (ENABLE_FEATURE_IPV6 && ftpcmd("EPSV", NULL, sfp) == 229) {
827                 /* good */
828         } else
829         if (ftpcmd("PASV", NULL, sfp) != 227) {
830  pasv_error:
831                 bb_error_msg_and_die("bad response to %s: %s", "PASV", G.wget_buf);
832         }
833         port = parse_pasv_epsv(G.wget_buf);
834         if (port < 0)
835                 goto pasv_error;
836
837         set_nport(&lsa->u.sa, htons(port));
838
839         *dfpp = open_socket(lsa);
840
841 #if ENABLE_FEATURE_WGET_HTTPS
842         if (target->protocol == P_FTPS) {
843                 /* "PROT P" enables encryption of data stream.
844                  * Without it (or with "PROT C"), data is sent unencrypted.
845                  */
846                 if (ftpcmd("PROT P", NULL, sfp) == 200)
847                         spawn_ssl_client(target->host, fileno(*dfpp), /*flags*/ 0);
848         }
849 #endif
850
851         if (G.beg_range != 0) {
852                 sprintf(G.wget_buf, "REST %"OFF_FMT"u", G.beg_range);
853                 if (ftpcmd(G.wget_buf, NULL, sfp) == 350)
854                         G.content_len -= G.beg_range;
855                 else
856                         reset_beg_range_to_zero();
857         }
858
859 //TODO: needs ftp-escaping 0xff and '\n' bytes here.
860 //Or disallow '\n' altogether via sanitize_string() in parse_url().
861 //But 0xff's are possible in valid utf8 filenames.
862         if (ftpcmd("RETR ", target->path, sfp) > 150)
863                 bb_error_msg_and_die("bad response to %s: %s", "RETR", G.wget_buf);
864
865         return sfp;
866 }
867
868 static void NOINLINE retrieve_file_data(FILE *dfp)
869 {
870 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
871 # if ENABLE_FEATURE_WGET_TIMEOUT
872         unsigned second_cnt = G.timeout_seconds;
873 # endif
874         struct pollfd polldata;
875
876         polldata.fd = fileno(dfp);
877         polldata.events = POLLIN | POLLPRI;
878 #endif
879         if (!(option_mask32 & WGET_OPT_QUIET)) {
880                 if (G.output_fd == 1)
881                         fprintf(stderr, "writing to stdout\n");
882                 else
883                         fprintf(stderr, "saving to '%s'\n", G.fname_out);
884         }
885         progress_meter(PROGRESS_START);
886
887         if (G.chunked)
888                 goto get_clen;
889
890         /* Loops only if chunked */
891         while (1) {
892
893 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
894                 /* Must use nonblocking I/O, otherwise fread will loop
895                  * and *block* until it reads full buffer,
896                  * which messes up progress bar and/or timeout logic.
897                  * Because of nonblocking I/O, we need to dance
898                  * very carefully around EAGAIN. See explanation at
899                  * clearerr() calls.
900                  */
901                 ndelay_on(polldata.fd);
902 #endif
903                 while (1) {
904                         int n;
905                         unsigned rdsz;
906
907 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
908                         /* fread internally uses read loop, which in our case
909                          * is usually exited when we get EAGAIN.
910                          * In this case, libc sets error marker on the stream.
911                          * Need to clear it before next fread to avoid possible
912                          * rare false positive ferror below. Rare because usually
913                          * fread gets more than zero bytes, and we don't fall
914                          * into if (n <= 0) ...
915                          */
916                         clearerr(dfp);
917 #endif
918                         errno = 0;
919                         rdsz = sizeof(G.wget_buf);
920                         if (G.got_clen) {
921                                 if (G.content_len < (off_t)sizeof(G.wget_buf)) {
922                                         if ((int)G.content_len <= 0)
923                                                 break;
924                                         rdsz = (unsigned)G.content_len;
925                                 }
926                         }
927                         n = fread(G.wget_buf, 1, rdsz, dfp);
928
929                         if (n > 0) {
930                                 xwrite(G.output_fd, G.wget_buf, n);
931 #if ENABLE_FEATURE_WGET_STATUSBAR
932                                 G.transferred += n;
933 #endif
934                                 if (G.got_clen) {
935                                         G.content_len -= n;
936                                         if (G.content_len == 0)
937                                                 break;
938                                 }
939 #if ENABLE_FEATURE_WGET_TIMEOUT
940                                 second_cnt = G.timeout_seconds;
941 #endif
942                                 goto bump;
943                         }
944
945                         /* n <= 0.
946                          * man fread:
947                          * If error occurs, or EOF is reached, the return value
948                          * is a short item count (or zero).
949                          * fread does not distinguish between EOF and error.
950                          */
951                         if (errno != EAGAIN) {
952                                 if (ferror(dfp)) {
953                                         progress_meter(PROGRESS_END);
954                                         bb_perror_msg_and_die(bb_msg_read_error);
955                                 }
956                                 break; /* EOF, not error */
957                         }
958
959 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
960                         /* It was EAGAIN. There is no data. Wait up to one second
961                          * then abort if timed out, or update the bar and try reading again.
962                          */
963                         if (safe_poll(&polldata, 1, 1000) == 0) {
964 # if ENABLE_FEATURE_WGET_TIMEOUT
965                                 if (second_cnt != 0 && --second_cnt == 0) {
966                                         progress_meter(PROGRESS_END);
967                                         bb_error_msg_and_die("download timed out");
968                                 }
969 # endif
970                                 /* We used to loop back to poll here,
971                                  * but there is no great harm in letting fread
972                                  * to try reading anyway.
973                                  */
974                         }
975 #endif
976  bump:
977                         /* Need to do it _every_ second for "stalled" indicator
978                          * to be shown properly.
979                          */
980                         progress_meter(PROGRESS_BUMP);
981                 } /* while (reading data) */
982
983 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
984                 clearerr(dfp);
985                 ndelay_off(polldata.fd); /* else fgets can get very unhappy */
986 #endif
987                 if (!G.chunked)
988                         break;
989
990                 /* Each chunk ends with "\r\n" - eat it */
991                 fgets_trim_sanitize(dfp, NULL);
992  get_clen:
993                 /* chunk size format is "HEXNUM[;name[=val]]\r\n" */
994                 fgets_trim_sanitize(dfp, NULL);
995                 errno = 0;
996                 G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
997                 /*
998                  * Had a bug with inputs like "ffffffff0001f400"
999                  * smashing the heap later. Ensure >= 0.
1000                  */
1001                 if (G.content_len < 0 || errno)
1002                         bb_error_msg_and_die("bad chunk length '%s'", G.wget_buf);
1003                 if (G.content_len == 0)
1004                         break; /* all done! */
1005                 G.got_clen = 1;
1006                 /*
1007                  * Note that fgets may result in some data being buffered in dfp.
1008                  * We loop back to fread, which will retrieve this data.
1009                  * Also note that code has to be arranged so that fread
1010                  * is done _before_ one-second poll wait - poll doesn't know
1011                  * about stdio buffering and can result in spurious one second waits!
1012                  */
1013         }
1014
1015         /* If -c failed, we restart from the beginning,
1016          * but we do not truncate file then, we do it only now, at the end.
1017          * This lets user to ^C if his 99% complete 10 GB file download
1018          * failed to restart *without* losing the almost complete file.
1019          */
1020         {
1021                 off_t pos = lseek(G.output_fd, 0, SEEK_CUR);
1022                 if (pos != (off_t)-1)
1023                         ftruncate(G.output_fd, pos);
1024         }
1025
1026         /* Draw full bar and free its resources */
1027         G.chunked = 0;  /* makes it show 100% even for chunked download */
1028         G.got_clen = 1; /* makes it show 100% even for download of (formerly) unknown size */
1029         progress_meter(PROGRESS_END);
1030         if (!(option_mask32 & WGET_OPT_QUIET)) {
1031                 if (G.output_fd == 1)
1032                         fprintf(stderr, "written to stdout\n");
1033                 else
1034                         fprintf(stderr, "'%s' saved\n", G.fname_out);
1035         }
1036 }
1037
1038 static void download_one_url(const char *url)
1039 {
1040         bool use_proxy;                 /* Use proxies if env vars are set  */
1041         int redir_limit;
1042         len_and_sockaddr *lsa;
1043         FILE *sfp;                      /* socket to web/ftp server         */
1044         FILE *dfp;                      /* socket to ftp server (data)      */
1045         char *fname_out_alloc;
1046         char *redirected_path = NULL;
1047         struct host_info server;
1048         struct host_info target;
1049
1050         server.allocated = NULL;
1051         target.allocated = NULL;
1052         server.user = NULL;
1053         target.user = NULL;
1054
1055         parse_url(url, &target);
1056
1057         /* Use the proxy if necessary */
1058         use_proxy = (strcmp(G.proxy_flag, "off") != 0);
1059         if (use_proxy) {
1060                 char *proxy = getenv(target.protocol[0] == 'f' ? "ftp_proxy" : "http_proxy");
1061 //FIXME: what if protocol is https? Ok to use http_proxy?
1062                 use_proxy = (proxy && proxy[0]);
1063                 if (use_proxy)
1064                         parse_url(proxy, &server);
1065         }
1066         if (!use_proxy) {
1067                 server.protocol = target.protocol;
1068                 server.port = target.port;
1069                 if (ENABLE_FEATURE_IPV6) {
1070                         //free(server.allocated); - can't be non-NULL
1071                         server.host = server.allocated = xstrdup(target.host);
1072                 } else {
1073                         server.host = target.host;
1074                 }
1075         }
1076
1077         if (ENABLE_FEATURE_IPV6)
1078                 strip_ipv6_scope_id(target.host);
1079
1080         /* If there was no -O FILE, guess output filename */
1081         fname_out_alloc = NULL;
1082         if (!(option_mask32 & WGET_OPT_OUTNAME)) {
1083                 G.fname_out = bb_get_last_path_component_nostrip(target.path);
1084                 /* handle "wget http://kernel.org//" */
1085                 if (G.fname_out[0] == '/' || !G.fname_out[0])
1086                         G.fname_out = (char*)"index.html";
1087                 /* -P DIR is considered only if there was no -O FILE */
1088                 if (G.dir_prefix)
1089                         G.fname_out = fname_out_alloc = concat_path_file(G.dir_prefix, G.fname_out);
1090                 else {
1091                         /* redirects may free target.path later, need to make a copy */
1092                         G.fname_out = fname_out_alloc = xstrdup(G.fname_out);
1093                 }
1094         }
1095 #if ENABLE_FEATURE_WGET_STATUSBAR
1096         G.curfile = bb_get_last_path_component_nostrip(G.fname_out);
1097 #endif
1098
1099         /* Determine where to start transfer */
1100         G.beg_range = 0;
1101         if (option_mask32 & WGET_OPT_CONTINUE) {
1102                 G.output_fd = open(G.fname_out, O_WRONLY);
1103                 if (G.output_fd >= 0) {
1104                         G.beg_range = xlseek(G.output_fd, 0, SEEK_END);
1105                 }
1106                 /* File doesn't exist. We do not create file here yet.
1107                  * We are not sure it exists on remote side */
1108         }
1109
1110         redir_limit = 5;
1111  resolve_lsa:
1112         lsa = xhost2sockaddr(server.host, server.port);
1113         if (!(option_mask32 & WGET_OPT_QUIET)) {
1114                 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
1115                 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
1116                 free(s);
1117         }
1118  establish_session:
1119         /*G.content_len = 0; - redundant, got_clen = 0 is enough */
1120         G.got_clen = 0;
1121         G.chunked = 0;
1122         if (use_proxy || target.protocol[0] != 'f' /*not ftp[s]*/) {
1123                 /*
1124                  *  HTTP session
1125                  */
1126                 char *str;
1127                 int status;
1128
1129                 /* Open socket to http(s) server */
1130 #if ENABLE_FEATURE_WGET_OPENSSL
1131                 /* openssl (and maybe internal TLS) support is configured */
1132                 if (server.protocol == P_HTTPS) {
1133                         /* openssl-based helper
1134                          * Inconvenient API since we can't give it an open fd
1135                          */
1136                         int fd = spawn_https_helper_openssl(server.host, server.port);
1137 # if ENABLE_FEATURE_WGET_HTTPS
1138                         if (fd < 0) { /* no openssl? try internal */
1139                                 sfp = open_socket(lsa);
1140                                 spawn_ssl_client(server.host, fileno(sfp), /*flags*/ 0);
1141                                 goto socket_opened;
1142                         }
1143 # else
1144                         /* We don't check for exec("openssl") failure in this case */
1145 # endif
1146                         sfp = fdopen(fd, "r+");
1147                         if (!sfp)
1148                                 bb_die_memory_exhausted();
1149                         goto socket_opened;
1150                 }
1151                 sfp = open_socket(lsa);
1152  socket_opened:
1153 #elif ENABLE_FEATURE_WGET_HTTPS
1154                 /* Only internal TLS support is configured */
1155                 sfp = open_socket(lsa);
1156                 if (server.protocol == P_HTTPS)
1157                         spawn_ssl_client(server.host, fileno(sfp), /*flags*/ 0);
1158 #else
1159                 /* ssl (https) support is not configured */
1160                 sfp = open_socket(lsa);
1161 #endif
1162                 /* Send HTTP request */
1163                 if (use_proxy) {
1164                         SENDFMT(sfp, "GET %s://%s/%s HTTP/1.1\r\n",
1165                                 target.protocol, target.host,
1166                                 target.path);
1167                 } else {
1168                         SENDFMT(sfp, "%s /%s HTTP/1.1\r\n",
1169                                 (option_mask32 & WGET_OPT_POST_DATA) ? "POST" : "GET",
1170                                 target.path);
1171                 }
1172                 if (!USR_HEADER_HOST)
1173                         SENDFMT(sfp, "Host: %s\r\n", target.host);
1174                 if (!USR_HEADER_USER_AGENT)
1175                         SENDFMT(sfp, "User-Agent: %s\r\n", G.user_agent);
1176
1177                 /* Ask server to close the connection as soon as we are done
1178                  * (IOW: we do not intend to send more requests)
1179                  */
1180                 SENDFMT(sfp, "Connection: close\r\n");
1181
1182 #if ENABLE_FEATURE_WGET_AUTHENTICATION
1183                 if (target.user && !USR_HEADER_AUTH) {
1184                         SENDFMT(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
1185                                 base64enc(target.user));
1186                 }
1187                 if (use_proxy && server.user && !USR_HEADER_PROXY_AUTH) {
1188                         SENDFMT(sfp, "Proxy-Authorization: Basic %s\r\n",
1189                                 base64enc(server.user));
1190                 }
1191 #endif
1192
1193                 if (G.beg_range != 0 && !USR_HEADER_RANGE)
1194                         SENDFMT(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
1195
1196 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1197                 if (G.extra_headers) {
1198                         log_io(G.extra_headers);
1199                         fputs(G.extra_headers, sfp);
1200                 }
1201
1202                 if (option_mask32 & WGET_OPT_POST_DATA) {
1203                         SENDFMT(sfp,
1204                                 "Content-Type: application/x-www-form-urlencoded\r\n"
1205                                 "Content-Length: %u\r\n"
1206                                 "\r\n"
1207                                 "%s",
1208                                 (int) strlen(G.post_data), G.post_data
1209                         );
1210                 } else
1211 #endif
1212                 {
1213                         SENDFMT(sfp, "\r\n");
1214                 }
1215
1216                 fflush(sfp);
1217
1218 /* Tried doing this unconditionally.
1219  * Cloudflare and nginx/1.11.5 are shocked to see SHUT_WR on non-HTTPS.
1220  */
1221 #if SSL_SUPPORTED
1222                 if (target.protocol == P_HTTPS) {
1223                         /* If we use SSL helper, keeping our end of the socket open for writing
1224                          * makes our end (i.e. the same fd!) readable (EAGAIN instead of EOF)
1225                          * even after child closes its copy of the fd.
1226                          * This helps:
1227                          */
1228                         shutdown(fileno(sfp), SHUT_WR);
1229                 }
1230 #endif
1231
1232                 /*
1233                  * Retrieve HTTP response line and check for "200" status code.
1234                  */
1235  read_response:
1236                 fgets_trim_sanitize(sfp, "  %s\n");
1237
1238                 str = G.wget_buf;
1239                 str = skip_non_whitespace(str);
1240                 str = skip_whitespace(str);
1241                 // FIXME: no error check
1242                 // xatou wouldn't work: "200 OK"
1243                 status = atoi(str);
1244                 switch (status) {
1245                 case 0:
1246                 case 100:
1247                         while (get_sanitized_hdr(sfp) != NULL)
1248                                 /* eat all remaining headers */;
1249                         goto read_response;
1250
1251                 /* Success responses */
1252                 case 200:
1253                         /* fall through */
1254                 case 201: /* 201 Created */
1255 /* "The request has been fulfilled and resulted in a new resource being created" */
1256                         /* Standard wget is reported to treat this as success */
1257                         /* fall through */
1258                 case 202: /* 202 Accepted */
1259 /* "The request has been accepted for processing, but the processing has not been completed" */
1260                         /* Treat as success: fall through */
1261                 case 203: /* 203 Non-Authoritative Information */
1262 /* "Use of this response code is not required and is only appropriate when the response would otherwise be 200 (OK)" */
1263                         /* fall through */
1264                 case 204: /* 204 No Content */
1265 /*
1266 Response 204 doesn't say "null file", it says "metadata
1267 has changed but data didn't":
1268
1269 "10.2.5 204 No Content
1270 The server has fulfilled the request but does not need to return
1271 an entity-body, and might want to return updated metainformation.
1272 The response MAY include new or updated metainformation in the form
1273 of entity-headers, which if present SHOULD be associated with
1274 the requested variant.
1275
1276 If the client is a user agent, it SHOULD NOT change its document
1277 view from that which caused the request to be sent. This response
1278 is primarily intended to allow input for actions to take place
1279 without causing a change to the user agent's active document view,
1280 although any new or updated metainformation SHOULD be applied
1281 to the document currently in the user agent's active view.
1282
1283 The 204 response MUST NOT include a message-body, and thus
1284 is always terminated by the first empty line after the header fields."
1285
1286 However, in real world it was observed that some web servers
1287 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
1288 */
1289                         if (G.beg_range != 0) {
1290                                 /* "Range:..." was not honored by the server.
1291                                  * Restart download from the beginning.
1292                                  */
1293                                 reset_beg_range_to_zero();
1294                         }
1295                         break;
1296                 /* 205 Reset Content ?? what to do on this ??   */
1297
1298                 case 300:  /* redirection */
1299                 case 301:
1300                 case 302:
1301                 case 303:
1302                         break;
1303
1304                 case 206: /* Partial Content */
1305                         if (G.beg_range != 0)
1306                                 /* "Range:..." worked. Good. */
1307                                 break;
1308                         /* Partial Content even though we did not ask for it??? */
1309                         /* fall through */
1310                 default:
1311                         bb_error_msg_and_die("server returned error: %s", G.wget_buf);
1312                 }
1313
1314                 /*
1315                  * Retrieve HTTP headers.
1316                  */
1317                 while ((str = get_sanitized_hdr(sfp)) != NULL) {
1318                         static const char keywords[] ALIGN1 =
1319                                 "content-length\0""transfer-encoding\0""location\0";
1320                         enum {
1321                                 KEY_content_length = 1, KEY_transfer_encoding, KEY_location
1322                         };
1323                         smalluint key;
1324
1325                         /* get_sanitized_hdr converted "FOO:" string to lowercase */
1326
1327                         /* strip trailing whitespace */
1328                         char *s = strchrnul(str, '\0') - 1;
1329                         while (s >= str && (*s == ' ' || *s == '\t')) {
1330                                 *s = '\0';
1331                                 s--;
1332                         }
1333                         key = index_in_strings(keywords, G.wget_buf) + 1;
1334                         if (key == KEY_content_length) {
1335                                 G.content_len = BB_STRTOOFF(str, NULL, 10);
1336                                 if (G.content_len < 0 || errno) {
1337                                         bb_error_msg_and_die("content-length %s is garbage", str);
1338                                 }
1339                                 G.got_clen = 1;
1340                                 continue;
1341                         }
1342                         if (key == KEY_transfer_encoding) {
1343                                 if (strcmp(str_tolower(str), "chunked") != 0)
1344                                         bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
1345                                 G.chunked = 1;
1346                         }
1347                         if (key == KEY_location && status >= 300) {
1348                                 if (--redir_limit == 0)
1349                                         bb_error_msg_and_die("too many redirections");
1350                                 fclose(sfp);
1351                                 if (str[0] == '/') {
1352                                         free(redirected_path);
1353                                         target.path = redirected_path = xstrdup(str + 1);
1354                                         /* lsa stays the same: it's on the same server */
1355                                 } else {
1356                                         parse_url(str, &target);
1357                                         if (!use_proxy) {
1358                                                 /* server.user remains untouched */
1359                                                 free(server.allocated);
1360                                                 server.allocated = NULL;
1361                                                 server.protocol = target.protocol;
1362                                                 server.host = target.host;
1363                                                 /* strip_ipv6_scope_id(target.host); - no! */
1364                                                 /* we assume remote never gives us IPv6 addr with scope id */
1365                                                 server.port = target.port;
1366                                                 free(lsa);
1367                                                 goto resolve_lsa;
1368                                         } /* else: lsa stays the same: we use proxy */
1369                                 }
1370                                 goto establish_session;
1371                         }
1372                 }
1373 //              if (status >= 300)
1374 //                      bb_error_msg_and_die("bad redirection (no Location: header from server)");
1375
1376                 /* For HTTP, data is pumped over the same connection */
1377                 dfp = sfp;
1378         } else {
1379                 /*
1380                  *  FTP session
1381                  */
1382                 sfp = prepare_ftp_session(&dfp, &target, lsa);
1383         }
1384
1385         free(lsa);
1386
1387         if (!(option_mask32 & WGET_OPT_SPIDER)) {
1388                 if (G.output_fd < 0)
1389                         G.output_fd = xopen(G.fname_out, G.o_flags);
1390                 retrieve_file_data(dfp);
1391                 if (!(option_mask32 & WGET_OPT_OUTNAME)) {
1392                         xclose(G.output_fd);
1393                         G.output_fd = -1;
1394                 }
1395         } else {
1396                 if (!(option_mask32 & WGET_OPT_QUIET))
1397                         fprintf(stderr, "remote file exists\n");
1398         }
1399
1400         if (dfp != sfp) {
1401                 /* It's ftp. Close data connection properly */
1402                 fclose(dfp);
1403                 if (ftpcmd(NULL, NULL, sfp) != 226)
1404                         bb_error_msg_and_die("ftp error: %s", G.wget_buf);
1405                 /* ftpcmd("QUIT", NULL, sfp); - why bother? */
1406         }
1407         fclose(sfp);
1408
1409         free(server.allocated);
1410         free(target.allocated);
1411         free(server.user);
1412         free(target.user);
1413         free(fname_out_alloc);
1414         free(redirected_path);
1415 }
1416
1417 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1418 int wget_main(int argc UNUSED_PARAM, char **argv)
1419 {
1420 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1421         static const char wget_longopts[] ALIGN1 =
1422                 /* name, has_arg, val */
1423                 "continue\0"         No_argument       "c"
1424                 "quiet\0"            No_argument       "q"
1425                 "server-response\0"  No_argument       "S"
1426                 "output-document\0"  Required_argument "O"
1427                 "output-file\0"      Required_argument "o"
1428                 "directory-prefix\0" Required_argument "P"
1429                 "proxy\0"            Required_argument "Y"
1430                 "user-agent\0"       Required_argument "U"
1431 IF_FEATURE_WGET_TIMEOUT(
1432                 "timeout\0"          Required_argument "T")
1433                 /* Ignored: */
1434 IF_DESKTOP(     "tries\0"            Required_argument "t")
1435                 "header\0"           Required_argument "\xff"
1436                 "post-data\0"        Required_argument "\xfe"
1437                 "spider\0"           No_argument       "\xfd"
1438                 "no-check-certificate\0" No_argument   "\xfc"
1439                 /* Ignored (we always use PASV): */
1440 IF_DESKTOP(     "passive-ftp\0"      No_argument       "\xf0")
1441                 /* Ignored (we don't support caching) */
1442 IF_DESKTOP(     "no-cache\0"         No_argument       "\xf0")
1443 IF_DESKTOP(     "no-verbose\0"       No_argument       "\xf0")
1444 IF_DESKTOP(     "no-clobber\0"       No_argument       "\xf0")
1445 IF_DESKTOP(     "no-host-directories\0" No_argument    "\xf0")
1446 IF_DESKTOP(     "no-parent\0"        No_argument       "\xf0")
1447                 ;
1448 # define GETOPT32 getopt32long
1449 # define LONGOPTS ,wget_longopts
1450 #else
1451 # define GETOPT32 getopt32
1452 # define LONGOPTS
1453 #endif
1454
1455 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1456         llist_t *headers_llist = NULL;
1457 #endif
1458
1459         INIT_G();
1460
1461 #if ENABLE_FEATURE_WGET_TIMEOUT
1462         G.timeout_seconds = 900;
1463         signal(SIGALRM, alarm_handler);
1464 #endif
1465         G.proxy_flag = "on";   /* use proxies if env vars are set */
1466         G.user_agent = "Wget"; /* "User-Agent" header field */
1467
1468 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1469 #endif
1470         GETOPT32(argv, "^"
1471                 "cqSO:o:P:Y:U:T:+"
1472                 /*ignored:*/ "t:"
1473                 /*ignored:*/ "n::"
1474                 /* wget has exactly four -n<letter> opts, all of which we can ignore:
1475                  * -nv --no-verbose: be moderately quiet (-q is full quiet)
1476                  * -nc --no-clobber: abort if exists, neither download to FILE.n nor overwrite FILE
1477                  * -nH --no-host-directories: wget -r http://host/ won't create host/
1478                  * -np --no-parent
1479                  * "n::" above says that we accept -n[ARG].
1480                  * Specifying "n:" would be a bug: "-n ARG" would eat ARG!
1481                  */
1482                 "\0"
1483                 "-1" /* at least one URL */
1484                 IF_FEATURE_WGET_LONG_OPTIONS(":\xff::") /* --header is a list */
1485                 LONGOPTS
1486                 , &G.fname_out, &G.fname_log, &G.dir_prefix,
1487                 &G.proxy_flag, &G.user_agent,
1488                 IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
1489                 NULL, /* -t RETRIES */
1490                 NULL  /* -n[ARG] */
1491                 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
1492                 IF_FEATURE_WGET_LONG_OPTIONS(, &G.post_data)
1493         );
1494 #if 0 /* option bits debug */
1495         if (option_mask32 & WGET_OPT_RETRIES) bb_error_msg("-t NUM");
1496         if (option_mask32 & WGET_OPT_nsomething) bb_error_msg("-nsomething");
1497         if (option_mask32 & WGET_OPT_HEADER) bb_error_msg("--header");
1498         if (option_mask32 & WGET_OPT_POST_DATA) bb_error_msg("--post-data");
1499         if (option_mask32 & WGET_OPT_SPIDER) bb_error_msg("--spider");
1500         if (option_mask32 & WGET_OPT_NO_CHECK_CERT) bb_error_msg("--no-check-certificate");
1501         exit(0);
1502 #endif
1503         argv += optind;
1504
1505 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1506         if (headers_llist) {
1507                 int size = 0;
1508                 char *hdr;
1509                 llist_t *ll = headers_llist;
1510                 while (ll) {
1511                         size += strlen(ll->data) + 2;
1512                         ll = ll->link;
1513                 }
1514                 G.extra_headers = hdr = xmalloc(size + 1);
1515                 while (headers_llist) {
1516                         int bit;
1517                         const char *words;
1518
1519                         size = sprintf(hdr, "%s\r\n",
1520                                         (char*)llist_pop(&headers_llist));
1521                         /* a bit like index_in_substrings but don't match full key */
1522                         bit = 1;
1523                         words = wget_user_headers;
1524                         while (*words) {
1525                                 if (strstr(hdr, words) == hdr) {
1526                                         G.user_headers |= bit;
1527                                         break;
1528                                 }
1529                                 bit <<= 1;
1530                                 words += strlen(words) + 1;
1531                         }
1532                         hdr += size;
1533                 }
1534         }
1535 #endif
1536
1537         G.output_fd = -1;
1538         G.o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
1539         if (G.fname_out) { /* -O FILE ? */
1540                 if (LONE_DASH(G.fname_out)) { /* -O - ? */
1541                         G.output_fd = 1;
1542                         option_mask32 &= ~WGET_OPT_CONTINUE;
1543                 }
1544                 /* compat with wget: -O FILE can overwrite */
1545                 G.o_flags = O_WRONLY | O_CREAT | O_TRUNC;
1546         }
1547
1548         G.log_fd = -1;
1549         if (G.fname_log) { /* -o FILE ? */
1550                 if (!LONE_DASH(G.fname_log)) { /* not -o - ? */
1551                         /* compat with wget: -o FILE can overwrite */
1552                         G.log_fd = xopen(G.fname_log, O_WRONLY | O_CREAT | O_TRUNC);
1553                         /* Redirect only stderr to log file, so -O - will work */
1554                         xdup2(G.log_fd, STDERR_FILENO);
1555                 }
1556         }
1557
1558         while (*argv)
1559                 download_one_url(*argv++);
1560
1561         if (G.output_fd >= 0)
1562                 xclose(G.output_fd);
1563
1564         if (G.log_fd >= 0)
1565                 xclose(G.log_fd);
1566
1567 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_WGET_LONG_OPTIONS
1568         free(G.extra_headers);
1569 #endif
1570         FINI_G();
1571
1572         return EXIT_SUCCESS;
1573 }