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