48688640a07f72fa22d8bb0c1f56e8a323d2dc75
[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         ndelay_on(polldata.fd);
470 #endif
471         progress_meter(PROGRESS_START);
472
473         if (G.chunked)
474                 goto get_clen;
475
476         /* Loops only if chunked */
477         while (1) {
478                 while (1) {
479                         int n;
480                         unsigned rdsz;
481
482                         rdsz = sizeof(G.wget_buf);
483                         if (G.got_clen) {
484                                 if (G.content_len < (off_t)sizeof(G.wget_buf)) {
485                                         if ((int)G.content_len <= 0)
486                                                 break;
487                                         rdsz = (unsigned)G.content_len;
488                                 }
489                         }
490
491 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
492 # if ENABLE_FEATURE_WGET_TIMEOUT
493                         second_cnt = G.timeout_seconds;
494 # endif
495                         while (1) {
496                                 if (safe_poll(&polldata, 1, 1000) != 0)
497                                         break; /* error, EOF, or data is available */
498 # if ENABLE_FEATURE_WGET_TIMEOUT
499                                 if (second_cnt != 0 && --second_cnt == 0) {
500                                         progress_meter(PROGRESS_END);
501                                         bb_error_msg_and_die("download timed out");
502                                 }
503 # endif
504                                 /* Needed for "stalled" indicator */
505                                 progress_meter(PROGRESS_BUMP);
506                         }
507 #endif
508                         /* fread internally uses read loop, which in our case
509                          * is usually exited when we get EAGAIN.
510                          * In this case, libc sets error marker on the stream.
511                          * Need to clear it before next fread to avoid possible
512                          * rare false positive ferror below. Rare because usually
513                          * fread gets more than zero bytes, and we don't fall
514                          * into if (n <= 0) ...
515                          */
516                         clearerr(dfp);
517                         errno = 0;
518                         n = fread(G.wget_buf, 1, rdsz, dfp);
519                         /* man fread:
520                          * If error occurs, or EOF is reached, the return value
521                          * is a short item count (or zero).
522                          * fread does not distinguish between EOF and error.
523                          */
524                         if (n <= 0) {
525 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
526                                 if (errno == EAGAIN) /* poll lied, there is no data? */
527                                         continue; /* yes */
528 #endif
529                                 if (ferror(dfp))
530                                         bb_perror_msg_and_die(bb_msg_read_error);
531                                 break; /* EOF, not error */
532                         }
533
534                         xwrite(output_fd, G.wget_buf, n);
535
536 #if ENABLE_FEATURE_WGET_STATUSBAR
537                         G.transferred += n;
538                         progress_meter(PROGRESS_BUMP);
539 #endif
540                         if (G.got_clen) {
541                                 G.content_len -= n;
542                                 if (G.content_len == 0)
543                                         break;
544                         }
545                 }
546
547                 if (!G.chunked)
548                         break;
549
550                 fgets_and_trim(dfp); /* This is a newline */
551  get_clen:
552                 fgets_and_trim(dfp);
553                 G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
554                 /* FIXME: error check? */
555                 if (G.content_len == 0)
556                         break; /* all done! */
557                 G.got_clen = 1;
558         }
559
560         progress_meter(PROGRESS_END);
561 }
562
563 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
564 int wget_main(int argc UNUSED_PARAM, char **argv)
565 {
566         struct host_info server, target;
567         len_and_sockaddr *lsa;
568         unsigned opt;
569         int redir_limit;
570         char *proxy = NULL;
571         char *dir_prefix = NULL;
572 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
573         char *post_data;
574         char *extra_headers = NULL;
575         llist_t *headers_llist = NULL;
576 #endif
577         FILE *sfp;                      /* socket to web/ftp server         */
578         FILE *dfp;                      /* socket to ftp server (data)      */
579         char *fname_out;                /* where to direct output (-O)      */
580         int output_fd = -1;
581         bool use_proxy;                 /* Use proxies if env vars are set  */
582         const char *proxy_flag = "on";  /* Use proxies if env vars are set  */
583         const char *user_agent = "Wget";/* "User-Agent" header field        */
584
585         static const char keywords[] ALIGN1 =
586                 "content-length\0""transfer-encoding\0""chunked\0""location\0";
587         enum {
588                 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
589         };
590 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
591         static const char wget_longopts[] ALIGN1 =
592                 /* name, has_arg, val */
593                 "continue\0"         No_argument       "c"
594                 "spider\0"           No_argument       "s"
595                 "quiet\0"            No_argument       "q"
596                 "output-document\0"  Required_argument "O"
597                 "directory-prefix\0" Required_argument "P"
598                 "proxy\0"            Required_argument "Y"
599                 "user-agent\0"       Required_argument "U"
600 #if ENABLE_FEATURE_WGET_TIMEOUT
601                 "timeout\0"          Required_argument "T"
602 #endif
603                 /* Ignored: */
604                 // "tries\0"            Required_argument "t"
605                 /* Ignored (we always use PASV): */
606                 "passive-ftp\0"      No_argument       "\xff"
607                 "header\0"           Required_argument "\xfe"
608                 "post-data\0"        Required_argument "\xfd"
609                 /* Ignored (we don't do ssl) */
610                 "no-check-certificate\0" No_argument   "\xfc"
611                 ;
612 #endif
613
614         INIT_G();
615
616 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
617         applet_long_options = wget_longopts;
618 #endif
619         /* server.allocated = target.allocated = NULL; */
620         opt_complementary = "-1" IF_FEATURE_WGET_TIMEOUT(":T+") IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
621         opt = getopt32(argv, "csqO:P:Y:U:T:" /*ignored:*/ "t:",
622                                 &fname_out, &dir_prefix,
623                                 &proxy_flag, &user_agent,
624                                 IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
625                                 NULL /* -t RETRIES */
626                                 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
627                                 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
628                                 );
629 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
630         if (headers_llist) {
631                 int size = 1;
632                 char *cp;
633                 llist_t *ll = headers_llist;
634                 while (ll) {
635                         size += strlen(ll->data) + 2;
636                         ll = ll->link;
637                 }
638                 extra_headers = cp = xmalloc(size);
639                 while (headers_llist) {
640                         cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
641                 }
642         }
643 #endif
644
645         /* TODO: compat issue: should handle "wget URL1 URL2..." */
646
647         target.user = NULL;
648         parse_url(argv[optind], &target);
649
650         /* Use the proxy if necessary */
651         use_proxy = (strcmp(proxy_flag, "off") != 0);
652         if (use_proxy) {
653                 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
654                 if (proxy && proxy[0]) {
655                         server.user = NULL;
656                         parse_url(proxy, &server);
657                 } else {
658                         use_proxy = 0;
659                 }
660         }
661         if (!use_proxy) {
662                 server.port = target.port;
663                 if (ENABLE_FEATURE_IPV6) {
664                         server.host = xstrdup(target.host);
665                 } else {
666                         server.host = target.host;
667                 }
668         }
669
670         if (ENABLE_FEATURE_IPV6)
671                 strip_ipv6_scope_id(target.host);
672
673         /* Guess an output filename, if there was no -O FILE */
674         if (!(opt & WGET_OPT_OUTNAME)) {
675                 fname_out = bb_get_last_path_component_nostrip(target.path);
676                 /* handle "wget http://kernel.org//" */
677                 if (fname_out[0] == '/' || !fname_out[0])
678                         fname_out = (char*)"index.html";
679                 /* -P DIR is considered only if there was no -O FILE */
680                 if (dir_prefix)
681                         fname_out = concat_path_file(dir_prefix, fname_out);
682         } else {
683                 if (LONE_DASH(fname_out)) {
684                         /* -O - */
685                         output_fd = 1;
686                         opt &= ~WGET_OPT_CONTINUE;
687                 }
688         }
689 #if ENABLE_FEATURE_WGET_STATUSBAR
690         G.curfile = bb_get_last_path_component_nostrip(fname_out);
691 #endif
692
693         /* Impossible?
694         if ((opt & WGET_OPT_CONTINUE) && !fname_out)
695                 bb_error_msg_and_die("can't specify continue (-c) without a filename (-O)");
696         */
697
698         /* Determine where to start transfer */
699         if (opt & WGET_OPT_CONTINUE) {
700                 output_fd = open(fname_out, O_WRONLY);
701                 if (output_fd >= 0) {
702                         G.beg_range = xlseek(output_fd, 0, SEEK_END);
703                 }
704                 /* File doesn't exist. We do not create file here yet.
705                  * We are not sure it exists on remove side */
706         }
707
708         redir_limit = 5;
709  resolve_lsa:
710         lsa = xhost2sockaddr(server.host, server.port);
711         if (!(opt & WGET_OPT_QUIET)) {
712                 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
713                 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
714                 free(s);
715         }
716  establish_session:
717         if (use_proxy || !target.is_ftp) {
718                 /*
719                  *  HTTP session
720                  */
721                 char *str;
722                 int status;
723
724                 /* Open socket to http server */
725                 sfp = open_socket(lsa);
726
727                 /* Send HTTP request */
728                 if (use_proxy) {
729                         fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
730                                 target.is_ftp ? "f" : "ht", target.host,
731                                 target.path);
732                 } else {
733                         if (opt & WGET_OPT_POST_DATA)
734                                 fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
735                         else
736                                 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
737                 }
738
739                 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
740                         target.host, user_agent);
741
742                 /* Ask server to close the connection as soon as we are done
743                  * (IOW: we do not intend to send more requests)
744                  */
745                 fprintf(sfp, "Connection: close\r\n");
746
747 #if ENABLE_FEATURE_WGET_AUTHENTICATION
748                 if (target.user) {
749                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
750                                 base64enc(target.user));
751                 }
752                 if (use_proxy && server.user) {
753                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
754                                 base64enc(server.user));
755                 }
756 #endif
757
758                 if (G.beg_range)
759                         fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
760
761 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
762                 if (extra_headers)
763                         fputs(extra_headers, sfp);
764
765                 if (opt & WGET_OPT_POST_DATA) {
766                         char *estr = URL_escape(post_data);
767                         fprintf(sfp,
768                                 "Content-Type: application/x-www-form-urlencoded\r\n"
769                                 "Content-Length: %u\r\n"
770                                 "\r\n"
771                                 "%s",
772                                 (int) strlen(estr), estr
773                         );
774                         free(estr);
775                 } else
776 #endif
777                 {
778                         fprintf(sfp, "\r\n");
779                 }
780
781                 fflush(sfp);
782
783                 /*
784                  * Retrieve HTTP response line and check for "200" status code.
785                  */
786  read_response:
787                 fgets_and_trim(sfp);
788
789                 str = G.wget_buf;
790                 str = skip_non_whitespace(str);
791                 str = skip_whitespace(str);
792                 // FIXME: no error check
793                 // xatou wouldn't work: "200 OK"
794                 status = atoi(str);
795                 switch (status) {
796                 case 0:
797                 case 100:
798                         while (gethdr(sfp /*, &n*/) != NULL)
799                                 /* eat all remaining headers */;
800                         goto read_response;
801                 case 200:
802 /*
803 Response 204 doesn't say "null file", it says "metadata
804 has changed but data didn't":
805
806 "10.2.5 204 No Content
807 The server has fulfilled the request but does not need to return
808 an entity-body, and might want to return updated metainformation.
809 The response MAY include new or updated metainformation in the form
810 of entity-headers, which if present SHOULD be associated with
811 the requested variant.
812
813 If the client is a user agent, it SHOULD NOT change its document
814 view from that which caused the request to be sent. This response
815 is primarily intended to allow input for actions to take place
816 without causing a change to the user agent's active document view,
817 although any new or updated metainformation SHOULD be applied
818 to the document currently in the user agent's active view.
819
820 The 204 response MUST NOT include a message-body, and thus
821 is always terminated by the first empty line after the header fields."
822
823 However, in real world it was observed that some web servers
824 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
825 */
826                 case 204:
827                         break;
828                 case 300:  /* redirection */
829                 case 301:
830                 case 302:
831                 case 303:
832                         break;
833                 case 206:
834                         if (G.beg_range)
835                                 break;
836                         /* fall through */
837                 default:
838                         bb_error_msg_and_die("server returned error: %s", sanitize_string(G.wget_buf));
839                 }
840
841                 /*
842                  * Retrieve HTTP headers.
843                  */
844                 while ((str = gethdr(sfp /*, &n*/)) != NULL) {
845                         /* gethdr converted "FOO:" string to lowercase */
846                         smalluint key;
847                         /* strip trailing whitespace */
848                         char *s = strchrnul(str, '\0') - 1;
849                         while (s >= str && (*s == ' ' || *s == '\t')) {
850                                 *s = '\0';
851                                 s--;
852                         }
853                         key = index_in_strings(keywords, G.wget_buf) + 1;
854                         if (key == KEY_content_length) {
855                                 G.content_len = BB_STRTOOFF(str, NULL, 10);
856                                 if (G.content_len < 0 || errno) {
857                                         bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
858                                 }
859                                 G.got_clen = 1;
860                                 continue;
861                         }
862                         if (key == KEY_transfer_encoding) {
863                                 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
864                                         bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
865                                 G.chunked = G.got_clen = 1;
866                         }
867                         if (key == KEY_location && status >= 300) {
868                                 if (--redir_limit == 0)
869                                         bb_error_msg_and_die("too many redirections");
870                                 fclose(sfp);
871                                 G.got_clen = 0;
872                                 G.chunked = 0;
873                                 if (str[0] == '/')
874                                         /* free(target.allocated); */
875                                         target.path = /* target.allocated = */ xstrdup(str+1);
876                                         /* lsa stays the same: it's on the same server */
877                                 else {
878                                         parse_url(str, &target);
879                                         if (!use_proxy) {
880                                                 server.host = target.host;
881                                                 /* strip_ipv6_scope_id(target.host); - no! */
882                                                 /* we assume remote never gives us IPv6 addr with scope id */
883                                                 server.port = target.port;
884                                                 free(lsa);
885                                                 goto resolve_lsa;
886                                         } /* else: lsa stays the same: we use proxy */
887                                 }
888                                 goto establish_session;
889                         }
890                 }
891 //              if (status >= 300)
892 //                      bb_error_msg_and_die("bad redirection (no Location: header from server)");
893
894                 /* For HTTP, data is pumped over the same connection */
895                 dfp = sfp;
896
897         } else {
898                 /*
899                  *  FTP session
900                  */
901                 sfp = prepare_ftp_session(&dfp, &target, lsa);
902         }
903
904         if (opt & WGET_OPT_SPIDER) {
905                 if (ENABLE_FEATURE_CLEAN_UP)
906                         fclose(sfp);
907                 return EXIT_SUCCESS;
908         }
909
910         if (output_fd < 0) {
911                 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
912                 /* compat with wget: -O FILE can overwrite */
913                 if (opt & WGET_OPT_OUTNAME)
914                         o_flags = O_WRONLY | O_CREAT | O_TRUNC;
915                 output_fd = xopen(fname_out, o_flags);
916         }
917
918         retrieve_file_data(dfp, output_fd);
919         xclose(output_fd);
920
921         if (dfp != sfp) {
922                 /* It's ftp. Close it properly */
923                 fclose(dfp);
924                 if (ftpcmd(NULL, NULL, sfp) != 226)
925                         bb_error_msg_and_die("ftp error: %s", sanitize_string(G.wget_buf + 4));
926                 /* ftpcmd("QUIT", NULL, sfp); - why bother? */
927         }
928
929         return EXIT_SUCCESS;
930 }