fix #>&- syntax for closing fds
[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  *
7  * Licensed under GPLv2, see file LICENSE in this tarball for details.
8  */
9
10 #include "libbb.h"
11
12 struct host_info {
13         // May be used if we ever will want to free() all xstrdup()s...
14         /* char *allocated; */
15         const char *path;
16         const char *user;
17         char       *host;
18         int         port;
19         smallint    is_ftp;
20 };
21
22
23 /* Globals (can be accessed from signal handlers) */
24 struct globals {
25         off_t content_len;        /* Content-length of the file */
26         off_t beg_range;          /* Range at which continue begins */
27 #if ENABLE_FEATURE_WGET_STATUSBAR
28         off_t lastsize;
29         off_t totalsize;
30         off_t transferred;        /* Number of bytes transferred so far */
31         const char *curfile;      /* Name of current file being transferred */
32         unsigned lastupdate_sec;
33         unsigned start_sec;
34 #endif
35         smallint chunked;             /* chunked transfer encoding */
36 };
37 #define G (*(struct globals*)&bb_common_bufsiz1)
38 struct BUG_G_too_big {
39         char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
40 };
41 #define content_len     (G.content_len    )
42 #define beg_range       (G.beg_range      )
43 #define lastsize        (G.lastsize       )
44 #define totalsize       (G.totalsize      )
45 #define transferred     (G.transferred    )
46 #define curfile         (G.curfile        )
47 #define lastupdate_sec  (G.lastupdate_sec )
48 #define start_sec       (G.start_sec      )
49 #define chunked         (G.chunked        )
50 #define INIT_G() do { } while (0)
51
52
53 #if ENABLE_FEATURE_WGET_STATUSBAR
54 enum {
55         STALLTIME = 5                   /* Seconds when xfer considered "stalled" */
56 };
57
58 static unsigned int get_tty2_width(void)
59 {
60         unsigned width;
61         get_terminal_width_height(2, &width, NULL);
62         return width;
63 }
64
65 static void progress_meter(int flag)
66 {
67         /* We can be called from signal handler */
68         int save_errno = errno;
69         off_t abbrevsize;
70         unsigned since_last_update, elapsed;
71         unsigned ratio;
72         int barlength, i;
73
74         if (flag == -1) { /* first call to progress_meter */
75                 start_sec = monotonic_sec();
76                 lastupdate_sec = start_sec;
77                 lastsize = 0;
78                 totalsize = content_len + beg_range; /* as content_len changes.. */
79         }
80
81         ratio = 100;
82         if (totalsize != 0 && !chunked) {
83                 /* long long helps to have it working even if !LFS */
84                 ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize);
85                 if (ratio > 100) ratio = 100;
86         }
87
88         fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
89
90         barlength = get_tty2_width() - 49;
91         if (barlength > 0) {
92                 /* god bless gcc for variable arrays :) */
93                 i = barlength * ratio / 100;
94                 {
95                         char buf[i+1];
96                         memset(buf, '*', i);
97                         buf[i] = '\0';
98                         fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
99                 }
100         }
101         i = 0;
102         abbrevsize = transferred + beg_range;
103         while (abbrevsize >= 100000) {
104                 i++;
105                 abbrevsize >>= 10;
106         }
107         /* see http://en.wikipedia.org/wiki/Tera */
108         fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
109
110 // Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
111
112         elapsed = monotonic_sec();
113         since_last_update = elapsed - lastupdate_sec;
114         if (transferred > lastsize) {
115                 lastupdate_sec = elapsed;
116                 lastsize = transferred;
117                 if (since_last_update >= STALLTIME) {
118                         /* We "cut off" these seconds from elapsed time
119                          * by adjusting start time */
120                         start_sec += since_last_update;
121                 }
122                 since_last_update = 0; /* we are un-stalled now */
123         }
124         elapsed -= start_sec; /* now it's "elapsed since start" */
125
126         if (since_last_update >= STALLTIME) {
127                 fprintf(stderr, " - stalled -");
128         } else {
129                 off_t to_download = totalsize - beg_range;
130                 if (transferred <= 0 || (int)elapsed <= 0 || transferred > to_download || chunked) {
131                         fprintf(stderr, "--:--:-- ETA");
132                 } else {
133                         /* to_download / (transferred/elapsed) - elapsed: */
134                         int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed);
135                         /* (long long helps to have working ETA even if !LFS) */
136                         i = eta % 3600;
137                         fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60);
138                 }
139         }
140
141         if (flag == 0) {
142                 /* last call to progress_meter */
143                 alarm(0);
144                 transferred = 0;
145                 fputc('\n', stderr);
146         } else {
147                 if (flag == -1) { /* first call to progress_meter */
148                         signal_SA_RESTART_empty_mask(SIGALRM, progress_meter);
149                 }
150                 alarm(1);
151         }
152
153         errno = save_errno;
154 }
155 /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
156  * much of which was blatantly stolen from openssh.  */
157 /*-
158  * Copyright (c) 1992, 1993
159  *      The Regents of the University of California.  All rights reserved.
160  *
161  * Redistribution and use in source and binary forms, with or without
162  * modification, are permitted provided that the following conditions
163  * are met:
164  * 1. Redistributions of source code must retain the above copyright
165  *    notice, this list of conditions and the following disclaimer.
166  * 2. Redistributions in binary form must reproduce the above copyright
167  *    notice, this list of conditions and the following disclaimer in the
168  *    documentation and/or other materials provided with the distribution.
169  *
170  * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
171  *              ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
172  *
173  * 4. Neither the name of the University nor the names of its contributors
174  *    may be used to endorse or promote products derived from this software
175  *    without specific prior written permission.
176  *
177  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
178  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
179  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
180  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
181  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
182  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
183  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
184  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
185  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
186  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
187  * SUCH DAMAGE.
188  *
189  */
190 #else /* FEATURE_WGET_STATUSBAR */
191
192 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
193
194 #endif
195
196
197 /* Read NMEMB bytes into PTR from STREAM.  Returns the number of bytes read,
198  * and a short count if an eof or non-interrupt error is encountered.  */
199 static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
200 {
201         size_t ret;
202         char *p = (char*)ptr;
203
204         do {
205                 clearerr(stream);
206                 errno = 0;
207                 ret = fread(p, 1, nmemb, stream);
208                 p += ret;
209                 nmemb -= ret;
210         } while (nmemb && ferror(stream) && errno == EINTR);
211
212         return p - (char*)ptr;
213 }
214
215 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
216  * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
217 static char *safe_fgets(char *s, int size, FILE *stream)
218 {
219         char *ret;
220
221         do {
222                 clearerr(stream);
223                 errno = 0;
224                 ret = fgets(s, size, stream);
225         } while (ret == NULL && ferror(stream) && errno == EINTR);
226
227         return ret;
228 }
229
230 #if ENABLE_FEATURE_WGET_AUTHENTICATION
231 /* Base64-encode character string. buf is assumed to be char buf[512]. */
232 static char *base64enc_512(char buf[512], const char *str)
233 {
234         unsigned len = strlen(str);
235         if (len > 512/4*3 - 10) /* paranoia */
236                 len = 512/4*3 - 10;
237         bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
238         return buf;
239 }
240 #endif
241
242
243 static FILE *open_socket(len_and_sockaddr *lsa)
244 {
245         FILE *fp;
246
247         /* glibc 2.4 seems to try seeking on it - ??! */
248         /* hopefully it understands what ESPIPE means... */
249         fp = fdopen(xconnect_stream(lsa), "r+");
250         if (fp == NULL)
251                 bb_perror_msg_and_die("fdopen");
252
253         return fp;
254 }
255
256
257 static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
258 {
259         int result;
260         if (s1) {
261                 if (!s2) s2 = "";
262                 fprintf(fp, "%s%s\r\n", s1, s2);
263                 fflush(fp);
264         }
265
266         do {
267                 char *buf_ptr;
268
269                 if (fgets(buf, 510, fp) == NULL) {
270                         bb_perror_msg_and_die("error getting response");
271                 }
272                 buf_ptr = strstr(buf, "\r\n");
273                 if (buf_ptr) {
274                         *buf_ptr = '\0';
275                 }
276         } while (!isdigit(buf[0]) || buf[3] != ' ');
277
278         buf[3] = '\0';
279         result = xatoi_u(buf);
280         buf[3] = ' ';
281         return result;
282 }
283
284
285 static void parse_url(char *src_url, struct host_info *h)
286 {
287         char *url, *p, *sp;
288
289         /* h->allocated = */ url = xstrdup(src_url);
290
291         if (strncmp(url, "http://", 7) == 0) {
292                 h->port = bb_lookup_port("http", "tcp", 80);
293                 h->host = url + 7;
294                 h->is_ftp = 0;
295         } else if (strncmp(url, "ftp://", 6) == 0) {
296                 h->port = bb_lookup_port("ftp", "tcp", 21);
297                 h->host = url + 6;
298                 h->is_ftp = 1;
299         } else
300                 bb_error_msg_and_die("not an http or ftp url: %s", url);
301
302         // FYI:
303         // "Real" wget 'http://busybox.net?var=a/b' sends this request:
304         //   'GET /?var=a/b HTTP 1.0'
305         //   and saves 'index.html?var=a%2Fb' (we save 'b')
306         // wget 'http://busybox.net?login=john@doe':
307         //   request: 'GET /?login=john@doe HTTP/1.0'
308         //   saves: 'index.html?login=john@doe' (we save '?login=john@doe')
309         // wget 'http://busybox.net#test/test':
310         //   request: 'GET / HTTP/1.0'
311         //   saves: 'index.html' (we save 'test')
312         //
313         // We also don't add unique .N suffix if file exists...
314         sp = strchr(h->host, '/');
315         p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
316         p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
317         if (!sp) {
318                 h->path = "";
319         } else if (*sp == '/') {
320                 *sp = '\0';
321                 h->path = sp + 1;
322         } else { // '#' or '?'
323                 // http://busybox.net?login=john@doe is a valid URL
324                 // memmove converts to:
325                 // http:/busybox.nett?login=john@doe...
326                 memmove(h->host - 1, h->host, sp - h->host);
327                 h->host--;
328                 sp[-1] = '\0';
329                 h->path = sp;
330         }
331
332         sp = strrchr(h->host, '@');
333         h->user = NULL;
334         if (sp != NULL) {
335                 h->user = h->host;
336                 *sp = '\0';
337                 h->host = sp + 1;
338         }
339
340         sp = h->host;
341 }
342
343
344 static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
345 {
346         char *s, *hdrval;
347         int c;
348
349         /* *istrunc = 0; */
350
351         /* retrieve header line */
352         if (fgets(buf, bufsiz, fp) == NULL)
353                 return NULL;
354
355         /* see if we are at the end of the headers */
356         for (s = buf; *s == '\r'; ++s)
357                 continue;
358         if (*s == '\n')
359                 return NULL;
360
361         /* convert the header name to lower case */
362         for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
363                 *s = tolower(*s);
364
365         /* verify we are at the end of the header name */
366         if (*s != ':')
367                 bb_error_msg_and_die("bad header line: %s", buf);
368
369         /* locate the start of the header value */
370         *s++ = '\0';
371         hdrval = skip_whitespace(s);
372
373         /* locate the end of header */
374         while (*s && *s != '\r' && *s != '\n')
375                 ++s;
376
377         /* end of header found */
378         if (*s) {
379                 *s = '\0';
380                 return hdrval;
381         }
382
383         /* Rats! The buffer isn't big enough to hold the entire header value. */
384         while (c = getc(fp), c != EOF && c != '\n')
385                 continue;
386         /* *istrunc = 1; */
387         return hdrval;
388 }
389
390 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
391 static char *URL_escape(const char *str)
392 {
393         /* URL encode, see RFC 2396 */
394         char *dst;
395         char *res = dst = xmalloc(strlen(str) * 3 + 1);
396         unsigned char c;
397
398         while (1) {
399                 c = *str++;
400                 if (c == '\0'
401                 /* || strchr("!&'()*-.=_~", c) - more code */
402                  || c == '!'
403                  || c == '&'
404                  || c == '\''
405                  || c == '('
406                  || c == ')'
407                  || c == '*'
408                  || c == '-'
409                  || c == '.'
410                  || c == '='
411                  || c == '_'
412                  || c == '~'
413                  || (c >= '0' && c <= '9')
414                  || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
415                 ) {
416                         *dst++ = c;
417                         if (c == '\0')
418                                 return res;
419                 } else {
420                         *dst++ = '%';
421                         *dst++ = bb_hexdigits_upcase[c >> 4];
422                         *dst++ = bb_hexdigits_upcase[c & 0xf];
423                 }
424         }
425 }
426 #endif
427
428 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
429 int wget_main(int argc UNUSED_PARAM, char **argv)
430 {
431         char buf[512];
432         struct host_info server, target;
433         len_and_sockaddr *lsa;
434         int status;
435         int port;
436         int try = 5;
437         unsigned opt;
438         char *str;
439         char *proxy = 0;
440         char *dir_prefix = NULL;
441 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
442         char *post_data;
443         char *extra_headers = NULL;
444         llist_t *headers_llist = NULL;
445 #endif
446         FILE *sfp = NULL;               /* socket to web/ftp server         */
447         FILE *dfp;                      /* socket to ftp server (data)      */
448         char *fname_out;                /* where to direct output (-O)      */
449         bool got_clen = 0;              /* got content-length: from server  */
450         int output_fd = -1;
451         bool use_proxy = 1;             /* Use proxies if env vars are set  */
452         const char *proxy_flag = "on";  /* Use proxies if env vars are set  */
453         const char *user_agent = "Wget";/* "User-Agent" header field        */
454
455         static const char keywords[] ALIGN1 =
456                 "content-length\0""transfer-encoding\0""chunked\0""location\0";
457         enum {
458                 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
459         };
460         enum {
461                 WGET_OPT_CONTINUE   = (1 << 0),
462                 WGET_OPT_SPIDER     = (1 << 1),
463                 WGET_OPT_QUIET      = (1 << 2),
464                 WGET_OPT_OUTNAME    = (1 << 3),
465                 WGET_OPT_PREFIX     = (1 << 4),
466                 WGET_OPT_PROXY      = (1 << 5),
467                 WGET_OPT_USER_AGENT = (1 << 6),
468                 WGET_OPT_RETRIES    = (1 << 7),
469                 WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
470                 WGET_OPT_PASSIVE    = (1 << 9),
471                 WGET_OPT_HEADER     = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
472                 WGET_OPT_POST_DATA  = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
473         };
474 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
475         static const char wget_longopts[] ALIGN1 =
476                 /* name, has_arg, val */
477                 "continue\0"         No_argument       "c"
478                 "spider\0"           No_argument       "s"
479                 "quiet\0"            No_argument       "q"
480                 "output-document\0"  Required_argument "O"
481                 "directory-prefix\0" Required_argument "P"
482                 "proxy\0"            Required_argument "Y"
483                 "user-agent\0"       Required_argument "U"
484                 /* Ignored: */
485                 // "tries\0"            Required_argument "t"
486                 // "timeout\0"          Required_argument "T"
487                 /* Ignored (we always use PASV): */
488                 "passive-ftp\0"      No_argument       "\xff"
489                 "header\0"           Required_argument "\xfe"
490                 "post-data\0"        Required_argument "\xfd"
491                 ;
492 #endif
493
494         INIT_G();
495
496 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
497         applet_long_options = wget_longopts;
498 #endif
499         /* server.allocated = target.allocated = NULL; */
500         opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
501         opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
502                                 &fname_out, &dir_prefix,
503                                 &proxy_flag, &user_agent,
504                                 NULL, /* -t RETRIES */
505                                 NULL /* -T NETWORK_READ_TIMEOUT */
506                                 USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
507                                 USE_FEATURE_WGET_LONG_OPTIONS(, &post_data)
508                                 );
509         if (strcmp(proxy_flag, "off") == 0) {
510                 /* Use the proxy if necessary */
511                 use_proxy = 0;
512         }
513 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
514         if (headers_llist) {
515                 int size = 1;
516                 char *cp;
517                 llist_t *ll = headers_llist;
518                 while (ll) {
519                         size += strlen(ll->data) + 2;
520                         ll = ll->link;
521                 }
522                 extra_headers = cp = xmalloc(size);
523                 while (headers_llist) {
524                         cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
525                 }
526         }
527 #endif
528
529         parse_url(argv[optind], &target);
530         server.host = target.host;
531         server.port = target.port;
532
533         /* Use the proxy if necessary */
534         if (use_proxy) {
535                 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
536                 if (proxy && *proxy) {
537                         parse_url(proxy, &server);
538                 } else {
539                         use_proxy = 0;
540                 }
541         }
542
543         /* Guess an output filename, if there was no -O FILE */
544         if (!(opt & WGET_OPT_OUTNAME)) {
545                 fname_out = bb_get_last_path_component_nostrip(target.path);
546                 /* handle "wget http://kernel.org//" */
547                 if (fname_out[0] == '/' || !fname_out[0])
548                         fname_out = (char*)"index.html";
549                 /* -P DIR is considered only if there was no -O FILE */
550                 if (dir_prefix)
551                         fname_out = concat_path_file(dir_prefix, fname_out);
552         } else {
553                 if (LONE_DASH(fname_out)) {
554                         /* -O - */
555                         output_fd = 1;
556                         opt &= ~WGET_OPT_CONTINUE;
557                 }
558         }
559 #if ENABLE_FEATURE_WGET_STATUSBAR
560         curfile = bb_get_last_path_component_nostrip(fname_out);
561 #endif
562
563         /* Impossible?
564         if ((opt & WGET_OPT_CONTINUE) && !fname_out)
565                 bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */
566
567         /* Determine where to start transfer */
568         if (opt & WGET_OPT_CONTINUE) {
569                 output_fd = open(fname_out, O_WRONLY);
570                 if (output_fd >= 0) {
571                         beg_range = xlseek(output_fd, 0, SEEK_END);
572                 }
573                 /* File doesn't exist. We do not create file here yet.
574                    We are not sure it exists on remove side */
575         }
576
577         /* We want to do exactly _one_ DNS lookup, since some
578          * sites (i.e. ftp.us.debian.org) use round-robin DNS
579          * and we want to connect to only one IP... */
580         lsa = xhost2sockaddr(server.host, server.port);
581         if (!(opt & WGET_OPT_QUIET)) {
582                 fprintf(stderr, "Connecting to %s (%s)\n", server.host,
583                                 xmalloc_sockaddr2dotted(&lsa->u.sa));
584                 /* We leak result of xmalloc_sockaddr2dotted */
585         }
586
587         if (use_proxy || !target.is_ftp) {
588                 /*
589                  *  HTTP session
590                  */
591                 do {
592                         got_clen = 0;
593                         chunked = 0;
594
595                         if (!--try)
596                                 bb_error_msg_and_die("too many redirections");
597
598                         /* Open socket to http server */
599                         if (sfp) fclose(sfp);
600                         sfp = open_socket(lsa);
601
602                         /* Send HTTP request.  */
603                         if (use_proxy) {
604                                 fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
605                                         target.is_ftp ? "f" : "ht", target.host,
606                                         target.path);
607                         } else {
608                                 if (opt & WGET_OPT_POST_DATA)
609                                         fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
610                                 else
611                                         fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
612                         }
613
614                         fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
615                                 target.host, user_agent);
616
617 #if ENABLE_FEATURE_WGET_AUTHENTICATION
618                         if (target.user) {
619                                 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
620                                         base64enc_512(buf, target.user));
621                         }
622                         if (use_proxy && server.user) {
623                                 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
624                                         base64enc_512(buf, server.user));
625                         }
626 #endif
627
628                         if (beg_range)
629                                 fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
630 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
631                         if (extra_headers)
632                                 fputs(extra_headers, sfp);
633
634                         if (opt & WGET_OPT_POST_DATA) {
635                                 char *estr = URL_escape(post_data);
636                                 fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
637                                 fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
638                                                 (int) strlen(estr), estr);
639                                 /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
640                                 /*fprintf(sfp, "%s\r\n", estr);*/
641                                 free(estr);
642                         } else
643 #endif
644                         { /* If "Connection:" is needed, document why */
645                                 fprintf(sfp, /* "Connection: close\r\n" */ "\r\n");
646                         }
647
648                         /*
649                          * Retrieve HTTP response line and check for "200" status code.
650                          */
651  read_response:
652                         if (fgets(buf, sizeof(buf), sfp) == NULL)
653                                 bb_error_msg_and_die("no response from server");
654
655                         str = buf;
656                         str = skip_non_whitespace(str);
657                         str = skip_whitespace(str);
658                         // FIXME: no error check
659                         // xatou wouldn't work: "200 OK"
660                         status = atoi(str);
661                         switch (status) {
662                         case 0:
663                         case 100:
664                                 while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
665                                         /* eat all remaining headers */;
666                                 goto read_response;
667                         case 200:
668 /*
669 Response 204 doesn't say "null file", it says "metadata
670 has changed but data didn't":
671
672 "10.2.5 204 No Content
673 The server has fulfilled the request but does not need to return
674 an entity-body, and might want to return updated metainformation.
675 The response MAY include new or updated metainformation in the form
676 of entity-headers, which if present SHOULD be associated with
677 the requested variant.
678
679 If the client is a user agent, it SHOULD NOT change its document
680 view from that which caused the request to be sent. This response
681 is primarily intended to allow input for actions to take place
682 without causing a change to the user agent's active document view,
683 although any new or updated metainformation SHOULD be applied
684 to the document currently in the user agent's active view.
685
686 The 204 response MUST NOT include a message-body, and thus
687 is always terminated by the first empty line after the header fields."
688
689 However, in real world it was observed that some web servers
690 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
691 */
692                         case 204:
693                                 break;
694                         case 300:       /* redirection */
695                         case 301:
696                         case 302:
697                         case 303:
698                                 break;
699                         case 206:
700                                 if (beg_range)
701                                         break;
702                                 /* fall through */
703                         default:
704                                 /* Show first line only and kill any ESC tricks */
705                                 buf[strcspn(buf, "\n\r\x1b")] = '\0';
706                                 bb_error_msg_and_die("server returned error: %s", buf);
707                         }
708
709                         /*
710                          * Retrieve HTTP headers.
711                          */
712                         while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
713                                 /* gethdr did already convert the "FOO:" string to lowercase */
714                                 smalluint key = index_in_strings(keywords, *&buf) + 1;
715                                 if (key == KEY_content_length) {
716                                         content_len = BB_STRTOOFF(str, NULL, 10);
717                                         if (errno || content_len < 0) {
718                                                 bb_error_msg_and_die("content-length %s is garbage", str);
719                                         }
720                                         got_clen = 1;
721                                         continue;
722                                 }
723                                 if (key == KEY_transfer_encoding) {
724                                         if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
725                                                 bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
726                                         chunked = got_clen = 1;
727                                 }
728                                 if (key == KEY_location) {
729                                         if (str[0] == '/')
730                                                 /* free(target.allocated); */
731                                                 target.path = /* target.allocated = */ xstrdup(str+1);
732                                         else {
733                                                 parse_url(str, &target);
734                                                 if (use_proxy == 0) {
735                                                         server.host = target.host;
736                                                         server.port = target.port;
737                                                 }
738                                                 free(lsa);
739                                                 lsa = xhost2sockaddr(server.host, server.port);
740                                                 break;
741                                         }
742                                 }
743                         }
744                 } while (status >= 300);
745
746                 dfp = sfp;
747
748         } else {
749
750                 /*
751                  *  FTP session
752                  */
753                 if (!target.user)
754                         target.user = xstrdup("anonymous:busybox@");
755
756                 sfp = open_socket(lsa);
757                 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
758                         bb_error_msg_and_die("%s", buf+4);
759
760                 /*
761                  * Splitting username:password pair,
762                  * trying to log in
763                  */
764                 str = strchr(target.user, ':');
765                 if (str)
766                         *(str++) = '\0';
767                 switch (ftpcmd("USER ", target.user, sfp, buf)) {
768                 case 230:
769                         break;
770                 case 331:
771                         if (ftpcmd("PASS ", str, sfp, buf) == 230)
772                                 break;
773                         /* fall through (failed login) */
774                 default:
775                         bb_error_msg_and_die("ftp login: %s", buf+4);
776                 }
777
778                 ftpcmd("TYPE I", NULL, sfp, buf);
779
780                 /*
781                  * Querying file size
782                  */
783                 if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) {
784                         content_len = BB_STRTOOFF(buf+4, NULL, 10);
785                         if (errno || content_len < 0) {
786                                 bb_error_msg_and_die("SIZE value is garbage");
787                         }
788                         got_clen = 1;
789                 }
790
791                 /*
792                  * Entering passive mode
793                  */
794                 if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
795  pasv_error:
796                         bb_error_msg_and_die("bad response to %s: %s", "PASV", buf);
797                 }
798                 // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
799                 // Server's IP is N1.N2.N3.N4 (we ignore it)
800                 // Server's port for data connection is P1*256+P2
801                 str = strrchr(buf, ')');
802                 if (str) str[0] = '\0';
803                 str = strrchr(buf, ',');
804                 if (!str) goto pasv_error;
805                 port = xatou_range(str+1, 0, 255);
806                 *str = '\0';
807                 str = strrchr(buf, ',');
808                 if (!str) goto pasv_error;
809                 port += xatou_range(str+1, 0, 255) * 256;
810                 set_nport(lsa, htons(port));
811                 dfp = open_socket(lsa);
812
813                 if (beg_range) {
814                         sprintf(buf, "REST %"OFF_FMT"d", beg_range);
815                         if (ftpcmd(buf, NULL, sfp, buf) == 350)
816                                 content_len -= beg_range;
817                 }
818
819                 if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
820                         bb_error_msg_and_die("bad response to %s: %s", "RETR", buf);
821         }
822
823         if (opt & WGET_OPT_SPIDER) {
824                 if (ENABLE_FEATURE_CLEAN_UP)
825                         fclose(sfp);
826                 return EXIT_SUCCESS;
827         }
828
829         /*
830          * Retrieve file
831          */
832
833         /* Do it before progress_meter (want to have nice error message) */
834         if (output_fd < 0) {
835                 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
836                 /* compat with wget: -O FILE can overwrite */
837                 if (opt & WGET_OPT_OUTNAME)
838                         o_flags = O_WRONLY | O_CREAT | O_TRUNC;
839                 output_fd = xopen(fname_out, o_flags);
840         }
841
842         if (!(opt & WGET_OPT_QUIET))
843                 progress_meter(-1);
844
845         if (chunked)
846                 goto get_clen;
847
848         /* Loops only if chunked */
849         while (1) {
850                 while (content_len > 0 || !got_clen) {
851                         int n;
852                         unsigned rdsz = sizeof(buf);
853
854                         if (content_len < sizeof(buf) && (chunked || got_clen))
855                                 rdsz = (unsigned)content_len;
856                         n = safe_fread(buf, rdsz, dfp);
857                         if (n <= 0) {
858                                 if (ferror(dfp)) {
859                                         /* perror will not work: ferror doesn't set errno */
860                                         bb_error_msg_and_die(bb_msg_read_error);
861                                 }
862                                 break;
863                         }
864                         xwrite(output_fd, buf, n);
865 #if ENABLE_FEATURE_WGET_STATUSBAR
866                         transferred += n;
867 #endif
868                         if (got_clen)
869                                 content_len -= n;
870                 }
871
872                 if (!chunked)
873                         break;
874
875                 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
876  get_clen:
877                 safe_fgets(buf, sizeof(buf), dfp);
878                 content_len = STRTOOFF(buf, NULL, 16);
879                 /* FIXME: error check? */
880                 if (content_len == 0)
881                         break; /* all done! */
882         }
883
884         if (!(opt & WGET_OPT_QUIET))
885                 progress_meter(0);
886
887         if ((use_proxy == 0) && target.is_ftp) {
888                 fclose(dfp);
889                 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
890                         bb_error_msg_and_die("ftp error: %s", buf+4);
891                 ftpcmd("QUIT", NULL, sfp, buf);
892         }
893
894         return EXIT_SUCCESS;
895 }