Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/networking/wget.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 816 - (hide annotations) (download)
Fri Apr 24 18:33:46 2009 UTC (15 years, 1 month ago) by niro
File MIME type: text/plain
File size: 23046 byte(s)
-updated to busybox-1.13.4
1 niro 532 /* 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 niro 816 #include "libbb.h"
10 niro 532
11     struct host_info {
12     // May be used if we ever will want to free() all xstrdup()s...
13     /* char *allocated; */
14 niro 816 const char *path;
15     const char *user;
16     char *host;
17     int port;
18     smallint is_ftp;
19 niro 532 };
20    
21    
22 niro 816 /* Globals (can be accessed from signal handlers) */
23     struct globals {
24     off_t content_len; /* Content-length of the file */
25     off_t beg_range; /* Range at which continue begins */
26 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
27 niro 816 off_t lastsize;
28     off_t totalsize;
29     off_t transferred; /* Number of bytes transferred so far */
30     const char *curfile; /* Name of current file being transferred */
31     unsigned lastupdate_sec;
32     unsigned start_sec;
33 niro 532 #endif
34 niro 816 smallint chunked; /* chunked transfer encoding */
35     };
36     #define G (*(struct globals*)&bb_common_bufsiz1)
37     struct BUG_G_too_big {
38     char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
39     };
40     #define content_len (G.content_len )
41     #define beg_range (G.beg_range )
42     #define lastsize (G.lastsize )
43     #define totalsize (G.totalsize )
44     #define transferred (G.transferred )
45     #define curfile (G.curfile )
46     #define lastupdate_sec (G.lastupdate_sec )
47     #define start_sec (G.start_sec )
48     #define chunked (G.chunked )
49     #define INIT_G() do { } while (0)
50    
51    
52 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
53     enum {
54     STALLTIME = 5 /* Seconds when xfer considered "stalled" */
55     };
56 niro 816
57     static unsigned int getttywidth(void)
58     {
59     unsigned width;
60     get_terminal_width_height(0, &width, NULL);
61     return width;
62     }
63    
64     static void progressmeter(int flag)
65     {
66     /* We can be called from signal handler */
67     int save_errno = errno;
68     off_t abbrevsize;
69     unsigned since_last_update, elapsed;
70     unsigned ratio;
71     int barlength, i;
72    
73     if (flag == -1) { /* first call to progressmeter */
74     start_sec = monotonic_sec();
75     lastupdate_sec = start_sec;
76     lastsize = 0;
77     totalsize = content_len + beg_range; /* as content_len changes.. */
78     }
79    
80     ratio = 100;
81     if (totalsize != 0 && !chunked) {
82     /* long long helps to have it working even if !LFS */
83     ratio = (unsigned) (100ULL * (transferred+beg_range) / totalsize);
84     if (ratio > 100) ratio = 100;
85     }
86    
87     fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
88    
89     barlength = getttywidth() - 49;
90     if (barlength > 0) {
91     /* god bless gcc for variable arrays :) */
92     i = barlength * ratio / 100;
93     {
94     char buf[i+1];
95     memset(buf, '*', i);
96     buf[i] = '\0';
97     fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
98     }
99     }
100     i = 0;
101     abbrevsize = transferred + beg_range;
102     while (abbrevsize >= 100000) {
103     i++;
104     abbrevsize >>= 10;
105     }
106     /* see http://en.wikipedia.org/wiki/Tera */
107     fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
108    
109     // Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
110    
111     elapsed = monotonic_sec();
112     since_last_update = elapsed - lastupdate_sec;
113     if (transferred > lastsize) {
114     lastupdate_sec = elapsed;
115     lastsize = transferred;
116     if (since_last_update >= STALLTIME) {
117     /* We "cut off" these seconds from elapsed time
118     * by adjusting start time */
119     start_sec += since_last_update;
120     }
121     since_last_update = 0; /* we are un-stalled now */
122     }
123     elapsed -= start_sec; /* now it's "elapsed since start" */
124    
125     if (since_last_update >= STALLTIME) {
126     fprintf(stderr, " - stalled -");
127     } else {
128     off_t to_download = totalsize - beg_range;
129     if (transferred <= 0 || (int)elapsed <= 0 || transferred > to_download || chunked) {
130     fprintf(stderr, "--:--:-- ETA");
131     } else {
132     /* to_download / (transferred/elapsed) - elapsed: */
133     int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed);
134     /* (long long helps to have working ETA even if !LFS) */
135     i = eta % 3600;
136     fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60);
137     }
138     }
139    
140     if (flag == 0) {
141     /* last call to progressmeter */
142     alarm(0);
143     transferred = 0;
144     fputc('\n', stderr);
145     } else {
146     if (flag == -1) { /* first call to progressmeter */
147     signal_SA_RESTART_empty_mask(SIGALRM, progressmeter);
148     }
149     alarm(1);
150     }
151    
152     errno = save_errno;
153     }
154     /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
155     * much of which was blatantly stolen from openssh. */
156     /*-
157     * Copyright (c) 1992, 1993
158     * The Regents of the University of California. All rights reserved.
159     *
160     * Redistribution and use in source and binary forms, with or without
161     * modification, are permitted provided that the following conditions
162     * are met:
163     * 1. Redistributions of source code must retain the above copyright
164     * notice, this list of conditions and the following disclaimer.
165     * 2. Redistributions in binary form must reproduce the above copyright
166     * notice, this list of conditions and the following disclaimer in the
167     * documentation and/or other materials provided with the distribution.
168     *
169     * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change
170     * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change>
171     *
172     * 4. Neither the name of the University nor the names of its contributors
173     * may be used to endorse or promote products derived from this software
174     * without specific prior written permission.
175     *
176     * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
177     * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
178     * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
179     * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
180     * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
181     * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
182     * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
183     * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
184     * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
185     * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
186     * SUCH DAMAGE.
187     *
188     */
189     #else /* FEATURE_WGET_STATUSBAR */
190    
191     static ALWAYS_INLINE void progressmeter(int flag UNUSED_PARAM) { }
192    
193 niro 532 #endif
194    
195 niro 816
196     /* Read NMEMB bytes into PTR from STREAM. Returns the number of bytes read,
197     * and a short count if an eof or non-interrupt error is encountered. */
198     static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
199 niro 532 {
200 niro 816 size_t ret;
201     char *p = (char*)ptr;
202 niro 532
203     do {
204     clearerr(stream);
205 niro 816 ret = fread(p, 1, nmemb, stream);
206     p += ret;
207     nmemb -= ret;
208     } while (nmemb && ferror(stream) && errno == EINTR);
209 niro 532
210 niro 816 return p - (char*)ptr;
211 niro 532 }
212    
213 niro 816 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
214 niro 532 * Returns S, or NULL if an eof or non-interrupt error is encountered. */
215     static char *safe_fgets(char *s, int size, FILE *stream)
216     {
217     char *ret;
218    
219     do {
220     clearerr(stream);
221     ret = fgets(s, size, stream);
222     } while (ret == NULL && ferror(stream) && errno == EINTR);
223    
224     return ret;
225     }
226    
227     #if ENABLE_FEATURE_WGET_AUTHENTICATION
228 niro 816 /* Base64-encode character string. buf is assumed to be char buf[512]. */
229     static char *base64enc_512(char buf[512], const char *str)
230 niro 532 {
231 niro 816 unsigned len = strlen(str);
232     if (len > 512/4*3 - 10) /* paranoia */
233     len = 512/4*3 - 10;
234     bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
235 niro 532 return buf;
236     }
237     #endif
238    
239 niro 816
240     static FILE *open_socket(len_and_sockaddr *lsa)
241 niro 532 {
242 niro 816 FILE *fp;
243    
244     /* glibc 2.4 seems to try seeking on it - ??! */
245     /* hopefully it understands what ESPIPE means... */
246     fp = fdopen(xconnect_stream(lsa), "r+");
247     if (fp == NULL)
248     bb_perror_msg_and_die("fdopen");
249    
250     return fp;
251     }
252    
253    
254     static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
255     {
256     int result;
257     if (s1) {
258     if (!s2) s2 = "";
259     fprintf(fp, "%s%s\r\n", s1, s2);
260     fflush(fp);
261     }
262    
263     do {
264     char *buf_ptr;
265    
266     if (fgets(buf, 510, fp) == NULL) {
267     bb_perror_msg_and_die("error getting response");
268     }
269     buf_ptr = strstr(buf, "\r\n");
270     if (buf_ptr) {
271     *buf_ptr = '\0';
272     }
273     } while (!isdigit(buf[0]) || buf[3] != ' ');
274    
275     buf[3] = '\0';
276     result = xatoi_u(buf);
277     buf[3] = ' ';
278     return result;
279     }
280    
281    
282     static void parse_url(char *src_url, struct host_info *h)
283     {
284     char *url, *p, *sp;
285    
286     /* h->allocated = */ url = xstrdup(src_url);
287    
288     if (strncmp(url, "http://", 7) == 0) {
289     h->port = bb_lookup_port("http", "tcp", 80);
290     h->host = url + 7;
291     h->is_ftp = 0;
292     } else if (strncmp(url, "ftp://", 6) == 0) {
293     h->port = bb_lookup_port("ftp", "tcp", 21);
294     h->host = url + 6;
295     h->is_ftp = 1;
296     } else
297     bb_error_msg_and_die("not an http or ftp url: %s", url);
298    
299     // FYI:
300     // "Real" wget 'http://busybox.net?var=a/b' sends this request:
301     // 'GET /?var=a/b HTTP 1.0'
302     // and saves 'index.html?var=a%2Fb' (we save 'b')
303     // wget 'http://busybox.net?login=john@doe':
304     // request: 'GET /?login=john@doe HTTP/1.0'
305     // saves: 'index.html?login=john@doe' (we save '?login=john@doe')
306     // wget 'http://busybox.net#test/test':
307     // request: 'GET / HTTP/1.0'
308     // saves: 'index.html' (we save 'test')
309     //
310     // We also don't add unique .N suffix if file exists...
311     sp = strchr(h->host, '/');
312     p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
313     p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
314     if (!sp) {
315     h->path = "";
316     } else if (*sp == '/') {
317     *sp = '\0';
318     h->path = sp + 1;
319     } else { // '#' or '?'
320     // http://busybox.net?login=john@doe is a valid URL
321     // memmove converts to:
322     // http:/busybox.nett?login=john@doe...
323     memmove(h->host - 1, h->host, sp - h->host);
324     h->host--;
325     sp[-1] = '\0';
326     h->path = sp;
327     }
328    
329     sp = strrchr(h->host, '@');
330     h->user = NULL;
331     if (sp != NULL) {
332     h->user = h->host;
333     *sp = '\0';
334     h->host = sp + 1;
335     }
336    
337     sp = h->host;
338     }
339    
340    
341     static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
342     {
343     char *s, *hdrval;
344     int c;
345    
346     /* *istrunc = 0; */
347    
348     /* retrieve header line */
349     if (fgets(buf, bufsiz, fp) == NULL)
350     return NULL;
351    
352     /* see if we are at the end of the headers */
353     for (s = buf; *s == '\r'; ++s)
354     continue;
355     if (*s == '\n')
356     return NULL;
357    
358     /* convert the header name to lower case */
359     for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
360     *s = tolower(*s);
361    
362     /* verify we are at the end of the header name */
363     if (*s != ':')
364     bb_error_msg_and_die("bad header line: %s", buf);
365    
366     /* locate the start of the header value */
367     *s++ = '\0';
368     hdrval = skip_whitespace(s);
369    
370     /* locate the end of header */
371     while (*s && *s != '\r' && *s != '\n')
372     ++s;
373    
374     /* end of header found */
375     if (*s) {
376     *s = '\0';
377     return hdrval;
378     }
379    
380     /* Rats! The buffer isn't big enough to hold the entire header value. */
381     while (c = getc(fp), c != EOF && c != '\n')
382     continue;
383     /* *istrunc = 1; */
384     return hdrval;
385     }
386    
387    
388     int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
389     int wget_main(int argc UNUSED_PARAM, char **argv)
390     {
391 niro 532 char buf[512];
392     struct host_info server, target;
393     len_and_sockaddr *lsa;
394 niro 816 int status;
395 niro 532 int port;
396     int try = 5;
397     unsigned opt;
398 niro 816 char *str;
399 niro 532 char *proxy = 0;
400     char *dir_prefix = NULL;
401     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
402     char *extra_headers = NULL;
403     llist_t *headers_llist = NULL;
404     #endif
405     FILE *sfp = NULL; /* socket to web/ftp server */
406 niro 816 FILE *dfp; /* socket to ftp server (data) */
407     char *fname_out; /* where to direct output (-O) */
408     bool got_clen = 0; /* got content-length: from server */
409 niro 532 int output_fd = -1;
410 niro 816 bool use_proxy = 1; /* Use proxies if env vars are set */
411 niro 532 const char *proxy_flag = "on"; /* Use proxies if env vars are set */
412 niro 816 const char *user_agent = "Wget";/* "User-Agent" header field */
413 niro 532
414 niro 816 static const char keywords[] ALIGN1 =
415     "content-length\0""transfer-encoding\0""chunked\0""location\0";
416 niro 532 enum {
417 niro 816 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
418 niro 532 };
419 niro 816 enum {
420     WGET_OPT_CONTINUE = (1 << 0),
421     WGET_OPT_SPIDER = (1 << 1),
422     WGET_OPT_QUIET = (1 << 2),
423     WGET_OPT_OUTNAME = (1 << 3),
424     WGET_OPT_PREFIX = (1 << 4),
425     WGET_OPT_PROXY = (1 << 5),
426     WGET_OPT_USER_AGENT = (1 << 6),
427     WGET_OPT_RETRIES = (1 << 7),
428     WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
429     WGET_OPT_PASSIVE = (1 << 9),
430     WGET_OPT_HEADER = (1 << 10),
431     };
432 niro 532 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
433 niro 816 static const char wget_longopts[] ALIGN1 =
434     /* name, has_arg, val */
435     "continue\0" No_argument "c"
436     "spider\0" No_argument "s"
437     "quiet\0" No_argument "q"
438     "output-document\0" Required_argument "O"
439     "directory-prefix\0" Required_argument "P"
440     "proxy\0" Required_argument "Y"
441     "user-agent\0" Required_argument "U"
442     /* Ignored: */
443     // "tries\0" Required_argument "t"
444     // "timeout\0" Required_argument "T"
445     /* Ignored (we always use PASV): */
446     "passive-ftp\0" No_argument "\xff"
447     "header\0" Required_argument "\xfe"
448     ;
449 niro 532 #endif
450 niro 816
451     INIT_G();
452    
453     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
454     applet_long_options = wget_longopts;
455     #endif
456     /* server.allocated = target.allocated = NULL; */
457 niro 532 opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
458 niro 816 opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
459 niro 532 &fname_out, &dir_prefix,
460 niro 816 &proxy_flag, &user_agent,
461     NULL, /* -t RETRIES */
462     NULL /* -T NETWORK_READ_TIMEOUT */
463 niro 532 USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
464     );
465     if (strcmp(proxy_flag, "off") == 0) {
466 niro 816 /* Use the proxy if necessary */
467 niro 532 use_proxy = 0;
468     }
469     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
470     if (headers_llist) {
471     int size = 1;
472     char *cp;
473 niro 816 llist_t *ll = headers_llist;
474 niro 532 while (ll) {
475     size += strlen(ll->data) + 2;
476     ll = ll->link;
477     }
478     extra_headers = cp = xmalloc(size);
479     while (headers_llist) {
480 niro 816 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
481 niro 532 }
482     }
483     #endif
484    
485     parse_url(argv[optind], &target);
486     server.host = target.host;
487     server.port = target.port;
488    
489 niro 816 /* Use the proxy if necessary */
490 niro 532 if (use_proxy) {
491     proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
492     if (proxy && *proxy) {
493     parse_url(proxy, &server);
494     } else {
495     use_proxy = 0;
496     }
497     }
498    
499 niro 816 /* Guess an output filename, if there was no -O FILE */
500     if (!(opt & WGET_OPT_OUTNAME)) {
501     fname_out = bb_get_last_path_component_nostrip(target.path);
502     /* handle "wget http://kernel.org//" */
503     if (fname_out[0] == '/' || !fname_out[0])
504     fname_out = (char*)"index.html";
505     /* -P DIR is considered only if there was no -O FILE */
506     if (dir_prefix)
507     fname_out = concat_path_file(dir_prefix, fname_out);
508     } else {
509     if (LONE_DASH(fname_out)) {
510     /* -O - */
511     output_fd = 1;
512     opt &= ~WGET_OPT_CONTINUE;
513 niro 532 }
514 niro 816 }
515 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
516 niro 816 curfile = bb_get_last_path_component_nostrip(fname_out);
517 niro 532 #endif
518 niro 816
519 niro 532 /* Impossible?
520     if ((opt & WGET_OPT_CONTINUE) && !fname_out)
521     bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */
522    
523 niro 816 /* Determine where to start transfer */
524 niro 532 if (opt & WGET_OPT_CONTINUE) {
525     output_fd = open(fname_out, O_WRONLY);
526     if (output_fd >= 0) {
527     beg_range = xlseek(output_fd, 0, SEEK_END);
528     }
529     /* File doesn't exist. We do not create file here yet.
530     We are not sure it exists on remove side */
531     }
532    
533     /* We want to do exactly _one_ DNS lookup, since some
534     * sites (i.e. ftp.us.debian.org) use round-robin DNS
535     * and we want to connect to only one IP... */
536 niro 816 lsa = xhost2sockaddr(server.host, server.port);
537 niro 532 if (!(opt & WGET_OPT_QUIET)) {
538 niro 816 fprintf(stderr, "Connecting to %s (%s)\n", server.host,
539     xmalloc_sockaddr2dotted(&lsa->u.sa));
540     /* We leak result of xmalloc_sockaddr2dotted */
541 niro 532 }
542    
543     if (use_proxy || !target.is_ftp) {
544     /*
545     * HTTP session
546     */
547     do {
548 niro 816 got_clen = 0;
549     chunked = 0;
550 niro 532
551     if (!--try)
552     bb_error_msg_and_die("too many redirections");
553    
554 niro 816 /* Open socket to http server */
555 niro 532 if (sfp) fclose(sfp);
556     sfp = open_socket(lsa);
557    
558 niro 816 /* Send HTTP request. */
559 niro 532 if (use_proxy) {
560     fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
561     target.is_ftp ? "f" : "ht", target.host,
562     target.path);
563     } else {
564     fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
565     }
566    
567     fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
568     target.host, user_agent);
569    
570     #if ENABLE_FEATURE_WGET_AUTHENTICATION
571     if (target.user) {
572 niro 816 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
573     base64enc_512(buf, target.user));
574 niro 532 }
575     if (use_proxy && server.user) {
576     fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
577 niro 816 base64enc_512(buf, server.user));
578 niro 532 }
579     #endif
580    
581     if (beg_range)
582     fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
583     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
584     if (extra_headers)
585     fputs(extra_headers, sfp);
586     #endif
587     fprintf(sfp, "Connection: close\r\n\r\n");
588    
589     /*
590     * Retrieve HTTP response line and check for "200" status code.
591     */
592     read_response:
593     if (fgets(buf, sizeof(buf), sfp) == NULL)
594     bb_error_msg_and_die("no response from server");
595    
596 niro 816 str = buf;
597     str = skip_non_whitespace(str);
598     str = skip_whitespace(str);
599 niro 532 // FIXME: no error check
600     // xatou wouldn't work: "200 OK"
601 niro 816 status = atoi(str);
602 niro 532 switch (status) {
603     case 0:
604     case 100:
605 niro 816 while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
606 niro 532 /* eat all remaining headers */;
607     goto read_response;
608     case 200:
609 niro 816 /*
610     Response 204 doesn't say "null file", it says "metadata
611     has changed but data didn't":
612    
613     "10.2.5 204 No Content
614     The server has fulfilled the request but does not need to return
615     an entity-body, and might want to return updated metainformation.
616     The response MAY include new or updated metainformation in the form
617     of entity-headers, which if present SHOULD be associated with
618     the requested variant.
619    
620     If the client is a user agent, it SHOULD NOT change its document
621     view from that which caused the request to be sent. This response
622     is primarily intended to allow input for actions to take place
623     without causing a change to the user agent's active document view,
624     although any new or updated metainformation SHOULD be applied
625     to the document currently in the user agent's active view.
626    
627     The 204 response MUST NOT include a message-body, and thus
628     is always terminated by the first empty line after the header fields."
629    
630     However, in real world it was observed that some web servers
631     (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
632     */
633     case 204:
634 niro 532 break;
635     case 300: /* redirection */
636     case 301:
637     case 302:
638     case 303:
639     break;
640     case 206:
641     if (beg_range)
642     break;
643 niro 816 /* fall through */
644 niro 532 default:
645     /* Show first line only and kill any ESC tricks */
646     buf[strcspn(buf, "\n\r\x1b")] = '\0';
647     bb_error_msg_and_die("server returned error: %s", buf);
648     }
649    
650     /*
651     * Retrieve HTTP headers.
652     */
653 niro 816 while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
654     /* gethdr did already convert the "FOO:" string to lowercase */
655     smalluint key = index_in_strings(keywords, *&buf) + 1;
656     if (key == KEY_content_length) {
657     content_len = BB_STRTOOFF(str, NULL, 10);
658 niro 532 if (errno || content_len < 0) {
659 niro 816 bb_error_msg_and_die("content-length %s is garbage", str);
660 niro 532 }
661     got_clen = 1;
662     continue;
663     }
664 niro 816 if (key == KEY_transfer_encoding) {
665     if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
666     bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
667 niro 532 chunked = got_clen = 1;
668     }
669 niro 816 if (key == KEY_location) {
670     if (str[0] == '/')
671 niro 532 /* free(target.allocated); */
672 niro 816 target.path = /* target.allocated = */ xstrdup(str+1);
673 niro 532 else {
674 niro 816 parse_url(str, &target);
675 niro 532 if (use_proxy == 0) {
676     server.host = target.host;
677     server.port = target.port;
678     }
679     free(lsa);
680 niro 816 lsa = xhost2sockaddr(server.host, server.port);
681 niro 532 break;
682     }
683     }
684     }
685     } while (status >= 300);
686    
687     dfp = sfp;
688    
689     } else {
690    
691     /*
692     * FTP session
693     */
694     if (!target.user)
695     target.user = xstrdup("anonymous:busybox@");
696    
697     sfp = open_socket(lsa);
698     if (ftpcmd(NULL, NULL, sfp, buf) != 220)
699     bb_error_msg_and_die("%s", buf+4);
700    
701     /*
702     * Splitting username:password pair,
703     * trying to log in
704     */
705 niro 816 str = strchr(target.user, ':');
706     if (str)
707     *(str++) = '\0';
708 niro 532 switch (ftpcmd("USER ", target.user, sfp, buf)) {
709     case 230:
710     break;
711     case 331:
712 niro 816 if (ftpcmd("PASS ", str, sfp, buf) == 230)
713 niro 532 break;
714 niro 816 /* fall through (failed login) */
715 niro 532 default:
716     bb_error_msg_and_die("ftp login: %s", buf+4);
717     }
718    
719     ftpcmd("TYPE I", NULL, sfp, buf);
720    
721     /*
722     * Querying file size
723     */
724     if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) {
725     content_len = BB_STRTOOFF(buf+4, NULL, 10);
726     if (errno || content_len < 0) {
727     bb_error_msg_and_die("SIZE value is garbage");
728     }
729     got_clen = 1;
730     }
731    
732     /*
733     * Entering passive mode
734     */
735     if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
736     pasv_error:
737     bb_error_msg_and_die("bad response to %s: %s", "PASV", buf);
738     }
739     // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
740     // Server's IP is N1.N2.N3.N4 (we ignore it)
741     // Server's port for data connection is P1*256+P2
742 niro 816 str = strrchr(buf, ')');
743     if (str) str[0] = '\0';
744     str = strrchr(buf, ',');
745     if (!str) goto pasv_error;
746     port = xatou_range(str+1, 0, 255);
747     *str = '\0';
748     str = strrchr(buf, ',');
749     if (!str) goto pasv_error;
750     port += xatou_range(str+1, 0, 255) * 256;
751 niro 532 set_nport(lsa, htons(port));
752     dfp = open_socket(lsa);
753    
754     if (beg_range) {
755     sprintf(buf, "REST %"OFF_FMT"d", beg_range);
756     if (ftpcmd(buf, NULL, sfp, buf) == 350)
757     content_len -= beg_range;
758     }
759    
760     if (ftpcmd("RETR ", target.path, sfp, buf) > 150)
761 niro 816 bb_error_msg_and_die("bad response to %s: %s", "RETR", buf);
762 niro 532 }
763    
764 niro 816 if (opt & WGET_OPT_SPIDER) {
765     if (ENABLE_FEATURE_CLEAN_UP)
766     fclose(sfp);
767     return EXIT_SUCCESS;
768     }
769 niro 532
770     /*
771     * Retrieve file
772     */
773    
774     /* Do it before progressmeter (want to have nice error message) */
775 niro 816 if (output_fd < 0) {
776     int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
777     /* compat with wget: -O FILE can overwrite */
778     if (opt & WGET_OPT_OUTNAME)
779     o_flags = O_WRONLY | O_CREAT | O_TRUNC;
780     output_fd = xopen(fname_out, o_flags);
781     }
782 niro 532
783     if (!(opt & WGET_OPT_QUIET))
784     progressmeter(-1);
785    
786 niro 816 if (chunked)
787     goto get_clen;
788    
789     /* Loops only if chunked */
790     while (1) {
791 niro 532 while (content_len > 0 || !got_clen) {
792 niro 816 int n;
793 niro 532 unsigned rdsz = sizeof(buf);
794 niro 816
795 niro 532 if (content_len < sizeof(buf) && (chunked || got_clen))
796     rdsz = (unsigned)content_len;
797 niro 816 n = safe_fread(buf, rdsz, dfp);
798     if (n <= 0) {
799     if (ferror(dfp)) {
800     /* perror will not work: ferror doesn't set errno */
801     bb_error_msg_and_die(bb_msg_read_error);
802     }
803 niro 532 break;
804     }
805 niro 816 xwrite(output_fd, buf, n);
806 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
807     transferred += n;
808     #endif
809 niro 816 if (got_clen)
810 niro 532 content_len -= n;
811     }
812    
813 niro 816 if (!chunked)
814     break;
815 niro 532
816 niro 816 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
817     get_clen:
818     safe_fgets(buf, sizeof(buf), dfp);
819     content_len = STRTOOFF(buf, NULL, 16);
820     /* FIXME: error check? */
821     if (content_len == 0)
822     break; /* all done! */
823     }
824 niro 532
825     if (!(opt & WGET_OPT_QUIET))
826 niro 816 progressmeter(0);
827 niro 532
828     if ((use_proxy == 0) && target.is_ftp) {
829     fclose(dfp);
830     if (ftpcmd(NULL, NULL, sfp, buf) != 226)
831     bb_error_msg_and_die("ftp error: %s", buf+4);
832     ftpcmd("QUIT", NULL, sfp, buf);
833     }
834    
835 niro 816 return EXIT_SUCCESS;
836 niro 532 }