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