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