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(sizeof(long));
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_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 * TODO add proper error checking when inet_pton() returns -1
393 * (some form of system error has occurred, and errno is set)
395 static int is_ip_address(const char *string)
397 struct sockaddr_in sa;
399 int result = inet_pton(AF_INET, string, &(sa.sin_addr));
400 # if ENABLE_FEATURE_IPV6
402 struct sockaddr_in6 sa6;
403 result = inet_pton(AF_INET6, string, &(sa6.sin6_addr));
406 return (result == 1);
410 static FILE *open_socket(len_and_sockaddr *lsa)
416 fd = xconnect_stream(lsa);
419 /* glibc 2.4 seems to try seeking on it - ??! */
420 /* hopefully it understands what ESPIPE means... */
421 fp = fdopen(fd, "r+");
423 bb_die_memory_exhausted();
428 /* We balk at any control chars in other side's messages.
429 * This prevents nasty surprises (e.g. ESC sequences) in "Location:" URLs
430 * and error messages.
432 * The only exception is tabs, which are converted to (one) space:
433 * HTTP's "headers: <whitespace> values" may have those.
435 static char* sanitize_string(char *s)
437 unsigned char *p = (void *) s;
450 /* Returns '\n' if it was seen, else '\0'. Trims at first '\r' or '\n' */
451 static char fgets_trim_sanitize(FILE *fp, const char *fmt)
457 if (fgets(G.wget_buf, sizeof(G.wget_buf), fp) == NULL)
458 bb_perror_msg_and_die("error getting response");
461 buf_ptr = strchrnul(G.wget_buf, '\n');
464 /* Disallow any control chars: trim at first char < 0x20 */
465 sanitize_string(G.wget_buf);
468 buf_ptr = strchrnul(G.wget_buf, '\r');
472 log_io("< %s", G.wget_buf);
474 if (fmt && (option_mask32 & WGET_OPT_SERVER_RESPONSE))
475 fprintf(stderr, fmt, G.wget_buf);
480 static int ftpcmd(const char *s1, const char *s2, FILE *fp)
486 fprintf(fp, "%s%s\r\n", s1, s2);
487 /* With --server-response, wget also shows its ftp commands */
488 if (option_mask32 & WGET_OPT_SERVER_RESPONSE)
489 fprintf(stderr, "--> %s%s\n\n", s1, s2);
491 log_io("> %s%s", s1, s2);
494 /* Read until "Nxx something" is received */
497 fgets_trim_sanitize(fp, "%s\n");
498 } while (!isdigit(G.wget_buf[0]) || G.wget_buf[3] != ' ');
500 G.wget_buf[3] = '\0';
501 result = xatoi_positive(G.wget_buf);
506 static void parse_url(const char *src_url, struct host_info *h)
511 h->allocated = url = xstrdup(src_url);
514 p = strstr(url, "://");
518 if (strcmp(url, P_FTP) == 0) {
519 h->port = bb_lookup_std_port(P_FTP, "tcp", 21);
522 # if ENABLE_FEATURE_WGET_HTTPS
523 if (strcmp(url, P_FTPS) == 0) {
524 h->port = bb_lookup_std_port(P_FTPS, "tcp", 990);
525 h->protocol = P_FTPS;
528 if (strcmp(url, P_HTTPS) == 0) {
529 h->port = bb_lookup_std_port(P_HTTPS, "tcp", 443);
530 h->protocol = P_HTTPS;
533 if (strcmp(url, P_HTTP) == 0) {
535 h->port = bb_lookup_std_port(P_HTTP, "tcp", 80);
536 h->protocol = P_HTTP;
539 bb_error_msg_and_die("not an http or ftp url: %s", url);
542 // GNU wget is user-friendly and falls back to http://
548 // "Real" wget 'http://busybox.net?var=a/b' sends this request:
549 // 'GET /?var=a/b HTTP/1.0'
550 // and saves 'index.html?var=a%2Fb' (we save 'b')
551 // wget 'http://busybox.net?login=john@doe':
552 // request: 'GET /?login=john@doe HTTP/1.0'
553 // saves: 'index.html?login=john@doe' (we save 'login=john@doe')
554 // wget 'http://busybox.net#test/test':
555 // request: 'GET / HTTP/1.0'
556 // saves: 'index.html' (we save 'test')
558 // We also don't add unique .N suffix if file exists...
559 sp = strchr(h->host, '/');
560 p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
561 p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
564 } else if (*sp == '/') {
568 // sp points to '#' or '?'
570 // http://busybox.net?login=john@doe is a valid URL
571 // (without '/' between ".net" and "?"),
572 // can't store NUL at sp[-1] - this destroys hostname.
577 sp = strrchr(h->host, '@');
579 // URL-decode "user:password" string before base64-encoding:
580 // wget http://test:my%20pass@example.com should send
581 // Authorization: Basic dGVzdDpteSBwYXNz
582 // which decodes to "test:my pass".
583 // Standard wget and curl do this too.
586 h->user = xstrdup(percent_decode_in_place(h->host, /*strict:*/ 0));
589 /* else: h->user remains NULL, or as set by original request
590 * before redirect (if we are here after a redirect).
594 static char *get_sanitized_hdr(FILE *fp)
599 /* retrieve header line */
600 c = fgets_trim_sanitize(fp, " %s\n");
602 /* end of the headers? */
603 if (G.wget_buf[0] == '\0')
606 /* convert the header name to lower case */
607 for (s = G.wget_buf; isalnum(*s) || *s == '-' || *s == '.' || *s == '_'; ++s) {
609 * No-op for 20-3f and 60-7f. "0-9a-z-." are in these ranges.
610 * 40-5f range ("@A-Z[\]^_") maps to 60-7f.
611 * "A-Z" maps to "a-z".
612 * "@[\]" can't occur in header names.
613 * "^_" maps to "~,DEL" (which is wrong).
614 * "^" was never seen yet, "_" was seen from web.archive.org
615 * (x-archive-orig-x_commoncrawl_Signature: HEXSTRING).
620 /* verify we are at the end of the header name */
622 bb_error_msg_and_die("bad header line: %s", G.wget_buf);
624 /* locate the start of the header value */
626 hdrval = skip_whitespace(s);
629 /* Rats! The buffer isn't big enough to hold the entire header value */
630 while (c = getc(fp), c != EOF && c != '\n')
637 static void reset_beg_range_to_zero(void)
639 bb_error_msg("restart failed");
641 xlseek(G.output_fd, 0, SEEK_SET);
642 /* Done at the end instead: */
643 /* ftruncate(G.output_fd, 0); */
646 #if ENABLE_FEATURE_WGET_OPENSSL
647 static int spawn_https_helper_openssl(const char *host, unsigned port)
649 char *allocated = NULL;
653 IF_FEATURE_WGET_HTTPS(volatile int child_failed = 0;)
655 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
656 /* Kernel can have AF_UNIX support disabled */
657 bb_perror_msg_and_die("socketpair");
659 if (!strchr(host, ':'))
660 host = allocated = xasprintf("%s:%u", host, port);
661 servername = xstrdup(host);
662 strrchr(servername, ':')[0] = '\0';
674 * openssl s_client -quiet -connect www.kernel.org:443 2>/dev/null
675 * It prints some debug stuff on stderr, don't know how to suppress it.
676 * Work around by dev-nulling stderr. We lose all error messages :(
679 xopen("/dev/null", O_RDWR);
680 memset(&argv, 0, sizeof(argv));
681 argv[0] = (char*)"openssl";
682 argv[1] = (char*)"s_client";
683 argv[2] = (char*)"-quiet";
684 argv[3] = (char*)"-connect";
685 argv[4] = (char*)host;
687 * Per RFC 6066 Section 3, the only permitted values in the
688 * TLS server_name (SNI) field are FQDNs (DNS hostnames).
689 * IPv4 and IPv6 addresses, port numbers are not allowed.
691 if (!is_ip_address(servername)) {
692 argv[5] = (char*)"-servername";
693 argv[6] = (char*)servername;
696 BB_EXECVP(argv[0], argv);
698 # if ENABLE_FEATURE_WGET_HTTPS
702 bb_perror_msg_and_die("can't execute '%s'", argv[0]);
711 # if ENABLE_FEATURE_WGET_HTTPS
721 #if ENABLE_FEATURE_WGET_HTTPS
722 static void spawn_ssl_client(const char *host, int network_fd, int flags)
726 char *servername, *p;
728 if (!(option_mask32 & WGET_OPT_NO_CHECK_CERT)) {
729 option_mask32 |= WGET_OPT_NO_CHECK_CERT;
730 bb_error_msg("note: TLS certificate validation not implemented");
733 servername = xstrdup(host);
734 p = strrchr(servername, ':');
737 if (socketpair(AF_UNIX, SOCK_STREAM, 0, sp) != 0)
738 /* Kernel can have AF_UNIX support disabled */
739 bb_perror_msg_and_die("socketpair");
742 pid = BB_MMU ? xfork() : xvfork();
749 tls_state_t *tls = new_tls_state();
750 tls->ifd = tls->ofd = network_fd;
751 tls_handshake(tls, servername);
752 tls_run_copy_loop(tls, flags);
757 xmove_fd(network_fd, 3);
758 argv[0] = (char*)"ssl_client";
759 argv[1] = (char*)"-s3";
760 //TODO: if (!is_ip_address(servername))...
761 argv[2] = (char*)"-n";
762 argv[3] = servername;
763 argv[4] = (flags & TLSLOOP_EXIT_ON_LOCAL_EOF ? (char*)"-e" : NULL);
765 BB_EXECVP(argv[0], argv);
766 bb_perror_msg_and_die("can't execute '%s'", argv[0]);
774 xmove_fd(sp[0], network_fd);
778 static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
784 sfp = open_socket(lsa);
785 #if ENABLE_FEATURE_WGET_HTTPS
786 if (target->protocol == P_FTPS)
787 spawn_ssl_client(target->host, fileno(sfp), TLSLOOP_EXIT_ON_LOCAL_EOF);
790 if (ftpcmd(NULL, NULL, sfp) != 220)
791 bb_error_msg_and_die("%s", G.wget_buf);
792 /* note: ftpcmd() sanitizes G.wget_buf, ok to print */
794 /* Split username:password pair */
795 pass = (char*)"busybox"; /* password for "anonymous" */
797 pass = strchr(target->user, ':');
803 switch (ftpcmd("USER ", target->user ?: "anonymous", sfp)) {
807 if (ftpcmd("PASS ", pass, sfp) == 230)
809 /* fall through (failed login) */
811 bb_error_msg_and_die("ftp login: %s", G.wget_buf);
814 ftpcmd("TYPE I", NULL, sfp);
816 /* Query file size */
817 if (ftpcmd("SIZE ", target->path, sfp) == 213) {
818 G.content_len = BB_STRTOOFF(G.wget_buf + 4, NULL, 10);
819 if (G.content_len < 0 || errno) {
820 bb_error_msg_and_die("bad SIZE value '%s'", G.wget_buf + 4);
825 /* Enter passive mode */
826 if (ENABLE_FEATURE_IPV6 && ftpcmd("EPSV", NULL, sfp) == 229) {
829 if (ftpcmd("PASV", NULL, sfp) != 227) {
831 bb_error_msg_and_die("bad response to %s: %s", "PASV", G.wget_buf);
833 port = parse_pasv_epsv(G.wget_buf);
837 set_nport(&lsa->u.sa, htons(port));
839 *dfpp = open_socket(lsa);
841 #if ENABLE_FEATURE_WGET_HTTPS
842 if (target->protocol == P_FTPS) {
843 /* "PROT P" enables encryption of data stream.
844 * Without it (or with "PROT C"), data is sent unencrypted.
846 if (ftpcmd("PROT P", NULL, sfp) == 200)
847 spawn_ssl_client(target->host, fileno(*dfpp), /*flags*/ 0);
851 if (G.beg_range != 0) {
852 sprintf(G.wget_buf, "REST %"OFF_FMT"u", G.beg_range);
853 if (ftpcmd(G.wget_buf, NULL, sfp) == 350)
854 G.content_len -= G.beg_range;
856 reset_beg_range_to_zero();
859 //TODO: needs ftp-escaping 0xff and '\n' bytes here.
860 //Or disallow '\n' altogether via sanitize_string() in parse_url().
861 //But 0xff's are possible in valid utf8 filenames.
862 if (ftpcmd("RETR ", target->path, sfp) > 150)
863 bb_error_msg_and_die("bad response to %s: %s", "RETR", G.wget_buf);
868 static void NOINLINE retrieve_file_data(FILE *dfp)
870 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
871 # if ENABLE_FEATURE_WGET_TIMEOUT
872 unsigned second_cnt = G.timeout_seconds;
874 struct pollfd polldata;
876 polldata.fd = fileno(dfp);
877 polldata.events = POLLIN | POLLPRI;
879 if (!(option_mask32 & WGET_OPT_QUIET)) {
880 if (G.output_fd == 1)
881 fprintf(stderr, "writing to stdout\n");
883 fprintf(stderr, "saving to '%s'\n", G.fname_out);
885 progress_meter(PROGRESS_START);
890 /* Loops only if chunked */
893 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
894 /* Must use nonblocking I/O, otherwise fread will loop
895 * and *block* until it reads full buffer,
896 * which messes up progress bar and/or timeout logic.
897 * Because of nonblocking I/O, we need to dance
898 * very carefully around EAGAIN. See explanation at
901 ndelay_on(polldata.fd);
907 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
908 /* fread internally uses read loop, which in our case
909 * is usually exited when we get EAGAIN.
910 * In this case, libc sets error marker on the stream.
911 * Need to clear it before next fread to avoid possible
912 * rare false positive ferror below. Rare because usually
913 * fread gets more than zero bytes, and we don't fall
914 * into if (n <= 0) ...
919 rdsz = sizeof(G.wget_buf);
921 if (G.content_len < (off_t)sizeof(G.wget_buf)) {
922 if ((int)G.content_len <= 0)
924 rdsz = (unsigned)G.content_len;
927 n = fread(G.wget_buf, 1, rdsz, dfp);
930 xwrite(G.output_fd, G.wget_buf, n);
931 #if ENABLE_FEATURE_WGET_STATUSBAR
936 if (G.content_len == 0)
939 #if ENABLE_FEATURE_WGET_TIMEOUT
940 second_cnt = G.timeout_seconds;
947 * If error occurs, or EOF is reached, the return value
948 * is a short item count (or zero).
949 * fread does not distinguish between EOF and error.
951 if (errno != EAGAIN) {
953 progress_meter(PROGRESS_END);
954 bb_perror_msg_and_die(bb_msg_read_error);
956 break; /* EOF, not error */
959 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
960 /* It was EAGAIN. There is no data. Wait up to one second
961 * then abort if timed out, or update the bar and try reading again.
963 if (safe_poll(&polldata, 1, 1000) == 0) {
964 # if ENABLE_FEATURE_WGET_TIMEOUT
965 if (second_cnt != 0 && --second_cnt == 0) {
966 progress_meter(PROGRESS_END);
967 bb_error_msg_and_die("download timed out");
970 /* We used to loop back to poll here,
971 * but there is no great harm in letting fread
972 * to try reading anyway.
977 /* Need to do it _every_ second for "stalled" indicator
978 * to be shown properly.
980 progress_meter(PROGRESS_BUMP);
981 } /* while (reading data) */
983 #if ENABLE_FEATURE_WGET_STATUSBAR || ENABLE_FEATURE_WGET_TIMEOUT
985 ndelay_off(polldata.fd); /* else fgets can get very unhappy */
990 /* Each chunk ends with "\r\n" - eat it */
991 fgets_trim_sanitize(dfp, NULL);
993 /* chunk size format is "HEXNUM[;name[=val]]\r\n" */
994 fgets_trim_sanitize(dfp, NULL);
996 G.content_len = STRTOOFF(G.wget_buf, NULL, 16);
998 * Had a bug with inputs like "ffffffff0001f400"
999 * smashing the heap later. Ensure >= 0.
1001 if (G.content_len < 0 || errno)
1002 bb_error_msg_and_die("bad chunk length '%s'", G.wget_buf);
1003 if (G.content_len == 0)
1004 break; /* all done! */
1007 * Note that fgets may result in some data being buffered in dfp.
1008 * We loop back to fread, which will retrieve this data.
1009 * Also note that code has to be arranged so that fread
1010 * is done _before_ one-second poll wait - poll doesn't know
1011 * about stdio buffering and can result in spurious one second waits!
1015 /* If -c failed, we restart from the beginning,
1016 * but we do not truncate file then, we do it only now, at the end.
1017 * This lets user to ^C if his 99% complete 10 GB file download
1018 * failed to restart *without* losing the almost complete file.
1021 off_t pos = lseek(G.output_fd, 0, SEEK_CUR);
1022 if (pos != (off_t)-1)
1023 ftruncate(G.output_fd, pos);
1026 /* Draw full bar and free its resources */
1027 G.chunked = 0; /* makes it show 100% even for chunked download */
1028 G.got_clen = 1; /* makes it show 100% even for download of (formerly) unknown size */
1029 progress_meter(PROGRESS_END);
1030 if (!(option_mask32 & WGET_OPT_QUIET)) {
1031 if (G.output_fd == 1)
1032 fprintf(stderr, "written to stdout\n");
1034 fprintf(stderr, "'%s' saved\n", G.fname_out);
1038 static void download_one_url(const char *url)
1040 bool use_proxy; /* Use proxies if env vars are set */
1042 len_and_sockaddr *lsa;
1043 FILE *sfp; /* socket to web/ftp server */
1044 FILE *dfp; /* socket to ftp server (data) */
1045 char *fname_out_alloc;
1046 char *redirected_path = NULL;
1047 struct host_info server;
1048 struct host_info target;
1050 server.allocated = NULL;
1051 target.allocated = NULL;
1055 parse_url(url, &target);
1057 /* Use the proxy if necessary */
1058 use_proxy = (strcmp(G.proxy_flag, "off") != 0);
1060 char *proxy = getenv(target.protocol[0] == 'f' ? "ftp_proxy" : "http_proxy");
1061 //FIXME: what if protocol is https? Ok to use http_proxy?
1062 use_proxy = (proxy && proxy[0]);
1064 parse_url(proxy, &server);
1067 server.protocol = target.protocol;
1068 server.port = target.port;
1069 if (ENABLE_FEATURE_IPV6) {
1070 //free(server.allocated); - can't be non-NULL
1071 server.host = server.allocated = xstrdup(target.host);
1073 server.host = target.host;
1077 if (ENABLE_FEATURE_IPV6)
1078 strip_ipv6_scope_id(target.host);
1080 /* If there was no -O FILE, guess output filename */
1081 fname_out_alloc = NULL;
1082 if (!(option_mask32 & WGET_OPT_OUTNAME)) {
1083 G.fname_out = bb_get_last_path_component_nostrip(target.path);
1084 /* handle "wget http://kernel.org//" */
1085 if (G.fname_out[0] == '/' || !G.fname_out[0])
1086 G.fname_out = (char*)"index.html";
1087 /* -P DIR is considered only if there was no -O FILE */
1089 G.fname_out = fname_out_alloc = concat_path_file(G.dir_prefix, G.fname_out);
1091 /* redirects may free target.path later, need to make a copy */
1092 G.fname_out = fname_out_alloc = xstrdup(G.fname_out);
1095 #if ENABLE_FEATURE_WGET_STATUSBAR
1096 G.curfile = bb_get_last_path_component_nostrip(G.fname_out);
1099 /* Determine where to start transfer */
1101 if (option_mask32 & WGET_OPT_CONTINUE) {
1102 G.output_fd = open(G.fname_out, O_WRONLY);
1103 if (G.output_fd >= 0) {
1104 G.beg_range = xlseek(G.output_fd, 0, SEEK_END);
1106 /* File doesn't exist. We do not create file here yet.
1107 * We are not sure it exists on remote side */
1112 lsa = xhost2sockaddr(server.host, server.port);
1113 if (!(option_mask32 & WGET_OPT_QUIET)) {
1114 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
1115 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
1119 /*G.content_len = 0; - redundant, got_clen = 0 is enough */
1122 if (use_proxy || target.protocol[0] != 'f' /*not ftp[s]*/) {
1129 /* Open socket to http(s) server */
1130 #if ENABLE_FEATURE_WGET_OPENSSL
1131 /* openssl (and maybe internal TLS) support is configured */
1132 if (server.protocol == P_HTTPS) {
1133 /* openssl-based helper
1134 * Inconvenient API since we can't give it an open fd
1136 int fd = spawn_https_helper_openssl(server.host, server.port);
1137 # if ENABLE_FEATURE_WGET_HTTPS
1138 if (fd < 0) { /* no openssl? try internal */
1139 sfp = open_socket(lsa);
1140 spawn_ssl_client(server.host, fileno(sfp), /*flags*/ 0);
1144 /* We don't check for exec("openssl") failure in this case */
1146 sfp = fdopen(fd, "r+");
1148 bb_die_memory_exhausted();
1151 sfp = open_socket(lsa);
1153 #elif ENABLE_FEATURE_WGET_HTTPS
1154 /* Only internal TLS support is configured */
1155 sfp = open_socket(lsa);
1156 if (server.protocol == P_HTTPS)
1157 spawn_ssl_client(server.host, fileno(sfp), /*flags*/ 0);
1159 /* ssl (https) support is not configured */
1160 sfp = open_socket(lsa);
1162 /* Send HTTP request */
1164 SENDFMT(sfp, "GET %s://%s/%s HTTP/1.1\r\n",
1165 target.protocol, target.host,
1168 SENDFMT(sfp, "%s /%s HTTP/1.1\r\n",
1169 (option_mask32 & WGET_OPT_POST_DATA) ? "POST" : "GET",
1172 if (!USR_HEADER_HOST)
1173 SENDFMT(sfp, "Host: %s\r\n", target.host);
1174 if (!USR_HEADER_USER_AGENT)
1175 SENDFMT(sfp, "User-Agent: %s\r\n", G.user_agent);
1177 /* Ask server to close the connection as soon as we are done
1178 * (IOW: we do not intend to send more requests)
1180 SENDFMT(sfp, "Connection: close\r\n");
1182 #if ENABLE_FEATURE_WGET_AUTHENTICATION
1183 if (target.user && !USR_HEADER_AUTH) {
1184 SENDFMT(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
1185 base64enc(target.user));
1187 if (use_proxy && server.user && !USR_HEADER_PROXY_AUTH) {
1188 SENDFMT(sfp, "Proxy-Authorization: Basic %s\r\n",
1189 base64enc(server.user));
1193 if (G.beg_range != 0 && !USR_HEADER_RANGE)
1194 SENDFMT(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
1196 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1197 if (G.extra_headers) {
1198 log_io(G.extra_headers);
1199 fputs(G.extra_headers, sfp);
1202 if (option_mask32 & WGET_OPT_POST_DATA) {
1204 "Content-Type: application/x-www-form-urlencoded\r\n"
1205 "Content-Length: %u\r\n"
1208 (int) strlen(G.post_data), G.post_data
1213 SENDFMT(sfp, "\r\n");
1218 /* Tried doing this unconditionally.
1219 * Cloudflare and nginx/1.11.5 are shocked to see SHUT_WR on non-HTTPS.
1222 if (target.protocol == P_HTTPS) {
1223 /* If we use SSL helper, keeping our end of the socket open for writing
1224 * makes our end (i.e. the same fd!) readable (EAGAIN instead of EOF)
1225 * even after child closes its copy of the fd.
1228 shutdown(fileno(sfp), SHUT_WR);
1233 * Retrieve HTTP response line and check for "200" status code.
1236 fgets_trim_sanitize(sfp, " %s\n");
1239 str = skip_non_whitespace(str);
1240 str = skip_whitespace(str);
1241 // FIXME: no error check
1242 // xatou wouldn't work: "200 OK"
1247 while (get_sanitized_hdr(sfp) != NULL)
1248 /* eat all remaining headers */;
1251 /* Success responses */
1254 case 201: /* 201 Created */
1255 /* "The request has been fulfilled and resulted in a new resource being created" */
1256 /* Standard wget is reported to treat this as success */
1258 case 202: /* 202 Accepted */
1259 /* "The request has been accepted for processing, but the processing has not been completed" */
1260 /* Treat as success: fall through */
1261 case 203: /* 203 Non-Authoritative Information */
1262 /* "Use of this response code is not required and is only appropriate when the response would otherwise be 200 (OK)" */
1264 case 204: /* 204 No Content */
1266 Response 204 doesn't say "null file", it says "metadata
1267 has changed but data didn't":
1269 "10.2.5 204 No Content
1270 The server has fulfilled the request but does not need to return
1271 an entity-body, and might want to return updated metainformation.
1272 The response MAY include new or updated metainformation in the form
1273 of entity-headers, which if present SHOULD be associated with
1274 the requested variant.
1276 If the client is a user agent, it SHOULD NOT change its document
1277 view from that which caused the request to be sent. This response
1278 is primarily intended to allow input for actions to take place
1279 without causing a change to the user agent's active document view,
1280 although any new or updated metainformation SHOULD be applied
1281 to the document currently in the user agent's active view.
1283 The 204 response MUST NOT include a message-body, and thus
1284 is always terminated by the first empty line after the header fields."
1286 However, in real world it was observed that some web servers
1287 (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
1289 if (G.beg_range != 0) {
1290 /* "Range:..." was not honored by the server.
1291 * Restart download from the beginning.
1293 reset_beg_range_to_zero();
1296 /* 205 Reset Content ?? what to do on this ?? */
1298 case 300: /* redirection */
1304 case 206: /* Partial Content */
1305 if (G.beg_range != 0)
1306 /* "Range:..." worked. Good. */
1308 /* Partial Content even though we did not ask for it??? */
1311 bb_error_msg_and_die("server returned error: %s", G.wget_buf);
1315 * Retrieve HTTP headers.
1317 while ((str = get_sanitized_hdr(sfp)) != NULL) {
1318 static const char keywords[] ALIGN1 =
1319 "content-length\0""transfer-encoding\0""location\0";
1321 KEY_content_length = 1, KEY_transfer_encoding, KEY_location
1325 /* get_sanitized_hdr converted "FOO:" string to lowercase */
1327 /* strip trailing whitespace */
1328 char *s = strchrnul(str, '\0') - 1;
1329 while (s >= str && (*s == ' ' || *s == '\t')) {
1333 key = index_in_strings(keywords, G.wget_buf) + 1;
1334 if (key == KEY_content_length) {
1335 G.content_len = BB_STRTOOFF(str, NULL, 10);
1336 if (G.content_len < 0 || errno) {
1337 bb_error_msg_and_die("content-length %s is garbage", str);
1342 if (key == KEY_transfer_encoding) {
1343 if (strcmp(str_tolower(str), "chunked") != 0)
1344 bb_error_msg_and_die("transfer encoding '%s' is not supported", str);
1347 if (key == KEY_location && status >= 300) {
1348 if (--redir_limit == 0)
1349 bb_error_msg_and_die("too many redirections");
1351 if (str[0] == '/') {
1352 free(redirected_path);
1353 target.path = redirected_path = xstrdup(str + 1);
1354 /* lsa stays the same: it's on the same server */
1356 parse_url(str, &target);
1358 /* server.user remains untouched */
1359 free(server.allocated);
1360 server.allocated = NULL;
1361 server.protocol = target.protocol;
1362 server.host = target.host;
1363 /* strip_ipv6_scope_id(target.host); - no! */
1364 /* we assume remote never gives us IPv6 addr with scope id */
1365 server.port = target.port;
1368 } /* else: lsa stays the same: we use proxy */
1370 goto establish_session;
1373 // if (status >= 300)
1374 // bb_error_msg_and_die("bad redirection (no Location: header from server)");
1376 /* For HTTP, data is pumped over the same connection */
1382 sfp = prepare_ftp_session(&dfp, &target, lsa);
1387 if (!(option_mask32 & WGET_OPT_SPIDER)) {
1388 if (G.output_fd < 0)
1389 G.output_fd = xopen(G.fname_out, G.o_flags);
1390 retrieve_file_data(dfp);
1391 if (!(option_mask32 & WGET_OPT_OUTNAME)) {
1392 xclose(G.output_fd);
1396 if (!(option_mask32 & WGET_OPT_QUIET))
1397 fprintf(stderr, "remote file exists\n");
1401 /* It's ftp. Close data connection properly */
1403 if (ftpcmd(NULL, NULL, sfp) != 226)
1404 bb_error_msg_and_die("ftp error: %s", G.wget_buf);
1405 /* ftpcmd("QUIT", NULL, sfp); - why bother? */
1409 free(server.allocated);
1410 free(target.allocated);
1413 free(fname_out_alloc);
1414 free(redirected_path);
1417 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
1418 int wget_main(int argc UNUSED_PARAM, char **argv)
1420 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1421 static const char wget_longopts[] ALIGN1 =
1422 /* name, has_arg, val */
1423 "continue\0" No_argument "c"
1424 "quiet\0" No_argument "q"
1425 "server-response\0" No_argument "S"
1426 "output-document\0" Required_argument "O"
1427 "output-file\0" Required_argument "o"
1428 "directory-prefix\0" Required_argument "P"
1429 "proxy\0" Required_argument "Y"
1430 "user-agent\0" Required_argument "U"
1431 IF_FEATURE_WGET_TIMEOUT(
1432 "timeout\0" Required_argument "T")
1434 IF_DESKTOP( "tries\0" Required_argument "t")
1435 "header\0" Required_argument "\xff"
1436 "post-data\0" Required_argument "\xfe"
1437 "spider\0" No_argument "\xfd"
1438 "no-check-certificate\0" No_argument "\xfc"
1439 /* Ignored (we always use PASV): */
1440 IF_DESKTOP( "passive-ftp\0" No_argument "\xf0")
1441 /* Ignored (we don't support caching) */
1442 IF_DESKTOP( "no-cache\0" No_argument "\xf0")
1443 IF_DESKTOP( "no-verbose\0" No_argument "\xf0")
1444 IF_DESKTOP( "no-clobber\0" No_argument "\xf0")
1445 IF_DESKTOP( "no-host-directories\0" No_argument "\xf0")
1446 IF_DESKTOP( "no-parent\0" No_argument "\xf0")
1448 # define GETOPT32 getopt32long
1449 # define LONGOPTS ,wget_longopts
1451 # define GETOPT32 getopt32
1455 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
1456 llist_t *headers_llist = NULL;
1461 #if ENABLE_FEATURE_WGET_TIMEOUT
1462 G.timeout_seconds = 900;
1463 signal(SIGALRM, alarm_handler);
1465 G.proxy_flag = "on"; /* use proxies if env vars are set */
1466 G.user_agent = "Wget"; /* "User-Agent" header field */
1468 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
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;