blob: 9d813dcc2a3361bfc305d4f374ca1900f169d3c5 [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,
53 G.chunked ? 0 : G.content_len + G.beg_range);
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);
Denis Vlasenko4daad902007-09-27 10:20:47 +000058 fputc('\n', stderr);
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 */
285 for (s = buf; isalnum(*s) || *s == '-' || *s == '.'; ++s)
286 *s = tolower(*s);
287
288 /* verify we are at the end of the header name */
289 if (*s != ':')
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200290 bb_error_msg_and_die("bad header line: %s", sanitize_string(buf));
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000291
292 /* locate the start of the header value */
293 *s++ = '\0';
294 hdrval = skip_whitespace(s);
295
296 /* locate the end of header */
297 while (*s && *s != '\r' && *s != '\n')
298 ++s;
299
300 /* end of header found */
301 if (*s) {
302 *s = '\0';
303 return hdrval;
304 }
305
Denys Vlasenko7f432802009-06-28 01:02:24 +0200306 /* Rats! The buffer isn't big enough to hold the entire header value */
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000307 while (c = getc(fp), c != EOF && c != '\n')
308 continue;
309 /* *istrunc = 1; */
310 return hdrval;
311}
312
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000313#if ENABLE_FEATURE_WGET_LONG_OPTIONS
314static char *URL_escape(const char *str)
315{
316 /* URL encode, see RFC 2396 */
317 char *dst;
318 char *res = dst = xmalloc(strlen(str) * 3 + 1);
319 unsigned char c;
320
321 while (1) {
322 c = *str++;
323 if (c == '\0'
324 /* || strchr("!&'()*-.=_~", c) - more code */
325 || c == '!'
326 || c == '&'
327 || c == '\''
328 || c == '('
329 || c == ')'
330 || c == '*'
331 || c == '-'
332 || c == '.'
333 || c == '='
334 || c == '_'
335 || c == '~'
336 || (c >= '0' && c <= '9')
337 || ((c|0x20) >= 'a' && (c|0x20) <= 'z')
338 ) {
339 *dst++ = c;
340 if (c == '\0')
341 return res;
342 } else {
343 *dst++ = '%';
344 *dst++ = bb_hexdigits_upcase[c >> 4];
345 *dst++ = bb_hexdigits_upcase[c & 0xf];
346 }
347 }
348}
349#endif
Denis Vlasenko47ddd012007-09-24 18:24:17 +0000350
Denys Vlasenko7f432802009-06-28 01:02:24 +0200351static FILE* prepare_ftp_session(FILE **dfpp, struct host_info *target, len_and_sockaddr *lsa)
352{
353 char buf[512];
354 FILE *sfp;
355 char *str;
356 int port;
357
358 if (!target->user)
359 target->user = xstrdup("anonymous:busybox@");
360
361 sfp = open_socket(lsa);
362 if (ftpcmd(NULL, NULL, sfp, buf) != 220)
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200363 bb_error_msg_and_die("%s", sanitize_string(buf+4));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200364
365 /*
366 * Splitting username:password pair,
367 * trying to log in
368 */
369 str = strchr(target->user, ':');
370 if (str)
371 *str++ = '\0';
372 switch (ftpcmd("USER ", target->user, sfp, buf)) {
373 case 230:
374 break;
375 case 331:
376 if (ftpcmd("PASS ", str, sfp, buf) == 230)
377 break;
378 /* fall through (failed login) */
379 default:
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200380 bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200381 }
382
383 ftpcmd("TYPE I", NULL, sfp, buf);
384
385 /*
386 * Querying file size
387 */
388 if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100389 G.content_len = BB_STRTOOFF(buf+4, NULL, 10);
390 if (G.content_len < 0 || errno) {
Denys Vlasenko7f432802009-06-28 01:02:24 +0200391 bb_error_msg_and_die("SIZE value is garbage");
392 }
393 G.got_clen = 1;
394 }
395
396 /*
397 * Entering passive mode
398 */
399 if (ftpcmd("PASV", NULL, sfp, buf) != 227) {
400 pasv_error:
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200401 bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200402 }
403 // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage]
404 // Server's IP is N1.N2.N3.N4 (we ignore it)
405 // Server's port for data connection is P1*256+P2
406 str = strrchr(buf, ')');
407 if (str) str[0] = '\0';
408 str = strrchr(buf, ',');
409 if (!str) goto pasv_error;
410 port = xatou_range(str+1, 0, 255);
411 *str = '\0';
412 str = strrchr(buf, ',');
413 if (!str) goto pasv_error;
414 port += xatou_range(str+1, 0, 255) * 256;
415 set_nport(lsa, htons(port));
416
417 *dfpp = open_socket(lsa);
418
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100419 if (G.beg_range) {
420 sprintf(buf, "REST %"OFF_FMT"u", G.beg_range);
Denys Vlasenko7f432802009-06-28 01:02:24 +0200421 if (ftpcmd(buf, NULL, sfp, buf) == 350)
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100422 G.content_len -= G.beg_range;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200423 }
424
425 if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200426 bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
Denys Vlasenko7f432802009-06-28 01:02:24 +0200427
428 return sfp;
429}
430
431/* Must match option string! */
432enum {
433 WGET_OPT_CONTINUE = (1 << 0),
434 WGET_OPT_SPIDER = (1 << 1),
435 WGET_OPT_QUIET = (1 << 2),
436 WGET_OPT_OUTNAME = (1 << 3),
437 WGET_OPT_PREFIX = (1 << 4),
438 WGET_OPT_PROXY = (1 << 5),
439 WGET_OPT_USER_AGENT = (1 << 6),
440 WGET_OPT_RETRIES = (1 << 7),
441 WGET_OPT_NETWORK_READ_TIMEOUT = (1 << 8),
442 WGET_OPT_PASSIVE = (1 << 9),
443 WGET_OPT_HEADER = (1 << 10) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
444 WGET_OPT_POST_DATA = (1 << 11) * ENABLE_FEATURE_WGET_LONG_OPTIONS,
445};
446
447static void NOINLINE retrieve_file_data(FILE *dfp, int output_fd)
448{
449 char buf[512];
450
451 if (!(option_mask32 & WGET_OPT_QUIET))
452 progress_meter(-1);
453
454 if (G.chunked)
455 goto get_clen;
456
457 /* Loops only if chunked */
458 while (1) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100459 while (1) {
Denys Vlasenko7f432802009-06-28 01:02:24 +0200460 int n;
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100461 unsigned rdsz;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200462
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100463 rdsz = sizeof(buf);
464 if (G.got_clen) {
Denys Vlasenkod2c879d2009-12-11 14:12:28 +0100465 if (G.content_len < (off_t)sizeof(buf)) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100466 if ((int)G.content_len <= 0)
467 break;
468 rdsz = (unsigned)G.content_len;
469 }
470 }
Denys Vlasenko7f432802009-06-28 01:02:24 +0200471 n = safe_fread(buf, rdsz, dfp);
472 if (n <= 0) {
473 if (ferror(dfp)) {
474 /* perror will not work: ferror doesn't set errno */
475 bb_error_msg_and_die(bb_msg_read_error);
476 }
477 break;
478 }
479 xwrite(output_fd, buf, n);
480#if ENABLE_FEATURE_WGET_STATUSBAR
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100481 G.transferred += n;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200482#endif
483 if (G.got_clen)
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100484 G.content_len -= n;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200485 }
486
487 if (!G.chunked)
488 break;
489
490 safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */
491 get_clen:
492 safe_fgets(buf, sizeof(buf), dfp);
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100493 G.content_len = STRTOOFF(buf, NULL, 16);
Denys Vlasenko7f432802009-06-28 01:02:24 +0200494 /* FIXME: error check? */
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100495 if (G.content_len == 0)
Denys Vlasenko7f432802009-06-28 01:02:24 +0200496 break; /* all done! */
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100497 G.got_clen = 1;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200498 }
499
500 if (!(option_mask32 & WGET_OPT_QUIET))
501 progress_meter(0);
502}
503
Denis Vlasenko9b49a5e2007-10-11 10:05:36 +0000504int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
Denis Vlasenkoa60f84e2008-07-05 09:18:54 +0000505int wget_main(int argc UNUSED_PARAM, char **argv)
Eric Andersen96700832000-09-04 15:15:55 +0000506{
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000507 char buf[512];
Eric Andersen79757c92001-04-05 21:45:54 +0000508 struct host_info server, target;
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000509 len_and_sockaddr *lsa;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000510 unsigned opt;
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200511 int redir_limit;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200512 char *proxy = NULL;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000513 char *dir_prefix = NULL;
514#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000515 char *post_data;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000516 char *extra_headers = NULL;
Glenn L McGrath514aeab2003-12-19 12:08:56 +0000517 llist_t *headers_llist = NULL;
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000518#endif
Denys Vlasenko7f432802009-06-28 01:02:24 +0200519 FILE *sfp; /* socket to web/ftp server */
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000520 FILE *dfp; /* socket to ftp server (data) */
521 char *fname_out; /* where to direct output (-O) */
Denis Vlasenkoa94554d2006-09-23 17:49:09 +0000522 int output_fd = -1;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200523 bool use_proxy; /* Use proxies if env vars are set */
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +0000524 const char *proxy_flag = "on"; /* Use proxies if env vars are set */
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000525 const char *user_agent = "Wget";/* "User-Agent" header field */
Denis Vlasenko77105632007-09-24 15:04:00 +0000526
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000527 static const char keywords[] ALIGN1 =
Denis Vlasenko990d0f62007-07-24 15:54:42 +0000528 "content-length\0""transfer-encoding\0""chunked\0""location\0";
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000529 enum {
530 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
531 };
Bernhard Reutner-Fischer289e86a2006-08-20 20:01:24 +0000532#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenko6ca409e2007-08-12 20:58:27 +0000533 static const char wget_longopts[] ALIGN1 =
Denis Vlasenkobdc88fd2007-07-23 17:14:14 +0000534 /* name, has_arg, val */
535 "continue\0" No_argument "c"
536 "spider\0" No_argument "s"
537 "quiet\0" No_argument "q"
538 "output-document\0" Required_argument "O"
539 "directory-prefix\0" Required_argument "P"
540 "proxy\0" Required_argument "Y"
541 "user-agent\0" Required_argument "U"
Denis Vlasenko50af9262009-03-02 15:08:06 +0000542 /* Ignored: */
543 // "tries\0" Required_argument "t"
544 // "timeout\0" Required_argument "T"
545 /* Ignored (we always use PASV): */
Denis Vlasenkobdc88fd2007-07-23 17:14:14 +0000546 "passive-ftp\0" No_argument "\xff"
547 "header\0" Required_argument "\xfe"
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000548 "post-data\0" Required_argument "\xfd"
Denis Vlasenko990d0f62007-07-24 15:54:42 +0000549 ;
Denis Vlasenko77105632007-09-24 15:04:00 +0000550#endif
551
552 INIT_G();
553
554#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenkobdc88fd2007-07-23 17:14:14 +0000555 applet_long_options = wget_longopts;
Bernhard Reutner-Fischer8d3a6f72006-05-31 14:11:38 +0000556#endif
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000557 /* server.allocated = target.allocated = NULL; */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000558 opt_complementary = "-1" IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
Denis Vlasenko540ab702008-06-29 00:32:35 +0000559 opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000560 &fname_out, &dir_prefix,
Denis Vlasenko540ab702008-06-29 00:32:35 +0000561 &proxy_flag, &user_agent,
562 NULL, /* -t RETRIES */
563 NULL /* -T NETWORK_READ_TIMEOUT */
Denis Vlasenko5e34ff22009-04-21 11:09:40 +0000564 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
565 IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000566 );
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000567#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denis Vlasenko7534e082006-10-23 23:21:58 +0000568 if (headers_llist) {
569 int size = 1;
570 char *cp;
Denis Vlasenko8d9f4952007-04-08 15:08:42 +0000571 llist_t *ll = headers_llist;
Denis Vlasenko7534e082006-10-23 23:21:58 +0000572 while (ll) {
573 size += strlen(ll->data) + 2;
574 ll = ll->link;
575 }
576 extra_headers = cp = xmalloc(size);
Glenn L McGrath514aeab2003-12-19 12:08:56 +0000577 while (headers_llist) {
Denis Vlasenkod50dda82008-06-15 05:40:56 +0000578 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
Eric Andersen96700832000-09-04 15:15:55 +0000579 }
580 }
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000581#endif
Tim Rikerc1ef7bd2006-01-25 00:08:53 +0000582
Denys Vlasenko7f432802009-06-28 01:02:24 +0200583 /* TODO: compat issue: should handle "wget URL1 URL2..." */
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200584
Vladimir Dronnikovbe168b12009-10-05 02:18:01 +0200585 target.user = NULL;
Eric Andersen79757c92001-04-05 21:45:54 +0000586 parse_url(argv[optind], &target);
Eric Andersen79757c92001-04-05 21:45:54 +0000587
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000588 /* Use the proxy if necessary */
Denys Vlasenko7f432802009-06-28 01:02:24 +0200589 use_proxy = (strcmp(proxy_flag, "off") != 0);
Glenn L McGrathf1c4b112004-02-22 00:27:34 +0000590 if (use_proxy) {
Robert Griebld7760112002-05-14 23:36:45 +0000591 proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200592 if (proxy && proxy[0]) {
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +0000593 parse_url(proxy, &server);
Glenn L McGrathf1c4b112004-02-22 00:27:34 +0000594 } else {
595 use_proxy = 0;
596 }
Robert Griebld7760112002-05-14 23:36:45 +0000597 }
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200598 if (!use_proxy) {
599 server.port = target.port;
600 if (ENABLE_FEATURE_IPV6) {
601 server.host = xstrdup(target.host);
602 } else {
603 server.host = target.host;
604 }
605 }
606
607 if (ENABLE_FEATURE_IPV6)
608 strip_ipv6_scope_id(target.host);
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000609
Denis Vlasenko818322b2007-09-24 18:27:04 +0000610 /* Guess an output filename, if there was no -O FILE */
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000611 if (!(opt & WGET_OPT_OUTNAME)) {
Denis Vlasenko818322b2007-09-24 18:27:04 +0000612 fname_out = bb_get_last_path_component_nostrip(target.path);
613 /* handle "wget http://kernel.org//" */
614 if (fname_out[0] == '/' || !fname_out[0])
Denis Vlasenkob6aae0f2007-01-29 22:51:25 +0000615 fname_out = (char*)"index.html";
Denis Vlasenko818322b2007-09-24 18:27:04 +0000616 /* -P DIR is considered only if there was no -O FILE */
617 if (dir_prefix)
Matt Kraai0382eb82001-07-19 19:13:55 +0000618 fname_out = concat_path_file(dir_prefix, fname_out);
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000619 } else {
620 if (LONE_DASH(fname_out)) {
621 /* -O - */
622 output_fd = 1;
623 opt &= ~WGET_OPT_CONTINUE;
624 }
Eric Andersen29edd002000-12-09 16:55:35 +0000625 }
Denis Vlasenko818322b2007-09-24 18:27:04 +0000626#if ENABLE_FEATURE_WGET_STATUSBAR
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100627 G.curfile = bb_get_last_path_component_nostrip(fname_out);
Denis Vlasenko818322b2007-09-24 18:27:04 +0000628#endif
629
Denis Vlasenko4e4662c2006-11-23 13:10:23 +0000630 /* Impossible?
Denis Vlasenkoa552eeb2006-09-26 09:22:12 +0000631 if ((opt & WGET_OPT_CONTINUE) && !fname_out)
Denys Vlasenko6331cf02009-11-13 09:08:27 +0100632 bb_error_msg_and_die("can't specify continue (-c) without a filename (-O)");
Denys Vlasenko7f432802009-06-28 01:02:24 +0200633 */
Eric Andersen29edd002000-12-09 16:55:35 +0000634
Bernhard Reutner-Fischer7e8a53a2007-04-10 09:37:29 +0000635 /* Determine where to start transfer */
Denis Vlasenko4e4662c2006-11-23 13:10:23 +0000636 if (opt & WGET_OPT_CONTINUE) {
Denis Vlasenko7039a662006-10-08 17:54:47 +0000637 output_fd = open(fname_out, O_WRONLY);
Denis Vlasenkoa94554d2006-09-23 17:49:09 +0000638 if (output_fd >= 0) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100639 G.beg_range = xlseek(output_fd, 0, SEEK_END);
Denis Vlasenkoa94554d2006-09-23 17:49:09 +0000640 }
641 /* File doesn't exist. We do not create file here yet.
Denys Vlasenko7f432802009-06-28 01:02:24 +0200642 * We are not sure it exists on remove side */
Eric Andersen96700832000-09-04 15:15:55 +0000643 }
644
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200645 redir_limit = 5;
646 resolve_lsa:
Denis Vlasenko42823d52007-02-04 02:39:08 +0000647 lsa = xhost2sockaddr(server.host, server.port);
Denis Vlasenkoa552eeb2006-09-26 09:22:12 +0000648 if (!(opt & WGET_OPT_QUIET)) {
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200649 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
650 fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
651 free(s);
Eric Andersene6dc4392003-10-31 09:31:46 +0000652 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200653 establish_session:
Glenn L McGrathf1c4b112004-02-22 00:27:34 +0000654 if (use_proxy || !target.is_ftp) {
Eric Andersen79757c92001-04-05 21:45:54 +0000655 /*
656 * HTTP session
657 */
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200658 char *str;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200659 int status;
Denys Vlasenko7f432802009-06-28 01:02:24 +0200660
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200661 /* Open socket to http server */
662 sfp = open_socket(lsa);
Denys Vlasenko7f432802009-06-28 01:02:24 +0200663
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200664 /* Send HTTP request */
665 if (use_proxy) {
666 fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
667 target.is_ftp ? "f" : "ht", target.host,
668 target.path);
669 } else {
670 if (opt & WGET_OPT_POST_DATA)
671 fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
672 else
673 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
674 }
Glenn L McGrathe7bdfcc2003-08-28 22:03:19 +0000675
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200676 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
677 target.host, user_agent);
Eric Andersen79757c92001-04-05 21:45:54 +0000678
Denis Vlasenko9cade082006-11-21 10:43:02 +0000679#if ENABLE_FEATURE_WGET_AUTHENTICATION
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200680 if (target.user) {
681 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
682 base64enc_512(buf, target.user));
683 }
684 if (use_proxy && server.user) {
685 fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
686 base64enc_512(buf, server.user));
687 }
Eric Andersen79757c92001-04-05 21:45:54 +0000688#endif
689
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100690 if (G.beg_range)
691 fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000692#if ENABLE_FEATURE_WGET_LONG_OPTIONS
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200693 if (extra_headers)
694 fputs(extra_headers, sfp);
Denis Vlasenko5a2ad692009-03-04 14:13:37 +0000695
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200696 if (opt & WGET_OPT_POST_DATA) {
697 char *estr = URL_escape(post_data);
698 fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
699 fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
700 (int) strlen(estr), estr);
701 /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
702 /*fprintf(sfp, "%s\r\n", estr);*/
703 free(estr);
704 } else
Denis Vlasenkoc8400a22006-10-25 00:33:44 +0000705#endif
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200706 { /* If "Connection:" is needed, document why */
707 fprintf(sfp, /* "Connection: close\r\n" */ "\r\n");
708 }
Eric Andersen79757c92001-04-05 21:45:54 +0000709
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200710 /*
711 * Retrieve HTTP response line and check for "200" status code.
712 */
Denis Vlasenko023b57d2006-10-15 17:05:55 +0000713 read_response:
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200714 if (fgets(buf, sizeof(buf), sfp) == NULL)
715 bb_error_msg_and_die("no response from server");
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000716
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200717 str = buf;
718 str = skip_non_whitespace(str);
719 str = skip_whitespace(str);
720 // FIXME: no error check
721 // xatou wouldn't work: "200 OK"
722 status = atoi(str);
723 switch (status) {
724 case 0:
725 case 100:
726 while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
727 /* eat all remaining headers */;
728 goto read_response;
729 case 200:
Denis Vlasenko50b5cac2008-06-22 16:28:02 +0000730/*
731Response 204 doesn't say "null file", it says "metadata
732has changed but data didn't":
733
734"10.2.5 204 No Content
735The server has fulfilled the request but does not need to return
736an entity-body, and might want to return updated metainformation.
737The response MAY include new or updated metainformation in the form
738of entity-headers, which if present SHOULD be associated with
739the requested variant.
740
741If the client is a user agent, it SHOULD NOT change its document
742view from that which caused the request to be sent. This response
743is primarily intended to allow input for actions to take place
744without causing a change to the user agent's active document view,
745although any new or updated metainformation SHOULD be applied
746to the document currently in the user agent's active view.
747
748The 204 response MUST NOT include a message-body, and thus
749is always terminated by the first empty line after the header fields."
750
751However, in real world it was observed that some web servers
752(e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
753*/
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200754 case 204:
755 break;
756 case 300: /* redirection */
757 case 301:
758 case 302:
759 case 303:
760 break;
761 case 206:
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100762 if (G.beg_range)
Denis Vlasenko023b57d2006-10-15 17:05:55 +0000763 break;
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200764 /* fall through */
765 default:
766 bb_error_msg_and_die("server returned error: %s", sanitize_string(buf));
767 }
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000768
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200769 /*
770 * Retrieve HTTP headers.
771 */
772 while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
773 /* gethdr converted "FOO:" string to lowercase */
Matthijs van de Water0d586662009-08-22 20:19:48 +0200774 smalluint key;
775 /* strip trailing whitespace */
776 char *s = strchrnul(str, '\0') - 1;
777 while (s >= str && (*s == ' ' || *s == '\t')) {
778 *s = '\0';
779 s--;
780 }
781 key = index_in_strings(keywords, buf) + 1;
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200782 if (key == KEY_content_length) {
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100783 G.content_len = BB_STRTOOFF(str, NULL, 10);
784 if (G.content_len < 0 || errno) {
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200785 bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
Eric Andersen79757c92001-04-05 21:45:54 +0000786 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200787 G.got_clen = 1;
788 continue;
789 }
790 if (key == KEY_transfer_encoding) {
791 if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
792 bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
793 G.chunked = G.got_clen = 1;
794 }
795 if (key == KEY_location && status >= 300) {
796 if (--redir_limit == 0)
797 bb_error_msg_and_die("too many redirections");
798 fclose(sfp);
799 G.got_clen = 0;
800 G.chunked = 0;
801 if (str[0] == '/')
802 /* free(target.allocated); */
803 target.path = /* target.allocated = */ xstrdup(str+1);
804 /* lsa stays the same: it's on the same server */
805 else {
806 parse_url(str, &target);
807 if (!use_proxy) {
808 server.host = target.host;
Denys Vlasenko7d5ddf12009-06-30 20:36:27 +0200809 /* strip_ipv6_scope_id(target.host); - no! */
810 /* we assume remote never gives us IPv6 addr with scope id */
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200811 server.port = target.port;
Denis Vlasenko6536a9b2007-01-12 10:35:23 +0000812 free(lsa);
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200813 goto resolve_lsa;
814 } /* else: lsa stays the same: we use proxy */
Eric Andersen79757c92001-04-05 21:45:54 +0000815 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200816 goto establish_session;
Eric Andersen79757c92001-04-05 21:45:54 +0000817 }
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200818 }
819// if (status >= 300)
820// bb_error_msg_and_die("bad redirection (no Location: header from server)");
Eric Andersenc7bda1c2004-03-15 08:29:22 +0000821
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200822 /* For HTTP, data is pumped over the same connection */
Eric Andersen79757c92001-04-05 21:45:54 +0000823 dfp = sfp;
Denis Vlasenko96e9d3c2006-10-07 14:28:55 +0000824
825 } else {
Eric Andersen79757c92001-04-05 21:45:54 +0000826 /*
827 * FTP session
828 */
Denys Vlasenko7f432802009-06-28 01:02:24 +0200829 sfp = prepare_ftp_session(&dfp, &target, lsa);
Eric Andersen96700832000-09-04 15:15:55 +0000830 }
Denis Vlasenko77105632007-09-24 15:04:00 +0000831
Bernhard Reutner-Fischer2e75dcc2007-04-05 10:31:47 +0000832 if (opt & WGET_OPT_SPIDER) {
833 if (ENABLE_FEATURE_CLEAN_UP)
834 fclose(sfp);
Denis Vlasenko77105632007-09-24 15:04:00 +0000835 return EXIT_SUCCESS;
Bernhard Reutner-Fischer2e75dcc2007-04-05 10:31:47 +0000836 }
Eric Andersen79757c92001-04-05 21:45:54 +0000837
Denis Vlasenkoa36535b2007-09-27 15:07:23 +0000838 if (output_fd < 0) {
839 int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
840 /* compat with wget: -O FILE can overwrite */
841 if (opt & WGET_OPT_OUTNAME)
842 o_flags = O_WRONLY | O_CREAT | O_TRUNC;
843 output_fd = xopen(fname_out, o_flags);
844 }
Denis Vlasenkof8aa1092006-10-01 10:58:54 +0000845
Denys Vlasenko7f432802009-06-28 01:02:24 +0200846 retrieve_file_data(dfp, output_fd);
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100847 xclose(output_fd);
Rob Landley19a39402006-06-13 17:10:26 +0000848
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200849 if (dfp != sfp) {
850 /* It's ftp. Close it properly */
Eric Andersen79757c92001-04-05 21:45:54 +0000851 fclose(dfp);
852 if (ftpcmd(NULL, NULL, sfp, buf) != 226)
Denys Vlasenkof1fab092009-06-28 03:33:57 +0200853 bb_error_msg_and_die("ftp error: %s", sanitize_string(buf+4));
Denys Vlasenkoa3aa3e32009-12-11 12:36:10 +0100854 /* ftpcmd("QUIT", NULL, sfp, buf); - why bother? */
Eric Andersen79757c92001-04-05 21:45:54 +0000855 }
Denis Vlasenko77105632007-09-24 15:04:00 +0000856
857 return EXIT_SUCCESS;
Eric Andersen96700832000-09-04 15:15:55 +0000858}