d518286a40da51dd73d8e27627ca5ccf85b88633
[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 /* Read NMEMB bytes into PTR from STREAM.  Returns the number of bytes read,
197  * and a short count if an eof or non-interrupt error is encountered.  */
198 static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
199 {
200         size_t ret;
201         char *p = (char*)ptr;
202
203         do {
204                 clearerr(stream);
205                 errno = 0;
206                 ret = fread(p, 1, nmemb, stream);
207                 p += ret;
208                 nmemb -= ret;
209         } while (nmemb && ferror(stream) && errno == EINTR);
210
211         return p - (char*)ptr;
212 }
213
214 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
215  * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
216 static char *safe_fgets(char *s, int size, FILE *stream)
217 {
218         char *ret;
219
220         do {
221                 clearerr(stream);
222                 errno = 0;
223                 ret = fgets(s, size, stream);
224         } while (ret == NULL && ferror(stream) && errno == EINTR);
225
226         return ret;
227 }
228
229 #if ENABLE_FEATURE_WGET_AUTHENTICATION
230 /* Base64-encode character string. buf is assumed to be char buf[512]. */
231 static char *base64enc_512(char buf[512], const char *str)
232 {
233         unsigned len = strlen(str);
234         if (len > 512/4*3 - 10) /* paranoia */
235                 len = 512/4*3 - 10;
236         bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
237         return buf;
238 }
239 #endif
240
241 static char* sanitize_string(char *s)
242 {
243         unsigned char *p = (void *) s;
244         while (*p >= ' ')
245                 p++;
246         *p = '\0';
247         return s;
248 }
249
250 static FILE *open_socket(len_and_sockaddr *lsa)
251 {
252         FILE *fp;
253
254         /* glibc 2.4 seems to try seeking on it - ??! */
255         /* hopefully it understands what ESPIPE means... */
256         fp = fdopen(xconnect_stream(lsa), "r+");
257         if (fp == NULL)
258                 bb_perror_msg_and_die("fdopen");
259
260         return fp;
261 }
262
263 static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
264 {
265         int result;
266         if (s1) {
267                 if (!s2) s2 = "";
268                 fprintf(fp, "%s%s\r\n", s1, s2);
269                 fflush(fp);
270         }
271
272         do {
273                 char *buf_ptr;
274
275                 if (fgets(buf, 510, fp) == NULL) {
276                         bb_perror_msg_and_die("error getting response");
277                 }
278                 buf_ptr = strstr(buf, "\r\n");
279                 if (buf_ptr) {
280                         *buf_ptr = '\0';
281                 }
282         } while (!isdigit(buf[0]) || buf[3] != ' ');
283
284         buf[3] = '\0';
285         result = xatoi_u(buf);
286         buf[3] = ' ';
287         return result;
288 }
289
290 static void parse_url(char *src_url, struct host_info *h)
291 {
292         char *url, *p, *sp;
293
294         /* h->allocated = */ url = xstrdup(src_url);
295
296         if (strncmp(url, "http://", 7) == 0) {
297                 h->port = bb_lookup_port("http", "tcp", 80);
298                 h->host = url + 7;
299                 h->is_ftp = 0;
300         } else if (strncmp(url, "ftp://", 6) == 0) {
301                 h->port = bb_lookup_port("ftp", "tcp", 21);
302                 h->host = url + 6;
303                 h->is_ftp = 1;
304         } else
305                 bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
306
307         // FYI:
308         // "Real" wget 'http://busybox.net?var=a/b' sends this request:
309         //   'GET /?var=a/b HTTP 1.0'
310         //   and saves 'index.html?var=a%2Fb' (we save 'b')
311         // wget 'http://busybox.net?login=john@doe':
312         //   request: 'GET /?login=john@doe HTTP/1.0'
313         //   saves: 'index.html?login=john@doe' (we save '?login=john@doe')
314         // wget 'http://busybox.net#test/test':
315         //   request: 'GET / HTTP/1.0'
316         //   saves: 'index.html' (we save 'test')
317         //
318         // We also don't add unique .N suffix if file exists...
319         sp = strchr(h->host, '/');
320         p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
321         p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
322         if (!sp) {
323                 h->path = "";
324         } else if (*sp == '/') {
325                 *sp = '\0';
326                 h->path = sp + 1;
327         } else { // '#' or '?'
328                 // http://busybox.net?login=john@doe is a valid URL
329                 // memmove converts to:
330                 // http:/busybox.nett?login=john@doe...
331                 memmove(h->host - 1, h->host, sp - h->host);
332                 h->host--;
333                 sp[-1] = '\0';
334                 h->path = sp;
335         }
336
337         sp = strrchr(h->host, '@');
338         h->user = NULL;
339         if (sp != NULL) {
340                 h->user = h->host;
341                 *sp = '\0';
342                 h->host = sp + 1;
343         }
344
345         sp = h->host;
346 }
347
348 static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
349 {
350         char *s, *hdrval;
351         int c;
352
353         /* *istrunc = 0; */
354
355         /* retrieve header line */
356         if (fgets(buf, bufsiz, fp) == NULL)
357                 return NULL;
358
359         /* see if we are at the end of the headers */
360         for (s = buf; *s == '\r'; ++s)
361                 continue;
362         if (*s == '\n')
363                 return NULL;
364
365         /* convert the header name to lower case */
366         for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
367                 *s = tolower(*s);
368
369         /* verify we are at the end of the header name */
370         if (*s != ':')
371                 bb_error_msg_and_die("bad header line: %s", sanitize_string(buf));
372
373         /* locate the start of the header value */
374         *s++ = '\0';
375         hdrval = skip_whitespace(s);
376
377         /* locate the end of header */
378         while (*s && *s != '\r' && *s != '\n')
379                 ++s;
380
381         /* end of header found */
382         if (*s) {
383                 *s = '\0';
384                 return hdrval;
385         }
386
387         /* Rats! The buffer isn't big enough to hold the entire header value */
388         while (c = getc(fp), c != EOF && c != '\n')
389                 continue;
390         /* *istrunc = 1; */
391         return hdrval;
392 }
393
394 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
395 static char *URL_escape(const char *str)
396 {
397         /* URL encode, see RFC 2396 */
398         char *dst;
399         char *res = dst = xmalloc(strlen(str) * 3 + 1);
400         unsigned char c;
401
402         while (1) {
403                 c = *str++;
404                 if (c == '\0'
405                 /* || strchr("!&'()*-.=_~", c) - more code */
406                  || c == '!'
407                  || c == '&'
408                  || c == '\''
409                  || c == '('
410                  || c == ')'
411                  || c == '*'
412                  || c == '-'
413                  || c == '.'
414                  || c == '='
415                  || c == '_'
416                  || c == '~'
417                  || (c >= '0' && c <= '9')
418                  || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
419                 ) {
420                         *dst++ = c;
421                         if (c == '\0')
422                                 return res;
423                 } else {
424                         *dst++ = '%';
425                         *dst++ = bb_hexdigits_upcase[c >> 4];
426                         *dst++ = bb_hexdigits_upcase[c & 0xf];
427                 }
428         }
429 }
430 #endif
431
432 static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
433 {
434         char buf[512];
435         FILE *sfp;
436         char *str;
437         int port;
438
439         if (!target->user)
440                 target->user = xstrdup("anonymous:busybox@");
441
442         sfp = open_socket(lsa);
443         if (ftpcmd(NULL, NULL, sfp, buf) != 220)
444                 bb_error_msg_and_die("%s", sanitize_string(buf+4));
445
446         /*
447          * Splitting username:password pair,
448          * trying to log in
449          */
450         str = strchr(target->user, ':');
451         if (str)
452                 *str++ = '\0';
453         switch (ftpcmd("USER ", target->user, sfp, buf)) {
454         case 230:
455                 break;
456         case 331:
457                 if (ftpcmd("PASS ", str, sfp, buf) == 230)
458                         break;
459                 /* fall through (failed login) */
460         default:
461                 bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
462         }
463
464         ftpcmd("TYPE I", NULL, sfp, buf);
465
466         /*
467          * Querying file size
468          */
469         if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
470                 content_len = BB_STRTOOFF(buf+4, NULL, 10);
471                 if (errno || content_len < 0) {
472                         bb_error_msg_and_die("SIZE value is garbage");
473                 }
474                 G.got_clen = 1;
475         }
476
477         /*
478          * Entering passive mode
479          */
480         if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
481  pasv_error:
482                 bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
483         }
484         // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
485         // Server's IP is N1.N2.N3.N4 (we ignore it)
486         // Server's port for data connection is P1*256+P2
487         str = strrchr(buf, ')');
488         if (str) str[0] = '\0';
489         str = strrchr(buf, ',');
490         if (!str) goto pasv_error;
491         port = xatou_range(str+1, 0, 255);
492         *str = '\0';
493         str = strrchr(buf, ',');
494         if (!str) goto pasv_error;
495         port += xatou_range(str+1, 0, 255) * 256;
496         set_nport(lsa, htons(port));
497
498         *dfpp = open_socket(lsa);
499
500         if (beg_range) {
501                 sprintf(buf, "REST %"OFF_FMT"d", beg_range);
502                 if (ftpcmd(buf, NULL, sfp, buf) == 350)
503                         content_len -= beg_range;
504         }
505
506         if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
507                 bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
508
509         return sfp;
510 }
511
512 /* Must match option string! */
513 enum {
514         WGET_OPT_CONTINUE   = (1 << 0),
515         WGET_OPT_SPIDER     = (1 << 1),
516         WGET_OPT_QUIET      = (1 << 2),
517         WGET_OPT_OUTNAME    = (1 << 3),
518         WGET_OPT_PREFIX     = (1 << 4),
519         WGET_OPT_PROXY      = (1 << 5),
520         WGET_OPT_USER_AGENT = (1 << 6),
521         WGET_OPT_RETRIES    = (1 << 7),
522         WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
523         WGET_OPT_PASSIVE    = (1 << 9),
524         WGET_OPT_HEADER     = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
525         WGET_OPT_POST_DATA  = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
526 };
527
528 static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
529 {
530         char buf[512];
531
532         if (!(option_mask32 & WGET_OPT_QUIET))
533                 progress_meter(-1);
534
535         if (G.chunked)
536                 goto get_clen;
537
538         /* Loops only if chunked */
539         while (1) {
540                 while (content_len > 0 || !G.got_clen) {
541                         int n;
542                         unsigned rdsz = sizeof(buf);
543
544                         if (content_len < sizeof(buf) && (G.chunked || G.got_clen))
545                                 rdsz = (unsigned)content_len;
546                         n = safe_fread(buf, rdsz, dfp);
547                         if (n <= 0) {
548                                 if (ferror(dfp)) {
549                                         /* perror will not work: ferror doesn't set errno */
550                                         bb_error_msg_and_die(bb_msg_read_error);
551                                 }
552                                 break;
553                         }
554                         xwrite(output_fd, buf, n);
555 #if ENABLE_FEATURE_WGET_STATUSBAR
556                         transferred += n;
557 #endif
558                         if (G.got_clen)
559                                 content_len -= n;
560                 }
561
562                 if (!G.chunked)
563                         break;
564
565                 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
566  get_clen:
567                 safe_fgets(buf, sizeof(buf), dfp);
568                 content_len = STRTOOFF(buf, NULL, 16);
569                 /* FIXME: error check? */
570                 if (content_len == 0)
571                         break; /* all done! */
572         }
573
574         if (!(option_mask32 & WGET_OPT_QUIET))
575                 progress_meter(0);
576 }
577
578 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
579 int wget_main(int argc UNUSED_PARAM, char **argv)
580 {
581         char buf[512];
582         struct host_info server, target;
583         len_and_sockaddr *lsa;
584         unsigned opt;
585         int redir_limit;
586         char *proxy = NULL;
587         char *dir_prefix = NULL;
588 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
589         char *post_data;
590         char *extra_headers = NULL;
591         llist_t *headers_llist = NULL;
592 #endif
593         FILE *sfp;                      /* socket to web/ftp server         */
594         FILE *dfp;                      /* socket to ftp server (data)      */
595         char *fname_out;                /* where to direct output (-O)      */
596         int output_fd = -1;
597         bool use_proxy;                 /* Use proxies if env vars are set  */
598         const char *proxy_flag = "on";  /* Use proxies if env vars are set  */
599         const char *user_agent = "Wget";/* "User-Agent" header field        */
600
601         static const char keywords[] ALIGN1 =
602                 "content-length\0""transfer-encoding\0""chunked\0""location\0";
603         enum {
604                 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
605         };
606 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
607         static const char wget_longopts[] ALIGN1 =
608                 /* name, has_arg, val */
609                 "continue\0"         No_argument       "c"
610                 "spider\0"           No_argument       "s"
611                 "quiet\0"            No_argument       "q"
612                 "output-document\0"  Required_argument "O"
613                 "directory-prefix\0" Required_argument "P"
614                 "proxy\0"            Required_argument "Y"
615                 "user-agent\0"       Required_argument "U"
616                 /* Ignored: */
617                 // "tries\0"            Required_argument "t"
618                 // "timeout\0"          Required_argument "T"
619                 /* Ignored (we always use PASV): */
620                 "passive-ftp\0"      No_argument       "\xff"
621                 "header\0"           Required_argument "\xfe"
622                 "post-data\0"        Required_argument "\xfd"
623                 ;
624 #endif
625
626         INIT_G();
627
628 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
629         applet_long_options = wget_longopts;
630 #endif
631         /* server.allocated = target.allocated = NULL; */
632         opt_complementary = "-1" IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
633         opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
634                                 &fname_out, &dir_prefix,
635                                 &proxy_flag, &user_agent,
636                                 NULL, /* -t RETRIES */
637                                 NULL /* -T NETWORK_READ_TIMEOUT */
638                                 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
639                                 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
640                                 );
641 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
642         if (headers_llist) {
643                 int size = 1;
644                 char *cp;
645                 llist_t *ll = headers_llist;
646                 while (ll) {
647                         size += strlen(ll->data) + 2;
648                         ll = ll->link;
649                 }
650                 extra_headers = cp = xmalloc(size);
651                 while (headers_llist) {
652                         cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
653                 }
654         }
655 #endif
656
657         /* TODO: compat issue: should handle "wget URL1 URL2..." */
658         parse_url(argv[optind], &target);
659         server.host = target.host;
660         server.port = target.port;
661
662         /* Use the proxy if necessary */
663         use_proxy = (strcmp(proxy_flag, "off") != 0);
664         if (use_proxy) {
665                 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
666                 if (proxy && *proxy) {
667                         parse_url(proxy, &server);
668                 } else {
669                         use_proxy = 0;
670                 }
671         }
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         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("cannot 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                         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 #if ENABLE_FEATURE_WGET_AUTHENTICATION
743                 if (target.user) {
744                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
745                                 base64enc_512(buf, target.user));
746                 }
747                 if (use_proxy && server.user) {
748                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
749                                 base64enc_512(buf, server.user));
750                 }
751 #endif
752
753                 if (beg_range)
754                         fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
755 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
756                 if (extra_headers)
757                         fputs(extra_headers, sfp);
758
759                 if (opt & WGET_OPT_POST_DATA) {
760                         char *estr = URL_escape(post_data);
761                         fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
762                         fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
763                                         (int) strlen(estr), estr);
764                         /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
765                         /*fprintf(sfp, "%s\r\n", estr);*/
766                         free(estr);
767                 } else
768 #endif
769                 { /* If "Connection:" is needed, document why */
770                         fprintf(sfp, /* "Connection: close\r\n" */ "\r\n");
771                 }
772
773                 /*
774                  * Retrieve HTTP response line and check for "200" status code.
775                  */
776  read_response:
777                 if (fgets(buf, sizeof(buf), sfp) == NULL)
778                         bb_error_msg_and_die("no response from server");
779
780                 str = buf;
781                 str = skip_non_whitespace(str);
782                 str = skip_whitespace(str);
783                 // FIXME: no error check
784                 // xatou wouldn't work: "200 OK"
785                 status = atoi(str);
786                 switch (status) {
787                 case 0:
788                 case 100:
789                         while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
790                                 /* eat all remaining headers */;
791                         goto read_response;
792                 case 200:
793 /*
794 Response 204 doesn't say "null file", it says "metadata
795 has changed but data didn't":
796
797 "10.2.5 204 No Content
798 The server has fulfilled the request but does not need to return
799 an entity-body, and might want to return updated metainformation.
800 The response MAY include new or updated metainformation in the form
801 of entity-headers, which if present SHOULD be associated with
802 the requested variant.
803
804 If the client is a user agent, it SHOULD NOT change its document
805 view from that which caused the request to be sent. This response
806 is primarily intended to allow input for actions to take place
807 without causing a change to the user agent's active document view,
808 although any new or updated metainformation SHOULD be applied
809 to the document currently in the user agent's active view.
810
811 The 204 response MUST NOT include a message-body, and thus
812 is always terminated by the first empty line after the header fields."
813
814 However, in real world it was observed that some web servers
815 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
816 */
817                 case 204:
818                         break;
819                 case 300:       /* redirection */
820                 case 301:
821                 case 302:
822                 case 303:
823                         break;
824                 case 206:
825                         if (beg_range)
826                                 break;
827                         /* fall through */
828                 default:
829                         bb_error_msg_and_die("server returned error: %s", sanitize_string(buf));
830                 }
831
832                 /*
833                  * Retrieve HTTP headers.
834                  */
835                 while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
836                         /* gethdr converted "FOO:" string to lowercase */
837                         smalluint key = index_in_strings(keywords, buf) + 1;
838                         if (key == KEY_content_length) {
839                                 content_len = BB_STRTOOFF(str, NULL, 10);
840                                 if (errno || content_len < 0) {
841                                         bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
842                                 }
843                                 G.got_clen = 1;
844                                 continue;
845                         }
846                         if (key == KEY_transfer_encoding) {
847                                 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
848                                         bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
849                                 G.chunked = G.got_clen = 1;
850                         }
851                         if (key == KEY_location && status >= 300) {
852                                 if (--redir_limit == 0)
853                                         bb_error_msg_and_die("too many redirections");
854                                 fclose(sfp);
855                                 G.got_clen = 0;
856                                 G.chunked = 0;
857                                 if (str[0] == '/')
858                                         /* free(target.allocated); */
859                                         target.path = /* target.allocated = */ xstrdup(str+1);
860                                         /* lsa stays the same: it's on the same server */
861                                 else {
862                                         parse_url(str, &target);
863                                         if (!use_proxy) {
864                                                 server.host = target.host;
865                                                 server.port = target.port;
866                                                 free(lsa);
867                                                 goto resolve_lsa;
868                                         } /* else: lsa stays the same: we use proxy */
869                                 }
870                                 goto establish_session;
871                         }
872                 }
873 //              if (status >= 300)
874 //                      bb_error_msg_and_die("bad redirection (no Location: header from server)");
875
876                 /* For HTTP, data is pumped over the same connection */
877                 dfp = sfp;
878
879         } else {
880                 /*
881                  *  FTP session
882                  */
883                 sfp = prepare_ftp_session(&dfp, &target, lsa);
884         }
885
886         if (opt & WGET_OPT_SPIDER) {
887                 if (ENABLE_FEATURE_CLEAN_UP)
888                         fclose(sfp);
889                 return EXIT_SUCCESS;
890         }
891
892         if (output_fd < 0) {
893                 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
894                 /* compat with wget: -O FILE can overwrite */
895                 if (opt & WGET_OPT_OUTNAME)
896                         o_flags = O_WRONLY | O_CREAT | O_TRUNC;
897                 output_fd = xopen(fname_out, o_flags);
898         }
899
900         retrieve_file_data(dfp, output_fd);
901
902         if (dfp != sfp) {
903                 /* It's ftp. Close it properly */
904                 fclose(dfp);
905                 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
906                         bb_error_msg_and_die("ftp error: %s", sanitize_string(buf+4));
907                 ftpcmd("QUIT", NULL, sfp, buf);
908         }
909
910         return EXIT_SUCCESS;
911 }