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