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