Tomato 1.28
[tomato.git] / release / src / router / busybox / networking / wget.c
blob48759049df49707c84b033ab7efd7579780ff72a
1 /* vi: set sw=4 ts=4: */
2 /*
3 * wget - retrieve a file using HTTP or FTP
5 * Chip Rosenthal Covad Communications <chip@laserlink.net>
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 */
10 #include "libbb.h"
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;
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 */
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];
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)
53 #if ENABLE_FEATURE_WGET_STATUSBAR
54 enum {
55 STALLTIME = 5 /* Seconds when xfer considered "stalled" */
58 static unsigned int get_tty2_width(void)
60 unsigned width;
61 get_terminal_width_height(2, &width, NULL);
62 return width;
65 static void progress_meter(int flag)
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;
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.. */
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;
88 fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
90 barlength = get_tty2_width() - 49;
91 if (barlength > 0) {
92 /* god bless gcc for variable arrays :) */
93 i = barlength * ratio / 100;
95 char buf[i+1];
96 memset(buf, '*', i);
97 buf[i] = '\0';
98 fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
101 i = 0;
102 abbrevsize = transferred + beg_range;
103 while (abbrevsize >= 100000) {
104 i++;
105 abbrevsize >>= 10;
107 /* see http://en.wikipedia.org/wiki/Tera */
108 fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
110 // Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
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;
122 since_last_update = 0; /* we are un-stalled now */
124 elapsed -= start_sec; /* now it's "elapsed since start" */
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);
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);
150 alarm(1);
153 errno = save_errno;
155 /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
156 * much of which was blatantly stolen from openssh. */
158 * Copyright (c) 1992, 1993
159 * The Regents of the University of California. All rights reserved.
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.
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>
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.
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.
190 #else /* FEATURE_WGET_STATUSBAR */
192 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
194 #endif
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)
201 size_t ret;
202 char *p = (char*)ptr;
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);
212 return p - (char*)ptr;
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)
219 char *ret;
221 do {
222 clearerr(stream);
223 errno = 0;
224 ret = fgets(s, size, stream);
225 } while (ret == NULL && ferror(stream) && errno == EINTR);
227 return ret;
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)
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;
240 #endif
243 static FILE *open_socket(len_and_sockaddr *lsa)
245 FILE *fp;
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");
253 return fp;
257 static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
259 int result;
260 if (s1) {
261 if (!s2) s2 = "";
262 fprintf(fp, "%s%s\r\n", s1, s2);
263 fflush(fp);
266 do {
267 char *buf_ptr;
269 if (fgets(buf, 510, fp) == NULL) {
270 bb_perror_msg_and_die("error getting response");
272 buf_ptr = strstr(buf, "\r\n");
273 if (buf_ptr) {
274 *buf_ptr = '\0';
276 } while (!isdigit(buf[0]) || buf[3] != ' ');
278 buf[3] = '\0';
279 result = xatoi_u(buf);
280 buf[3] = ' ';
281 return result;
285 static void parse_url(char *src_url, struct host_info *h)
287 char *url, *p, *sp;
289 /* h->allocated = */ url = xstrdup(src_url);
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);
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')
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;
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;
340 sp = h->host;
344 static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
346 char *s, *hdrval;
347 int c;
349 /* *istrunc = 0; */
351 /* retrieve header line */
352 if (fgets(buf, bufsiz, fp) == NULL)
353 return NULL;
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;
361 /* convert the header name to lower case */
362 for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
363 *s = tolower(*s);
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);
369 /* locate the start of the header value */
370 *s++ = '\0';
371 hdrval = skip_whitespace(s);
373 /* locate the end of header */
374 while (*s && *s != '\r' && *s != '\n')
375 ++s;
377 /* end of header found */
378 if (*s) {
379 *s = '\0';
380 return hdrval;
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;
390 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
391 static char *URL_escape(const char *str)
393 /* URL encode, see RFC 2396 */
394 char *dst;
395 char *res = dst = xmalloc(strlen(str) * 3 + 1);
396 unsigned char c;
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')
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];
426 #endif
428 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
429 int wget_main(int argc UNUSED_PARAM, char **argv)
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 */
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
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,
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"
492 #endif
494 INIT_G();
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)
509 if (strcmp(proxy_flag, "off") == 0) {
510 /* Use the proxy if necessary */
511 use_proxy = 0;
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;
522 extra_headers = cp = xmalloc(size);
523 while (headers_llist) {
524 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
527 #endif
529 parse_url(argv[optind], &target);
530 server.host = target.host;
531 server.port = target.port;
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;
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;
559 #if ENABLE_FEATURE_WGET_STATUSBAR
560 curfile = bb_get_last_path_component_nostrip(fname_out);
561 #endif
563 /* Impossible?
564 if ((opt & WGET_OPT_CONTINUE) && !fname_out)
565 bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */
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);
573 /* File doesn't exist. We do not create file here yet.
574 We are not sure it exists on remove side */
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 */
587 if (use_proxy || !target.is_ftp) {
589 * HTTP session
591 do {
592 got_clen = 0;
593 chunked = 0;
595 if (!--try)
596 bb_error_msg_and_die("too many redirections");
598 /* Open socket to http server */
599 if (sfp) fclose(sfp);
600 sfp = open_socket(lsa);
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);
614 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
615 target.host, user_agent);
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));
622 if (use_proxy && server.user) {
623 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
624 base64enc_512(buf, server.user));
626 #endif
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);
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");
649 * Retrieve HTTP response line and check for "200" status code.
651 read_response:
652 if (fgets(buf, sizeof(buf), sfp) == NULL)
653 bb_error_msg_and_die("no response from server");
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:
669 Response 204 doesn't say "null file", it says "metadata
670 has changed but data didn't":
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.
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.
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."
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.
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);
710 * Retrieve HTTP headers.
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);
720 got_clen = 1;
721 continue;
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;
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;
738 free(lsa);
739 lsa = xhost2sockaddr(server.host, server.port);
740 break;
744 } while (status >= 300);
746 dfp = sfp;
748 } else {
751 * FTP session
753 if (!target.user)
754 target.user = xstrdup("anonymous:busybox@");
756 sfp = open_socket(lsa);
757 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
758 bb_error_msg_and_die("%s", buf+4);
761 * Splitting username:password pair,
762 * trying to log in
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);
778 ftpcmd("TYPE I", NULL, sfp, buf);
781 * Querying file size
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");
788 got_clen = 1;
792 * Entering passive mode
794 if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
795 pasv_error:
796 bb_error_msg_and_die("bad response to %s: %s", "PASV", buf);
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);
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;
819 if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
820 bb_error_msg_and_die("bad response to %s: %s", "RETR", buf);
823 if (opt & WGET_OPT_SPIDER) {
824 if (ENABLE_FEATURE_CLEAN_UP)
825 fclose(sfp);
826 return EXIT_SUCCESS;
830 * Retrieve file
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);
842 if (!(opt & WGET_OPT_QUIET))
843 progress_meter(-1);
845 if (chunked)
846 goto get_clen;
848 /* Loops only if chunked */
849 while (1) {
850 while (content_len > 0 || !got_clen) {
851 int n;
852 unsigned rdsz = sizeof(buf);
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);
862 break;
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;
872 if (!chunked)
873 break;
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! */
884 if (!(opt & WGET_OPT_QUIET))
885 progress_meter(0);
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);
894 return EXIT_SUCCESS;