44eb4cf188a1bc9cffabe30dc8f208ee39ce3e37
[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 #include "libbb.h"
12
13 //#define log_io(...) bb_error_msg(__VA_ARGS__)
14 #define log_io(...) ((void)0)
15
16
17 struct host_info {
18         // May be used if we ever will want to free() all xstrdup()s...
19         /* char *allocated; */
20         const char *path;
21         const char *user;
22         char       *host;
23         int         port;
24         smallint    is_ftp;
25 };
26
27
28 /* Globals */
29 struct globals {
30         off_t content_len;        /* Content-length of the file */
31         off_t beg_range;          /* Range at which continue begins */
32 #if ENABLE_FEATURE_WGET_STATUSBAR
33         off_t transferred;        /* Number of bytes transferred so far */
34         const char *curfile;      /* Name of current file being transferred */
35         bb_progress_t pmt;
36 #endif
37 #if ENABLE_FEATURE_WGET_TIMEOUT
38         unsigned timeout_seconds;
39 #endif
40         smallint chunked;         /* chunked transfer encoding */
41         smallint got_clen;        /* got content-length: from server  */
42         /* Local downloads do benefit from big buffer.
43          * With 512 byte buffer, it was measured to be
44          * an order of magnitude slower than with big one.
45          */
46         uint64_t just_to_align_next_member;
47         char wget_buf[CONFIG_FEATURE_COPYBUF_KB*1024];
48 } FIX_ALIASING;
49 #define G (*ptr_to_globals)
50 #define INIT_G() do { \
51         SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
52         IF_FEATURE_WGET_TIMEOUT(G.timeout_seconds = 900;) \
53 } while (0)
54
55
56 /* Must match option string! */
57 enum {
58         WGET_OPT_CONTINUE   = (1 << 0),
59         WGET_OPT_SPIDER     = (1 << 1),
60         WGET_OPT_QUIET      = (1 << 2),
61         WGET_OPT_OUTNAME    = (1 << 3),
62         WGET_OPT_PREFIX     = (1 << 4),
63         WGET_OPT_PROXY      = (1 << 5),
64         WGET_OPT_USER_AGENT = (1 << 6),
65         WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 7),
66         WGET_OPT_RETRIES    = (1 << 8),
67         WGET_OPT_PASSIVE    = (1 << 9),
68         WGET_OPT_HEADER     = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
69         WGET_OPT_POST_DATA  = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
70 };
71
72 enum {
73         PROGRESS_START = -1,
74         PROGRESS_END   = 0,
75         PROGRESS_BUMP  = 1,
76 };
77 #if ENABLE_FEATURE_WGET_STATUSBAR
78 static void progress_meter(int flag)
79 {
80         if (option_mask32 & WGET_OPT_QUIET)
81                 return;
82
83         if (flag == PROGRESS_START)
84                 bb_progress_init(&G.pmt, G.curfile);
85
86         bb_progress_update(&G.pmt, G.beg_range, G.transferred,
87                            G.chunked ? 0 : G.beg_range + G.transferred + G.content_len);
88
89         if (flag == PROGRESS_END) {
90                 bb_putchar_stderr('\n');
91                 G.transferred = 0;
92         }
93 }
94 #else
95 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
96 #endif
97
98
99 /* IPv6 knows scoped address types i.e. link and site local addresses. Link
100  * local addresses can have a scope identifier to specify the
101  * interface/link an address is valid on (e.g. fe80::1%eth0). This scope
102  * identifier is only valid on a single node.
103  *
104  * RFC 4007 says that the scope identifier MUST NOT be sent across the wire,
105  * unless all nodes agree on the semantic. Apache e.g. regards zone identifiers
106  * in the Host header as invalid requests, see
107  * https://issues.apache.org/bugzilla/show_bug.cgi?id=35122
108  */
109 static void strip_ipv6_scope_id(char *host)
110 {
111         char *scope, *cp;
112
113         /* bbox wget actually handles IPv6 addresses without [], like
114          * wget "http://::1/xxx", but this is not standard.
115          * To save code, _here_ we do not support it. */
116
117         if (host[0] != '[')
118                 return; /* not IPv6 */
119
120         scope = strchr(host, '%');
121         if (!scope)
122                 return;
123
124         /* Remove the IPv6 zone identifier from the host address */
125         cp = strchr(host, ']');
126         if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
127                 /* malformed address (not "[xx]:nn" or "[xx]") */
128                 return;
129         }
130
131         /* cp points to "]...", scope points to "%eth0]..." */
132         overlapping_strcpy(scope, cp);
133 }
134
135 #if 0 /* were needed when we used signal-driven progress bar */
136 /* Read NMEMB bytes into PTR from STREAM.  Returns the number of bytes read,
137  * and a short count if an eof or non-interrupt error is encountered.  */
138 static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
139 {
140         size_t ret;
141         char *p = (char*)ptr;
142
143         do {
144                 clearerr(stream);
145                 errno = 0;
146                 ret = fread(p, 1, nmemb, stream);
147                 p += ret;
148                 nmemb -= ret;
149         } while (nmemb && ferror(stream) && errno == EINTR);
150
151         return p - (char*)ptr;
152 }
153
154 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
155  * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
156 static char *safe_fgets(char *s, int size, FILE *stream)
157 {
158         char *ret;
159
160         do {
161                 clearerr(stream);
162                 errno = 0;
163                 ret = fgets(s, size, stream);
164         } while (ret == NULL && ferror(stream) && errno == EINTR);
165
166         return ret;
167 }
168 #endif
169
170 #if ENABLE_FEATURE_WGET_AUTHENTICATION
171 /* Base64-encode character string. */
172 static char *base64enc(const char *str)
173 {
174         unsigned len = strlen(str);
175         if (len > sizeof(G.wget_buf)/4*3 - 10) /* paranoia */
176                 len = sizeof(G.wget_buf)/4*3 - 10;
177         bb_uuencode(G.wget_buf, str, len, bb_uuenc_tbl_base64);
178         return G.wget_buf;
179 }
180 #endif
181
182 static char* sanitize_string(char *s)
183 {
184         unsigned char *p = (void *) s;
185         while (*p >= ' ')
186                 p++;
187         *p = '\0';
188         return s;
189 }
190
191 static FILE *open_socket(len_and_sockaddr *lsa)
192 {
193         FILE *fp;
194
195         /* glibc 2.4 seems to try seeking on it - ??! */
196         /* hopefully it understands what ESPIPE means... */
197         fp = fdopen(xconnect_stream(lsa), "r+");
198         if (fp == NULL)
199                 bb_perror_msg_and_die(bb_msg_memory_exhausted);
200
201         return fp;
202 }
203
204 /* Returns '\n' if it was seen, else '\0'. Trims at first '\r' or '\n' */
205 static char fgets_and_trim(FILE *fp)
206 {
207         char c;
208         char *buf_ptr;
209
210         if (fgets(G.wget_buf, sizeof(G.wget_buf) - 1, fp) == NULL)
211                 bb_perror_msg_and_die("error getting response");
212
213         buf_ptr = strchrnul(G.wget_buf, '\n');
214         c = *buf_ptr;
215         *buf_ptr = '\0';
216         buf_ptr = strchrnul(G.wget_buf, '\r');
217         *buf_ptr = '\0';
218
219         log_io("< %s", G.wget_buf);
220
221         return c;
222 }
223
224 static int ftpcmd(const char *s1, const char *s2, FILE *fp)
225 {
226         int result;
227         if (s1) {
228                 if (!s2)
229                         s2 = "";
230                 fprintf(fp, "%s%s\r\n", s1, s2);
231                 fflush(fp);
232                 log_io("> %s%s", s1, s2);
233         }
234
235         do {
236                 fgets_and_trim(fp);
237         } while (!isdigit(G.wget_buf[0]) || G.wget_buf[3] != ' ');
238
239         G.wget_buf[3] = '\0';
240         result = xatoi_positive(G.wget_buf);
241         G.wget_buf[3] = ' ';
242         return result;
243 }
244
245 static void parse_url(char *src_url, struct host_info *h)
246 {
247         char *url, *p, *sp;
248
249         /* h->allocated = */ url = xstrdup(src_url);
250
251         if (strncmp(url, "http://", 7) == 0) {
252                 h->port = bb_lookup_port("http", "tcp", 80);
253                 h->host = url + 7;
254                 h->is_ftp = 0;
255         } else if (strncmp(url, "ftp://", 6) == 0) {
256                 h->port = bb_lookup_port("ftp", "tcp", 21);
257                 h->host = url + 6;
258                 h->is_ftp = 1;
259         } else
260                 bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
261
262         // FYI:
263         // "Real" wget 'http://busybox.net?var=a/b' sends this request:
264         //   'GET /?var=a/b HTTP 1.0'
265         //   and saves 'index.html?var=a%2Fb' (we save 'b')
266         // wget 'http://busybox.net?login=john@doe':
267         //   request: 'GET /?login=john@doe HTTP/1.0'
268         //   saves: 'index.html?login=john@doe' (we save '?login=john@doe')
269         // wget 'http://busybox.net#test/test':
270         //   request: 'GET / HTTP/1.0'
271         //   saves: 'index.html' (we save 'test')
272         //
273         // We also don't add unique .N suffix if file exists...
274         sp = strchr(h->host, '/');
275         p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
276         p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
277         if (!sp) {
278                 h->path = "";
279         } else if (*sp == '/') {
280                 *sp = '\0';
281                 h->path = sp + 1;
282         } else { // '#' or '?'
283                 // http://busybox.net?login=john@doe is a valid URL
284                 // memmove converts to:
285                 // http:/busybox.nett?login=john@doe...
286                 memmove(h->host - 1, h->host, sp - h->host);
287                 h->host--;
288                 sp[-1] = '\0';
289                 h->path = sp;
290         }
291
292         // We used to set h->user to NULL here, but this interferes
293         // with handling of code 302 ("object was moved")
294
295         sp = strrchr(h->host, '@');
296         if (sp != NULL) {
297                 h->user = h->host;
298                 *sp = '\0';
299                 h->host = sp + 1;
300         }
301
302         sp = h->host;
303 }
304
305 static char *gethdr(FILE *fp)
306 {
307         char *s, *hdrval;
308         int c;
309
310         /* *istrunc = 0; */
311
312         /* retrieve header line */
313         c = fgets_and_trim(fp);
314
315         /* end of the headers? */
316         if (G.wget_buf[0] == '\0')
317                 return NULL;
318
319         /* convert the header name to lower case */
320         for (s = G.wget_buf; isalnum(*s) || *s == '-' || *s == '.'; ++s) {
321                 /* tolower for "A-Z", no-op for "0-9a-z-." */
322                 *s |= 0x20;
323         }
324
325         /* verify we are at the end of the header name */
326         if (*s != ':')
327                 bb_error_msg_and_die("bad header line: %s", sanitize_string(G.wget_buf));
328
329         /* locate the start of the header value */
330         *s++ = '\0';
331         hdrval = skip_whitespace(s);
332
333         if (c != '\n') {
334                 /* Rats! The buffer isn't big enough to hold the entire header value */
335                 while (c = getc(fp), c != EOF && c != '\n')
336                         continue;
337         }
338
339         return hdrval;
340 }
341
342 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
343 static char *URL_escape(const char *str)
344 {
345         /* URL encode, see RFC 2396 */
346         char *dst;
347         char *res = dst = xmalloc(strlen(str) * 3 + 1);
348         unsigned char c;
349
350         while (1) {
351                 c = *str++;
352                 if (c == '\0'
353                 /* || strchr("!&'()*-.=_~", c) - more code */
354                  || c == '!'
355                  || c == '&'
356                  || c == '\''
357                  || c == '('
358                  || c == ')'
359                  || c == '*'
360                  || c == '-'
361                  || c == '.'
362                  || c == '='
363                  || c == '_'
364                  || c == '~'
365                  || (c >= '0' && c <= '9')
366                  || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
367                 ) {
368                         *dst++ = c;
369                         if (c == '\0')
370                                 return res;
371                 } else {
372                         *dst++ = '%';
373                         *dst++ = bb_hexdigits_upcase[c >> 4];
374                         *dst++ = bb_hexdigits_upcase[c & 0xf];
375                 }
376         }
377 }
378 #endif
379
380 static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
381 {
382         FILE *sfp;
383         char *str;
384         int port;
385
386         if (!target->user)
387                 target->user = xstrdup("anonymous:busybox@");
388
389         sfp = open_socket(lsa);
390         if (ftpcmd(NULL, NULL, sfp) != 220)
391                 bb_error_msg_and_die("%s", sanitize_string(G.wget_buf + 4));
392
393         /*
394          * Splitting username:password pair,
395          * trying to log in
396          */
397         str = strchr(target->user, ':');
398         if (str)
399                 *str++ = '\0';
400         switch (ftpcmd("USER ", target->user, sfp)) {
401         case 230:
402                 break;
403         case 331:
404                 if (ftpcmd("PASS ", str, sfp) == 230)
405                         break;
406                 /* fall through (failed login) */
407         default:
408                 bb_error_msg_and_die("ftp login: %s", sanitize_string(G.wget_buf + 4));
409         }
410
411         ftpcmd("TYPE I", NULL, sfp);
412
413         /*
414          * Querying file size
415          */
416         if (ftpcmd("SIZE ", target->path, sfp) == 213) {
417                 G.content_len = BB_STRTOOFF(G.wget_buf + 4, NULL, 10);
418                 if (G.content_len < 0 || errno) {
419                         bb_error_msg_and_die("SIZE value is garbage");
420                 }
421                 G.got_clen = 1;
422         }
423
424         /*
425          * Entering passive mode
426          */
427         if (ftpcmd("PASV", NULL, sfp) != 227) {
428  pasv_error:
429                 bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(G.wget_buf));
430         }
431         // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
432         // Server's IP is N1.N2.N3.N4 (we ignore it)
433         // Server's port for data connection is P1*256+P2
434         str = strrchr(G.wget_buf, ')');
435         if (str) str[0] = '\0';
436         str = strrchr(G.wget_buf, ',');
437         if (!str) goto pasv_error;
438         port = xatou_range(str+1, 0, 255);
439         *str = '\0';
440         str = strrchr(G.wget_buf, ',');
441         if (!str) goto pasv_error;
442         port += xatou_range(str+1, 0, 255) * 256;
443         set_nport(lsa, htons(port));
444
445         *dfpp = open_socket(lsa);
446
447         if (G.beg_range) {
448                 sprintf(G.wget_buf, "REST %"OFF_FMT"u", G.beg_range);
449                 if (ftpcmd(G.wget_buf, NULL, sfp) == 350)
450                         G.content_len -= G.beg_range;
451         }
452
453         if (ftpcmd("RETR ", target->path, sfp) > 150)
454                 bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(G.wget_buf));
455
456         return sfp;
457 }
458
459 static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
460 {
461 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
462 # if ENABLE_FEATURE_WGET_TIMEOUT
463         unsigned second_cnt;
464 # endif
465         struct pollfd polldata;
466
467         polldata.fd = fileno(dfp);
468         polldata.events = POLLIN | POLLPRI;
469 #endif
470         progress_meter(PROGRESS_START);
471
472         if (G.chunked)
473                 goto get_clen;
474
475         /* Loops only if chunked */
476         while (1) {
477
478 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
479                 /* Must use nonblocking I/O, otherwise fread will loop
480                  * and *block* until it reads full buffer,
481                  * which messes up progress bar and/or timeout logic.
482                  * Because of nonblocking I/O, we need to dance
483                  * very carefully around EAGAIN. See explanation at
484                  * clearerr() call.
485                  */
486                 ndelay_on(polldata.fd);
487 #endif
488                 while (1) {
489                         int n;
490                         unsigned rdsz;
491
492                         rdsz = sizeof(G.wget_buf);
493                         if (G.got_clen) {
494                                 if (G.content_len < (off_t)sizeof(G.wget_buf)) {
495                                         if ((int)G.content_len <= 0)
496                                                 break;
497                                         rdsz = (unsigned)G.content_len;
498                                 }
499                         }
500
501 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
502 # if ENABLE_FEATURE_WGET_TIMEOUT
503                         second_cnt = G.timeout_seconds;
504 # endif
505                         while (1) {
506                                 if (safe_poll(&polldata, 1, 1000) != 0)
507                                         break; /* error, EOF, or data is available */
508 # if ENABLE_FEATURE_WGET_TIMEOUT
509                                 if (second_cnt != 0 && --second_cnt == 0) {
510                                         progress_meter(PROGRESS_END);
511                                         bb_error_msg_and_die("download timed out");
512                                 }
513 # endif
514                                 /* Needed for "stalled" indicator */
515                                 progress_meter(PROGRESS_BUMP);
516                         }
517
518                         /* fread internally uses read loop, which in our case
519                          * is usually exited when we get EAGAIN.
520                          * In this case, libc sets error marker on the stream.
521                          * Need to clear it before next fread to avoid possible
522                          * rare false positive ferror below. Rare because usually
523                          * fread gets more than zero bytes, and we don't fall
524                          * into if (n <= 0) ...
525                          */
526                         clearerr(dfp);
527                         errno = 0;
528 #endif
529                         n = fread(G.wget_buf, 1, rdsz, dfp);
530                         /* man fread:
531                          * If error occurs, or EOF is reached, the return value
532                          * is a short item count (or zero).
533                          * fread does not distinguish between EOF and error.
534                          */
535                         if (n <= 0) {
536 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
537                                 if (errno == EAGAIN) /* poll lied, there is no data? */
538                                         continue; /* yes */
539 #endif
540                                 if (ferror(dfp))
541                                         bb_perror_msg_and_die(bb_msg_read_error);
542                                 break; /* EOF, not error */
543                         }
544
545                         xwrite(output_fd, G.wget_buf, n);
546
547 #if ENABLE_FEATURE_WGET_STATUSBAR
548                         G.transferred += n;
549                         progress_meter(PROGRESS_BUMP);
550 #endif
551                         if (G.got_clen) {
552                                 G.content_len -= n;
553                                 if (G.content_len == 0)
554                                         break;
555                         }
556                 }
557 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
558                 clearerr(dfp);
559                 ndelay_off(polldata.fd); /* else fgets can get very unhappy */
560 #endif
561                 if (!G.chunked)
562                         break;
563
564                 fgets_and_trim(dfp); /* Eat empty line */
565  get_clen:
566                 fgets_and_trim(dfp);
567                 G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
568                 /* FIXME: error check? */
569                 if (G.content_len == 0)
570                         break; /* all done! */
571                 G.got_clen = 1;
572         }
573
574         G.chunked = 0; /* make progress meter show 100% even for chunked */
575         progress_meter(PROGRESS_END);
576 }
577
578 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
579 int wget_main(int argc UNUSED_PARAM, char **argv)
580 {
581         struct host_info server, target;
582         len_and_sockaddr *lsa;
583         unsigned opt;
584         int redir_limit;
585         char *proxy = NULL;
586         char *dir_prefix = NULL;
587 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
588         char *post_data;
589         char *extra_headers = NULL;
590         llist_t *headers_llist = NULL;
591 #endif
592         FILE *sfp;                      /* socket to web/ftp server         */
593         FILE *dfp;                      /* socket to ftp server (data)      */
594         char *fname_out;                /* where to direct output (-O)      */
595         int output_fd = -1;
596         bool use_proxy;                 /* Use proxies if env vars are set  */
597         const char *proxy_flag = "on";  /* Use proxies if env vars are set  */
598         const char *user_agent = "Wget";/* "User-Agent" header field        */
599
600         static const char keywords[] ALIGN1 =
601                 "content-length\0""transfer-encoding\0""chunked\0""location\0";
602         enum {
603                 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
604         };
605 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
606         static const char wget_longopts[] ALIGN1 =
607                 /* name, has_arg, val */
608                 "continue\0"         No_argument       "c"
609                 "spider\0"           No_argument       "s"
610                 "quiet\0"            No_argument       "q"
611                 "output-document\0"  Required_argument "O"
612                 "directory-prefix\0" Required_argument "P"
613                 "proxy\0"            Required_argument "Y"
614                 "user-agent\0"       Required_argument "U"
615 #if ENABLE_FEATURE_WGET_TIMEOUT
616                 "timeout\0"          Required_argument "T"
617 #endif
618                 /* Ignored: */
619                 // "tries\0"            Required_argument "t"
620                 /* Ignored (we always use PASV): */
621                 "passive-ftp\0"      No_argument       "\xff"
622                 "header\0"           Required_argument "\xfe"
623                 "post-data\0"        Required_argument "\xfd"
624                 /* Ignored (we don't do ssl) */
625                 "no-check-certificate\0" No_argument   "\xfc"
626                 ;
627 #endif
628
629         INIT_G();
630
631 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
632         applet_long_options = wget_longopts;
633 #endif
634         /* server.allocated = target.allocated = NULL; */
635         opt_complementary = "-1" IF_FEATURE_WGET_TIMEOUT(":T+") IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
636         opt = getopt32(argv, "csqO:P:Y:U:T:" /*ignored:*/ "t:",
637                                 &fname_out, &dir_prefix,
638                                 &proxy_flag, &user_agent,
639                                 IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
640                                 NULL /* -t RETRIES */
641                                 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
642                                 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
643                                 );
644 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
645         if (headers_llist) {
646                 int size = 1;
647                 char *cp;
648                 llist_t *ll = headers_llist;
649                 while (ll) {
650                         size += strlen(ll->data) + 2;
651                         ll = ll->link;
652                 }
653                 extra_headers = cp = xmalloc(size);
654                 while (headers_llist) {
655                         cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
656                 }
657         }
658 #endif
659
660         /* TODO: compat issue: should handle "wget URL1 URL2..." */
661
662         target.user = NULL;
663         parse_url(argv[optind], &target);
664
665         /* Use the proxy if necessary */
666         use_proxy = (strcmp(proxy_flag, "off") != 0);
667         if (use_proxy) {
668                 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
669                 if (proxy && proxy[0]) {
670                         server.user = NULL;
671                         parse_url(proxy, &server);
672                 } else {
673                         use_proxy = 0;
674                 }
675         }
676         if (!use_proxy) {
677                 server.port = target.port;
678                 if (ENABLE_FEATURE_IPV6) {
679                         server.host = xstrdup(target.host);
680                 } else {
681                         server.host = target.host;
682                 }
683         }
684
685         if (ENABLE_FEATURE_IPV6)
686                 strip_ipv6_scope_id(target.host);
687
688         /* Guess an output filename, if there was no -O FILE */
689         if (!(opt & WGET_OPT_OUTNAME)) {
690                 fname_out = bb_get_last_path_component_nostrip(target.path);
691                 /* handle "wget http://kernel.org//" */
692                 if (fname_out[0] == '/' || !fname_out[0])
693                         fname_out = (char*)"index.html";
694                 /* -P DIR is considered only if there was no -O FILE */
695                 if (dir_prefix)
696                         fname_out = concat_path_file(dir_prefix, fname_out);
697         } else {
698                 if (LONE_DASH(fname_out)) {
699                         /* -O - */
700                         output_fd = 1;
701                         opt &= ~WGET_OPT_CONTINUE;
702                 }
703         }
704 #if ENABLE_FEATURE_WGET_STATUSBAR
705         G.curfile = bb_get_last_path_component_nostrip(fname_out);
706 #endif
707
708         /* Impossible?
709         if ((opt & WGET_OPT_CONTINUE) && !fname_out)
710                 bb_error_msg_and_die("can't specify continue (-c) without a filename (-O)");
711         */
712
713         /* Determine where to start transfer */
714         if (opt & WGET_OPT_CONTINUE) {
715                 output_fd = open(fname_out, O_WRONLY);
716                 if (output_fd >= 0) {
717                         G.beg_range = xlseek(output_fd, 0, SEEK_END);
718                 }
719                 /* File doesn't exist. We do not create file here yet.
720                  * We are not sure it exists on remove side */
721         }
722
723         redir_limit = 5;
724  resolve_lsa:
725         lsa = xhost2sockaddr(server.host, server.port);
726         if (!(opt & WGET_OPT_QUIET)) {
727                 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
728                 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
729                 free(s);
730         }
731  establish_session:
732         if (use_proxy || !target.is_ftp) {
733                 /*
734                  *  HTTP session
735                  */
736                 char *str;
737                 int status;
738
739                 /* Open socket to http server */
740                 sfp = open_socket(lsa);
741
742                 /* Send HTTP request */
743                 if (use_proxy) {
744                         fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
745                                 target.is_ftp ? "f" : "ht", target.host,
746                                 target.path);
747                 } else {
748                         if (opt & WGET_OPT_POST_DATA)
749                                 fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
750                         else
751                                 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
752                 }
753
754                 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
755                         target.host, user_agent);
756
757                 /* Ask server to close the connection as soon as we are done
758                  * (IOW: we do not intend to send more requests)
759                  */
760                 fprintf(sfp, "Connection: close\r\n");
761
762 #if ENABLE_FEATURE_WGET_AUTHENTICATION
763                 if (target.user) {
764                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
765                                 base64enc(target.user));
766                 }
767                 if (use_proxy && server.user) {
768                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
769                                 base64enc(server.user));
770                 }
771 #endif
772
773                 if (G.beg_range)
774                         fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
775
776 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
777                 if (extra_headers)
778                         fputs(extra_headers, sfp);
779
780                 if (opt & WGET_OPT_POST_DATA) {
781                         char *estr = URL_escape(post_data);
782                         fprintf(sfp,
783                                 "Content-Type: application/x-www-form-urlencoded\r\n"
784                                 "Content-Length: %u\r\n"
785                                 "\r\n"
786                                 "%s",
787                                 (int) strlen(estr), estr
788                         );
789                         free(estr);
790                 } else
791 #endif
792                 {
793                         fprintf(sfp, "\r\n");
794                 }
795
796                 fflush(sfp);
797
798                 /*
799                  * Retrieve HTTP response line and check for "200" status code.
800                  */
801  read_response:
802                 fgets_and_trim(sfp);
803
804                 str = G.wget_buf;
805                 str = skip_non_whitespace(str);
806                 str = skip_whitespace(str);
807                 // FIXME: no error check
808                 // xatou wouldn't work: "200 OK"
809                 status = atoi(str);
810                 switch (status) {
811                 case 0:
812                 case 100:
813                         while (gethdr(sfp /*, &n*/) != NULL)
814                                 /* eat all remaining headers */;
815                         goto read_response;
816                 case 200:
817 /*
818 Response 204 doesn't say "null file", it says "metadata
819 has changed but data didn't":
820
821 "10.2.5 204 No Content
822 The server has fulfilled the request but does not need to return
823 an entity-body, and might want to return updated metainformation.
824 The response MAY include new or updated metainformation in the form
825 of entity-headers, which if present SHOULD be associated with
826 the requested variant.
827
828 If the client is a user agent, it SHOULD NOT change its document
829 view from that which caused the request to be sent. This response
830 is primarily intended to allow input for actions to take place
831 without causing a change to the user agent's active document view,
832 although any new or updated metainformation SHOULD be applied
833 to the document currently in the user agent's active view.
834
835 The 204 response MUST NOT include a message-body, and thus
836 is always terminated by the first empty line after the header fields."
837
838 However, in real world it was observed that some web servers
839 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
840 */
841                 case 204:
842                         break;
843                 case 300:  /* redirection */
844                 case 301:
845                 case 302:
846                 case 303:
847                         break;
848                 case 206:
849                         if (G.beg_range)
850                                 break;
851                         /* fall through */
852                 default:
853                         bb_error_msg_and_die("server returned error: %s", sanitize_string(G.wget_buf));
854                 }
855
856                 /*
857                  * Retrieve HTTP headers.
858                  */
859                 while ((str = gethdr(sfp /*, &n*/)) != NULL) {
860                         /* gethdr converted "FOO:" string to lowercase */
861                         smalluint key;
862                         /* strip trailing whitespace */
863                         char *s = strchrnul(str, '\0') - 1;
864                         while (s >= str && (*s == ' ' || *s == '\t')) {
865                                 *s = '\0';
866                                 s--;
867                         }
868                         key = index_in_strings(keywords, G.wget_buf) + 1;
869                         if (key == KEY_content_length) {
870                                 G.content_len = BB_STRTOOFF(str, NULL, 10);
871                                 if (G.content_len < 0 || errno) {
872                                         bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
873                                 }
874                                 G.got_clen = 1;
875                                 continue;
876                         }
877                         if (key == KEY_transfer_encoding) {
878                                 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
879                                         bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
880                                 G.chunked = G.got_clen = 1;
881                         }
882                         if (key == KEY_location && status >= 300) {
883                                 if (--redir_limit == 0)
884                                         bb_error_msg_and_die("too many redirections");
885                                 fclose(sfp);
886                                 G.got_clen = 0;
887                                 G.chunked = 0;
888                                 if (str[0] == '/')
889                                         /* free(target.allocated); */
890                                         target.path = /* target.allocated = */ xstrdup(str+1);
891                                         /* lsa stays the same: it's on the same server */
892                                 else {
893                                         parse_url(str, &target);
894                                         if (!use_proxy) {
895                                                 server.host = target.host;
896                                                 /* strip_ipv6_scope_id(target.host); - no! */
897                                                 /* we assume remote never gives us IPv6 addr with scope id */
898                                                 server.port = target.port;
899                                                 free(lsa);
900                                                 goto resolve_lsa;
901                                         } /* else: lsa stays the same: we use proxy */
902                                 }
903                                 goto establish_session;
904                         }
905                 }
906 //              if (status >= 300)
907 //                      bb_error_msg_and_die("bad redirection (no Location: header from server)");
908
909                 /* For HTTP, data is pumped over the same connection */
910                 dfp = sfp;
911
912         } else {
913                 /*
914                  *  FTP session
915                  */
916                 sfp = prepare_ftp_session(&dfp, &target, lsa);
917         }
918
919         if (opt & WGET_OPT_SPIDER) {
920                 if (ENABLE_FEATURE_CLEAN_UP)
921                         fclose(sfp);
922                 return EXIT_SUCCESS;
923         }
924
925         if (output_fd < 0) {
926                 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
927                 /* compat with wget: -O FILE can overwrite */
928                 if (opt & WGET_OPT_OUTNAME)
929                         o_flags = O_WRONLY | O_CREAT | O_TRUNC;
930                 output_fd = xopen(fname_out, o_flags);
931         }
932
933         retrieve_file_data(dfp, output_fd);
934         xclose(output_fd);
935
936         if (dfp != sfp) {
937                 /* It's ftp. Close it properly */
938                 fclose(dfp);
939                 if (ftpcmd(NULL, NULL, sfp) != 226)
940                         bb_error_msg_and_die("ftp error: %s", sanitize_string(G.wget_buf + 4));
941                 /* ftpcmd("QUIT", NULL, sfp); - why bother? */
942         }
943
944         return EXIT_SUCCESS;
945 }