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