bc922836a6a69101bdad419da414897397f80887
[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                 else {
608                         if (G.dir_prefix)
609                                 G.fname_out = fname_out_alloc = concat_path_file(G.dir_prefix, G.fname_out);
610                         else {
611                                 /* redirects may free target.path later, need to make a copy */
612                                 G.fname_out = fname_out_alloc = xstrdup(G.fname_out);
613                         }
614                 }
615         }
616 #if ENABLE_FEATURE_WGET_STATUSBAR
617         G.curfile = bb_get_last_path_component_nostrip(G.fname_out);
618 #endif
619
620         /* Determine where to start transfer */
621         G.beg_range = 0;
622         if (option_mask32 & WGET_OPT_CONTINUE) {
623                 G.output_fd = open(G.fname_out, O_WRONLY);
624                 if (G.output_fd >= 0) {
625                         G.beg_range = xlseek(G.output_fd, 0, SEEK_END);
626                 }
627                 /* File doesn't exist. We do not create file here yet.
628                  * We are not sure it exists on remote side */
629         }
630
631         redir_limit = 5;
632  resolve_lsa:
633         lsa = xhost2sockaddr(server.host, server.port);
634         if (!(option_mask32 & WGET_OPT_QUIET)) {
635                 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
636                 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
637                 free(s);
638         }
639  establish_session:
640         /*G.content_len = 0; - redundant, got_clen = 0 is enough */
641         G.got_clen = 0;
642         G.chunked = 0;
643         if (use_proxy || !target.is_ftp) {
644                 /*
645                  *  HTTP session
646                  */
647                 char *str;
648                 int status;
649
650
651                 /* Open socket to http server */
652                 sfp = open_socket(lsa);
653
654                 /* Send HTTP request */
655                 if (use_proxy) {
656                         fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
657                                 target.is_ftp ? "f" : "ht", target.host,
658                                 target.path);
659                 } else {
660                         if (option_mask32 & WGET_OPT_POST_DATA)
661                                 fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
662                         else
663                                 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
664                 }
665
666                 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
667                         target.host, G.user_agent);
668
669                 /* Ask server to close the connection as soon as we are done
670                  * (IOW: we do not intend to send more requests)
671                  */
672                 fprintf(sfp, "Connection: close\r\n");
673
674 #if ENABLE_FEATURE_WGET_AUTHENTICATION
675                 if (target.user) {
676                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
677                                 base64enc(target.user));
678                 }
679                 if (use_proxy && server.user) {
680                         fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
681                                 base64enc(server.user));
682                 }
683 #endif
684
685                 if (G.beg_range)
686                         fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
687
688 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
689                 if (G.extra_headers)
690                         fputs(G.extra_headers, sfp);
691
692                 if (option_mask32 & WGET_OPT_POST_DATA) {
693                         char *estr = URL_escape(G.post_data);
694                         fprintf(sfp,
695                                 "Content-Type: application/x-www-form-urlencoded\r\n"
696                                 "Content-Length: %u\r\n"
697                                 "\r\n"
698                                 "%s",
699                                 (int) strlen(estr), estr
700                         );
701                         free(estr);
702                 } else
703 #endif
704                 {
705                         fprintf(sfp, "\r\n");
706                 }
707
708                 fflush(sfp);
709
710                 /*
711                  * Retrieve HTTP response line and check for "200" status code.
712                  */
713  read_response:
714                 fgets_and_trim(sfp);
715
716                 str = G.wget_buf;
717                 str = skip_non_whitespace(str);
718                 str = skip_whitespace(str);
719                 // FIXME: no error check
720                 // xatou wouldn't work: "200 OK"
721                 status = atoi(str);
722                 switch (status) {
723                 case 0:
724                 case 100:
725                         while (gethdr(sfp) != NULL)
726                                 /* eat all remaining headers */;
727                         goto read_response;
728                 case 200:
729 /*
730 Response 204 doesn't say "null file", it says "metadata
731 has changed but data didn't":
732
733 "10.2.5 204 No Content
734 The server has fulfilled the request but does not need to return
735 an entity-body, and might want to return updated metainformation.
736 The response MAY include new or updated metainformation in the form
737 of entity-headers, which if present SHOULD be associated with
738 the requested variant.
739
740 If the client is a user agent, it SHOULD NOT change its document
741 view from that which caused the request to be sent. This response
742 is primarily intended to allow input for actions to take place
743 without causing a change to the user agent's active document view,
744 although any new or updated metainformation SHOULD be applied
745 to the document currently in the user agent's active view.
746
747 The 204 response MUST NOT include a message-body, and thus
748 is always terminated by the first empty line after the header fields."
749
750 However, in real world it was observed that some web servers
751 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
752 */
753                 case 204:
754                         break;
755                 case 300:  /* redirection */
756                 case 301:
757                 case 302:
758                 case 303:
759                         break;
760                 case 206:
761                         if (G.beg_range)
762                                 break;
763                         /* fall through */
764                 default:
765                         bb_error_msg_and_die("server returned error: %s", sanitize_string(G.wget_buf));
766                 }
767
768                 /*
769                  * Retrieve HTTP headers.
770                  */
771                 while ((str = gethdr(sfp)) != NULL) {
772                         static const char keywords[] ALIGN1 =
773                                 "content-length\0""transfer-encoding\0""location\0";
774                         enum {
775                                 KEY_content_length = 1, KEY_transfer_encoding, KEY_location
776                         };
777                         smalluint key;
778
779                         /* gethdr converted "FOO:" string to lowercase */
780
781                         /* strip trailing whitespace */
782                         char *s = strchrnul(str, '\0') - 1;
783                         while (s >= str && (*s == ' ' || *s == '\t')) {
784                                 *s = '\0';
785                                 s--;
786                         }
787                         key = index_in_strings(keywords, G.wget_buf) + 1;
788                         if (key == KEY_content_length) {
789                                 G.content_len = BB_STRTOOFF(str, NULL, 10);
790                                 if (G.content_len < 0 || errno) {
791                                         bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
792                                 }
793                                 G.got_clen = 1;
794                                 continue;
795                         }
796                         if (key == KEY_transfer_encoding) {
797                                 if (strcmp(str_tolower(str), "chunked") != 0)
798                                         bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
799                                 G.chunked = 1;
800                         }
801                         if (key == KEY_location && status >= 300) {
802                                 if (--redir_limit == 0)
803                                         bb_error_msg_and_die("too many redirections");
804                                 fclose(sfp);
805                                 if (str[0] == '/') {
806                                         free(target.allocated);
807                                         target.path = target.allocated = xstrdup(str+1);
808                                         /* lsa stays the same: it's on the same server */
809                                 } else {
810                                         parse_url(str, &target);
811                                         if (!use_proxy) {
812                                                 free(server.allocated);
813                                                 server.allocated = NULL;
814                                                 server.host = target.host;
815                                                 /* strip_ipv6_scope_id(target.host); - no! */
816                                                 /* we assume remote never gives us IPv6 addr with scope id */
817                                                 server.port = target.port;
818                                                 free(lsa);
819                                                 goto resolve_lsa;
820                                         } /* else: lsa stays the same: we use proxy */
821                                 }
822                                 goto establish_session;
823                         }
824                 }
825 //              if (status >= 300)
826 //                      bb_error_msg_and_die("bad redirection (no Location: header from server)");
827
828                 /* For HTTP, data is pumped over the same connection */
829                 dfp = sfp;
830
831         } else {
832                 /*
833                  *  FTP session
834                  */
835                 sfp = prepare_ftp_session(&dfp, &target, lsa);
836         }
837
838         free(lsa);
839
840         if (!(option_mask32 & WGET_OPT_SPIDER)) {
841                 if (G.output_fd < 0)
842                         G.output_fd = xopen(G.fname_out, G.o_flags);
843                 retrieve_file_data(dfp);
844                 if (!(option_mask32 & WGET_OPT_OUTNAME)) {
845                         xclose(G.output_fd);
846                         G.output_fd = -1;
847                 }
848         }
849
850         if (dfp != sfp) {
851                 /* It's ftp. Close data connection properly */
852                 fclose(dfp);
853                 if (ftpcmd(NULL, NULL, sfp) != 226)
854                         bb_error_msg_and_die("ftp error: %s", sanitize_string(G.wget_buf + 4));
855                 /* ftpcmd("QUIT", NULL, sfp); - why bother? */
856         }
857         fclose(sfp);
858
859         free(server.allocated);
860         free(target.allocated);
861         free(fname_out_alloc);
862 }
863
864 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
865 int wget_main(int argc UNUSED_PARAM, char **argv)
866 {
867 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
868         static const char wget_longopts[] ALIGN1 =
869                 /* name, has_arg, val */
870                 "continue\0"         No_argument       "c"
871 //FIXME: -s isn't --spider, it's --save-headers!
872                 "spider\0"           No_argument       "s"
873                 "quiet\0"            No_argument       "q"
874                 "output-document\0"  Required_argument "O"
875                 "directory-prefix\0" Required_argument "P"
876                 "proxy\0"            Required_argument "Y"
877                 "user-agent\0"       Required_argument "U"
878 #if ENABLE_FEATURE_WGET_TIMEOUT
879                 "timeout\0"          Required_argument "T"
880 #endif
881                 /* Ignored: */
882                 // "tries\0"            Required_argument "t"
883                 /* Ignored (we always use PASV): */
884                 "passive-ftp\0"      No_argument       "\xff"
885                 "header\0"           Required_argument "\xfe"
886                 "post-data\0"        Required_argument "\xfd"
887                 /* Ignored (we don't do ssl) */
888                 "no-check-certificate\0" No_argument   "\xfc"
889                 ;
890 #endif
891
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         G.output_fd = -1;
933         G.o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
934         if (G.fname_out) { /* -O FILE ? */
935                 if (LONE_DASH(G.fname_out)) { /* -O - ? */
936                         G.output_fd = 1;
937                         option_mask32 &= ~WGET_OPT_CONTINUE;
938                 }
939                 /* compat with wget: -O FILE can overwrite */
940                 G.o_flags = O_WRONLY | O_CREAT | O_TRUNC;
941         }
942
943         while (*argv)
944                 download_one_url(*argv++);
945
946         if (G.output_fd >= 0)
947                 xclose(G.output_fd);
948
949         return EXIT_SUCCESS;
950 }