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