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