1 /* vi: set sw=4 ts=4: */
3 * wget - retrieve a file using HTTP or FTP
5 * Chip Rosenthal Covad Communications <chip@laserlink.net>
7 * Licensed under GPLv2, see file LICENSE in this tarball for details.
12 // May be used if we ever will want to free() all xstrdup()s...
13 /* char *allocated; */
22 /* Globals (can be accessed from signal handlers) */
24 off_t content_len; /* Content-length of the file */
25 off_t beg_range; /* Range at which continue begins */
26 #if ENABLE_FEATURE_WGET_STATUSBAR
29 off_t transferred; /* Number of bytes transferred so far */
30 const char *curfile; /* Name of current file being transferred */
31 unsigned lastupdate_sec;
34 smallint chunked; /* chunked transfer encoding */
35 smallint got_clen; /* got content-length: from server */
37 #define G (*(struct globals*)&bb_common_bufsiz1)
38 struct BUG_G_too_big {
39 char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
41 #define content_len (G.content_len )
42 #define beg_range (G.beg_range )
43 #define lastsize (G.lastsize )
44 #define totalsize (G.totalsize )
45 #define transferred (G.transferred )
46 #define curfile (G.curfile )
47 #define lastupdate_sec (G.lastupdate_sec )
48 #define start_sec (G.start_sec )
49 #define INIT_G() do { } while (0)
52 #if ENABLE_FEATURE_WGET_STATUSBAR
54 STALLTIME = 5 /* Seconds when xfer considered "stalled" */
57 static unsigned int get_tty2_width(void)
60 get_terminal_width_height(2, &width, NULL);
64 static void progress_meter(int flag)
66 /* We can be called from signal handler */
67 int save_errno = errno;
69 unsigned since_last_update, elapsed;
73 if (flag == -1) { /* first call to progress_meter */
74 start_sec = monotonic_sec();
75 lastupdate_sec = start_sec;
77 totalsize = content_len + beg_range; /* as content_len changes.. */
81 if (totalsize != 0 && !G.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;
87 fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio);
89 barlength = get_tty2_width() - 49;
91 /* god bless gcc for variable arrays :) */
92 i = barlength * ratio / 100;
97 fprintf(stderr, "|%s%*s|", buf, barlength - i, "");
101 abbrevsize = transferred + beg_range;
102 while (abbrevsize >= 100000) {
106 /* see http://en.wikipedia.org/wiki/Tera */
107 fprintf(stderr, "%6d%c ", (int)abbrevsize, " kMGTPEZY"[i]);
109 // Nuts! Ain't it easier to update progress meter ONLY when we transferred++?
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;
121 since_last_update = 0; /* we are un-stalled now */
123 elapsed -= start_sec; /* now it's "elapsed since start" */
125 if (since_last_update >= STALLTIME) {
126 fprintf(stderr, " - stalled -");
128 off_t to_download = totalsize - beg_range;
129 if (transferred <= 0 || (int)elapsed <= 0 || transferred > to_download || G.chunked) {
130 fprintf(stderr, "--:--:-- ETA");
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) */
136 fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60);
141 /* last call to progress_meter */
146 if (flag == -1) { /* first call to progress_meter */
147 signal_SA_RESTART_empty_mask(SIGALRM, progress_meter);
154 /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff,
155 * much of which was blatantly stolen from openssh. */
157 * Copyright (c) 1992, 1993
158 * The Regents of the University of California. All rights reserved.
160 * Redistribution and use in source and binary forms, with or without
161 * modification, are permitted provided that the following conditions
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.
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>
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.
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
189 #else /* FEATURE_WGET_STATUSBAR */
191 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
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)
201 char *p = (char*)ptr;
206 ret = fread(p, 1, nmemb, stream);
209 } while (nmemb && ferror(stream) && errno == EINTR);
211 return p - (char*)ptr;
214 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
215 * Returns S, or NULL if an eof or non-interrupt error is encountered. */
216 static char *safe_fgets(char *s, int size, FILE *stream)
223 ret = fgets(s, size, stream);
224 } while (ret == NULL && ferror(stream) && errno == EINTR);
229 #if ENABLE_FEATURE_WGET_AUTHENTICATION
230 /* Base64-encode character string. buf is assumed to be char buf[512]. */
231 static char *base64enc_512(char buf[512], const char *str)
233 unsigned len = strlen(str);
234 if (len > 512/4*3 - 10) /* paranoia */
236 bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
241 static char* sanitize_string(char *s)
243 unsigned char *p = (void *) s;
250 static FILE *open_socket(len_and_sockaddr *lsa)
254 /* glibc 2.4 seems to try seeking on it - ??! */
255 /* hopefully it understands what ESPIPE means... */
256 fp = fdopen(xconnect_stream(lsa), "r+");
258 bb_perror_msg_and_die("fdopen");
263 static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
268 fprintf(fp, "%s%s\r\n", s1, s2);
275 if (fgets(buf, 510, fp) == NULL) {
276 bb_perror_msg_and_die("error getting response");
278 buf_ptr = strstr(buf, "\r\n");
282 } while (!isdigit(buf[0]) || buf[3] != ' ');
285 result = xatoi_u(buf);
290 static void parse_url(char *src_url, struct host_info *h)
294 /* h->allocated = */ url = xstrdup(src_url);
296 if (strncmp(url, "http://", 7) == 0) {
297 h->port = bb_lookup_port("http", "tcp", 80);
300 } else if (strncmp(url, "ftp://", 6) == 0) {
301 h->port = bb_lookup_port("ftp", "tcp", 21);
305 bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
308 // "Real" wget 'http://busybox.net?var=a/b' sends this request:
309 // 'GET /?var=a/b HTTP 1.0'
310 // and saves 'index.html?var=a%2Fb' (we save 'b')
311 // wget 'http://busybox.net?login=john@doe':
312 // request: 'GET /?login=john@doe HTTP/1.0'
313 // saves: 'index.html?login=john@doe' (we save '?login=john@doe')
314 // wget 'http://busybox.net#test/test':
315 // request: 'GET / HTTP/1.0'
316 // saves: 'index.html' (we save 'test')
318 // We also don't add unique .N suffix if file exists...
319 sp = strchr(h->host, '/');
320 p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
321 p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
324 } else if (*sp == '/') {
327 } else { // '#' or '?'
328 // http://busybox.net?login=john@doe is a valid URL
329 // memmove converts to:
330 // http:/busybox.nett?login=john@doe...
331 memmove(h->host - 1, h->host, sp - h->host);
337 sp = strrchr(h->host, '@');
348 static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
355 /* retrieve header line */
356 if (fgets(buf, bufsiz, fp) == NULL)
359 /* see if we are at the end of the headers */
360 for (s = buf; *s == '\r'; ++s)
365 /* convert the header name to lower case */
366 for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
369 /* verify we are at the end of the header name */
371 bb_error_msg_and_die("bad header line: %s", sanitize_string(buf));
373 /* locate the start of the header value */
375 hdrval = skip_whitespace(s);
377 /* locate the end of header */
378 while (*s && *s != '\r' && *s != '\n')
381 /* end of header found */
387 /* Rats! The buffer isn't big enough to hold the entire header value */
388 while (c = getc(fp), c != EOF && c != '\n')
394 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
395 static char *URL_escape(const char *str)
397 /* URL encode, see RFC 2396 */
399 char *res = dst = xmalloc(strlen(str) * 3 + 1);
405 /* || strchr("!&'()*-.=_~", c) - more code */
417 || (c >= '0' && c <= '9')
418 || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
425 *dst++ = bb_hexdigits_upcase[c >> 4];
426 *dst++ = bb_hexdigits_upcase[c & 0xf];
432 static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
440 target->user = xstrdup("anonymous:busybox@");
442 sfp = open_socket(lsa);
443 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
444 bb_error_msg_and_die("%s", sanitize_string(buf+4));
447 * Splitting username:password pair,
450 str = strchr(target->user, ':');
453 switch (ftpcmd("USER ", target->user, sfp, buf)) {
457 if (ftpcmd("PASS ", str, sfp, buf) == 230)
459 /* fall through (failed login) */
461 bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
464 ftpcmd("TYPE I", NULL, sfp, buf);
469 if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
470 content_len = BB_STRTOOFF(buf+4, NULL, 10);
471 if (errno || content_len < 0) {
472 bb_error_msg_and_die("SIZE value is garbage");
478 * Entering passive mode
480 if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
482 bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
484 // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
485 // Server's IP is N1.N2.N3.N4 (we ignore it)
486 // Server's port for data connection is P1*256+P2
487 str = strrchr(buf, ')');
488 if (str) str[0] = '\0';
489 str = strrchr(buf, ',');
490 if (!str) goto pasv_error;
491 port = xatou_range(str+1, 0, 255);
493 str = strrchr(buf, ',');
494 if (!str) goto pasv_error;
495 port += xatou_range(str+1, 0, 255) * 256;
496 set_nport(lsa, htons(port));
498 *dfpp = open_socket(lsa);
501 sprintf(buf, "REST %"OFF_FMT"d", beg_range);
502 if (ftpcmd(buf, NULL, sfp, buf) == 350)
503 content_len -= beg_range;
506 if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
507 bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
512 /* Must match option string! */
514 WGET_OPT_CONTINUE = (1 << 0),
515 WGET_OPT_SPIDER = (1 << 1),
516 WGET_OPT_QUIET = (1 << 2),
517 WGET_OPT_OUTNAME = (1 << 3),
518 WGET_OPT_PREFIX = (1 << 4),
519 WGET_OPT_PROXY = (1 << 5),
520 WGET_OPT_USER_AGENT = (1 << 6),
521 WGET_OPT_RETRIES = (1 << 7),
522 WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
523 WGET_OPT_PASSIVE = (1 << 9),
524 WGET_OPT_HEADER = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
525 WGET_OPT_POST_DATA = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
528 static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
532 if (!(option_mask32 & WGET_OPT_QUIET))
538 /* Loops only if chunked */
540 while (content_len > 0 || !G.got_clen) {
542 unsigned rdsz = sizeof(buf);
544 if (content_len < sizeof(buf) && (G.chunked || G.got_clen))
545 rdsz = (unsigned)content_len;
546 n = safe_fread(buf, rdsz, dfp);
549 /* perror will not work: ferror doesn't set errno */
550 bb_error_msg_and_die(bb_msg_read_error);
554 xwrite(output_fd, buf, n);
555 #if ENABLE_FEATURE_WGET_STATUSBAR
565 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
567 safe_fgets(buf, sizeof(buf), dfp);
568 content_len = STRTOOFF(buf, NULL, 16);
569 /* FIXME: error check? */
570 if (content_len == 0)
571 break; /* all done! */
574 if (!(option_mask32 & WGET_OPT_QUIET))
578 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
579 int wget_main(int argc UNUSED_PARAM, char **argv)
582 struct host_info server, target;
583 len_and_sockaddr *lsa;
587 char *dir_prefix = NULL;
588 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
590 char *extra_headers = NULL;
591 llist_t *headers_llist = NULL;
593 FILE *sfp; /* socket to web/ftp server */
594 FILE *dfp; /* socket to ftp server (data) */
595 char *fname_out; /* where to direct output (-O) */
597 bool use_proxy; /* Use proxies if env vars are set */
598 const char *proxy_flag = "on"; /* Use proxies if env vars are set */
599 const char *user_agent = "Wget";/* "User-Agent" header field */
601 static const char keywords[] ALIGN1 =
602 "content-length\0""transfer-encoding\0""chunked\0""location\0";
604 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
606 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
607 static const char wget_longopts[] ALIGN1 =
608 /* name, has_arg, val */
609 "continue\0" No_argument "c"
610 "spider\0" No_argument "s"
611 "quiet\0" No_argument "q"
612 "output-document\0" Required_argument "O"
613 "directory-prefix\0" Required_argument "P"
614 "proxy\0" Required_argument "Y"
615 "user-agent\0" Required_argument "U"
617 // "tries\0" Required_argument "t"
618 // "timeout\0" Required_argument "T"
619 /* Ignored (we always use PASV): */
620 "passive-ftp\0" No_argument "\xff"
621 "header\0" Required_argument "\xfe"
622 "post-data\0" Required_argument "\xfd"
628 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
629 applet_long_options = wget_longopts;
631 /* server.allocated = target.allocated = NULL; */
632 opt_complementary = "-1" IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
633 opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
634 &fname_out, &dir_prefix,
635 &proxy_flag, &user_agent,
636 NULL, /* -t RETRIES */
637 NULL /* -T NETWORK_READ_TIMEOUT */
638 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
639 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
641 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
645 llist_t *ll = headers_llist;
647 size += strlen(ll->data) + 2;
650 extra_headers = cp = xmalloc(size);
651 while (headers_llist) {
652 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
657 /* TODO: compat issue: should handle "wget URL1 URL2..." */
658 parse_url(argv[optind], &target);
659 server.host = target.host;
660 server.port = target.port;
662 /* Use the proxy if necessary */
663 use_proxy = (strcmp(proxy_flag, "off") != 0);
665 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
666 if (proxy && *proxy) {
667 parse_url(proxy, &server);
673 /* Guess an output filename, if there was no -O FILE */
674 if (!(opt & WGET_OPT_OUTNAME)) {
675 fname_out = bb_get_last_path_component_nostrip(target.path);
676 /* handle "wget http://kernel.org//" */
677 if (fname_out[0] == '/' || !fname_out[0])
678 fname_out = (char*)"index.html";
679 /* -P DIR is considered only if there was no -O FILE */
681 fname_out = concat_path_file(dir_prefix, fname_out);
683 if (LONE_DASH(fname_out)) {
686 opt &= ~WGET_OPT_CONTINUE;
689 #if ENABLE_FEATURE_WGET_STATUSBAR
690 curfile = bb_get_last_path_component_nostrip(fname_out);
694 if ((opt & WGET_OPT_CONTINUE) && !fname_out)
695 bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)");
698 /* Determine where to start transfer */
699 if (opt & WGET_OPT_CONTINUE) {
700 output_fd = open(fname_out, O_WRONLY);
701 if (output_fd >= 0) {
702 beg_range = xlseek(output_fd, 0, SEEK_END);
704 /* File doesn't exist. We do not create file here yet.
705 * We are not sure it exists on remove side */
710 lsa = xhost2sockaddr(server.host, server.port);
711 if (!(opt & WGET_OPT_QUIET)) {
712 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
713 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
717 if (use_proxy || !target.is_ftp) {
724 /* Open socket to http server */
725 sfp = open_socket(lsa);
727 /* Send HTTP request */
729 fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
730 target.is_ftp ? "f" : "ht", target.host,
733 if (opt & WGET_OPT_POST_DATA)
734 fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
736 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
739 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
740 target.host, user_agent);
742 #if ENABLE_FEATURE_WGET_AUTHENTICATION
744 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
745 base64enc_512(buf, target.user));
747 if (use_proxy && server.user) {
748 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
749 base64enc_512(buf, server.user));
754 fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range);
755 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
757 fputs(extra_headers, sfp);
759 if (opt & WGET_OPT_POST_DATA) {
760 char *estr = URL_escape(post_data);
761 fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
762 fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
763 (int) strlen(estr), estr);
764 /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
765 /*fprintf(sfp, "%s\r\n", estr);*/
769 { /* If "Connection:" is needed, document why */
770 fprintf(sfp, /* "Connection: close\r\n" */ "\r\n");
774 * Retrieve HTTP response line and check for "200" status code.
777 if (fgets(buf, sizeof(buf), sfp) == NULL)
778 bb_error_msg_and_die("no response from server");
781 str = skip_non_whitespace(str);
782 str = skip_whitespace(str);
783 // FIXME: no error check
784 // xatou wouldn't work: "200 OK"
789 while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
790 /* eat all remaining headers */;
794 Response 204 doesn't say "null file", it says "metadata
795 has changed but data didn't":
797 "10.2.5 204 No Content
798 The server has fulfilled the request but does not need to return
799 an entity-body, and might want to return updated metainformation.
800 The response MAY include new or updated metainformation in the form
801 of entity-headers, which if present SHOULD be associated with
802 the requested variant.
804 If the client is a user agent, it SHOULD NOT change its document
805 view from that which caused the request to be sent. This response
806 is primarily intended to allow input for actions to take place
807 without causing a change to the user agent's active document view,
808 although any new or updated metainformation SHOULD be applied
809 to the document currently in the user agent's active view.
811 The 204 response MUST NOT include a message-body, and thus
812 is always terminated by the first empty line after the header fields."
814 However, in real world it was observed that some web servers
815 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
819 case 300: /* redirection */
829 bb_error_msg_and_die("server returned error: %s", sanitize_string(buf));
833 * Retrieve HTTP headers.
835 while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
836 /* gethdr converted "FOO:" string to lowercase */
837 smalluint key = index_in_strings(keywords, buf) + 1;
838 if (key == KEY_content_length) {
839 content_len = BB_STRTOOFF(str, NULL, 10);
840 if (errno || content_len < 0) {
841 bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
846 if (key == KEY_transfer_encoding) {
847 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
848 bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
849 G.chunked = G.got_clen = 1;
851 if (key == KEY_location && status >= 300) {
852 if (--redir_limit == 0)
853 bb_error_msg_and_die("too many redirections");
858 /* free(target.allocated); */
859 target.path = /* target.allocated = */ xstrdup(str+1);
860 /* lsa stays the same: it's on the same server */
862 parse_url(str, &target);
864 server.host = target.host;
865 server.port = target.port;
868 } /* else: lsa stays the same: we use proxy */
870 goto establish_session;
873 // if (status >= 300)
874 // bb_error_msg_and_die("bad redirection (no Location: header from server)");
876 /* For HTTP, data is pumped over the same connection */
883 sfp = prepare_ftp_session(&dfp, &target, lsa);
886 if (opt & WGET_OPT_SPIDER) {
887 if (ENABLE_FEATURE_CLEAN_UP)
893 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
894 /* compat with wget: -O FILE can overwrite */
895 if (opt & WGET_OPT_OUTNAME)
896 o_flags = O_WRONLY | O_CREAT | O_TRUNC;
897 output_fd = xopen(fname_out, o_flags);
900 retrieve_file_data(dfp, output_fd);
903 /* It's ftp. Close it properly */
905 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
906 bb_error_msg_and_die("ftp error: %s", sanitize_string(buf+4));
907 ftpcmd("QUIT", NULL, sfp, buf);