1 /* vi: set sw=4 ts=4: */
3 * wget - retrieve a file using HTTP or FTP
5 * Chip Rosenthal Covad Communications <chip@laserlink.net>
6 * Licensed under GPLv2, see file LICENSE in this source tree.
8 * Copyright (C) 2010 Bradley M. Kuhn <bkuhn@ebb.org>
9 * Kuhn's copyrights are licensed GPLv2-or-later. File as a whole remains GPLv2.
12 //config: bool "wget (38 kb)"
15 //config: wget is a utility for non-interactive download of files from HTTP
16 //config: and FTP servers.
18 //config:config FEATURE_WGET_LONG_OPTIONS
19 //config: bool "Enable long options"
21 //config: depends on WGET && LONG_OPTS
23 //config:config FEATURE_WGET_STATUSBAR
24 //config: bool "Enable progress bar (+2k)"
26 //config: depends on WGET
28 //config:config FEATURE_WGET_AUTHENTICATION
29 //config: bool "Enable HTTP authentication"
31 //config: depends on WGET
33 //config: Support authenticated HTTP transfers.
35 //config:config FEATURE_WGET_TIMEOUT
36 //config: bool "Enable timeout option -T SEC"
38 //config: depends on WGET
40 //config: Supports network read and connect timeouts for wget,
41 //config: so that wget will give up and timeout, through the -T
42 //config: command line option.
44 //config: Currently only connect and network data read timeout are
45 //config: supported (i.e., timeout is not applied to the DNS query). When
46 //config: FEATURE_WGET_LONG_OPTIONS is also enabled, the --timeout option
47 //config: will work in addition to -T.
49 //config:config FEATURE_WGET_HTTPS
50 //config: bool "Support HTTPS using internal TLS code"
51 //it also enables FTPS support, but it's not well tested yet
53 //config: depends on WGET
56 //config: wget will use internal TLS code to connect to https:// URLs.
58 //config: On NOMMU machines, ssl_helper applet should be available
59 //config: in the $PATH for this to work. Make sure to select that applet.
61 //config: Note: currently, TLS code only makes TLS I/O work, it
62 //config: does *not* check that the peer is who it claims to be, etc.
63 //config: IOW: it uses peer-supplied public keys to establish encryption
64 //config: and signing keys, then encrypts and signs outgoing data and
65 //config: decrypts incoming data.
66 //config: It does not check signature hashes on the incoming data:
67 //config: this means that attackers manipulating TCP packets can
68 //config: send altered data and we unknowingly receive garbage.
69 //config: (This check might be relatively easy to add).
70 //config: It does not check public key's certificate:
71 //config: this means that the peer may be an attacker impersonating
72 //config: the server we think we are talking to.
74 //config: If you think this is unacceptable, consider this. As more and more
75 //config: servers switch to HTTPS-only operation, without such "crippled"
76 //config: TLS code it is *impossible* to simply download a kernel source
77 //config: from kernel.org. Which can in real world translate into
78 //config: "my small automatic tooling to build cross-compilers from sources
79 //config: no longer works, I need to additionally keep a local copy
80 //config: of ~4 megabyte source tarball of a SSL library and ~2 megabyte
81 //config: source of wget, need to compile and built both before I can
82 //config: download anything. All this despite the fact that the build
83 //config: is done in a QEMU sandbox on a machine with absolutely nothing
84 //config: worth stealing, so I don't care if someone would go to a lot
85 //config: of trouble to intercept my HTTPS download to send me an altered
86 //config: kernel tarball".
88 //config: If you still think this is unacceptable, send patches.
90 //config: If you still think this is unacceptable, do not want to send
91 //config: patches, but do want to waste bandwidth expaining how wrong
92 //config: it is, you will be ignored.
94 //config:config FEATURE_WGET_OPENSSL
95 //config: bool "Try to connect to HTTPS using openssl"
97 //config: depends on WGET
99 //config: Try to use openssl to handle HTTPS.
101 //config: OpenSSL has a simple SSL client for debug purposes.
102 //config: If you select this option, wget will effectively run:
103 //config: "openssl s_client -quiet -connect hostname:443
104 //config: -servername hostname 2>/dev/null" and pipe its data
105 //config: through it. -servername is not used if hostname is numeric.
106 //config: Note inconvenient API: host resolution is done twice,
107 //config: and there is no guarantee openssl's idea of IPv6 address
108 //config: format is the same as ours.
109 //config: Another problem is that s_client prints debug information
110 //config: to stderr, and it needs to be suppressed. This means
111 //config: all error messages get suppressed too.
112 //config: openssl is also a big binary, often dynamically linked
113 //config: against ~15 libraries.
115 //config: If openssl can't be executed, internal TLS code will be used
116 //config: (if you enabled it); if openssl can be executed but fails later,
117 //config: wget can't detect this, and download will fail.
119 //applet:IF_WGET(APPLET(wget, BB_DIR_USR_BIN, BB_SUID_DROP))
121 //kbuild:lib-$(CONFIG_WGET) += wget.o
123 //usage:#define wget_trivial_usage
124 //usage: IF_FEATURE_WGET_LONG_OPTIONS(
125 //usage: "[-c|--continue] [--spider] [-q|--quiet] [-O|--output-document FILE]\n"
126 //usage: " [-o|--output-file FILE] [--header 'header: value'] [-Y|--proxy on/off]\n"
127 /* Since we ignore these opts, we don't show them in --help */
128 /* //usage: " [--no-check-certificate] [--no-cache] [--passive-ftp] [-t TRIES]" */
129 /* //usage: " [-nv] [-nc] [-nH] [-np]" */
130 //usage: " [-P DIR] [-S|--server-response] [-U|--user-agent AGENT]" IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
132 //usage: IF_NOT_FEATURE_WGET_LONG_OPTIONS(
133 //usage: "[-cq] [-O FILE] [-o FILE] [-Y on/off] [-P DIR] [-S] [-U AGENT]"
134 //usage: IF_FEATURE_WGET_TIMEOUT(" [-T SEC]") " URL..."
136 //usage:#define wget_full_usage "\n\n"
137 //usage: "Retrieve files via HTTP or FTP\n"
138 //usage: IF_FEATURE_WGET_LONG_OPTIONS(
139 //usage: "\n --spider Only check URL existence: $? is 0 if exists"
140 ///////: "\n --no-check-certificate Don't validate the server's certificate"
142 //usage: "\n -c Continue retrieval of aborted transfer"
143 //usage: "\n -q Quiet"
144 //usage: "\n -P DIR Save to DIR (default .)"
145 //usage: "\n -S Show server response"
146 //usage: IF_FEATURE_WGET_TIMEOUT(
147 //usage: "\n -T SEC Network read timeout is SEC seconds"
149 //usage: "\n -O FILE Save to FILE ('-' for stdout)"
150 //usage: "\n -o FILE Log messages to FILE"
151 //usage: "\n -U STR Use STR for User-Agent header"
152 //usage: "\n -Y on/off Use proxy"
157 # define log_io(...) bb_error_msg(__VA_ARGS__)
158 # define SENDFMT(fp, fmt, ...) \
160 log_io("> " fmt, ##__VA_ARGS__); \
161 fprintf(fp, fmt, ##__VA_ARGS__); \
164 # define log_io(...) ((void)0)
165 # define SENDFMT(fp, fmt, ...) fprintf(fp, fmt, ##__VA_ARGS__)
169 #define SSL_SUPPORTED (ENABLE_FEATURE_WGET_OPENSSL || ENABLE_FEATURE_WGET_HTTPS)
175 const char *protocol;
179 static const char P_FTP[] ALIGN1 = "ftp";
180 static const char P_HTTP[] ALIGN1 = "http";
182 # if ENABLE_FEATURE_WGET_HTTPS
183 static const char P_FTPS[] ALIGN1 = "ftps";
185 static const char P_HTTPS[] ALIGN1 = "https";
188 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
189 /* User-specified headers prevent using our corresponding built-in headers. */
192 HDR_USER_AGENT = (1<<1),
194 HDR_AUTH = (1<<3) * ENABLE_FEATURE_WGET_AUTHENTICATION,
195 HDR_PROXY_AUTH = (1<<4) * ENABLE_FEATURE_WGET_AUTHENTICATION,
197 static const char wget_user_headers[] ALIGN1 =
201 # if ENABLE_FEATURE_WGET_AUTHENTICATION
203 "Proxy-Authorization:\0"
206 # define USR_HEADER_HOST (G.user_headers & HDR_HOST)
207 # define USR_HEADER_USER_AGENT (G.user_headers & HDR_USER_AGENT)
208 # define USR_HEADER_RANGE (G.user_headers & HDR_RANGE)
209 # define USR_HEADER_AUTH (G.user_headers & HDR_AUTH)
210 # define USR_HEADER_PROXY_AUTH (G.user_headers & HDR_PROXY_AUTH)
211 #else /* No long options, no user-headers :( */
212 # define USR_HEADER_HOST 0
213 # define USR_HEADER_USER_AGENT 0
214 # define USR_HEADER_RANGE 0
215 # define USR_HEADER_AUTH 0
216 # define USR_HEADER_PROXY_AUTH 0
221 off_t content_len; /* Content-length of the file */
222 off_t beg_range; /* Range at which continue begins */
223 #if ENABLE_FEATURE_WGET_STATUSBAR
224 off_t transferred; /* Number of bytes transferred so far */
225 const char *curfile; /* Name of current file being transferred */
229 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
232 unsigned char user_headers; /* Headers mentioned by the user */
234 char *fname_out; /* where to direct output (-O) */
235 char *fname_log; /* where to direct log (-o) */
236 const char *proxy_flag; /* Use proxies if env vars are set */
237 const char *user_agent; /* "User-Agent" header field */
241 #if ENABLE_FEATURE_WGET_TIMEOUT
242 unsigned timeout_seconds;
243 smallint die_if_timed_out;
245 smallint chunked; /* chunked transfer encoding */
246 smallint got_clen; /* got content-length: from server */
247 /* Local downloads do benefit from big buffer.
248 * With 512 byte buffer, it was measured to be
249 * an order of magnitude slower than with big one.
251 char wget_buf[CONFIG_FEATURE_COPYBUF_KB*1024] ALIGNED(16);
253 #define G (*ptr_to_globals)
254 #define INIT_G() do { \
255 SET_PTR_TO_GLOBALS(xzalloc(sizeof(G))); \
257 #define FINI_G() do { \
258 FREE_PTR_TO_GLOBALS(); \
262 /* Must match option string! */
264 WGET_OPT_CONTINUE = (1 << 0),
265 WGET_OPT_QUIET = (1 << 1),
266 WGET_OPT_SERVER_RESPONSE = (1 << 2),
267 WGET_OPT_OUTNAME = (1 << 3),
268 WGET_OPT_LOGNAME = (1 << 4),
269 WGET_OPT_PREFIX = (1 << 5),
270 WGET_OPT_PROXY = (1 << 6),
271 WGET_OPT_USER_AGENT = (1 << 7),
272 WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
273 WGET_OPT_RETRIES = (1 << 9),
274 WGET_OPT_nsomething = (1 << 10),
275 WGET_OPT_HEADER = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
276 WGET_OPT_POST_DATA = (1 << 12) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
277 WGET_OPT_SPIDER = (1 << 13) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
278 WGET_OPT_NO_CHECK_CERT = (1 << 14) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
286 #if ENABLE_FEATURE_WGET_STATUSBAR
287 static void progress_meter(int flag)
291 if (option_mask32 & WGET_OPT_QUIET)
294 /* Don't save progress to log file */
298 if (flag == PROGRESS_START)
299 bb_progress_init(&G.pmt, G.curfile);
301 notty = bb_progress_update(&G.pmt,
304 (G.chunked || !G.got_clen) ? 0 : G.beg_range + G.transferred + G.content_len
307 if (flag == PROGRESS_END) {
308 bb_progress_free(&G.pmt);
310 bb_putchar_stderr('\n'); /* it's tty */
315 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) {}
319 /* IPv6 knows scoped address types i.e. link and site local addresses. Link
320 * local addresses can have a scope identifier to specify the
321 * interface/link an address is valid on (e.g. fe80::1%eth0). This scope
322 * identifier is only valid on a single node.
324 * RFC 4007 says that the scope identifier MUST NOT be sent across the wire,
325 * unless all nodes agree on the semantic. Apache e.g. regards zone identifiers
326 * in the Host header as invalid requests, see
327 * https://issues.apache.org/bugzilla/show_bug.cgi?id=35122
329 static void strip_ipv6_scope_id(char *host)
333 /* bbox wget actually handles IPv6 addresses without [], like
334 * wget "http://::1/xxx", but this is not standard.
335 * To save code, _here_ we do not support it. */
338 return; /* not IPv6 */
340 scope = strchr(host, '%');
344 /* Remove the IPv6 zone identifier from the host address */
345 cp = strchr(host, ']');
346 if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
347 /* malformed address (not "[xx]:nn" or "[xx]") */
351 /* cp points to "]...", scope points to "%eth0]..." */
352 overlapping_strcpy(scope, cp);
355 #if ENABLE_FEATURE_WGET_AUTHENTICATION
356 /* Base64-encode character string. */
357 static char *base64enc(const char *str)
360 unsigned len = strnlen(str, sizeof(G.wget_buf)/4*3 - 10);
361 bb_uuencode(G.wget_buf, str, len, bb_uuenc_tbl_base64);
366 #if ENABLE_FEATURE_WGET_TIMEOUT
367 static void alarm_handler(int sig UNUSED_PARAM)
369 /* This is theoretically unsafe (uses stdio and malloc in signal handler) */
370 if (G.die_if_timed_out)
371 bb_simple_error_msg_and_die("download timed out");
373 static void set_alarm(void)
375 if (G.timeout_seconds) {
376 alarm(G.timeout_seconds);
377 G.die_if_timed_out = 1;
380 # define clear_alarm() ((void)(G.die_if_timed_out = 0))
382 # define set_alarm() ((void)0)
383 # define clear_alarm() ((void)0)
386 #if ENABLE_FEATURE_WGET_OPENSSL
388 * is_ip_address() attempts to verify whether or not a string
389 * contains an IPv4 or IPv6 address (vs. an FQDN). The result
390 * of inet_pton() can be used to determine this.
392 static int is_ip_address(const char *string)
394 struct sockaddr_in sa;
396 int result = inet_pton(AF_INET, string, &(sa.sin_addr));
397 # if ENABLE_FEATURE_IPV6
399 struct sockaddr_in6 sa6;
400 result = inet_pton(AF_INET6, string, &(sa6.sin6_addr));
403 return (result == 1);
407 static FILE *open_socket(len_and_sockaddr *lsa)
413 fd = xconnect_stream(lsa);
416 /* glibc 2.4 seems to try seeking on it - ??! */
417 /* hopefully it understands what ESPIPE means... */
418 fp = fdopen(fd, "r+");
420 bb_die_memory_exhausted();
425 /* We balk at any control chars in other side's messages.
426 * This prevents nasty surprises (e.g. ESC sequences) in "Location:" URLs
427 * and error messages.
429 * The only exception is tabs, which are converted to (one) space:
430 * HTTP's "headers: <whitespace> values" may have those.
432 static char* sanitize_string(char *s)
434 unsigned char *p = (void *) s;
447 /* Returns '\n' if it was seen, else '\0'. Trims at first '\r' or '\n' */
448 static char fgets_trim_sanitize(FILE *fp, const char *fmt)
454 if (fgets(G.wget_buf, sizeof(G.wget_buf), fp) == NULL)
455 bb_simple_perror_msg_and_die("error getting response");
458 buf_ptr = strchrnul(G.wget_buf, '\n');
461 /* Disallow any control chars: trim at first char < 0x20 */
462 sanitize_string(G.wget_buf);
465 buf_ptr = strchrnul(G.wget_buf, '\r');
469 log_io("< %s", G.wget_buf);
471 if (fmt && (option_mask32 & WGET_OPT_SERVER_RESPONSE))
472 fprintf(stderr, fmt, G.wget_buf);
477 static int ftpcmd(const char *s1, const char *s2, FILE *fp)
483 fprintf(fp, "%s%s\r\n", s1, s2);
484 /* With --server-response, wget also shows its ftp commands */
485 if (option_mask32 & WGET_OPT_SERVER_RESPONSE)
486 fprintf(stderr, "--> %s%s\n\n", s1, s2);
488 log_io("> %s%s", s1, s2);
491 /* Read until "Nxx something" is received */
494 fgets_trim_sanitize(fp, "%s\n");
495 } while (!isdigit(G.wget_buf[0]) || G.wget_buf[3] != ' ');
497 G.wget_buf[3] = '\0';
498 result = xatoi_positive(G.wget_buf);
503 static void parse_url(const char *src_url, struct host_info *h)
508 h->allocated = url = xstrdup(src_url);
511 p = strstr(url, "://");
515 if (strcmp(url, P_FTP) == 0) {
516 h->port = bb_lookup_std_port(P_FTP, "tcp", 21);
519 # if ENABLE_FEATURE_WGET_HTTPS
520 if (strcmp(url, P_FTPS) == 0) {
521 h->port = bb_lookup_std_port(P_FTPS, "tcp", 990);
522 h->protocol = P_FTPS;
525 if (strcmp(url, P_HTTPS) == 0) {
526 h->port = bb_lookup_std_port(P_HTTPS, "tcp", 443);
527 h->protocol = P_HTTPS;
530 if (strcmp(url, P_HTTP) == 0) {
532 h->port = bb_lookup_std_port(P_HTTP, "tcp", 80);
533 h->protocol = P_HTTP;
536 bb_error_msg_and_die("not an http or ftp url: %s", url);
539 // GNU wget is user-friendly and falls back to http://
545 // "Real" wget 'http://busybox.net?var=a/b' sends this request:
546 // 'GET /?var=a/b HTTP/1.0'
547 // and saves 'index.html?var=a%2Fb' (we save 'b')
548 // wget 'http://busybox.net?login=john@doe':
549 // request: 'GET /?login=john@doe HTTP/1.0'
550 // saves: 'index.html?login=john@doe' (we save 'login=john@doe')
551 // wget 'http://busybox.net#test/test':
552 // request: 'GET / HTTP/1.0'
553 // saves: 'index.html' (we save 'test')
555 // We also don't add unique .N suffix if file exists...
556 sp = strchr(h->host, '/');
557 p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
558 p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
561 } else if (*sp == '/') {
565 // sp points to '#' or '?'
567 // http://busybox.net?login=john@doe is a valid URL
568 // (without '/' between ".net" and "?"),
569 // can't store NUL at sp[-1] - this destroys hostname.
574 sp = strrchr(h->host, '@');
576 // URL-decode "user:password" string before base64-encoding:
577 // wget http://test:my%20pass@example.com should send
578 // Authorization: Basic dGVzdDpteSBwYXNz
579 // which decodes to "test:my pass".
580 // Standard wget and curl do this too.
583 h->user = xstrdup(percent_decode_in_place(h->host, /*strict:*/ 0));
586 /* else: h->user remains NULL, or as set by original request
587 * before redirect (if we are here after a redirect).
591 static char *get_sanitized_hdr(FILE *fp)
596 /* retrieve header line */
597 c = fgets_trim_sanitize(fp, " %s\n");
599 /* end of the headers? */
600 if (G.wget_buf[0] == '\0')
603 /* convert the header name to lower case */
604 for (s = G.wget_buf; isalnum(*s) || *s == '-' || *s == '.' || *s == '_'; ++s) {
606 * No-op for 20-3f and 60-7f. "0-9a-z-." are in these ranges.
607 * 40-5f range ("@A-Z[\]^_") maps to 60-7f.
608 * "A-Z" maps to "a-z".
609 * "@[\]" can't occur in header names.
610 * "^_" maps to "~,DEL" (which is wrong).
611 * "^" was never seen yet, "_" was seen from web.archive.org
612 * (x-archive-orig-x_commoncrawl_Signature: HEXSTRING).
617 /* verify we are at the end of the header name */
619 bb_error_msg_and_die("bad header line: %s", G.wget_buf);
621 /* locate the start of the header value */
623 hdrval = skip_whitespace(s);
626 /* Rats! The buffer isn't big enough to hold the entire header value */
627 while (c = getc(fp), c != EOF && c != '\n')
634 static void reset_beg_range_to_zero(void)
636 bb_simple_error_msg("restart failed");
638 xlseek(G.output_fd, 0, SEEK_SET);
639 /* Done at the end instead: */
640 /* ftruncate(G.output_fd, 0); */
643 #if ENABLE_FEATURE_WGET_OPENSSL
644 static int spawn_https_helper_openssl(const char *host, unsigned port)
646 char *allocated = NULL;
650 IF_FEATURE_WGET_HTTPS(volatile int child_failed = 0;)
652 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
653 /* Kernel can have AF_UNIX support disabled */
654 bb_simple_perror_msg_and_die("socketpair");
656 if (!strchr(host, ':'))
657 host = allocated = xasprintf("%s:%u", host, port);
658 servername = xstrdup(host);
659 strrchr(servername, ':')[0] = '\0';
671 * openssl s_client -quiet -connect www.kernel.org:443 2>/dev/null
672 * It prints some debug stuff on stderr, don't know how to suppress it.
673 * Work around by dev-nulling stderr. We lose all error messages :(
676 xopen("/dev/null", O_RDWR);
677 memset(&argv, 0, sizeof(argv));
678 argv[0] = (char*)"openssl";
679 argv[1] = (char*)"s_client";
680 argv[2] = (char*)"-quiet";
681 argv[3] = (char*)"-connect";
682 argv[4] = (char*)host;
684 * Per RFC 6066 Section 3, the only permitted values in the
685 * TLS server_name (SNI) field are FQDNs (DNS hostnames).
686 * IPv4 and IPv6 addresses, port numbers are not allowed.
688 if (!is_ip_address(servername)) {
689 argv[5] = (char*)"-servername";
690 argv[6] = (char*)servername;
693 BB_EXECVP(argv[0], argv);
695 # if ENABLE_FEATURE_WGET_HTTPS
699 bb_perror_msg_and_die("can't execute '%s'", argv[0]);
708 # if ENABLE_FEATURE_WGET_HTTPS
718 #if ENABLE_FEATURE_WGET_HTTPS
719 static void spawn_ssl_client(const char *host, int network_fd, int flags)
723 char *servername, *p;
725 if (!(option_mask32 & WGET_OPT_NO_CHECK_CERT)) {
726 option_mask32 |= WGET_OPT_NO_CHECK_CERT;
727 bb_simple_error_msg("note: TLS certificate validation not implemented");
730 servername = xstrdup(host);
731 p = strrchr(servername, ':');
734 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
735 /* Kernel can have AF_UNIX support disabled */
736 bb_simple_perror_msg_and_die("socketpair");
739 pid = BB_MMU ? xfork() : xvfork();
746 tls_state_t *tls = new_tls_state();
747 tls->ifd = tls->ofd = network_fd;
748 tls_handshake(tls, servername);
749 tls_run_copy_loop(tls, flags);
754 xmove_fd(network_fd, 3);
755 argv[0] = (char*)"ssl_client";
756 argv[1] = (char*)"-s3";
757 //TODO: if (!is_ip_address(servername))...
758 argv[2] = (char*)"-n";
759 argv[3] = servername;
760 argv[4] = (flags & TLSLOOP_EXIT_ON_LOCAL_EOF ? (char*)"-e" : NULL);
762 BB_EXECVP(argv[0], argv);
763 bb_perror_msg_and_die("can't execute '%s'", argv[0]);
771 xmove_fd(sp[0], network_fd);
775 static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
781 sfp = open_socket(lsa);
782 #if ENABLE_FEATURE_WGET_HTTPS
783 if (target->protocol == P_FTPS)
784 spawn_ssl_client(target->host, fileno(sfp), TLSLOOP_EXIT_ON_LOCAL_EOF);
787 if (ftpcmd(NULL, NULL, sfp) != 220)
788 bb_simple_error_msg_and_die(G.wget_buf);
789 /* note: ftpcmd() sanitizes G.wget_buf, ok to print */
791 /* Split username:password pair */
792 pass = (char*)"busybox"; /* password for "anonymous" */
794 pass = strchr(target->user, ':');
800 switch (ftpcmd("USER ", target->user ?: "anonymous", sfp)) {
804 if (ftpcmd("PASS ", pass, sfp) == 230)
806 /* fall through (failed login) */
808 bb_error_msg_and_die("ftp login: %s", G.wget_buf);
811 ftpcmd("TYPE I", NULL, sfp);
813 /* Query file size */
814 if (ftpcmd("SIZE ", target->path, sfp) == 213) {
815 G.content_len = BB_STRTOOFF(G.wget_buf + 4, NULL, 10);
816 if (G.content_len < 0 || errno) {
817 bb_error_msg_and_die("bad SIZE value '%s'", G.wget_buf + 4);
822 /* Enter passive mode */
823 if (ENABLE_FEATURE_IPV6 && ftpcmd("EPSV", NULL, sfp) == 229) {
826 if (ftpcmd("PASV", NULL, sfp) != 227) {
828 bb_error_msg_and_die("bad response to %s: %s", "PASV", G.wget_buf);
830 port = parse_pasv_epsv(G.wget_buf);
834 set_nport(&lsa->u.sa, htons(port));
836 *dfpp = open_socket(lsa);
838 #if ENABLE_FEATURE_WGET_HTTPS
839 if (target->protocol == P_FTPS) {
840 /* "PROT P" enables encryption of data stream.
841 * Without it (or with "PROT C"), data is sent unencrypted.
843 if (ftpcmd("PROT P", NULL, sfp) == 200)
844 spawn_ssl_client(target->host, fileno(*dfpp), /*flags*/ 0);
848 if (G.beg_range != 0) {
849 sprintf(G.wget_buf, "REST %"OFF_FMT"u", G.beg_range);
850 if (ftpcmd(G.wget_buf, NULL, sfp) == 350)
851 G.content_len -= G.beg_range;
853 reset_beg_range_to_zero();
856 //TODO: needs ftp-escaping 0xff and '\n' bytes here.
857 //Or disallow '\n' altogether via sanitize_string() in parse_url().
858 //But 0xff's are possible in valid utf8 filenames.
859 if (ftpcmd("RETR ", target->path, sfp) > 150)
860 bb_error_msg_and_die("bad response to %s: %s", "RETR", G.wget_buf);
865 static void NOINLINE retrieve_file_data(FILE *dfp)
867 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
868 # if ENABLE_FEATURE_WGET_TIMEOUT
869 unsigned second_cnt = G.timeout_seconds;
871 struct pollfd polldata;
873 polldata.fd = fileno(dfp);
874 polldata.events = POLLIN | POLLPRI;
876 if (!(option_mask32 & WGET_OPT_QUIET)) {
877 if (G.output_fd == 1)
878 fprintf(stderr, "writing to stdout\n");
880 fprintf(stderr, "saving to '%s'\n", G.fname_out);
882 progress_meter(PROGRESS_START);
887 /* Loops only if chunked */
890 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
891 /* Must use nonblocking I/O, otherwise fread will loop
892 * and *block* until it reads full buffer,
893 * which messes up progress bar and/or timeout logic.
894 * Because of nonblocking I/O, we need to dance
895 * very carefully around EAGAIN. See explanation at
898 ndelay_on(polldata.fd);
904 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
905 /* fread internally uses read loop, which in our case
906 * is usually exited when we get EAGAIN.
907 * In this case, libc sets error marker on the stream.
908 * Need to clear it before next fread to avoid possible
909 * rare false positive ferror below. Rare because usually
910 * fread gets more than zero bytes, and we don't fall
911 * into if (n <= 0) ...
916 rdsz = sizeof(G.wget_buf);
918 if (G.content_len < (off_t)sizeof(G.wget_buf)) {
919 if ((int)G.content_len <= 0)
921 rdsz = (unsigned)G.content_len;
924 n = fread(G.wget_buf, 1, rdsz, dfp);
927 xwrite(G.output_fd, G.wget_buf, n);
928 #if ENABLE_FEATURE_WGET_STATUSBAR
933 if (G.content_len == 0)
936 #if ENABLE_FEATURE_WGET_TIMEOUT
937 second_cnt = G.timeout_seconds;
944 * If error occurs, or EOF is reached, the return value
945 * is a short item count (or zero).
946 * fread does not distinguish between EOF and error.
948 if (errno != EAGAIN) {
950 progress_meter(PROGRESS_END);
951 bb_simple_perror_msg_and_die(bb_msg_read_error);
953 break; /* EOF, not error */
956 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
957 /* It was EAGAIN. There is no data. Wait up to one second
958 * then abort if timed out, or update the bar and try reading again.
960 if (safe_poll(&polldata, 1, 1000) == 0) {
961 # if ENABLE_FEATURE_WGET_TIMEOUT
962 if (second_cnt != 0 && --second_cnt == 0) {
963 progress_meter(PROGRESS_END);
964 bb_simple_error_msg_and_die("download timed out");
967 /* We used to loop back to poll here,
968 * but there is no great harm in letting fread
969 * to try reading anyway.
974 /* Need to do it _every_ second for "stalled" indicator
975 * to be shown properly.
977 progress_meter(PROGRESS_BUMP);
978 } /* while (reading data) */
980 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
982 ndelay_off(polldata.fd); /* else fgets can get very unhappy */
987 /* Each chunk ends with "\r\n" - eat it */
988 fgets_trim_sanitize(dfp, NULL);
990 /* chunk size format is "HEXNUM[;name[=val]]\r\n" */
991 fgets_trim_sanitize(dfp, NULL);
993 G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
995 * Had a bug with inputs like "ffffffff0001f400"
996 * smashing the heap later. Ensure >= 0.
998 if (G.content_len < 0 || errno)
999 bb_error_msg_and_die("bad chunk length '%s'", G.wget_buf);
1000 if (G.content_len == 0)
1001 break; /* all done! */
1004 * Note that fgets may result in some data being buffered in dfp.
1005 * We loop back to fread, which will retrieve this data.
1006 * Also note that code has to be arranged so that fread
1007 * is done _before_ one-second poll wait - poll doesn't know
1008 * about stdio buffering and can result in spurious one second waits!
1012 /* Draw full bar and free its resources */
1013 G.chunked = 0; /* makes it show 100% even for chunked download */
1014 G.got_clen = 1; /* makes it show 100% even for download of (formerly) unknown size */
1015 progress_meter(PROGRESS_END);
1016 if (G.content_len != 0) {
1017 bb_simple_perror_msg_and_die("connection closed prematurely");
1018 /* GNU wget says "DATE TIME (NN MB/s) - Connection closed at byte NNN. Retrying." */
1021 /* If -c failed, we restart from the beginning,
1022 * but we do not truncate file then, we do it only now, at the end.
1023 * This lets user to ^C if his 99% complete 10 GB file download
1024 * failed to restart *without* losing the almost complete file.
1027 off_t pos = lseek(G.output_fd, 0, SEEK_CUR);
1028 if (pos != (off_t)-1)
1029 ftruncate(G.output_fd, pos);
1032 if (!(option_mask32 & WGET_OPT_QUIET)) {
1033 if (G.output_fd == 1)
1034 fprintf(stderr, "written to stdout\n");
1036 fprintf(stderr, "'%s' saved\n", G.fname_out);
1040 static void download_one_url(const char *url)
1042 bool use_proxy; /* Use proxies if env vars are set */
1044 len_and_sockaddr *lsa;
1045 FILE *sfp; /* socket to web/ftp server */
1046 FILE *dfp; /* socket to ftp server (data) */
1047 char *fname_out_alloc;
1048 char *redirected_path = NULL;
1049 struct host_info server;
1050 struct host_info target;
1052 server.allocated = NULL;
1053 target.allocated = NULL;
1057 parse_url(url, &target);
1059 /* Use the proxy if necessary */
1060 use_proxy = (strcmp(G.proxy_flag, "off") != 0);
1062 char *proxy = getenv(target.protocol[0] == 'f' ? "ftp_proxy" : "http_proxy");
1063 //FIXME: what if protocol is https? Ok to use http_proxy?
1064 use_proxy = (proxy && proxy[0]);
1066 parse_url(proxy, &server);
1069 server.protocol = target.protocol;
1070 server.port = target.port;
1071 if (ENABLE_FEATURE_IPV6) {
1072 //free(server.allocated); - can't be non-NULL
1073 server.host = server.allocated = xstrdup(target.host);
1075 server.host = target.host;
1079 if (ENABLE_FEATURE_IPV6)
1080 strip_ipv6_scope_id(target.host);
1082 /* If there was no -O FILE, guess output filename */
1083 fname_out_alloc = NULL;
1084 if (!(option_mask32 & WGET_OPT_OUTNAME)) {
1085 G.fname_out = bb_get_last_path_component_nostrip(target.path);
1086 /* handle "wget http://kernel.org//" */
1087 if (G.fname_out[0] == '/' || !G.fname_out[0])
1088 G.fname_out = (char*)"index.html";
1089 /* -P DIR is considered only if there was no -O FILE */
1091 G.fname_out = fname_out_alloc = concat_path_file(G.dir_prefix, G.fname_out);
1093 /* redirects may free target.path later, need to make a copy */
1094 G.fname_out = fname_out_alloc = xstrdup(G.fname_out);
1097 #if ENABLE_FEATURE_WGET_STATUSBAR
1098 G.curfile = bb_get_last_path_component_nostrip(G.fname_out);
1101 /* Determine where to start transfer */
1103 if (option_mask32 & WGET_OPT_CONTINUE) {
1104 G.output_fd = open(G.fname_out, O_WRONLY);
1105 if (G.output_fd >= 0) {
1106 G.beg_range = xlseek(G.output_fd, 0, SEEK_END);
1108 /* File doesn't exist. We do not create file here yet.
1109 * We are not sure it exists on remote side */
1114 lsa = xhost2sockaddr(server.host, server.port);
1115 if (!(option_mask32 & WGET_OPT_QUIET)) {
1116 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
1117 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
1121 /*G.content_len = 0; - redundant, got_clen = 0 is enough */
1124 if (use_proxy || target.protocol[0] != 'f' /*not ftp[s]*/) {
1131 /* Open socket to http(s) server */
1132 #if ENABLE_FEATURE_WGET_OPENSSL
1133 /* openssl (and maybe internal TLS) support is configured */
1134 if (server.protocol == P_HTTPS) {
1135 /* openssl-based helper
1136 * Inconvenient API since we can't give it an open fd
1138 int fd = spawn_https_helper_openssl(server.host, server.port);
1139 # if ENABLE_FEATURE_WGET_HTTPS
1140 if (fd < 0) { /* no openssl? try internal */
1141 sfp = open_socket(lsa);
1142 spawn_ssl_client(server.host, fileno(sfp), /*flags*/ 0);
1146 /* We don't check for exec("openssl") failure in this case */
1148 sfp = fdopen(fd, "r+");
1150 bb_die_memory_exhausted();
1153 sfp = open_socket(lsa);
1155 #elif ENABLE_FEATURE_WGET_HTTPS
1156 /* Only internal TLS support is configured */
1157 sfp = open_socket(lsa);
1158 if (server.protocol == P_HTTPS)
1159 spawn_ssl_client(server.host, fileno(sfp), /*flags*/ 0);
1161 /* ssl (https) support is not configured */
1162 sfp = open_socket(lsa);
1164 /* Send HTTP request */
1166 SENDFMT(sfp, "GET %s://%s/%s HTTP/1.1\r\n",
1167 target.protocol, target.host,
1170 SENDFMT(sfp, "%s /%s HTTP/1.1\r\n",
1171 (option_mask32 & WGET_OPT_POST_DATA) ? "POST" : "GET",
1174 if (!USR_HEADER_HOST)
1175 SENDFMT(sfp, "Host: %s\r\n", target.host);
1176 if (!USR_HEADER_USER_AGENT)
1177 SENDFMT(sfp, "User-Agent: %s\r\n", G.user_agent);
1179 /* Ask server to close the connection as soon as we are done
1180 * (IOW: we do not intend to send more requests)
1182 SENDFMT(sfp, "Connection: close\r\n");
1184 #if ENABLE_FEATURE_WGET_AUTHENTICATION
1185 if (target.user && !USR_HEADER_AUTH) {
1186 SENDFMT(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
1187 base64enc(target.user));
1189 if (use_proxy && server.user && !USR_HEADER_PROXY_AUTH) {
1190 SENDFMT(sfp, "Proxy-Authorization: Basic %s\r\n",
1191 base64enc(server.user));
1195 if (G.beg_range != 0 && !USR_HEADER_RANGE)
1196 SENDFMT(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
1198 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1199 if (G.extra_headers) {
1200 log_io(G.extra_headers);
1201 fputs(G.extra_headers, sfp);
1204 if (option_mask32 & WGET_OPT_POST_DATA) {
1206 "Content-Type: application/x-www-form-urlencoded\r\n"
1207 "Content-Length: %u\r\n"
1210 (int) strlen(G.post_data), G.post_data
1215 SENDFMT(sfp, "\r\n");
1220 /* Tried doing this unconditionally.
1221 * Cloudflare and nginx/1.11.5 are shocked to see SHUT_WR on non-HTTPS.
1224 if (target.protocol == P_HTTPS) {
1225 /* If we use SSL helper, keeping our end of the socket open for writing
1226 * makes our end (i.e. the same fd!) readable (EAGAIN instead of EOF)
1227 * even after child closes its copy of the fd.
1230 shutdown(fileno(sfp), SHUT_WR);
1235 * Retrieve HTTP response line and check for "200" status code.
1238 fgets_trim_sanitize(sfp, " %s\n");
1241 str = skip_non_whitespace(str);
1242 str = skip_whitespace(str);
1243 // FIXME: no error check
1244 // xatou wouldn't work: "200 OK"
1249 while (get_sanitized_hdr(sfp) != NULL)
1250 /* eat all remaining headers */;
1253 /* Success responses */
1256 case 201: /* 201 Created */
1257 /* "The request has been fulfilled and resulted in a new resource being created" */
1258 /* Standard wget is reported to treat this as success */
1260 case 202: /* 202 Accepted */
1261 /* "The request has been accepted for processing, but the processing has not been completed" */
1262 /* Treat as success: fall through */
1263 case 203: /* 203 Non-Authoritative Information */
1264 /* "Use of this response code is not required and is only appropriate when the response would otherwise be 200 (OK)" */
1266 case 204: /* 204 No Content */
1268 Response 204 doesn't say "null file", it says "metadata
1269 has changed but data didn't":
1271 "10.2.5 204 No Content
1272 The server has fulfilled the request but does not need to return
1273 an entity-body, and might want to return updated metainformation.
1274 The response MAY include new or updated metainformation in the form
1275 of entity-headers, which if present SHOULD be associated with
1276 the requested variant.
1278 If the client is a user agent, it SHOULD NOT change its document
1279 view from that which caused the request to be sent. This response
1280 is primarily intended to allow input for actions to take place
1281 without causing a change to the user agent's active document view,
1282 although any new or updated metainformation SHOULD be applied
1283 to the document currently in the user agent's active view.
1285 The 204 response MUST NOT include a message-body, and thus
1286 is always terminated by the first empty line after the header fields."
1288 However, in real world it was observed that some web servers
1289 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
1291 if (G.beg_range != 0) {
1292 /* "Range:..." was not honored by the server.
1293 * Restart download from the beginning.
1295 reset_beg_range_to_zero();
1298 /* 205 Reset Content ?? what to do on this ?? */
1300 case 300: /* redirection */
1306 case 206: /* Partial Content */
1307 if (G.beg_range != 0)
1308 /* "Range:..." worked. Good. */
1310 /* Partial Content even though we did not ask for it??? */
1313 bb_error_msg_and_die("server returned error: %s", G.wget_buf);
1317 * Retrieve HTTP headers.
1319 while ((str = get_sanitized_hdr(sfp)) != NULL) {
1320 static const char keywords[] ALIGN1 =
1321 "content-length\0""transfer-encoding\0""location\0";
1323 KEY_content_length = 1, KEY_transfer_encoding, KEY_location
1327 /* get_sanitized_hdr converted "FOO:" string to lowercase */
1329 /* strip trailing whitespace */
1330 char *s = strchrnul(str, '\0') - 1;
1331 while (s >= str && (*s == ' ' || *s == '\t')) {
1335 key = index_in_strings(keywords, G.wget_buf) + 1;
1336 if (key == KEY_content_length) {
1337 G.content_len = BB_STRTOOFF(str, NULL, 10);
1338 if (G.content_len < 0 || errno) {
1339 bb_error_msg_and_die("content-length %s is garbage", str);
1344 if (key == KEY_transfer_encoding) {
1345 if (strcmp(str_tolower(str), "chunked") != 0)
1346 bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
1349 if (key == KEY_location && status >= 300) {
1350 if (--redir_limit == 0)
1351 bb_simple_error_msg_and_die("too many redirections");
1353 if (str[0] == '/') {
1354 free(redirected_path);
1355 target.path = redirected_path = xstrdup(str + 1);
1356 /* lsa stays the same: it's on the same server */
1358 parse_url(str, &target);
1360 /* server.user remains untouched */
1361 free(server.allocated);
1362 server.allocated = NULL;
1363 server.protocol = target.protocol;
1364 server.host = target.host;
1365 /* strip_ipv6_scope_id(target.host); - no! */
1366 /* we assume remote never gives us IPv6 addr with scope id */
1367 server.port = target.port;
1370 } /* else: lsa stays the same: we use proxy */
1372 goto establish_session;
1375 // if (status >= 300)
1376 // bb_error_msg_and_die("bad redirection (no Location: header from server)");
1378 /* For HTTP, data is pumped over the same connection */
1384 sfp = prepare_ftp_session(&dfp, &target, lsa);
1389 if (!(option_mask32 & WGET_OPT_SPIDER)) {
1390 if (G.output_fd < 0)
1391 G.output_fd = xopen(G.fname_out, G.o_flags);
1392 retrieve_file_data(dfp);
1393 if (!(option_mask32 & WGET_OPT_OUTNAME)) {
1394 xclose(G.output_fd);
1398 if (!(option_mask32 & WGET_OPT_QUIET))
1399 fprintf(stderr, "remote file exists\n");
1403 /* It's ftp. Close data connection properly */
1405 if (ftpcmd(NULL, NULL, sfp) != 226)
1406 bb_error_msg_and_die("ftp error: %s", G.wget_buf);
1407 /* ftpcmd("QUIT", NULL, sfp); - why bother? */
1411 free(server.allocated);
1412 free(target.allocated);
1415 free(fname_out_alloc);
1416 free(redirected_path);
1419 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1420 int wget_main(int argc UNUSED_PARAM, char **argv)
1422 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1423 static const char wget_longopts[] ALIGN1 =
1424 /* name, has_arg, val */
1425 "continue\0" No_argument "c"
1426 "quiet\0" No_argument "q"
1427 "server-response\0" No_argument "S"
1428 "output-document\0" Required_argument "O"
1429 "output-file\0" Required_argument "o"
1430 "directory-prefix\0" Required_argument "P"
1431 "proxy\0" Required_argument "Y"
1432 "user-agent\0" Required_argument "U"
1433 IF_FEATURE_WGET_TIMEOUT(
1434 "timeout\0" Required_argument "T")
1436 IF_DESKTOP( "tries\0" Required_argument "t")
1437 "header\0" Required_argument "\xff"
1438 "post-data\0" Required_argument "\xfe"
1439 "spider\0" No_argument "\xfd"
1440 "no-check-certificate\0" No_argument "\xfc"
1441 /* Ignored (we always use PASV): */
1442 IF_DESKTOP( "passive-ftp\0" No_argument "\xf0")
1443 /* Ignored (we don't support caching) */
1444 IF_DESKTOP( "no-cache\0" No_argument "\xf0")
1445 IF_DESKTOP( "no-verbose\0" No_argument "\xf0")
1446 IF_DESKTOP( "no-clobber\0" No_argument "\xf0")
1447 IF_DESKTOP( "no-host-directories\0" No_argument "\xf0")
1448 IF_DESKTOP( "no-parent\0" No_argument "\xf0")
1450 # define GETOPT32 getopt32long
1451 # define LONGOPTS ,wget_longopts
1453 # define GETOPT32 getopt32
1457 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1458 llist_t *headers_llist = NULL;
1463 #if ENABLE_FEATURE_WGET_TIMEOUT
1464 G.timeout_seconds = 900;
1465 signal(SIGALRM, alarm_handler);
1467 G.proxy_flag = "on"; /* use proxies if env vars are set */
1468 G.user_agent = "Wget"; /* "User-Agent" header field */
1474 /* wget has exactly four -n<letter> opts, all of which we can ignore:
1475 * -nv --no-verbose: be moderately quiet (-q is full quiet)
1476 * -nc --no-clobber: abort if exists, neither download to FILE.n nor overwrite FILE
1477 * -nH --no-host-directories: wget -r http://host/ won't create host/
1479 * "n::" above says that we accept -n[ARG].
1480 * Specifying "n:" would be a bug: "-n ARG" would eat ARG!
1483 "-1" /* at least one URL */
1484 IF_FEATURE_WGET_LONG_OPTIONS(":\xff::") /* --header is a list */
1486 , &G.fname_out, &G.fname_log, &G.dir_prefix,
1487 &G.proxy_flag, &G.user_agent,
1488 IF_FEATURE_WGET_TIMEOUT(&G.timeout_seconds) IF_NOT_FEATURE_WGET_TIMEOUT(NULL),
1489 NULL, /* -t RETRIES */
1491 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
1492 IF_FEATURE_WGET_LONG_OPTIONS(, &G.post_data)
1494 #if 0 /* option bits debug */
1495 if (option_mask32 & WGET_OPT_RETRIES) bb_error_msg("-t NUM");
1496 if (option_mask32 & WGET_OPT_nsomething) bb_error_msg("-nsomething");
1497 if (option_mask32 & WGET_OPT_HEADER) bb_error_msg("--header");
1498 if (option_mask32 & WGET_OPT_POST_DATA) bb_error_msg("--post-data");
1499 if (option_mask32 & WGET_OPT_SPIDER) bb_error_msg("--spider");
1500 if (option_mask32 & WGET_OPT_NO_CHECK_CERT) bb_error_msg("--no-check-certificate");
1505 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1506 if (headers_llist) {
1509 llist_t *ll = headers_llist;
1511 size += strlen(ll->data) + 2;
1514 G.extra_headers = hdr = xmalloc(size + 1);
1515 while (headers_llist) {
1519 size = sprintf(hdr, "%s\r\n",
1520 (char*)llist_pop(&headers_llist));
1521 /* a bit like index_in_substrings but don't match full key */
1523 words = wget_user_headers;
1525 if (strstr(hdr, words) == hdr) {
1526 G.user_headers |= bit;
1530 words += strlen(words) + 1;
1538 G.o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
1539 if (G.fname_out) { /* -O FILE ? */
1540 if (LONE_DASH(G.fname_out)) { /* -O - ? */
1542 option_mask32 &= ~WGET_OPT_CONTINUE;
1544 /* compat with wget: -O FILE can overwrite */
1545 G.o_flags = O_WRONLY | O_CREAT | O_TRUNC;
1549 if (G.fname_log) { /* -o FILE ? */
1550 if (!LONE_DASH(G.fname_log)) { /* not -o - ? */
1551 /* compat with wget: -o FILE can overwrite */
1552 G.log_fd = xopen(G.fname_log, O_WRONLY | O_CREAT | O_TRUNC);
1553 /* Redirect only stderr to log file, so -O - will work */
1554 xdup2(G.log_fd, STDERR_FILENO);
1559 download_one_url(*argv++);
1561 if (G.output_fd >= 0)
1562 xclose(G.output_fd);
1567 #if ENABLE_FEATURE_CLEAN_UP && ENABLE_FEATURE_WGET_LONG_OPTIONS
1568 free(G.extra_headers);
1572 return EXIT_SUCCESS;