1
2
3
4
5
6
7
8
9#include "libbb.h"
10
11struct host_info {
12
13
14 const char *path;
15 const char *user;
16 char *host;
17 int port;
18 smallint is_ftp;
19};
20
21
22
23struct globals {
24 off_t content_len;
25 off_t beg_range;
26#if ENABLE_FEATURE_WGET_STATUSBAR
27 off_t lastsize;
28 off_t totalsize;
29 off_t transferred;
30 const char *curfile;
31 unsigned lastupdate_sec;
32 unsigned start_sec;
33#endif
34 smallint chunked;
35};
36#define G (*(struct globals*)&bb_common_bufsiz1)
37struct 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
53enum {
54 STALLTIME = 5
55};
56
57static unsigned int getttywidth(void)
58{
59 unsigned width;
60 get_terminal_width_height(0, &width, NULL);
61 return width;
62}
63
64static void progressmeter(int flag)
65{
66
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) {
74 start_sec = monotonic_sec();
75 lastupdate_sec = start_sec;
76 lastsize = 0;
77 totalsize = content_len + beg_range;
78 }
79
80 ratio = 100;
81 if (totalsize != 0 && !chunked) {
82
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
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
107 fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
108
109
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
118
119 start_sec += since_last_update;
120 }
121 since_last_update = 0;
122 }
123 elapsed -= start_sec;
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
133 int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed);
134
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
142 alarm(0);
143 transferred = 0;
144 fputc('\n', stderr);
145 } else {
146 if (flag == -1) {
147 signal_SA_RESTART_empty_mask(SIGALRM, progressmeter);
148 }
149 alarm(1);
150 }
151
152 errno = save_errno;
153}
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189#else
190
191static ALWAYS_INLINE void progressmeter(int flag UNUSED_PARAM) { }
192
193#endif
194
195
196
197
198static 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
214
215static 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
229static char *base64enc_512(char buf[512], const char *str)
230{
231 unsigned len = strlen(str);
232 if (len > 512/4*3 - 10)
233 len = 512/4*3 - 10;
234 bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
235 return buf;
236}
237#endif
238
239
240static FILE *open_socket(len_and_sockaddr *lsa)
241{
242 FILE *fp;
243
244
245
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
254static 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
282static void parse_url(char *src_url, struct host_info *h)
283{
284 char *url, *p, *sp;
285
286 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
300
301
302
303
304
305
306
307
308
309
310
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 {
320
321
322
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
341static char *gethdr(char *buf, size_t bufsiz, FILE *fp )
342{
343 char *s, *hdrval;
344 int c;
345
346
347
348
349 if (fgets(buf, bufsiz, fp) == NULL)
350 return NULL;
351
352
353 for (s = buf; *s == '\r'; ++s)
354 continue;
355 if (*s == '\n')
356 return NULL;
357
358
359 for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
360 *s = tolower(*s);
361
362
363 if (*s != ':')
364 bb_error_msg_and_die("bad header line: %s", buf);
365
366
367 *s++ = '\0';
368 hdrval = skip_whitespace(s);
369
370
371 while (*s && *s != '\r' && *s != '\n')
372 ++s;
373
374
375 if (*s) {
376 *s = '\0';
377 return hdrval;
378 }
379
380
381 while (c = getc(fp), c != EOF && c != '\n')
382 continue;
383
384 return hdrval;
385}
386
387
388int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
389int 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;
406 FILE *dfp;
407 char *fname_out;
408 bool got_clen = 0;
409 int output_fd = -1;
410 bool use_proxy = 1;
411 const char *proxy_flag = "on";
412 const char *user_agent = "Wget";
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 = 0x1,
421 WGET_OPT_SPIDER = 0x2,
422 WGET_OPT_QUIET = 0x4,
423 WGET_OPT_OUTNAME = 0x8,
424 WGET_OPT_PREFIX = 0x10,
425 WGET_OPT_PROXY = 0x20,
426 WGET_OPT_USER_AGENT = 0x40,
427 WGET_OPT_PASSIVE = 0x80,
428 WGET_OPT_HEADER = 0x100,
429 };
430#if ENABLE_FEATURE_WGET_LONG_OPTIONS
431 static const char wget_longopts[] ALIGN1 =
432
433 "continue\0" No_argument "c"
434 "spider\0" No_argument "s"
435 "quiet\0" No_argument "q"
436 "output-document\0" Required_argument "O"
437 "directory-prefix\0" Required_argument "P"
438 "proxy\0" Required_argument "Y"
439 "user-agent\0" Required_argument "U"
440 "passive-ftp\0" No_argument "\xff"
441 "header\0" Required_argument "\xfe"
442 ;
443#endif
444
445 INIT_G();
446
447#if ENABLE_FEATURE_WGET_LONG_OPTIONS
448 applet_long_options = wget_longopts;
449#endif
450
451 opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
452 opt = getopt32(argv, "csqO:P:Y:U:" "t:T:",
453 &fname_out, &dir_prefix,
454 &proxy_flag, &user_agent,
455 NULL,
456 NULL
457 USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
458 );
459 if (strcmp(proxy_flag, "off") == 0) {
460
461 use_proxy = 0;
462 }
463#if ENABLE_FEATURE_WGET_LONG_OPTIONS
464 if (headers_llist) {
465 int size = 1;
466 char *cp;
467 llist_t *ll = headers_llist;
468 while (ll) {
469 size += strlen(ll->data) + 2;
470 ll = ll->link;
471 }
472 extra_headers = cp = xmalloc(size);
473 while (headers_llist) {
474 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
475 }
476 }
477#endif
478
479 parse_url(argv[optind], &target);
480 server.host = target.host;
481 server.port = target.port;
482
483
484 if (use_proxy) {
485 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
486 if (proxy && *proxy) {
487 parse_url(proxy, &server);
488 } else {
489 use_proxy = 0;
490 }
491 }
492
493
494 if (!(opt & WGET_OPT_OUTNAME)) {
495 fname_out = bb_get_last_path_component_nostrip(target.path);
496
497 if (fname_out[0] == '/' || !fname_out[0])
498 fname_out = (char*)"index.html";
499
500 if (dir_prefix)
501 fname_out = concat_path_file(dir_prefix, fname_out);
502 } else {
503 if (LONE_DASH(fname_out)) {
504
505 output_fd = 1;
506 opt &= ~WGET_OPT_CONTINUE;
507 }
508 }
509#if ENABLE_FEATURE_WGET_STATUSBAR
510 curfile = bb_get_last_path_component_nostrip(fname_out);
511#endif
512
513
514
515
516
517
518 if (opt & WGET_OPT_CONTINUE) {
519 output_fd = open(fname_out, O_WRONLY);
520 if (output_fd >= 0) {
521 beg_range = xlseek(output_fd, 0, SEEK_END);
522 }
523
524
525 }
526
527
528
529
530 lsa = xhost2sockaddr(server.host, server.port);
531 if (!(opt & WGET_OPT_QUIET)) {
532 fprintf(stderr, "Connecting to %s (%s)\n", server.host,
533 xmalloc_sockaddr2dotted(&lsa->u.sa));
534
535 }
536
537 if (use_proxy || !target.is_ftp) {
538
539
540
541 do {
542 got_clen = 0;
543 chunked = 0;
544
545 if (!--try)
546 bb_error_msg_and_die("too many redirections");
547
548
549 if (sfp) fclose(sfp);
550 sfp = open_socket(lsa);
551
552
553 if (use_proxy) {
554 fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
555 target.is_ftp ? "f" : "ht", target.host,
556 target.path);
557 } else {
558 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
559 }
560
561 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
562 target.host, user_agent);
563
564#if ENABLE_FEATURE_WGET_AUTHENTICATION
565 if (target.user) {
566 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
567 base64enc_512(buf, target.user));
568 }
569 if (use_proxy && server.user) {
570 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
571 base64enc_512(buf, server.user));
572 }
573#endif
574
575 if (beg_range)
576 fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
577#if ENABLE_FEATURE_WGET_LONG_OPTIONS
578 if (extra_headers)
579 fputs(extra_headers, sfp);
580#endif
581 fprintf(sfp, "Connection: close\r\n\r\n");
582
583
584
585
586 read_response:
587 if (fgets(buf, sizeof(buf), sfp) == NULL)
588 bb_error_msg_and_die("no response from server");
589
590 str = buf;
591 str = skip_non_whitespace(str);
592 str = skip_whitespace(str);
593
594
595 status = atoi(str);
596 switch (status) {
597 case 0:
598 case 100:
599 while (gethdr(buf, sizeof(buf), sfp ) != NULL)
600 ;
601 goto read_response;
602 case 200:
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627 case 204:
628 break;
629 case 300:
630 case 301:
631 case 302:
632 case 303:
633 break;
634 case 206:
635 if (beg_range)
636 break;
637
638 default:
639
640 buf[strcspn(buf, "\n\r\x1b")] = '\0';
641 bb_error_msg_and_die("server returned error: %s", buf);
642 }
643
644
645
646
647 while ((str = gethdr(buf, sizeof(buf), sfp )) != NULL) {
648
649 smalluint key = index_in_strings(keywords, *&buf) + 1;
650 if (key == KEY_content_length) {
651 content_len = BB_STRTOOFF(str, NULL, 10);
652 if (errno || content_len < 0) {
653 bb_error_msg_and_die("content-length %s is garbage", str);
654 }
655 got_clen = 1;
656 continue;
657 }
658 if (key == KEY_transfer_encoding) {
659 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
660 bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
661 chunked = got_clen = 1;
662 }
663 if (key == KEY_location) {
664 if (str[0] == '/')
665
666 target.path = xstrdup(str+1);
667 else {
668 parse_url(str, &target);
669 if (use_proxy == 0) {
670 server.host = target.host;
671 server.port = target.port;
672 }
673 free(lsa);
674 lsa = xhost2sockaddr(server.host, server.port);
675 break;
676 }
677 }
678 }
679 } while (status >= 300);
680
681 dfp = sfp;
682
683 } else {
684
685
686
687
688 if (!target.user)
689 target.user = xstrdup("anonymous:busybox@");
690
691 sfp = open_socket(lsa);
692 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
693 bb_error_msg_and_die("%s", buf+4);
694
695
696
697
698
699 str = strchr(target.user, ':');
700 if (str)
701 *(str++) = '\0';
702 switch (ftpcmd("USER ", target.user, sfp, buf)) {
703 case 230:
704 break;
705 case 331:
706 if (ftpcmd("PASS ", str, sfp, buf) == 230)
707 break;
708
709 default:
710 bb_error_msg_and_die("ftp login: %s", buf+4);
711 }
712
713 ftpcmd("TYPE I", NULL, sfp, buf);
714
715
716
717
718 if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) {
719 content_len = BB_STRTOOFF(buf+4, NULL, 10);
720 if (errno || content_len < 0) {
721 bb_error_msg_and_die("SIZE value is garbage");
722 }
723 got_clen = 1;
724 }
725
726
727
728
729 if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
730 pasv_error:
731 bb_error_msg_and_die("bad response to %s: %s", "PASV", buf);
732 }
733
734
735
736 str = strrchr(buf, ')');
737 if (str) str[0] = '\0';
738 str = strrchr(buf, ',');
739 if (!str) goto pasv_error;
740 port = xatou_range(str+1, 0, 255);
741 *str = '\0';
742 str = strrchr(buf, ',');
743 if (!str) goto pasv_error;
744 port += xatou_range(str+1, 0, 255) * 256;
745 set_nport(lsa, htons(port));
746 dfp = open_socket(lsa);
747
748 if (beg_range) {
749 sprintf(buf, "REST %"OFF_FMT"d", beg_range);
750 if (ftpcmd(buf, NULL, sfp, buf) == 350)
751 content_len -= beg_range;
752 }
753
754 if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
755 bb_error_msg_and_die("bad response to %s: %s", "RETR", buf);
756 }
757
758 if (opt & WGET_OPT_SPIDER) {
759 if (ENABLE_FEATURE_CLEAN_UP)
760 fclose(sfp);
761 return EXIT_SUCCESS;
762 }
763
764
765
766
767
768
769 if (output_fd < 0) {
770 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
771
772 if (opt & WGET_OPT_OUTNAME)
773 o_flags = O_WRONLY | O_CREAT | O_TRUNC;
774 output_fd = xopen(fname_out, o_flags);
775 }
776
777 if (!(opt & WGET_OPT_QUIET))
778 progressmeter(-1);
779
780 if (chunked)
781 goto get_clen;
782
783
784 while (1) {
785 while (content_len > 0 || !got_clen) {
786 int n;
787 unsigned rdsz = sizeof(buf);
788
789 if (content_len < sizeof(buf) && (chunked || got_clen))
790 rdsz = (unsigned)content_len;
791 n = safe_fread(buf, rdsz, dfp);
792 if (n <= 0) {
793 if (ferror(dfp)) {
794
795 bb_error_msg_and_die(bb_msg_read_error);
796 }
797 break;
798 }
799 xwrite(output_fd, buf, n);
800#if ENABLE_FEATURE_WGET_STATUSBAR
801 transferred += n;
802#endif
803 if (got_clen)
804 content_len -= n;
805 }
806
807 if (!chunked)
808 break;
809
810 safe_fgets(buf, sizeof(buf), dfp);
811 get_clen:
812 safe_fgets(buf, sizeof(buf), dfp);
813 content_len = STRTOOFF(buf, NULL, 16);
814
815 if (content_len == 0)
816 break;
817 }
818
819 if (!(opt & WGET_OPT_QUIET))
820 progressmeter(0);
821
822 if ((use_proxy == 0) && target.is_ftp) {
823 fclose(dfp);
824 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
825 bb_error_msg_and_die("ftp error: %s", buf+4);
826 ftpcmd("QUIT", NULL, sfp, buf);
827 }
828
829 return EXIT_SUCCESS;
830}
831