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