1 /* vi: set sw=4 ts=4: */
3 * wget - retrieve a file using HTTP
5 * Chip Rosenthal Covad Communications <chip@laserlink.net>
7 * Note: According to RFC2616 section 3.6.1, "All HTTP/1.1 applications MUST be
8 * able to receive and decode the "chunked" transfer-coding, and MUST ignore
9 * chunk-extension extensions they do not understand."
11 * This prevents this particular wget app from completely RFC compliant, and as
12 * such, prevents it from being used as a general purpose web browser... This
13 * is a design decision, since it makes the code smaller.
25 #include <sys/ioctl.h>
28 #include <sys/types.h>
30 #include <sys/socket.h>
31 #include <netinet/in.h>
32 #include <arpa/inet.h>
36 void parse_url(char *url, char **uri_host, int *uri_port, char **uri_path);
37 FILE *open_socket(char *host, int port);
38 char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc);
39 void progressmeter(int flag);
41 /* Globals (can be accessed from signal handlers */
42 static off_t filesize = 0; /* content-length of the file */
43 #ifdef BB_FEATURE_STATUSBAR
44 static char *curfile; /* Name of current file being transferred. */
45 static struct timeval start; /* Time a transfer started. */
46 volatile unsigned long statbytes; /* Number of bytes transferred so far. */
47 /* For progressmeter() -- number of seconds before xfer considered "stalled" */
51 int wget_main(int argc, char **argv)
53 FILE *sfp; /* socket to web server */
54 char *uri_host, *uri_path; /* parsed from command line url */
59 char *fname_out = NULL; /* where to direct output (-O) */
60 int do_continue = 0; /* continue a prev transfer (-c) */
61 long beg_range = 0L; /* range at which continue begins */
62 int got_clen = 0; /* got content-length: from server */
63 FILE *output; /* socket to web server */
68 while ((n = getopt(argc, argv, "cO:")) != EOF) {
74 fname_out = (strcmp(optarg, "-") == 0 ? NULL : optarg);
81 if (argc - optind != 1)
84 /* Guess an output filename */
87 #ifdef BB_FEATURE_STATUSBAR
90 get_last_path_component(argv[optind]);
91 #ifdef BB_FEATURE_STATUSBAR
98 if (do_continue && !fname_out)
99 fatalError("cannot specify continue (-c) without a filename (-O)\n");
101 * Parse url into components.
103 parse_url(argv[optind], &uri_host, &uri_port, &uri_path);
106 * Open socket to server.
108 sfp = open_socket(uri_host, uri_port);
111 * Open the output stream.
113 if (fname_out != NULL) {
114 if ( (output=fopen(fname_out, (do_continue ? "a" : "w")))
116 fatalPerror("fopen(%s)", fname_out);
122 * Determine where to start transfer.
126 if (fstat(fileno(output), &sbuf) < 0)
127 fatalError("fstat()");
128 if (sbuf.st_size > 0)
129 beg_range = sbuf.st_size;
137 fprintf(sfp, "GET %s HTTP/1.1\r\nHost: %s\r\n", uri_path, uri_host);
139 fprintf(sfp, "Range: bytes=%ld-\r\n", beg_range);
140 fputs("Connection: close\r\n\r\n", sfp);
143 * Retrieve HTTP response line and check for "200" status code.
145 if (fgets(buf, sizeof(buf), sfp) == NULL)
146 fatalError("no response from server\n");
147 for (s = buf ; *s != '\0' && !isspace(*s) ; ++s)
149 for ( ; isspace(*s) ; ++s)
155 fatalError("server does not support ranges\n");
161 fatalError("server returned error: %s", buf);
165 * Retrieve HTTP headers.
167 while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) {
168 if (strcmp(buf, "content-length") == 0) {
173 if (strcmp(buf, "transfer-encoding") == 0) {
174 fatalError("server wants to do %s transfer encoding\n", s);
180 * Retrieve HTTP body.
182 #ifdef BB_FEATURE_STATUSBAR
186 while (filesize > 0 && (n = fread(buf, 1, sizeof(buf), sfp)) > 0) {
187 fwrite(buf, 1, n, output);
188 #ifdef BB_FEATURE_STATUSBAR
195 if (n == 0 && ferror(sfp))
196 fatalPerror("network read error");
202 void parse_url(char *url, char **uri_host, int *uri_port, char **uri_path)
208 if (strncmp(url, "http://", 7) != 0)
209 fatalError("not an http url: %s\n", url);
211 /* pull the host portion to the front of the buffer */
212 for (s = url, h = url+7 ; *h != '/' ; ++h) {
214 fatalError("cannot parse url: %s\n", url);
216 *uri_port = atoi(h+1);
227 FILE *open_socket(char *host, int port)
229 struct sockaddr_in sin;
234 memzero(&sin, sizeof(sin));
235 sin.sin_family = AF_INET;
236 if ((hp = (struct hostent *) gethostbyname(host)) == NULL)
237 fatalError("cannot resolve %s\n", host);
238 memcpy(&sin.sin_addr, hp->h_addr_list[0], hp->h_length);
239 sin.sin_port = htons(port);
242 * Get the server onto a stdio stream.
244 if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
245 fatalPerror("socket()");
246 if (connect(fd, (struct sockaddr *) &sin, sizeof(sin)) < 0)
247 fatalPerror("connect(%s)", host);
248 if ((fp = fdopen(fd, "r+")) == NULL)
249 fatalPerror("fdopen()");
255 char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc)
262 /* retrieve header line */
263 if (fgets(buf, bufsiz, fp) == NULL)
266 /* see if we are at the end of the headers */
267 for (s = buf ; *s == '\r' ; ++s)
272 /* convert the header name to lower case */
273 for (s = buf ; isalnum(*s) || *s == '-' ; ++s)
276 /* verify we are at the end of the header name */
278 fatalError("bad header line: %s\n", buf);
280 /* locate the start of the header value */
281 for (*s++ = '\0' ; *s == ' ' || *s == '\t' ; ++s)
285 /* locate the end of header */
286 while (*s != '\0' && *s != '\r' && *s != '\n')
289 /* end of header found */
295 /* Rats! The buffer isn't big enough to hold the entire header value. */
296 while (c = getc(fp), c != EOF && c != '\n')
302 #ifdef BB_FEATURE_STATUSBAR
303 /* Stuff below is from BSD rcp util.c, as added to openshh.
304 * Original copyright notice is retained at the end of this file.
312 struct winsize winsize;
314 if (ioctl(fileno(stdout), TIOCGWINSZ, &winsize) != -1)
315 return (winsize.ws_col ? winsize.ws_col : 80);
321 updateprogressmeter(int ignore)
323 int save_errno = errno;
332 struct itimerval itv;
334 itv.it_value.tv_sec = wait;
335 itv.it_value.tv_usec = 0;
336 itv.it_interval = itv.it_value;
337 setitimer(ITIMER_REAL, &itv, NULL);
342 progressmeter(int flag)
344 static const char prefixes[] = " KMGTP";
345 static struct timeval lastupdate;
346 static off_t lastsize;
347 struct timeval now, td, wait;
348 off_t cursize, abbrevsize;
350 int ratio, barlength, i, remaining;
354 (void) gettimeofday(&start, (struct timezone *) 0);
359 (void) gettimeofday(&now, (struct timezone *) 0);
362 ratio = 100.0 * cursize / filesize;
363 ratio = MAX(ratio, 0);
364 ratio = MIN(ratio, 100);
368 snprintf(buf, sizeof(buf), "\r%-20.20s %3d%% ", curfile, ratio);
370 barlength = getttywidth() - 51;
372 i = barlength * ratio / 100;
373 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
375 "*****************************************************************************"
376 "*****************************************************************************",
380 abbrevsize = cursize;
381 while (abbrevsize >= 100000 && i < sizeof(prefixes)) {
385 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf), " %5d %c%c ",
386 (int) abbrevsize, prefixes[i], prefixes[i] == ' ' ? ' ' :
389 timersub(&now, &lastupdate, &wait);
390 if (cursize > lastsize) {
393 if (wait.tv_sec >= STALLTIME) {
394 start.tv_sec += wait.tv_sec;
395 start.tv_usec += wait.tv_usec;
399 timersub(&now, &start, &td);
400 elapsed = td.tv_sec + (td.tv_usec / 1000000.0);
402 if (statbytes <= 0 || elapsed <= 0.0 || cursize > filesize) {
403 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
405 } else if (wait.tv_sec >= STALLTIME) {
406 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
409 remaining = (int) (filesize / (statbytes / elapsed) - elapsed);
410 i = remaining / 3600;
412 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
415 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
417 i = remaining % 3600;
418 snprintf(buf + strlen(buf), sizeof(buf) - strlen(buf),
419 "%02d:%02d ETA", i / 60, i % 60);
421 write(fileno(stdout), buf, strlen(buf));
425 sa.sa_handler = updateprogressmeter;
426 sigemptyset(&sa.sa_mask);
427 sa.sa_flags = SA_RESTART;
428 sigaction(SIGALRM, &sa, NULL);
430 } else if (flag == 1) {
437 /* Original copyright notice which applies to the BB_FEATURE_STATUSBAR stuff,
438 * much of which was blatently stolen from openssh. */
441 * Copyright (c) 1992, 1993
442 * The Regents of the University of California. All rights reserved.
444 * Redistribution and use in source and binary forms, with or without
445 * modification, are permitted provided that the following conditions
447 * 1. Redistributions of source code must retain the above copyright
448 * notice, this list of conditions and the following disclaimer.
449 * 2. Redistributions in binary form must reproduce the above copyright
450 * notice, this list of conditions and the following disclaimer in the
451 * documentation and/or other materials provided with the distribution.
453 * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
454 * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
456 * 4. Neither the name of the University nor the names of its contributors
457 * may be used to endorse or promote products derived from this software
458 * without specific prior written permission.
460 * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
461 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
462 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
463 * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
464 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
465 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
466 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
467 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
468 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
469 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
472 * $Id: wget.c,v 1.7 2000/11/14 23:29:24 andersen Exp $
479 c-file-style: "linux"