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