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