getopt_ulflags -> getopt32.
[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  */
8
9 /* We want libc to give us xxx64 functions also */
10 /* http://www.unix.org/version2/whatsnew/lfs20mar.html */
11 #define _LARGEFILE64_SOURCE 1
12
13 #include "busybox.h"
14 #include <getopt.h>     /* for struct option */
15
16 #ifdef CONFIG_LFS
17 # define FILEOFF_TYPE off64_t
18 # define FILEOFF_FMT "%lld"
19 # define LSEEK lseek64
20 # define STRTOOFF strtoll
21 # define SAFE_STRTOOFF safe_strtoll
22 /* stat64 etc as needed...  */
23 #else
24 # define FILEOFF_TYPE off_t
25 # define FILEOFF_FMT "%ld"
26 # define LSEEK lseek
27 # define STRTOOFF strtol
28 # define SAFE_STRTOOFF safe_strtol
29 /* Do we need to undefine O_LARGEFILE? */
30 #endif
31
32 struct host_info {
33         char *host;
34         int port;
35         char *path;
36         int is_ftp;
37         char *user;
38 };
39
40 static void parse_url(char *url, struct host_info *h);
41 static FILE *open_socket(struct sockaddr_in *s_in);
42 static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc);
43 static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf);
44
45 /* Globals (can be accessed from signal handlers */
46 static FILEOFF_TYPE content_len;        /* Content-length of the file */
47 static FILEOFF_TYPE beg_range;          /* Range at which continue begins */
48 static FILEOFF_TYPE transferred;        /* Number of bytes transferred so far */
49 static int chunked;                     /* chunked transfer encoding */
50 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
51 static void progressmeter(int flag);
52 static char *curfile;                   /* Name of current file being transferred */
53 static struct timeval start;            /* Time a transfer started */
54 enum {
55         STALLTIME = 5                   /* Seconds when xfer considered "stalled" */
56 };
57 #else
58 static void progressmeter(int flag) {}
59 #endif
60
61 /* Read NMEMB elements of SIZE bytes into PTR from STREAM.  Returns the
62  * number of elements read, and a short count if an eof or non-interrupt
63  * error is encountered.  */
64 static size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream)
65 {
66         size_t ret = 0;
67
68         do {
69                 clearerr(stream);
70                 ret += fread((char *)ptr + (ret * size), size, nmemb - ret, stream);
71         } while (ret < nmemb && ferror(stream) && errno == EINTR);
72
73         return ret;
74 }
75
76 /* Read a line or SIZE - 1 bytes into S, whichever is less, from STREAM.
77  * Returns S, or NULL if an eof or non-interrupt error is encountered.  */
78 static char *safe_fgets(char *s, int size, FILE *stream)
79 {
80         char *ret;
81
82         do {
83                 clearerr(stream);
84                 ret = fgets(s, size, stream);
85         } while (ret == NULL && ferror(stream) && errno == EINTR);
86
87         return ret;
88 }
89
90 #ifdef CONFIG_FEATURE_WGET_AUTHENTICATION
91 /*
92  *  Base64-encode character string and return the string.
93  */
94 static char *base64enc(unsigned char *p, char *buf, int len)
95 {
96         bb_uuencode(p, buf, len, bb_uuenc_tbl_base64);
97         return buf;
98 }
99 #endif
100
101 #define WGET_OPT_CONTINUE     1
102 #define WGET_OPT_QUIET        2
103 #define WGET_OPT_PASSIVE      4
104 #define WGET_OPT_OUTNAME      8
105 #define WGET_OPT_HEADER      16
106 #define WGET_OPT_PREFIX      32
107 #define WGET_OPT_PROXY       64
108 #define WGET_OPT_USER_AGENT 128
109
110 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
111 static const struct option wget_long_options[] = {
112         { "continue",        0, NULL, 'c' },
113         { "quiet",           0, NULL, 'q' },
114         { "passive-ftp",     0, NULL, 139 }, /* FIXME: what is this - 139?? */
115         { "output-document", 1, NULL, 'O' },
116         { "header",          1, NULL, 131 },
117         { "directory-prefix",1, NULL, 'P' },
118         { "proxy",           1, NULL, 'Y' },
119         { "user-agent",      1, NULL, 'U' },
120         { 0,                 0, 0, 0 }
121 };
122 #endif
123
124 int wget_main(int argc, char **argv)
125 {
126         int n, try=5, status;
127         unsigned opt;
128         int port;
129         char *proxy = 0;
130         char *dir_prefix=NULL;
131         char *s, buf[512];
132         char extra_headers[1024];
133         char *extra_headers_ptr = extra_headers;
134         int extra_headers_left = sizeof(extra_headers);
135         struct host_info server, target;
136         struct sockaddr_in s_in;
137         llist_t *headers_llist = NULL;
138
139         FILE *sfp = NULL;               /* socket to web/ftp server         */
140         FILE *dfp = NULL;               /* socket to ftp server (data)      */
141         char *fname_out = NULL;         /* where to direct output (-O)      */
142         int got_clen = 0;               /* got content-length: from server  */
143         int output_fd = -1;
144         int use_proxy = 1;              /* Use proxies if env vars are set  */
145         char *proxy_flag = "on";        /* Use proxies if env vars are set  */
146         char *user_agent = "Wget";      /* Content of the "User-Agent" header field */
147
148         /*
149          * Crack command line.
150          */
151         opt_complementary = "-1:\203::";
152 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
153         applet_long_options = wget_long_options;
154 #endif
155         opt = getopt32(argc, argv, "cq\213O:\203:P:Y:U:",
156                                         &fname_out, &headers_llist,
157                                         &dir_prefix, &proxy_flag, &user_agent);
158         if (strcmp(proxy_flag, "off") == 0) {
159                 /* Use the proxy if necessary. */
160                 use_proxy = 0;
161         }
162         if (opt & WGET_OPT_HEADER) {
163                 while (headers_llist) {
164                         int arglen = strlen(headers_llist->data);
165                         if (extra_headers_left - arglen - 2 <= 0)
166                                 bb_error_msg_and_die("extra_headers buffer too small "
167                                         "(need %i)", extra_headers_left - arglen);
168                         strcpy(extra_headers_ptr, headers_llist->data);
169                         extra_headers_ptr += arglen;
170                         extra_headers_left -= ( arglen + 2 );
171                         *extra_headers_ptr++ = '\r';
172                         *extra_headers_ptr++ = '\n';
173                         *(extra_headers_ptr + 1) = 0;
174                         headers_llist = headers_llist->link;
175                 }
176         }
177
178         parse_url(argv[optind], &target);
179         server.host = target.host;
180         server.port = target.port;
181
182         /*
183          * Use the proxy if necessary.
184          */
185         if (use_proxy) {
186                 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
187                 if (proxy && *proxy) {
188                         parse_url(xstrdup(proxy), &server);
189                 } else {
190                         use_proxy = 0;
191                 }
192         }
193
194         /* Guess an output filename */
195         if (!fname_out) {
196                 // Dirty hack. Needed because bb_get_last_path_component
197                 // will destroy trailing / by storing '\0' in last byte!
198                 if (*target.path && target.path[strlen(target.path)-1] != '/') {
199                         fname_out =
200 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
201                                 curfile =
202 #endif
203                                 bb_get_last_path_component(target.path);
204                 }
205                 if (!fname_out || !fname_out[0]) {
206                         fname_out =
207 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
208                                 curfile =
209 #endif
210                                 "index.html";
211                 }
212                 if (dir_prefix != NULL)
213                         fname_out = concat_path_file(dir_prefix, fname_out);
214 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
215         } else {
216                 curfile = bb_get_last_path_component(fname_out);
217 #endif
218         }
219         if ((opt & WGET_OPT_CONTINUE) && !fname_out)
220                 bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)");
221
222         /*
223          * Determine where to start transfer.
224          */
225         if (!strcmp(fname_out, "-")) {
226                 output_fd = 1;
227                 opt |= WGET_OPT_QUIET;
228                 opt &= ~WGET_OPT_CONTINUE;
229         } else if (opt & WGET_OPT_CONTINUE) {
230                 output_fd = open(fname_out, O_WRONLY|O_LARGEFILE);
231                 if (output_fd >= 0) {
232                         beg_range = LSEEK(output_fd, 0, SEEK_END);
233                         if (beg_range == (FILEOFF_TYPE)-1)
234                                 bb_perror_msg_and_die("lseek");
235                 }
236                 /* File doesn't exist. We do not create file here yet.
237                    We are not sure it exists on remove side */
238         }
239
240         /* We want to do exactly _one_ DNS lookup, since some
241          * sites (i.e. ftp.us.debian.org) use round-robin DNS
242          * and we want to connect to only one IP... */
243         bb_lookup_host(&s_in, server.host);
244         s_in.sin_port = server.port;
245         if (!(opt & WGET_OPT_QUIET)) {
246                 printf("Connecting to %s[%s]:%d\n",
247                                 server.host, inet_ntoa(s_in.sin_addr), ntohs(server.port));
248         }
249
250         if (use_proxy || !target.is_ftp) {
251                 /*
252                  *  HTTP session
253                  */
254                 do {
255                         got_clen = chunked = 0;
256
257                         if (!--try)
258                                 bb_error_msg_and_die("too many redirections");
259
260                         /*
261                          * Open socket to http server
262                          */
263                         if (sfp) fclose(sfp);
264                         sfp = open_socket(&s_in);
265
266                         /*
267                          * Send HTTP request.
268                          */
269                         if (use_proxy) {
270                                 const char *format = "GET %stp://%s:%d/%s HTTP/1.1\r\n";
271 #ifdef CONFIG_FEATURE_WGET_IP6_LITERAL
272                                 if (strchr(target.host, ':'))
273                                         format = "GET %stp://[%s]:%d/%s HTTP/1.1\r\n";
274 #endif
275                                 fprintf(sfp, format,
276                                         target.is_ftp ? "f" : "ht", target.host,
277                                         ntohs(target.port), target.path);
278                         } else {
279                                 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
280                         }
281
282                         fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", target.host,
283                                 user_agent);
284
285 #ifdef CONFIG_FEATURE_WGET_AUTHENTICATION
286                         if (target.user) {
287                                 fprintf(sfp, "Authorization: Basic %s\r\n",
288                                         base64enc((unsigned char*)target.user, buf, sizeof(buf)));
289                         }
290                         if (use_proxy && server.user) {
291                                 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
292                                         base64enc((unsigned char*)server.user, buf, sizeof(buf)));
293                         }
294 #endif
295
296                         if (beg_range)
297                                 fprintf(sfp, "Range: bytes="FILEOFF_FMT"-\r\n", beg_range);
298                         if(extra_headers_left < sizeof(extra_headers))
299                                 fputs(extra_headers,sfp);
300                         fprintf(sfp,"Connection: close\r\n\r\n");
301
302                         /*
303                         * Retrieve HTTP response line and check for "200" status code.
304                         */
305 read_response:
306                         if (fgets(buf, sizeof(buf), sfp) == NULL)
307                                 bb_error_msg_and_die("no response from server");
308
309                         for (s = buf ; *s != '\0' && !isspace(*s) ; ++s)
310                                 ;
311                         for ( ; isspace(*s) ; ++s)
312                                 ;
313                         switch (status = atoi(s)) {
314                                 case 0:
315                                 case 100:
316                                         while (gethdr(buf, sizeof(buf), sfp, &n) != NULL);
317                                         goto read_response;
318                                 case 200:
319                                         break;
320                                 case 300:       /* redirection */
321                                 case 301:
322                                 case 302:
323                                 case 303:
324                                         break;
325                                 case 206:
326                                         if (beg_range)
327                                                 break;
328                                         /*FALLTHRU*/
329                                 default:
330                                         chomp(buf);
331                                         bb_error_msg_and_die("server returned error %d: %s", atoi(s), buf);
332                         }
333
334                         /*
335                          * Retrieve HTTP headers.
336                          */
337                         while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) {
338                                 if (strcasecmp(buf, "content-length") == 0) {
339                                         if (SAFE_STRTOOFF(s, &content_len) || content_len < 0) {
340                                                 bb_error_msg_and_die("content-length %s is garbage", s);
341                                         }
342                                         got_clen = 1;
343                                         continue;
344                                 }
345                                 if (strcasecmp(buf, "transfer-encoding") == 0) {
346                                         if (strcasecmp(s, "chunked") == 0) {
347                                                 chunked = got_clen = 1;
348                                         } else {
349                                                 bb_error_msg_and_die("server wants to do %s transfer encoding", s);
350                                         }
351                                 }
352                                 if (strcasecmp(buf, "location") == 0) {
353                                         if (s[0] == '/')
354                                                 target.path = xstrdup(s+1);
355                                         else {
356                                                 parse_url(xstrdup(s), &target);
357                                                 if (use_proxy == 0) {
358                                                         server.host = target.host;
359                                                         server.port = target.port;
360                                                 }
361                                                 bb_lookup_host(&s_in, server.host);
362                                                 s_in.sin_port = server.port;
363                                                 break;
364                                         }
365                                 }
366                         }
367                 } while(status >= 300);
368
369                 dfp = sfp;
370         }
371         else
372         {
373                 /*
374                  *  FTP session
375                  */
376                 if (!target.user)
377                         target.user = xstrdup("anonymous:busybox@");
378
379                 sfp = open_socket(&s_in);
380                 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
381                         bb_error_msg_and_die("%s", buf+4);
382
383                 /*
384                  * Splitting username:password pair,
385                  * trying to log in
386                  */
387                 s = strchr(target.user, ':');
388                 if (s)
389                         *(s++) = '\0';
390                 switch (ftpcmd("USER ", target.user, sfp, buf)) {
391                         case 230:
392                                 break;
393                         case 331:
394                                 if (ftpcmd("PASS ", s, sfp, buf) == 230)
395                                         break;
396                                 /* FALLTHRU (failed login) */
397                         default:
398                                 bb_error_msg_and_die("ftp login: %s", buf+4);
399                 }
400
401                 ftpcmd("TYPE I", NULL, sfp, buf);
402
403                 /*
404                  * Querying file size
405                  */
406                 if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) {
407                         if (SAFE_STRTOOFF(buf+4, &content_len) || content_len < 0) {
408                                 bb_error_msg_and_die("SIZE value is garbage");
409                         }
410                         got_clen = 1;
411                 }
412
413                 /*
414                  * Entering passive mode
415                  */
416                 if (ftpcmd("PASV", NULL, sfp, buf) !=  227)
417                         bb_error_msg_and_die("PASV: %s", buf+4);
418                 s = strrchr(buf, ',');
419                 *s = 0;
420                 port = atoi(s+1);
421                 s = strrchr(buf, ',');
422                 port += atoi(s+1) * 256;
423                 s_in.sin_port = htons(port);
424                 dfp = open_socket(&s_in);
425
426                 if (beg_range) {
427                         sprintf(buf, "REST "FILEOFF_FMT, beg_range);
428                         if (ftpcmd(buf, NULL, sfp, buf) == 350)
429                                 content_len -= beg_range;
430                 }
431
432                 if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
433                         bb_error_msg_and_die("RETR: %s", buf+4);
434         }
435
436
437         /*
438          * Retrieve file
439          */
440         if (chunked) {
441                 fgets(buf, sizeof(buf), dfp);
442                 content_len = STRTOOFF(buf, (char **) NULL, 16);
443                 /* FIXME: error check?? */
444         }
445
446         /* Do it before progressmeter (want to have nice error message) */
447         if (output_fd < 0)
448                 output_fd = xopen3(fname_out,
449                         O_WRONLY|O_CREAT|O_EXCL|O_TRUNC|O_LARGEFILE, 0666);
450
451         if (!(opt & WGET_OPT_QUIET))
452                 progressmeter(-1);
453
454         do {
455                 while (content_len > 0 || !got_clen) {
456                         unsigned rdsz = sizeof(buf);
457                         if (content_len < sizeof(buf) && (chunked || got_clen))
458                                 rdsz = (unsigned)content_len;
459                         n = safe_fread(buf, 1, rdsz, dfp);
460                         if (n <= 0)
461                                 break;
462                         if (full_write(output_fd, buf, n) != n) {
463                                 bb_perror_msg_and_die(bb_msg_write_error);
464                         }
465 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
466                         transferred += n;
467 #endif
468                         if (got_clen) {
469                                 content_len -= n;
470                         }
471                 }
472
473                 if (chunked) {
474                         safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
475                         safe_fgets(buf, sizeof(buf), dfp);
476                         content_len = STRTOOFF(buf, (char **) NULL, 16);
477                         /* FIXME: error check? */
478                         if (content_len == 0) {
479                                 chunked = 0; /* all done! */
480                         }
481                 }
482
483                 if (n == 0 && ferror(dfp)) {
484                         bb_perror_msg_and_die(bb_msg_read_error);
485                 }
486         } while (chunked);
487
488         if (!(opt & WGET_OPT_QUIET))
489                 progressmeter(1);
490
491         if ((use_proxy == 0) && target.is_ftp) {
492                 fclose(dfp);
493                 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
494                         bb_error_msg_and_die("ftp error: %s", buf+4);
495                 ftpcmd("QUIT", NULL, sfp, buf);
496         }
497         exit(EXIT_SUCCESS);
498 }
499
500
501 static void parse_url(char *url, struct host_info *h)
502 {
503         char *cp, *sp, *up, *pp;
504
505         if (strncmp(url, "http://", 7) == 0) {
506                 h->port = bb_lookup_port("http", "tcp", 80);
507                 h->host = url + 7;
508                 h->is_ftp = 0;
509         } else if (strncmp(url, "ftp://", 6) == 0) {
510                 h->port = bb_lookup_port("ftp", "tfp", 21);
511                 h->host = url + 6;
512                 h->is_ftp = 1;
513         } else
514                 bb_error_msg_and_die("not an http or ftp url: %s", url);
515
516         sp = strchr(h->host, '/');
517         if (sp) {
518                 *sp++ = '\0';
519                 h->path = sp;
520         } else
521                 h->path = xstrdup("");
522
523         up = strrchr(h->host, '@');
524         if (up != NULL) {
525                 h->user = h->host;
526                 *up++ = '\0';
527                 h->host = up;
528         } else
529                 h->user = NULL;
530
531         pp = h->host;
532
533 #ifdef CONFIG_FEATURE_WGET_IP6_LITERAL
534         if (h->host[0] == '[') {
535                 char *ep;
536
537                 ep = h->host + 1;
538                 while (*ep == ':' || isxdigit (*ep))
539                         ep++;
540                 if (*ep == ']') {
541                         h->host++;
542                         *ep = '\0';
543                         pp = ep + 1;
544                 }
545         }
546 #endif
547
548         cp = strchr(pp, ':');
549         if (cp != NULL) {
550                 *cp++ = '\0';
551                 h->port = htons(atoi(cp));
552         }
553 }
554
555
556 static FILE *open_socket(struct sockaddr_in *s_in)
557 {
558         FILE *fp;
559
560         fp = fdopen(xconnect(s_in), "r+");
561         if (fp == NULL)
562                 bb_perror_msg_and_die("fdopen");
563
564         return fp;
565 }
566
567
568 static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc)
569 {
570         char *s, *hdrval;
571         int c;
572
573         *istrunc = 0;
574
575         /* retrieve header line */
576         if (fgets(buf, bufsiz, fp) == NULL)
577                 return NULL;
578
579         /* see if we are at the end of the headers */
580         for (s = buf ; *s == '\r' ; ++s)
581                 ;
582         if (s[0] == '\n')
583                 return NULL;
584
585         /* convert the header name to lower case */
586         for (s = buf ; isalnum(*s) || *s == '-' ; ++s)
587                 *s = tolower(*s);
588
589         /* verify we are at the end of the header name */
590         if (*s != ':')
591                 bb_error_msg_and_die("bad header line: %s", buf);
592
593         /* locate the start of the header value */
594         for (*s++ = '\0' ; *s == ' ' || *s == '\t' ; ++s)
595                 ;
596         hdrval = s;
597
598         /* locate the end of header */
599         while (*s != '\0' && *s != '\r' && *s != '\n')
600                 ++s;
601
602         /* end of header found */
603         if (*s != '\0') {
604                 *s = '\0';
605                 return hdrval;
606         }
607
608         /* Rats!  The buffer isn't big enough to hold the entire header value. */
609         while (c = getc(fp), c != EOF && c != '\n')
610                 ;
611         *istrunc = 1;
612         return hdrval;
613 }
614
615 static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf)
616 {
617         if (s1) {
618                 if (!s2) s2="";
619                 fprintf(fp, "%s%s\r\n", s1, s2);
620                 fflush(fp);
621         }
622
623         do {
624                 char *buf_ptr;
625
626                 if (fgets(buf, 510, fp) == NULL) {
627                         bb_perror_msg_and_die("fgets");
628                 }
629                 buf_ptr = strstr(buf, "\r\n");
630                 if (buf_ptr) {
631                         *buf_ptr = '\0';
632                 }
633         } while (!isdigit(buf[0]) || buf[3] != ' ');
634
635         return atoi(buf);
636 }
637
638 #ifdef CONFIG_FEATURE_WGET_STATUSBAR
639 /* Stuff below is from BSD rcp util.c, as added to openshh.
640  * Original copyright notice is retained at the end of this file.
641  */
642 static int
643 getttywidth(void)
644 {
645         int width=0;
646         get_terminal_width_height(0, &width, NULL);
647         return (width);
648 }
649
650 static void
651 updateprogressmeter(int ignore)
652 {
653         int save_errno = errno;
654
655         progressmeter(0);
656         errno = save_errno;
657 }
658
659 static void alarmtimer(int iwait)
660 {
661         struct itimerval itv;
662
663         itv.it_value.tv_sec = iwait;
664         itv.it_value.tv_usec = 0;
665         itv.it_interval = itv.it_value;
666         setitimer(ITIMER_REAL, &itv, NULL);
667 }
668
669
670 static void
671 progressmeter(int flag)
672 {
673         static struct timeval lastupdate;
674         static FILEOFF_TYPE lastsize, totalsize;
675
676         struct timeval now, td, tvwait;
677         FILEOFF_TYPE abbrevsize;
678         int elapsed, ratio, barlength, i;
679         char buf[256];
680
681         if (flag == -1) { /* first call to progressmeter */
682                 (void) gettimeofday(&start, (struct timezone *) 0);
683                 lastupdate = start;
684                 lastsize = 0;
685                 totalsize = content_len + beg_range; /* as content_len changes.. */
686         }
687
688         (void) gettimeofday(&now, (struct timezone *) 0);
689         ratio = 100;
690         if (totalsize != 0 && !chunked) {
691                 ratio = (int) (100 * (transferred+beg_range) / totalsize);
692                 ratio = MIN(ratio, 100);
693         }
694
695         fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
696
697         barlength = getttywidth() - 51;
698         if (barlength > 0 && barlength < sizeof(buf)) {
699                 i = barlength * ratio / 100;
700                 memset(buf, '*', i);
701                 memset(buf + i, ' ', barlength - i);
702                 buf[barlength] = '\0';
703                 fprintf(stderr, "|%s|", buf);
704         }
705         i = 0;
706         abbrevsize = transferred + beg_range;
707         while (abbrevsize >= 100000) {
708                 i++;
709                 abbrevsize >>= 10;
710         }
711         /* See http://en.wikipedia.org/wiki/Tera */
712         fprintf(stderr, "%6d %c%c ", (int)abbrevsize, " KMGTPEZY"[i], i?'B':' ');
713
714         timersub(&now, &lastupdate, &tvwait);
715         if (transferred > lastsize) {
716                 lastupdate = now;
717                 lastsize = transferred;
718                 if (tvwait.tv_sec >= STALLTIME)
719                         timeradd(&start, &tvwait, &start);
720                 tvwait.tv_sec = 0;
721         }
722         timersub(&now, &start, &td);
723         elapsed = td.tv_sec;
724
725         if (tvwait.tv_sec >= STALLTIME) {
726                 fprintf(stderr, " - stalled -");
727         } else {
728                 FILEOFF_TYPE to_download = totalsize - beg_range;
729                 if (transferred <= 0 || elapsed <= 0 || transferred > to_download || chunked) {
730                         fprintf(stderr, "--:--:-- ETA");
731                 } else {
732                         /* to_download / (transferred/elapsed) - elapsed: */
733                         int eta = (int) (to_download*elapsed/transferred - elapsed);
734                         i = eta % 3600;
735                         fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60);
736                 }
737         }
738
739         if (flag == -1) { /* first call to progressmeter */
740                 struct sigaction sa;
741                 sa.sa_handler = updateprogressmeter;
742                 sigemptyset(&sa.sa_mask);
743                 sa.sa_flags = SA_RESTART;
744                 sigaction(SIGALRM, &sa, NULL);
745                 alarmtimer(1);
746         } else if (flag == 1) { /* last call to progressmeter */
747                 alarmtimer(0);
748                 transferred = 0;
749                 putc('\n', stderr);
750         }
751 }
752 #endif
753
754 /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
755  * much of which was blatantly stolen from openssh.  */
756
757 /*-
758  * Copyright (c) 1992, 1993
759  *      The Regents of the University of California.  All rights reserved.
760  *
761  * Redistribution and use in source and binary forms, with or without
762  * modification, are permitted provided that the following conditions
763  * are met:
764  * 1. Redistributions of source code must retain the above copyright
765  *    notice, this list of conditions and the following disclaimer.
766  * 2. Redistributions in binary form must reproduce the above copyright
767  *    notice, this list of conditions and the following disclaimer in the
768  *    documentation and/or other materials provided with the distribution.
769  *
770  * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
771  *              ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
772  *
773  * 4. Neither the name of the University nor the names of its contributors
774  *    may be used to endorse or promote products derived from this software
775  *    without specific prior written permission.
776  *
777  * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
778  * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
779  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
780  * ARE DISCLAIMED.  IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
781  * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
782  * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
783  * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
784  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
785  * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
786  * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
787  * SUCH DAMAGE.
788  *
789  *      $Id: wget.c,v 1.75 2004/10/08 08:27:40 andersen Exp $
790  */