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