Magellan Linux

Contents of /tags/mkinitrd-6_1_11/busybox/networking/wget.c

Parent Directory Parent Directory | Revision Log Revision Log


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