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