blob: 1f35f8b035a8067eaa5b8d13f4487b6d86303280 [file] [log] [blame]
Eric Andersen96700832000-09-04 15:15:55 +00001/* vi: set sw=4 ts=4: */
2/*
Eric Andersen79757c92001-04-05 21:45:54 +00003 * wget - retrieve a file using HTTP or FTP
Eric Andersen96700832000-09-04 15:15:55 +00004 *
Eric Andersen4e573f42000-11-14 23:29:24 +00005 * Chip Rosenthal Covad Communications <chip@laserlink.net>
Eric Andersenb520e082000-10-03 00:21:45 +00006 *
Denis Vlasenkodb12d1d2008-12-07 00:52:58 +00007 * Licensed under GPLv2, see file LICENSE in this tarball for details.
Eric Andersen96700832000-09-04 15:15:55 +00008 */
Denis Vlasenkob6adbf12007-05-26 19:00:18 +00009#include "libbb.h"
Denis Vlasenkoa552eeb2006-09-26 09:22:12 +000010
Eric Andersen79757c92001-04-05 21:45:54 +000011struct host_info {
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +000012 // May be used if we ever will want to free() all xstrdup()s...
13 /* char *allocated; */
Denis Vlasenko818322b2007-09-24 18:27:04 +000014 const char *path;
15 const char *user;
16 char *host;
17 int port;
18 smallint is_ftp;
Eric Andersen79757c92001-04-05 21:45:54 +000019};
20
Denis Vlasenko77105632007-09-24 15:04:00 +000021
22/* Globals (can be accessed from signal handlers) */
23struct globals {
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
Denis Vlasenko77105632007-09-24 15:04:00 +000027 off_t transferred; /* Number of bytes transferred so far */
28 const char *curfile; /* Name of current file being transferred */
Magnus Dammf5914992009-11-08 16:34:43 +010029 bb_progress_t pmt;
Denis Vlasenko77105632007-09-24 15:04:00 +000030#endif
Denys Vlasenko7f432802009-06-28 01:02:24 +020031 smallint chunked; /* chunked transfer encoding */
32 smallint got_clen; /* got content-length: from server */
Denys Vlasenko98a4c7c2010-02-04 15:00:15 +010033} FIX_ALIASING;
Denis Vlasenko77105632007-09-24 15:04:00 +000034#define G (*(struct globals*)&bb_common_bufsiz1)
35struct BUG_G_too_big {
Denis Vlasenko6b404432008-01-07 16:13:14 +000036 char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
Denis Vlasenko77105632007-09-24 15:04:00 +000037};
Denis Vlasenko77105632007-09-24 15:04:00 +000038#define INIT_G() do { } while (0)
39
40
Denis Vlasenko9cade082006-11-21 10:43:02 +000041#if ENABLE_FEATURE_WGET_STATUSBAR
Denis Vlasenko47ddd012007-09-24 18:24:17 +000042
Denis Vlasenko00d84172008-11-24 07:34:42 +000043static void progress_meter(int flag)
Denis Vlasenko47ddd012007-09-24 18:24:17 +000044{
Denis Vlasenkoa7ce2072007-09-24 18:25:08 +000045 /* We can be called from signal handler */
46 int save_errno = errno;
Denis Vlasenko47ddd012007-09-24 18:24:17 +000047
Denis Vlasenko00d84172008-11-24 07:34:42 +000048 if (flag == -1) { /* first call to progress_meter */
Magnus Dammf5914992009-11-08 16:34:43 +010049 bb_progress_init(&G.pmt);
Denis Vlasenko47ddd012007-09-24 18:24:17 +000050 }
51
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +010052 bb_progress_update(&G.pmt, G.curfile, G.beg_range, G.transferred,
Denys Vlasenkoc5bbd5d2010-07-12 03:27:09 +020053 G.chunked ? 0 : G.beg_range + G.transferred + G.content_len);
Denis Vlasenko47ddd012007-09-24 18:24:17 +000054
Denis Vlasenkoa7ce2072007-09-24 18:25:08 +000055 if (flag == 0) {
Denis Vlasenko00d84172008-11-24 07:34:42 +000056 /* last call to progress_meter */
Denis Vlasenkoa7ce2072007-09-24 18:25:08 +000057 alarm(0);
Denys Vlasenko19ced5c2010-06-06 21:53:09 +020058 bb_putchar_stderr('\n');
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +010059 G.transferred = 0;
Denis Vlasenkoa7ce2072007-09-24 18:25:08 +000060 } else {
Denis Vlasenko00d84172008-11-24 07:34:42 +000061 if (flag == -1) { /* first call to progress_meter */
62 signal_SA_RESTART_empty_mask(SIGALRM, progress_meter);
Denis Vlasenkoa7ce2072007-09-24 18:25:08 +000063 }
64 alarm(1);
Denis Vlasenko47ddd012007-09-24 18:24:17 +000065 }
Denis Vlasenkoa7ce2072007-09-24 18:25:08 +000066
67 errno = save_errno;
Denis Vlasenko47ddd012007-09-24 18:24:17 +000068}
Magnus Dammf5914992009-11-08 16:34:43 +010069
Denis Vlasenko47ddd012007-09-24 18:24:17 +000070#else /* FEATURE_WGET_STATUSBAR */
71
Denis Vlasenko00d84172008-11-24 07:34:42 +000072static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
Denis Vlasenko47ddd012007-09-24 18:24:17 +000073
Eric Andersenb520e082000-10-03 00:21:45 +000074#endif
Eric Andersenc7bda1c2004-03-15 08:29:22 +000075
Denis Vlasenko47ddd012007-09-24 18:24:17 +000076
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +020077/* IPv6 knows scoped address types i.e. link and site local addresses. Link
78 * local addresses can have a scope identifier to specify the
79 * interface/link an address is valid on (e.g. fe80::1%eth0). This scope
80 * identifier is only valid on a single node.
81 *
82 * RFC 4007 says that the scope identifier MUST NOT be sent across the wire,
83 * unless all nodes agree on the semantic. Apache e.g. regards zone identifiers
84 * in the Host header as invalid requests, see
85 * https://issues.apache.org/bugzilla/show_bug.cgi?id=35122
86 */
87static void strip_ipv6_scope_id(char *host)
88{
89 char *scope, *cp;
90
91 /* bbox wget actually handles IPv6 addresses without [], like
92 * wget "http://::1/xxx", but this is not standard.
93 * To save code, _here_ we do not support it. */
94
95 if (host[0] != '[')
96 return; /* not IPv6 */
97
98 scope = strchr(host, '%');
99 if (!scope)
100 return;
101
102 /* Remove the IPv6 zone identifier from the host address */
103 cp = strchr(host, ']');
104 if (!cp || (cp[1] != ':' && cp[1] != '\0')) {
105 /* malformed address (not "[xx]:nn" or "[xx]") */
106 return;
107 }
108
109 /* cp points to "]...", scope points to "%eth0]..." */
110 overlapping_strcpy(scope, cp);
111}
112
Denis Vlasenko12d21292007-06-27 21:40:07 +0000113/* Read NMEMB bytes into PTR from STREAM. Returns the number of bytes read,
114 * and a short count if an eof or non-interrupt error is encountered. */
115static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
Matt Kraai854125f2001-05-09 19:15:46 +0000116{
Denis Vlasenko12d21292007-06-27 21:40:07 +0000117 size_t ret;
118 char *p = (char*)ptr;
Matt Kraai854125f2001-05-09 19:15:46 +0000119
120 do {
121 clearerr(stream);
Denis Vlasenko00d84172008-11-24 07:34:42 +0000122 errno = 0;
Denis Vlasenko12d21292007-06-27 21:40:07 +0000123 ret = fread(p, 1, nmemb, stream);
124 p += ret;
125 nmemb -= ret;
126 } while (nmemb && ferror(stream) && errno == EINTR);
Matt Kraai854125f2001-05-09 19:15:46 +0000127
Denis Vlasenko12d21292007-06-27 21:40:07 +0000128 return p - (char*)ptr;
Matt Kraai854125f2001-05-09 19:15:46 +0000129}
130
Denis Vlasenko12d21292007-06-27 21:40:07 +0000131/* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
Matt Kraai854125f2001-05-09 19:15:46 +0000132 * Returns S, or NULL if an eof or non-interrupt error is encountered. */
133static char *safe_fgets(char *s, int size, FILE *stream)
134{
135 char *ret;
136
137 do {
138 clearerr(stream);
Denis Vlasenko00d84172008-11-24 07:34:42 +0000139 errno = 0;
Matt Kraai854125f2001-05-09 19:15:46 +0000140 ret = fgets(s, size, stream);
141 } while (ret == NULL && ferror(stream) && errno == EINTR);
142
143 return ret;
144}
145
Denis Vlasenko9cade082006-11-21 10:43:02 +0000146#if ENABLE_FEATURE_WGET_AUTHENTICATION
Denis Vlasenko12d21292007-06-27 21:40:07 +0000147/* Base64-encode character string. buf is assumed to be char buf[512]. */
148static char *base64enc_512(char buf[512], const char *str)
Denis Vlasenko3526a132006-09-09 12:20:57 +0000149{
Denis Vlasenko12d21292007-06-27 21:40:07 +0000150 unsigned len = strlen(str);
151 if (len > 512/4*3 - 10) /* paranoia */
152 len = 512/4*3 - 10;
153 bb_uuencode(buf, str, len, bb_uuenc_tbl_base64);
Rob Landley76ef08c2006-06-13 16:44:26 +0000154 return buf;
Eric Andersen79757c92001-04-05 21:45:54 +0000155}
156#endif
157
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200158static char* sanitize_string(char *s)
159{
160 unsigned char *p = (void *) s;
161 while (*p >= ' ')
162 p++;
163 *p = '\0';
164 return s;
165}
166
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000167static FILE *open_socket(len_and_sockaddr *lsa)
168{
169 FILE *fp;
170
171 /* glibc 2.4 seems to try seeking on it - ??! */
172 /* hopefully it understands what ESPIPE means... */
173 fp = fdopen(xconnect_stream(lsa), "r+");
174 if (fp == NULL)
175 bb_perror_msg_and_die("fdopen");
176
177 return fp;
178}
179
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000180static int ftpcmd(const char *s1, const char *s2, FILE *fp, char *buf)
181{
182 int result;
183 if (s1) {
184 if (!s2) s2 = "";
185 fprintf(fp, "%s%s\r\n", s1, s2);
186 fflush(fp);
187 }
188
189 do {
190 char *buf_ptr;
191
192 if (fgets(buf, 510, fp) == NULL) {
193 bb_perror_msg_and_die("error getting response");
194 }
195 buf_ptr = strstr(buf, "\r\n");
196 if (buf_ptr) {
197 *buf_ptr = '\0';
198 }
199 } while (!isdigit(buf[0]) || buf[3] != ' ');
200
201 buf[3] = '\0';
202 result = xatoi_u(buf);
203 buf[3] = ' ';
204 return result;
205}
206
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000207static void parse_url(char *src_url, struct host_info *h)
208{
209 char *url, *p, *sp;
210
211 /* h->allocated = */ url = xstrdup(src_url);
212
213 if (strncmp(url, "http://", 7) == 0) {
214 h->port = bb_lookup_port("http", "tcp", 80);
215 h->host = url + 7;
216 h->is_ftp = 0;
217 } else if (strncmp(url, "ftp://", 6) == 0) {
218 h->port = bb_lookup_port("ftp", "tcp", 21);
219 h->host = url + 6;
220 h->is_ftp = 1;
221 } else
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200222 bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000223
224 // FYI:
225 // "Real" wget 'http://busybox.net?var=a/b' sends this request:
226 // 'GET /?var=a/b HTTP 1.0'
227 // and saves 'index.html?var=a%2Fb' (we save 'b')
228 // wget 'http://busybox.net?login=john@doe':
229 // request: 'GET /?login=john@doe HTTP/1.0'
230 // saves: 'index.html?login=john@doe' (we save '?login=john@doe')
231 // wget 'http://busybox.net#test/test':
232 // request: 'GET / HTTP/1.0'
233 // saves: 'index.html' (we save 'test')
234 //
235 // We also don't add unique .N suffix if file exists...
236 sp = strchr(h->host, '/');
237 p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p;
238 p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p;
239 if (!sp) {
Denis Vlasenko818322b2007-09-24 18:27:04 +0000240 h->path = "";
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000241 } else if (*sp == '/') {
242 *sp = '\0';
243 h->path = sp + 1;
244 } else { // '#' or '?'
245 // http://busybox.net?login=john@doe is a valid URL
246 // memmove converts to:
247 // http:/busybox.nett?login=john@doe...
Denis Vlasenko818322b2007-09-24 18:27:04 +0000248 memmove(h->host - 1, h->host, sp - h->host);
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000249 h->host--;
250 sp[-1] = '\0';
251 h->path = sp;
252 }
253
Vladimir Dronnikovbe168b12009-10-05 02:18:01 +0200254 // We used to set h->user to NULL here, but this interferes
255 // with handling of code 302 ("object was moved")
256
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000257 sp = strrchr(h->host, '@');
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000258 if (sp != NULL) {
259 h->user = h->host;
260 *sp = '\0';
261 h->host = sp + 1;
262 }
263
264 sp = h->host;
265}
266
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000267static char *gethdr(char *buf, size_t bufsiz, FILE *fp /*, int *istrunc*/)
268{
269 char *s, *hdrval;
270 int c;
271
272 /* *istrunc = 0; */
273
274 /* retrieve header line */
275 if (fgets(buf, bufsiz, fp) == NULL)
276 return NULL;
277
278 /* see if we are at the end of the headers */
279 for (s = buf; *s == '\r'; ++s)
280 continue;
281 if (*s == '\n')
282 return NULL;
283
284 /* convert the header name to lower case */
Denys Vlasenko48363312010-04-04 15:29:32 +0200285 for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s) {
286 /* tolower for "A-Z", no-op for "0-9a-z-." */
287 *s = (*s | 0x20);
288 }
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000289
290 /* verify we are at the end of the header name */
291 if (*s != ':')
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200292 bb_error_msg_and_die("bad header line: %s", sanitize_string(buf));
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000293
294 /* locate the start of the header value */
295 *s++ = '\0';
296 hdrval = skip_whitespace(s);
297
298 /* locate the end of header */
299 while (*s && *s != '\r' && *s != '\n')
300 ++s;
301
302 /* end of header found */
303 if (*s) {
304 *s = '\0';
305 return hdrval;
306 }
307
Denys Vlasenko7f432802009-06-28 01:02:24 +0200308 /* Rats! The buffer isn't big enough to hold the entire header value */
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000309 while (c = getc(fp), c != EOF && c != '\n')
310 continue;
311 /* *istrunc = 1; */
312 return hdrval;
313}
314
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000315#if ENABLE_FEATURE_WGET_LONG_OPTIONS
316static char *URL_escape(const char *str)
317{
318 /* URL encode, see RFC 2396 */
319 char *dst;
320 char *res = dst = xmalloc(strlen(str) * 3 + 1);
321 unsigned char c;
322
323 while (1) {
324 c = *str++;
325 if (c == '\0'
326 /* || strchr("!&'()*-.=_~", c) - more code */
327 || c == '!'
328 || c == '&'
329 || c == '\''
330 || c == '('
331 || c == ')'
332 || c == '*'
333 || c == '-'
334 || c == '.'
335 || c == '='
336 || c == '_'
337 || c == '~'
338 || (c >= '0' && c <= '9')
339 || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
340 ) {
341 *dst++ = c;
342 if (c == '\0')
343 return res;
344 } else {
345 *dst++ = '%';
346 *dst++ = bb_hexdigits_upcase[c >> 4];
347 *dst++ = bb_hexdigits_upcase[c & 0xf];
348 }
349 }
350}
351#endif
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000352
Denys Vlasenko7f432802009-06-28 01:02:24 +0200353static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
354{
355 char buf[512];
356 FILE *sfp;
357 char *str;
358 int port;
359
360 if (!target->user)
361 target->user = xstrdup("anonymous:busybox@");
362
363 sfp = open_socket(lsa);
364 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200365 bb_error_msg_and_die("%s", sanitize_string(buf+4));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200366
367 /*
368 * Splitting username:password pair,
369 * trying to log in
370 */
371 str = strchr(target->user, ':');
372 if (str)
373 *str++ = '\0';
374 switch (ftpcmd("USER ", target->user, sfp, buf)) {
375 case 230:
376 break;
377 case 331:
378 if (ftpcmd("PASS ", str, sfp, buf) == 230)
379 break;
380 /* fall through (failed login) */
381 default:
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200382 bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200383 }
384
385 ftpcmd("TYPE I", NULL, sfp, buf);
386
387 /*
388 * Querying file size
389 */
390 if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100391 G.content_len = BB_STRTOOFF(buf+4, NULL, 10);
392 if (G.content_len < 0 || errno) {
Denys Vlasenko7f432802009-06-28 01:02:24 +0200393 bb_error_msg_and_die("SIZE value is garbage");
394 }
395 G.got_clen = 1;
396 }
397
398 /*
399 * Entering passive mode
400 */
401 if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
402 pasv_error:
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200403 bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200404 }
405 // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
406 // Server's IP is N1.N2.N3.N4 (we ignore it)
407 // Server's port for data connection is P1*256+P2
408 str = strrchr(buf, ')');
409 if (str) str[0] = '\0';
410 str = strrchr(buf, ',');
411 if (!str) goto pasv_error;
412 port = xatou_range(str+1, 0, 255);
413 *str = '\0';
414 str = strrchr(buf, ',');
415 if (!str) goto pasv_error;
416 port += xatou_range(str+1, 0, 255) * 256;
417 set_nport(lsa, htons(port));
418
419 *dfpp = open_socket(lsa);
420
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100421 if (G.beg_range) {
422 sprintf(buf, "REST %"OFF_FMT"u", G.beg_range);
Denys Vlasenko7f432802009-06-28 01:02:24 +0200423 if (ftpcmd(buf, NULL, sfp, buf) == 350)
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100424 G.content_len -= G.beg_range;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200425 }
426
427 if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200428 bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200429
430 return sfp;
431}
432
433/* Must match option string! */
434enum {
435 WGET_OPT_CONTINUE = (1 << 0),
436 WGET_OPT_SPIDER = (1 << 1),
437 WGET_OPT_QUIET = (1 << 2),
438 WGET_OPT_OUTNAME = (1 << 3),
439 WGET_OPT_PREFIX = (1 << 4),
440 WGET_OPT_PROXY = (1 << 5),
441 WGET_OPT_USER_AGENT = (1 << 6),
442 WGET_OPT_RETRIES = (1 << 7),
443 WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
444 WGET_OPT_PASSIVE = (1 << 9),
445 WGET_OPT_HEADER = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
446 WGET_OPT_POST_DATA = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
447};
448
449static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
450{
451 char buf[512];
452
453 if (!(option_mask32 & WGET_OPT_QUIET))
454 progress_meter(-1);
455
456 if (G.chunked)
457 goto get_clen;
458
459 /* Loops only if chunked */
460 while (1) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100461 while (1) {
Denys Vlasenko7f432802009-06-28 01:02:24 +0200462 int n;
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100463 unsigned rdsz;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200464
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100465 rdsz = sizeof(buf);
466 if (G.got_clen) {
Denys Vlasenkod2c879d2009-12-11 14:12:28 +0100467 if (G.content_len < (off_t)sizeof(buf)) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100468 if ((int)G.content_len <= 0)
469 break;
470 rdsz = (unsigned)G.content_len;
471 }
472 }
Denys Vlasenko7f432802009-06-28 01:02:24 +0200473 n = safe_fread(buf, rdsz, dfp);
474 if (n <= 0) {
475 if (ferror(dfp)) {
476 /* perror will not work: ferror doesn't set errno */
477 bb_error_msg_and_die(bb_msg_read_error);
478 }
479 break;
480 }
481 xwrite(output_fd, buf, n);
482#if ENABLE_FEATURE_WGET_STATUSBAR
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100483 G.transferred += n;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200484#endif
485 if (G.got_clen)
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100486 G.content_len -= n;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200487 }
488
489 if (!G.chunked)
490 break;
491
492 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
493 get_clen:
494 safe_fgets(buf, sizeof(buf), dfp);
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100495 G.content_len = STRTOOFF(buf, NULL, 16);
Denys Vlasenko7f432802009-06-28 01:02:24 +0200496 /* FIXME: error check? */
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100497 if (G.content_len == 0)
Denys Vlasenko7f432802009-06-28 01:02:24 +0200498 break; /* all done! */
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100499 G.got_clen = 1;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200500 }
501
502 if (!(option_mask32 & WGET_OPT_QUIET))
503 progress_meter(0);
504}
505
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000506int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000507int wget_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen96700832000-09-04 15:15:55 +0000508{
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000509 char buf[512];
Eric Andersen79757c92001-04-05 21:45:54 +0000510 struct host_info server, target;
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000511 len_and_sockaddr *lsa;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000512 unsigned opt;
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200513 int redir_limit;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200514 char *proxy = NULL;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000515 char *dir_prefix = NULL;
516#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000517 char *post_data;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000518 char *extra_headers = NULL;
Glenn L McGrath514aeab2003-12-19 12:08:56 +0000519 llist_t *headers_llist = NULL;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000520#endif
Denys Vlasenko7f432802009-06-28 01:02:24 +0200521 FILE *sfp; /* socket to web/ftp server */
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000522 FILE *dfp; /* socket to ftp server (data) */
523 char *fname_out; /* where to direct output (-O) */
Denis Vlasenkoa94554d2006-09-23 17:49:09 +0000524 int output_fd = -1;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200525 bool use_proxy; /* Use proxies if env vars are set */
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +0000526 const char *proxy_flag = "on"; /* Use proxies if env vars are set */
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000527 const char *user_agent = "Wget";/* "User-Agent" header field */
Denis Vlasenko77105632007-09-24 15:04:00 +0000528
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000529 static const char keywords[] ALIGN1 =
Denis Vlasenko990d0f62007-07-24 15:54:42 +0000530 "content-length\0""transfer-encoding\0""chunked\0""location\0";
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000531 enum {
532 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
533 };
Bernhard Reutner-Fischer289e86a2006-08-20 20:01:24 +0000534#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000535 static const char wget_longopts[] ALIGN1 =
Denis Vlasenkobdc88fd2007-07-23 17:14:14 +0000536 /* name, has_arg, val */
537 "continue\0" No_argument "c"
538 "spider\0" No_argument "s"
539 "quiet\0" No_argument "q"
540 "output-document\0" Required_argument "O"
541 "directory-prefix\0" Required_argument "P"
542 "proxy\0" Required_argument "Y"
543 "user-agent\0" Required_argument "U"
Denis Vlasenko50af9262009-03-02 15:08:06 +0000544 /* Ignored: */
545 // "tries\0" Required_argument "t"
546 // "timeout\0" Required_argument "T"
547 /* Ignored (we always use PASV): */
Denis Vlasenkobdc88fd2007-07-23 17:14:14 +0000548 "passive-ftp\0" No_argument "\xff"
549 "header\0" Required_argument "\xfe"
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000550 "post-data\0" Required_argument "\xfd"
Bernhard Reutner-Fischer3fdba182010-02-10 19:37:29 +0100551 /* Ignored (we don't do ssl) */
552 "no-check-certificate\0" No_argument "\xfc"
Denis Vlasenko990d0f62007-07-24 15:54:42 +0000553 ;
Denis Vlasenko77105632007-09-24 15:04:00 +0000554#endif
555
556 INIT_G();
557
558#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenkobdc88fd2007-07-23 17:14:14 +0000559 applet_long_options = wget_longopts;
Bernhard Reutner-Fischer8d3a6f72006-05-31 14:11:38 +0000560#endif
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000561 /* server.allocated = target.allocated = NULL; */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000562 opt_complementary = "-1" IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
Denis Vlasenko540ab702008-06-29 00:32:35 +0000563 opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000564 &fname_out, &dir_prefix,
Denis Vlasenko540ab702008-06-29 00:32:35 +0000565 &proxy_flag, &user_agent,
566 NULL, /* -t RETRIES */
567 NULL /* -T NETWORK_READ_TIMEOUT */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000568 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
569 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000570 );
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000571#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenko7534e082006-10-23 23:21:58 +0000572 if (headers_llist) {
573 int size = 1;
574 char *cp;
Denis Vlasenko8d9f4952007-04-08 15:08:42 +0000575 llist_t *ll = headers_llist;
Denis Vlasenko7534e082006-10-23 23:21:58 +0000576 while (ll) {
577 size += strlen(ll->data) + 2;
578 ll = ll->link;
579 }
580 extra_headers = cp = xmalloc(size);
Glenn L McGrath514aeab2003-12-19 12:08:56 +0000581 while (headers_llist) {
Denis Vlasenkod50dda82008-06-15 05:40:56 +0000582 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
Eric Andersen96700832000-09-04 15:15:55 +0000583 }
584 }
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000585#endif
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000586
Denys Vlasenko7f432802009-06-28 01:02:24 +0200587 /* TODO: compat issue: should handle "wget URL1 URL2..." */
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200588
Vladimir Dronnikovbe168b12009-10-05 02:18:01 +0200589 target.user = NULL;
Eric Andersen79757c92001-04-05 21:45:54 +0000590 parse_url(argv[optind], &target);
Eric Andersen79757c92001-04-05 21:45:54 +0000591
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000592 /* Use the proxy if necessary */
Denys Vlasenko7f432802009-06-28 01:02:24 +0200593 use_proxy = (strcmp(proxy_flag, "off") != 0);
Glenn L McGrathf1c4b112004-02-22 00:27:34 +0000594 if (use_proxy) {
Robert Griebld7760112002-05-14 23:36:45 +0000595 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200596 if (proxy && proxy[0]) {
Denys Vlasenko81fe2b12010-02-11 04:23:43 +0100597 server.user = NULL;
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +0000598 parse_url(proxy, &server);
Glenn L McGrathf1c4b112004-02-22 00:27:34 +0000599 } else {
600 use_proxy = 0;
601 }
Robert Griebld7760112002-05-14 23:36:45 +0000602 }
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200603 if (!use_proxy) {
604 server.port = target.port;
605 if (ENABLE_FEATURE_IPV6) {
606 server.host = xstrdup(target.host);
607 } else {
608 server.host = target.host;
609 }
610 }
611
612 if (ENABLE_FEATURE_IPV6)
613 strip_ipv6_scope_id(target.host);
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000614
Denis Vlasenko818322b2007-09-24 18:27:04 +0000615 /* Guess an output filename, if there was no -O FILE */
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000616 if (!(opt & WGET_OPT_OUTNAME)) {
Denis Vlasenko818322b2007-09-24 18:27:04 +0000617 fname_out = bb_get_last_path_component_nostrip(target.path);
618 /* handle "wget http://kernel.org//" */
619 if (fname_out[0] == '/' || !fname_out[0])
Denis Vlasenkob6aae0f2007-01-29 22:51:25 +0000620 fname_out = (char*)"index.html";
Denis Vlasenko818322b2007-09-24 18:27:04 +0000621 /* -P DIR is considered only if there was no -O FILE */
622 if (dir_prefix)
Matt Kraai0382eb82001-07-19 19:13:55 +0000623 fname_out = concat_path_file(dir_prefix, fname_out);
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000624 } else {
625 if (LONE_DASH(fname_out)) {
626 /* -O - */
627 output_fd = 1;
628 opt &= ~WGET_OPT_CONTINUE;
629 }
Eric Andersen29edd002000-12-09 16:55:35 +0000630 }
Denis Vlasenko818322b2007-09-24 18:27:04 +0000631#if ENABLE_FEATURE_WGET_STATUSBAR
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100632 G.curfile = bb_get_last_path_component_nostrip(fname_out);
Denis Vlasenko818322b2007-09-24 18:27:04 +0000633#endif
634
Denis Vlasenko4e4662c2006-11-23 13:10:23 +0000635 /* Impossible?
Denis Vlasenkoa552eeb2006-09-26 09:22:12 +0000636 if ((opt & WGET_OPT_CONTINUE) && !fname_out)
Denys Vlasenko6331cf02009-11-13 09:08:27 +0100637 bb_error_msg_and_die("can't specify continue (-c) without a filename (-O)");
Denys Vlasenko7f432802009-06-28 01:02:24 +0200638 */
Eric Andersen29edd002000-12-09 16:55:35 +0000639
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000640 /* Determine where to start transfer */
Denis Vlasenko4e4662c2006-11-23 13:10:23 +0000641 if (opt & WGET_OPT_CONTINUE) {
Denis Vlasenko7039a662006-10-08 17:54:47 +0000642 output_fd = open(fname_out, O_WRONLY);
Denis Vlasenkoa94554d2006-09-23 17:49:09 +0000643 if (output_fd >= 0) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100644 G.beg_range = xlseek(output_fd, 0, SEEK_END);
Denis Vlasenkoa94554d2006-09-23 17:49:09 +0000645 }
646 /* File doesn't exist. We do not create file here yet.
Denys Vlasenko7f432802009-06-28 01:02:24 +0200647 * We are not sure it exists on remove side */
Eric Andersen96700832000-09-04 15:15:55 +0000648 }
649
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200650 redir_limit = 5;
651 resolve_lsa:
Denis Vlasenko42823d52007-02-04 02:39:08 +0000652 lsa = xhost2sockaddr(server.host, server.port);
Denis Vlasenkoa552eeb2006-09-26 09:22:12 +0000653 if (!(opt & WGET_OPT_QUIET)) {
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200654 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
655 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
656 free(s);
Eric Andersene6dc4392003-10-31 09:31:46 +0000657 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200658 establish_session:
Glenn L McGrathf1c4b112004-02-22 00:27:34 +0000659 if (use_proxy || !target.is_ftp) {
Eric Andersen79757c92001-04-05 21:45:54 +0000660 /*
661 * HTTP session
662 */
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200663 char *str;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200664 int status;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200665
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200666 /* Open socket to http server */
667 sfp = open_socket(lsa);
Denys Vlasenko7f432802009-06-28 01:02:24 +0200668
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200669 /* Send HTTP request */
670 if (use_proxy) {
671 fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
672 target.is_ftp ? "f" : "ht", target.host,
673 target.path);
674 } else {
675 if (opt & WGET_OPT_POST_DATA)
676 fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
677 else
678 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
679 }
Glenn L McGrathe7bdfcc2003-08-28 22:03:19 +0000680
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200681 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
682 target.host, user_agent);
Eric Andersen79757c92001-04-05 21:45:54 +0000683
Denis Vlasenko9cade082006-11-21 10:43:02 +0000684#if ENABLE_FEATURE_WGET_AUTHENTICATION
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200685 if (target.user) {
686 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
687 base64enc_512(buf, target.user));
688 }
689 if (use_proxy && server.user) {
690 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
691 base64enc_512(buf, server.user));
692 }
Eric Andersen79757c92001-04-05 21:45:54 +0000693#endif
694
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100695 if (G.beg_range)
696 fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000697#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200698 if (extra_headers)
699 fputs(extra_headers, sfp);
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000700
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200701 if (opt & WGET_OPT_POST_DATA) {
702 char *estr = URL_escape(post_data);
703 fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
704 fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
705 (int) strlen(estr), estr);
706 /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
707 /*fprintf(sfp, "%s\r\n", estr);*/
708 free(estr);
709 } else
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000710#endif
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200711 { /* If "Connection:" is needed, document why */
712 fprintf(sfp, /* "Connection: close\r\n" */ "\r\n");
713 }
Eric Andersen79757c92001-04-05 21:45:54 +0000714
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200715 /*
716 * Retrieve HTTP response line and check for "200" status code.
717 */
Denis Vlasenko023b57d2006-10-15 17:05:55 +0000718 read_response:
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200719 if (fgets(buf, sizeof(buf), sfp) == NULL)
720 bb_error_msg_and_die("no response from server");
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000721
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200722 str = buf;
723 str = skip_non_whitespace(str);
724 str = skip_whitespace(str);
725 // FIXME: no error check
726 // xatou wouldn't work: "200 OK"
727 status = atoi(str);
728 switch (status) {
729 case 0:
730 case 100:
731 while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
732 /* eat all remaining headers */;
733 goto read_response;
734 case 200:
Denis Vlasenko50b5cac2008-06-22 16:28:02 +0000735/*
736Response 204 doesn't say "null file", it says "metadata
737has changed but data didn't":
738
739"10.2.5 204 No Content
740The server has fulfilled the request but does not need to return
741an entity-body, and might want to return updated metainformation.
742The response MAY include new or updated metainformation in the form
743of entity-headers, which if present SHOULD be associated with
744the requested variant.
745
746If the client is a user agent, it SHOULD NOT change its document
747view from that which caused the request to be sent. This response
748is primarily intended to allow input for actions to take place
749without causing a change to the user agent's active document view,
750although any new or updated metainformation SHOULD be applied
751to the document currently in the user agent's active view.
752
753The 204 response MUST NOT include a message-body, and thus
754is always terminated by the first empty line after the header fields."
755
756However, in real world it was observed that some web servers
757(e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
758*/
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200759 case 204:
760 break;
761 case 300: /* redirection */
762 case 301:
763 case 302:
764 case 303:
765 break;
766 case 206:
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100767 if (G.beg_range)
Denis Vlasenko023b57d2006-10-15 17:05:55 +0000768 break;
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200769 /* fall through */
770 default:
771 bb_error_msg_and_die("server returned error: %s", sanitize_string(buf));
772 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000773
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200774 /*
775 * Retrieve HTTP headers.
776 */
777 while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
778 /* gethdr converted "FOO:" string to lowercase */
Matthijs van de Water0d586662009-08-22 20:19:48 +0200779 smalluint key;
780 /* strip trailing whitespace */
781 char *s = strchrnul(str, '\0') - 1;
782 while (s >= str && (*s == ' ' || *s == '\t')) {
783 *s = '\0';
784 s--;
785 }
786 key = index_in_strings(keywords, buf) + 1;
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200787 if (key == KEY_content_length) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100788 G.content_len = BB_STRTOOFF(str, NULL, 10);
789 if (G.content_len < 0 || errno) {
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200790 bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
Eric Andersen79757c92001-04-05 21:45:54 +0000791 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200792 G.got_clen = 1;
793 continue;
794 }
795 if (key == KEY_transfer_encoding) {
796 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
797 bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
798 G.chunked = G.got_clen = 1;
799 }
800 if (key == KEY_location && status >= 300) {
801 if (--redir_limit == 0)
802 bb_error_msg_and_die("too many redirections");
803 fclose(sfp);
804 G.got_clen = 0;
805 G.chunked = 0;
806 if (str[0] == '/')
807 /* free(target.allocated); */
808 target.path = /* target.allocated = */ xstrdup(str+1);
809 /* lsa stays the same: it's on the same server */
810 else {
811 parse_url(str, &target);
812 if (!use_proxy) {
813 server.host = target.host;
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200814 /* strip_ipv6_scope_id(target.host); - no! */
815 /* we assume remote never gives us IPv6 addr with scope id */
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200816 server.port = target.port;
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000817 free(lsa);
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200818 goto resolve_lsa;
819 } /* else: lsa stays the same: we use proxy */
Eric Andersen79757c92001-04-05 21:45:54 +0000820 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200821 goto establish_session;
Eric Andersen79757c92001-04-05 21:45:54 +0000822 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200823 }
824// if (status >= 300)
825// bb_error_msg_and_die("bad redirection (no Location: header from server)");
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000826
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200827 /* For HTTP, data is pumped over the same connection */
Eric Andersen79757c92001-04-05 21:45:54 +0000828 dfp = sfp;
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +0000829
830 } else {
Eric Andersen79757c92001-04-05 21:45:54 +0000831 /*
832 * FTP session
833 */
Denys Vlasenko7f432802009-06-28 01:02:24 +0200834 sfp = prepare_ftp_session(&dfp, &target, lsa);
Eric Andersen96700832000-09-04 15:15:55 +0000835 }
Denis Vlasenko77105632007-09-24 15:04:00 +0000836
Bernhard Reutner-Fischer2e75dcc2007-04-05 10:31:47 +0000837 if (opt & WGET_OPT_SPIDER) {
838 if (ENABLE_FEATURE_CLEAN_UP)
839 fclose(sfp);
Denis Vlasenko77105632007-09-24 15:04:00 +0000840 return EXIT_SUCCESS;
Bernhard Reutner-Fischer2e75dcc2007-04-05 10:31:47 +0000841 }
Eric Andersen79757c92001-04-05 21:45:54 +0000842
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000843 if (output_fd < 0) {
844 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
845 /* compat with wget: -O FILE can overwrite */
846 if (opt & WGET_OPT_OUTNAME)
847 o_flags = O_WRONLY | O_CREAT | O_TRUNC;
848 output_fd = xopen(fname_out, o_flags);
849 }
Denis Vlasenkof8aa1092006-10-01 10:58:54 +0000850
Denys Vlasenko7f432802009-06-28 01:02:24 +0200851 retrieve_file_data(dfp, output_fd);
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100852 xclose(output_fd);
Rob Landley19a39402006-06-13 17:10:26 +0000853
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200854 if (dfp != sfp) {
855 /* It's ftp. Close it properly */
Eric Andersen79757c92001-04-05 21:45:54 +0000856 fclose(dfp);
857 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200858 bb_error_msg_and_die("ftp error: %s", sanitize_string(buf+4));
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100859 /* ftpcmd("QUIT", NULL, sfp, buf); - why bother? */
Eric Andersen79757c92001-04-05 21:45:54 +0000860 }
Denis Vlasenko77105632007-09-24 15:04:00 +0000861
862 return EXIT_SUCCESS;
Eric Andersen96700832000-09-04 15:15:55 +0000863}