diff options
Diffstat (limited to 'networking/wget.c')
-rw-r--r-- | networking/wget.c | 832 |
1 files changed, 832 insertions, 0 deletions
diff --git a/networking/wget.c b/networking/wget.c new file mode 100644 index 000000000..028e18c73 --- /dev/null +++ b/networking/wget.c | |||
@@ -0,0 +1,832 @@ | |||
1 | /* vi: set sw=4 ts=4: */ | ||
2 | /* | ||
3 | * wget - retrieve a file using HTTP or FTP | ||
4 | * | ||
5 | * Chip Rosenthal Covad Communications <chip@laserlink.net> | ||
6 | * | ||
7 | */ | ||
8 | |||
9 | /* We want libc to give us xxx64 functions also */ | ||
10 | /* http://www.unix.org/version2/whatsnew/lfs20mar.html */ | ||
11 | #define _LARGEFILE64_SOURCE 1 | ||
12 | |||
13 | #include "busybox.h" | ||
14 | #include <getopt.h> /* for struct option */ | ||
15 | |||
16 | struct host_info { | ||
17 | // May be used if we ever will want to free() all xstrdup()s... | ||
18 | /* char *allocated; */ | ||
19 | char *host; | ||
20 | int port; | ||
21 | char *path; | ||
22 | int is_ftp; | ||
23 | char *user; | ||
24 | }; | ||
25 | |||
26 | static void parse_url(char *url, struct host_info *h); | ||
27 | static FILE *open_socket(struct sockaddr_in *s_in); | ||
28 | static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc); | ||
29 | static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf); | ||
30 | |||
31 | /* Globals (can be accessed from signal handlers */ | ||
32 | static off_t content_len; /* Content-length of the file */ | ||
33 | static off_t beg_range; /* Range at which continue begins */ | ||
34 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
35 | static off_t transferred; /* Number of bytes transferred so far */ | ||
36 | #endif | ||
37 | static int chunked; /* chunked transfer encoding */ | ||
38 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
39 | static void progressmeter(int flag); | ||
40 | static char *curfile; /* Name of current file being transferred */ | ||
41 | static struct timeval start; /* Time a transfer started */ | ||
42 | enum { | ||
43 | STALLTIME = 5 /* Seconds when xfer considered "stalled" */ | ||
44 | }; | ||
45 | #else | ||
46 | static void progressmeter(int flag) {} | ||
47 | #endif | ||
48 | |||
49 | /* Read NMEMB elements of SIZE bytes into PTR from STREAM. Returns the | ||
50 | * number of elements read, and a short count if an eof or non-interrupt | ||
51 | * error is encountered. */ | ||
52 | static size_t safe_fread(void *ptr, size_t size, size_t nmemb, FILE *stream) | ||
53 | { | ||
54 | size_t ret = 0; | ||
55 | |||
56 | do { | ||
57 | clearerr(stream); | ||
58 | ret += fread((char *)ptr + (ret * size), size, nmemb - ret, stream); | ||
59 | } while (ret < nmemb && ferror(stream) && errno == EINTR); | ||
60 | |||
61 | return ret; | ||
62 | } | ||
63 | |||
64 | /* Read a line or SIZE - 1 bytes into S, whichever is less, from STREAM. | ||
65 | * Returns S, or NULL if an eof or non-interrupt error is encountered. */ | ||
66 | static char *safe_fgets(char *s, int size, FILE *stream) | ||
67 | { | ||
68 | char *ret; | ||
69 | |||
70 | do { | ||
71 | clearerr(stream); | ||
72 | ret = fgets(s, size, stream); | ||
73 | } while (ret == NULL && ferror(stream) && errno == EINTR); | ||
74 | |||
75 | return ret; | ||
76 | } | ||
77 | |||
78 | #if ENABLE_FEATURE_WGET_AUTHENTICATION | ||
79 | /* | ||
80 | * Base64-encode character string and return the string. | ||
81 | */ | ||
82 | static char *base64enc(unsigned char *p, char *buf, int len) | ||
83 | { | ||
84 | bb_uuencode(p, buf, len, bb_uuenc_tbl_base64); | ||
85 | return buf; | ||
86 | } | ||
87 | #endif | ||
88 | |||
89 | int wget_main(int argc, char **argv) | ||
90 | { | ||
91 | char buf[512]; | ||
92 | struct host_info server, target; | ||
93 | struct sockaddr_in s_in; | ||
94 | int n, status; | ||
95 | int port; | ||
96 | int try = 5; | ||
97 | unsigned opt; | ||
98 | char *s; | ||
99 | char *proxy = 0; | ||
100 | char *dir_prefix = NULL; | ||
101 | #if ENABLE_FEATURE_WGET_LONG_OPTIONS | ||
102 | char *extra_headers = NULL; | ||
103 | llist_t *headers_llist = NULL; | ||
104 | #endif | ||
105 | |||
106 | /* server.allocated = target.allocated = NULL; */ | ||
107 | |||
108 | FILE *sfp = NULL; /* socket to web/ftp server */ | ||
109 | FILE *dfp = NULL; /* socket to ftp server (data) */ | ||
110 | char *fname_out = NULL; /* where to direct output (-O) */ | ||
111 | int got_clen = 0; /* got content-length: from server */ | ||
112 | int output_fd = -1; | ||
113 | int use_proxy = 1; /* Use proxies if env vars are set */ | ||
114 | const char *proxy_flag = "on"; /* Use proxies if env vars are set */ | ||
115 | const char *user_agent = "Wget";/* Content of the "User-Agent" header field */ | ||
116 | |||
117 | /* | ||
118 | * Crack command line. | ||
119 | */ | ||
120 | enum { | ||
121 | WGET_OPT_CONTINUE = 0x1, | ||
122 | WGET_OPT_QUIET = 0x2, | ||
123 | WGET_OPT_OUTNAME = 0x4, | ||
124 | WGET_OPT_PREFIX = 0x8, | ||
125 | WGET_OPT_PROXY = 0x10, | ||
126 | WGET_OPT_USER_AGENT = 0x20, | ||
127 | WGET_OPT_PASSIVE = 0x40, | ||
128 | WGET_OPT_HEADER = 0x80, | ||
129 | }; | ||
130 | #if ENABLE_FEATURE_WGET_LONG_OPTIONS | ||
131 | static const struct option wget_long_options[] = { | ||
132 | // name, has_arg, flag, val | ||
133 | { "continue", no_argument, NULL, 'c' }, | ||
134 | { "quiet", no_argument, NULL, 'q' }, | ||
135 | { "output-document", required_argument, NULL, 'O' }, | ||
136 | { "directory-prefix", required_argument, NULL, 'P' }, | ||
137 | { "proxy", required_argument, NULL, 'Y' }, | ||
138 | { "user-agent", required_argument, NULL, 'U' }, | ||
139 | { "passive-ftp", no_argument, NULL, 0xff }, | ||
140 | { "header", required_argument, NULL, 0xfe }, | ||
141 | { 0, 0, 0, 0 } | ||
142 | }; | ||
143 | applet_long_options = wget_long_options; | ||
144 | #endif | ||
145 | opt_complementary = "-1" USE_FEATURE_WGET_LONG_OPTIONS(":\xfe::"); | ||
146 | opt = getopt32(argc, argv, "cqO:P:Y:U:", | ||
147 | &fname_out, &dir_prefix, | ||
148 | &proxy_flag, &user_agent | ||
149 | USE_FEATURE_WGET_LONG_OPTIONS(, &headers_llist) | ||
150 | ); | ||
151 | if (strcmp(proxy_flag, "off") == 0) { | ||
152 | /* Use the proxy if necessary. */ | ||
153 | use_proxy = 0; | ||
154 | } | ||
155 | #if ENABLE_FEATURE_WGET_LONG_OPTIONS | ||
156 | if (headers_llist) { | ||
157 | int size = 1; | ||
158 | char *cp; | ||
159 | llist_t *ll = headers_llist = rev_llist(headers_llist); | ||
160 | while (ll) { | ||
161 | size += strlen(ll->data) + 2; | ||
162 | ll = ll->link; | ||
163 | } | ||
164 | extra_headers = cp = xmalloc(size); | ||
165 | while (headers_llist) { | ||
166 | cp += sprintf(cp, "%s\r\n", headers_llist->data); | ||
167 | headers_llist = headers_llist->link; | ||
168 | } | ||
169 | } | ||
170 | #endif | ||
171 | |||
172 | parse_url(argv[optind], &target); | ||
173 | server.host = target.host; | ||
174 | server.port = target.port; | ||
175 | |||
176 | /* | ||
177 | * Use the proxy if necessary. | ||
178 | */ | ||
179 | if (use_proxy) { | ||
180 | proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy"); | ||
181 | if (proxy && *proxy) { | ||
182 | parse_url(proxy, &server); | ||
183 | } else { | ||
184 | use_proxy = 0; | ||
185 | } | ||
186 | } | ||
187 | |||
188 | /* Guess an output filename */ | ||
189 | if (!fname_out) { | ||
190 | // Dirty hack. Needed because bb_get_last_path_component | ||
191 | // will destroy trailing / by storing '\0' in last byte! | ||
192 | if (*target.path && target.path[strlen(target.path)-1] != '/') { | ||
193 | fname_out = | ||
194 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
195 | curfile = | ||
196 | #endif | ||
197 | bb_get_last_path_component(target.path); | ||
198 | } | ||
199 | if (!fname_out || !fname_out[0]) { | ||
200 | fname_out = | ||
201 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
202 | curfile = | ||
203 | #endif | ||
204 | "index.html"; | ||
205 | } | ||
206 | if (dir_prefix != NULL) | ||
207 | fname_out = concat_path_file(dir_prefix, fname_out); | ||
208 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
209 | } else { | ||
210 | curfile = bb_get_last_path_component(fname_out); | ||
211 | #endif | ||
212 | } | ||
213 | /* Impossible? | ||
214 | if ((opt & WGET_OPT_CONTINUE) && !fname_out) | ||
215 | bb_error_msg_and_die("cannot specify continue (-c) without a filename (-O)"); */ | ||
216 | |||
217 | /* | ||
218 | * Determine where to start transfer. | ||
219 | */ | ||
220 | if (fname_out[0] == '-' && !fname_out[1]) { | ||
221 | output_fd = 1; | ||
222 | opt &= ~WGET_OPT_CONTINUE; | ||
223 | } | ||
224 | if (opt & WGET_OPT_CONTINUE) { | ||
225 | output_fd = open(fname_out, O_WRONLY); | ||
226 | if (output_fd >= 0) { | ||
227 | beg_range = xlseek(output_fd, 0, SEEK_END); | ||
228 | } | ||
229 | /* File doesn't exist. We do not create file here yet. | ||
230 | We are not sure it exists on remove side */ | ||
231 | } | ||
232 | |||
233 | /* We want to do exactly _one_ DNS lookup, since some | ||
234 | * sites (i.e. ftp.us.debian.org) use round-robin DNS | ||
235 | * and we want to connect to only one IP... */ | ||
236 | bb_lookup_host(&s_in, server.host); | ||
237 | s_in.sin_port = server.port; | ||
238 | if (!(opt & WGET_OPT_QUIET)) { | ||
239 | fprintf(stderr, "Connecting to %s[%s]:%d\n", | ||
240 | server.host, inet_ntoa(s_in.sin_addr), ntohs(server.port)); | ||
241 | } | ||
242 | |||
243 | if (use_proxy || !target.is_ftp) { | ||
244 | /* | ||
245 | * HTTP session | ||
246 | */ | ||
247 | do { | ||
248 | got_clen = chunked = 0; | ||
249 | |||
250 | if (!--try) | ||
251 | bb_error_msg_and_die("too many redirections"); | ||
252 | |||
253 | /* | ||
254 | * Open socket to http server | ||
255 | */ | ||
256 | if (sfp) fclose(sfp); | ||
257 | sfp = open_socket(&s_in); | ||
258 | |||
259 | /* | ||
260 | * Send HTTP request. | ||
261 | */ | ||
262 | if (use_proxy) { | ||
263 | const char *format = "GET %stp://%s:%d/%s HTTP/1.1\r\n"; | ||
264 | #if ENABLE_FEATURE_WGET_IP6_LITERAL | ||
265 | if (strchr(target.host, ':')) | ||
266 | format = "GET %stp://[%s]:%d/%s HTTP/1.1\r\n"; | ||
267 | #endif | ||
268 | fprintf(sfp, format, | ||
269 | target.is_ftp ? "f" : "ht", target.host, | ||
270 | ntohs(target.port), target.path); | ||
271 | } else { | ||
272 | fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path); | ||
273 | } | ||
274 | |||
275 | fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", target.host, | ||
276 | user_agent); | ||
277 | |||
278 | #if ENABLE_FEATURE_WGET_AUTHENTICATION | ||
279 | if (target.user) { | ||
280 | fprintf(sfp, "Authorization: Basic %s\r\n", | ||
281 | base64enc((unsigned char*)target.user, buf, sizeof(buf))); | ||
282 | } | ||
283 | if (use_proxy && server.user) { | ||
284 | fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", | ||
285 | base64enc((unsigned char*)server.user, buf, sizeof(buf))); | ||
286 | } | ||
287 | #endif | ||
288 | |||
289 | if (beg_range) | ||
290 | fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range); | ||
291 | #if ENABLE_FEATURE_WGET_LONG_OPTIONS | ||
292 | if (extra_headers) | ||
293 | fputs(extra_headers, sfp); | ||
294 | #endif | ||
295 | fprintf(sfp, "Connection: close\r\n\r\n"); | ||
296 | |||
297 | /* | ||
298 | * Retrieve HTTP response line and check for "200" status code. | ||
299 | */ | ||
300 | read_response: | ||
301 | if (fgets(buf, sizeof(buf), sfp) == NULL) | ||
302 | bb_error_msg_and_die("no response from server"); | ||
303 | |||
304 | s = buf; | ||
305 | while (*s != '\0' && !isspace(*s)) ++s; | ||
306 | s = skip_whitespace(s); | ||
307 | // FIXME: no error check | ||
308 | // xatou wouldn't work: "200 OK" | ||
309 | status = atoi(s); | ||
310 | switch (status) { | ||
311 | case 0: | ||
312 | case 100: | ||
313 | while (gethdr(buf, sizeof(buf), sfp, &n) != NULL) | ||
314 | /* eat all remaining headers */; | ||
315 | goto read_response; | ||
316 | case 200: | ||
317 | break; | ||
318 | case 300: /* redirection */ | ||
319 | case 301: | ||
320 | case 302: | ||
321 | case 303: | ||
322 | break; | ||
323 | case 206: | ||
324 | if (beg_range) | ||
325 | break; | ||
326 | /*FALLTHRU*/ | ||
327 | default: | ||
328 | /* Show first line only and kill any ESC tricks */ | ||
329 | buf[strcspn(buf, "\n\r\x1b")] = '\0'; | ||
330 | bb_error_msg_and_die("server returned error: %s", buf); | ||
331 | } | ||
332 | |||
333 | /* | ||
334 | * Retrieve HTTP headers. | ||
335 | */ | ||
336 | while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) { | ||
337 | if (strcasecmp(buf, "content-length") == 0) { | ||
338 | content_len = BB_STRTOOFF(s, NULL, 10); | ||
339 | if (errno || content_len < 0) { | ||
340 | bb_error_msg_and_die("content-length %s is garbage", s); | ||
341 | } | ||
342 | got_clen = 1; | ||
343 | continue; | ||
344 | } | ||
345 | if (strcasecmp(buf, "transfer-encoding") == 0) { | ||
346 | if (strcasecmp(s, "chunked") != 0) | ||
347 | bb_error_msg_and_die("server wants to do %s transfer encoding", s); | ||
348 | chunked = got_clen = 1; | ||
349 | } | ||
350 | if (strcasecmp(buf, "location") == 0) { | ||
351 | if (s[0] == '/') | ||
352 | /* free(target.allocated); */ | ||
353 | target.path = /* target.allocated = */ xstrdup(s+1); | ||
354 | else { | ||
355 | parse_url(s, &target); | ||
356 | if (use_proxy == 0) { | ||
357 | server.host = target.host; | ||
358 | server.port = target.port; | ||
359 | } | ||
360 | bb_lookup_host(&s_in, server.host); | ||
361 | s_in.sin_port = server.port; | ||
362 | break; | ||
363 | } | ||
364 | } | ||
365 | } | ||
366 | } while(status >= 300); | ||
367 | |||
368 | dfp = sfp; | ||
369 | |||
370 | } else { | ||
371 | |||
372 | /* | ||
373 | * FTP session | ||
374 | */ | ||
375 | if (!target.user) | ||
376 | target.user = xstrdup("anonymous:busybox@"); | ||
377 | |||
378 | sfp = open_socket(&s_in); | ||
379 | if (ftpcmd(NULL, NULL, sfp, buf) != 220) | ||
380 | bb_error_msg_and_die("%s", buf+4); | ||
381 | |||
382 | /* | ||
383 | * Splitting username:password pair, | ||
384 | * trying to log in | ||
385 | */ | ||
386 | s = strchr(target.user, ':'); | ||
387 | if (s) | ||
388 | *(s++) = '\0'; | ||
389 | switch (ftpcmd("USER ", target.user, sfp, buf)) { | ||
390 | case 230: | ||
391 | break; | ||
392 | case 331: | ||
393 | if (ftpcmd("PASS ", s, sfp, buf) == 230) | ||
394 | break; | ||
395 | /* FALLTHRU (failed login) */ | ||
396 | default: | ||
397 | bb_error_msg_and_die("ftp login: %s", buf+4); | ||
398 | } | ||
399 | |||
400 | ftpcmd("TYPE I", NULL, sfp, buf); | ||
401 | |||
402 | /* | ||
403 | * Querying file size | ||
404 | */ | ||
405 | if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) { | ||
406 | content_len = BB_STRTOOFF(buf+4, NULL, 10); | ||
407 | if (errno || content_len < 0) { | ||
408 | bb_error_msg_and_die("SIZE value is garbage"); | ||
409 | } | ||
410 | got_clen = 1; | ||
411 | } | ||
412 | |||
413 | /* | ||
414 | * Entering passive mode | ||
415 | */ | ||
416 | if (ftpcmd("PASV", NULL, sfp, buf) != 227) { | ||
417 | pasv_error: | ||
418 | bb_error_msg_and_die("bad response to %s: %s", "PASV", buf); | ||
419 | } | ||
420 | // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage] | ||
421 | // Server's IP is N1.N2.N3.N4 (we ignore it) | ||
422 | // Server's port for data connection is P1*256+P2 | ||
423 | s = strrchr(buf, ')'); | ||
424 | if (s) s[0] = '\0'; | ||
425 | s = strrchr(buf, ','); | ||
426 | if (!s) goto pasv_error; | ||
427 | port = xatou_range(s+1, 0, 255); | ||
428 | *s = '\0'; | ||
429 | s = strrchr(buf, ','); | ||
430 | if (!s) goto pasv_error; | ||
431 | port += xatou_range(s+1, 0, 255) * 256; | ||
432 | s_in.sin_port = htons(port); | ||
433 | dfp = open_socket(&s_in); | ||
434 | |||
435 | if (beg_range) { | ||
436 | sprintf(buf, "REST %"OFF_FMT"d", beg_range); | ||
437 | if (ftpcmd(buf, NULL, sfp, buf) == 350) | ||
438 | content_len -= beg_range; | ||
439 | } | ||
440 | |||
441 | if (ftpcmd("RETR ", target.path, sfp, buf) > 150) | ||
442 | bb_error_msg_and_die("bad response to RETR: %s", buf); | ||
443 | } | ||
444 | |||
445 | |||
446 | /* | ||
447 | * Retrieve file | ||
448 | */ | ||
449 | if (chunked) { | ||
450 | fgets(buf, sizeof(buf), dfp); | ||
451 | content_len = STRTOOFF(buf, NULL, 16); | ||
452 | /* FIXME: error check?? */ | ||
453 | } | ||
454 | |||
455 | /* Do it before progressmeter (want to have nice error message) */ | ||
456 | if (output_fd < 0) | ||
457 | output_fd = xopen(fname_out, | ||
458 | O_WRONLY|O_CREAT|O_EXCL|O_TRUNC); | ||
459 | |||
460 | if (!(opt & WGET_OPT_QUIET)) | ||
461 | progressmeter(-1); | ||
462 | |||
463 | do { | ||
464 | while (content_len > 0 || !got_clen) { | ||
465 | unsigned rdsz = sizeof(buf); | ||
466 | if (content_len < sizeof(buf) && (chunked || got_clen)) | ||
467 | rdsz = (unsigned)content_len; | ||
468 | n = safe_fread(buf, 1, rdsz, dfp); | ||
469 | if (n <= 0) | ||
470 | break; | ||
471 | if (full_write(output_fd, buf, n) != n) { | ||
472 | bb_perror_msg_and_die(bb_msg_write_error); | ||
473 | } | ||
474 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
475 | transferred += n; | ||
476 | #endif | ||
477 | if (got_clen) { | ||
478 | content_len -= n; | ||
479 | } | ||
480 | } | ||
481 | |||
482 | if (chunked) { | ||
483 | safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ | ||
484 | safe_fgets(buf, sizeof(buf), dfp); | ||
485 | content_len = STRTOOFF(buf, NULL, 16); | ||
486 | /* FIXME: error check? */ | ||
487 | if (content_len == 0) { | ||
488 | chunked = 0; /* all done! */ | ||
489 | } | ||
490 | } | ||
491 | |||
492 | if (n == 0 && ferror(dfp)) { | ||
493 | bb_perror_msg_and_die(bb_msg_read_error); | ||
494 | } | ||
495 | } while (chunked); | ||
496 | |||
497 | if (!(opt & WGET_OPT_QUIET)) | ||
498 | progressmeter(1); | ||
499 | |||
500 | if ((use_proxy == 0) && target.is_ftp) { | ||
501 | fclose(dfp); | ||
502 | if (ftpcmd(NULL, NULL, sfp, buf) != 226) | ||
503 | bb_error_msg_and_die("ftp error: %s", buf+4); | ||
504 | ftpcmd("QUIT", NULL, sfp, buf); | ||
505 | } | ||
506 | exit(EXIT_SUCCESS); | ||
507 | } | ||
508 | |||
509 | |||
510 | static void parse_url(char *src_url, struct host_info *h) | ||
511 | { | ||
512 | char *url, *p, *cp, *sp, *up, *pp; | ||
513 | |||
514 | /* h->allocated = */ url = xstrdup(src_url); | ||
515 | |||
516 | if (strncmp(url, "http://", 7) == 0) { | ||
517 | h->port = bb_lookup_port("http", "tcp", 80); | ||
518 | h->host = url + 7; | ||
519 | h->is_ftp = 0; | ||
520 | } else if (strncmp(url, "ftp://", 6) == 0) { | ||
521 | h->port = bb_lookup_port("ftp", "tcp", 21); | ||
522 | h->host = url + 6; | ||
523 | h->is_ftp = 1; | ||
524 | } else | ||
525 | bb_error_msg_and_die("not an http or ftp url: %s", url); | ||
526 | |||
527 | // FYI: | ||
528 | // "Real" wget 'http://busybox.net?var=a/b' sends this request: | ||
529 | // 'GET /?var=a/b HTTP 1.0' | ||
530 | // and saves 'index.html?var=a%2Fb' (we save 'b') | ||
531 | // wget 'http://busybox.net?login=john@doe': | ||
532 | // request: 'GET /?login=john@doe HTTP/1.0' | ||
533 | // saves: 'index.html?login=john@doe' (we save '?login=john@doe') | ||
534 | // wget 'http://busybox.net#test/test': | ||
535 | // request: 'GET / HTTP/1.0' | ||
536 | // saves: 'index.html' (we save 'test') | ||
537 | // | ||
538 | // We also don't add unique .N suffix if file exists... | ||
539 | sp = strchr(h->host, '/'); | ||
540 | p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p; | ||
541 | p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p; | ||
542 | if (!sp) { | ||
543 | h->path = ""; | ||
544 | } else if (*sp == '/') { | ||
545 | *sp++ = '\0'; | ||
546 | h->path = sp; | ||
547 | } else { // '#' or '?' | ||
548 | // http://busybox.net?login=john@doe is a valid URL | ||
549 | // memmove converts to: | ||
550 | // http:/busybox.nett?login=john@doe... | ||
551 | memmove(h->host-1, h->host, sp - h->host); | ||
552 | h->host--; | ||
553 | sp[-1] = '\0'; | ||
554 | h->path = sp; | ||
555 | } | ||
556 | |||
557 | up = strrchr(h->host, '@'); | ||
558 | if (up != NULL) { | ||
559 | h->user = h->host; | ||
560 | *up++ = '\0'; | ||
561 | h->host = up; | ||
562 | } else | ||
563 | h->user = NULL; | ||
564 | |||
565 | pp = h->host; | ||
566 | |||
567 | #if ENABLE_FEATURE_WGET_IP6_LITERAL | ||
568 | if (h->host[0] == '[') { | ||
569 | char *ep; | ||
570 | |||
571 | ep = h->host + 1; | ||
572 | while (*ep == ':' || isxdigit(*ep)) | ||
573 | ep++; | ||
574 | if (*ep == ']') { | ||
575 | h->host++; | ||
576 | *ep = '\0'; | ||
577 | pp = ep + 1; | ||
578 | } | ||
579 | } | ||
580 | #endif | ||
581 | |||
582 | cp = strchr(pp, ':'); | ||
583 | if (cp != NULL) { | ||
584 | *cp++ = '\0'; | ||
585 | h->port = htons(xatou16(cp)); | ||
586 | } | ||
587 | } | ||
588 | |||
589 | |||
590 | static FILE *open_socket(struct sockaddr_in *s_in) | ||
591 | { | ||
592 | FILE *fp; | ||
593 | |||
594 | /* glibc 2.4 seems to try seeking on it - ??! */ | ||
595 | /* hopefully it understands what ESPIPE means... */ | ||
596 | fp = fdopen(xconnect_tcp_v4(s_in), "r+"); | ||
597 | if (fp == NULL) | ||
598 | bb_perror_msg_and_die("fdopen"); | ||
599 | |||
600 | return fp; | ||
601 | } | ||
602 | |||
603 | |||
604 | static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc) | ||
605 | { | ||
606 | char *s, *hdrval; | ||
607 | int c; | ||
608 | |||
609 | *istrunc = 0; | ||
610 | |||
611 | /* retrieve header line */ | ||
612 | if (fgets(buf, bufsiz, fp) == NULL) | ||
613 | return NULL; | ||
614 | |||
615 | /* see if we are at the end of the headers */ | ||
616 | for (s = buf; *s == '\r'; ++s) | ||
617 | ; | ||
618 | if (s[0] == '\n') | ||
619 | return NULL; | ||
620 | |||
621 | /* convert the header name to lower case */ | ||
622 | for (s = buf; isalnum(*s) || *s == '-'; ++s) | ||
623 | *s = tolower(*s); | ||
624 | |||
625 | /* verify we are at the end of the header name */ | ||
626 | if (*s != ':') | ||
627 | bb_error_msg_and_die("bad header line: %s", buf); | ||
628 | |||
629 | /* locate the start of the header value */ | ||
630 | for (*s++ = '\0'; *s == ' ' || *s == '\t'; ++s) | ||
631 | ; | ||
632 | hdrval = s; | ||
633 | |||
634 | /* locate the end of header */ | ||
635 | while (*s != '\0' && *s != '\r' && *s != '\n') | ||
636 | ++s; | ||
637 | |||
638 | /* end of header found */ | ||
639 | if (*s != '\0') { | ||
640 | *s = '\0'; | ||
641 | return hdrval; | ||
642 | } | ||
643 | |||
644 | /* Rats! The buffer isn't big enough to hold the entire header value. */ | ||
645 | while (c = getc(fp), c != EOF && c != '\n') | ||
646 | ; | ||
647 | *istrunc = 1; | ||
648 | return hdrval; | ||
649 | } | ||
650 | |||
651 | static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf) | ||
652 | { | ||
653 | int result; | ||
654 | if (s1) { | ||
655 | if (!s2) s2 = ""; | ||
656 | fprintf(fp, "%s%s\r\n", s1, s2); | ||
657 | fflush(fp); | ||
658 | } | ||
659 | |||
660 | do { | ||
661 | char *buf_ptr; | ||
662 | |||
663 | if (fgets(buf, 510, fp) == NULL) { | ||
664 | bb_perror_msg_and_die("error getting response"); | ||
665 | } | ||
666 | buf_ptr = strstr(buf, "\r\n"); | ||
667 | if (buf_ptr) { | ||
668 | *buf_ptr = '\0'; | ||
669 | } | ||
670 | } while (!isdigit(buf[0]) || buf[3] != ' '); | ||
671 | |||
672 | buf[3] = '\0'; | ||
673 | result = xatoi_u(buf); | ||
674 | buf[3] = ' '; | ||
675 | return result; | ||
676 | } | ||
677 | |||
678 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
679 | /* Stuff below is from BSD rcp util.c, as added to openshh. | ||
680 | * Original copyright notice is retained at the end of this file. | ||
681 | */ | ||
682 | static int | ||
683 | getttywidth(void) | ||
684 | { | ||
685 | int width; | ||
686 | get_terminal_width_height(0, &width, NULL); | ||
687 | return width; | ||
688 | } | ||
689 | |||
690 | static void | ||
691 | updateprogressmeter(int ignore) | ||
692 | { | ||
693 | int save_errno = errno; | ||
694 | |||
695 | progressmeter(0); | ||
696 | errno = save_errno; | ||
697 | } | ||
698 | |||
699 | static void alarmtimer(int iwait) | ||
700 | { | ||
701 | struct itimerval itv; | ||
702 | |||
703 | itv.it_value.tv_sec = iwait; | ||
704 | itv.it_value.tv_usec = 0; | ||
705 | itv.it_interval = itv.it_value; | ||
706 | setitimer(ITIMER_REAL, &itv, NULL); | ||
707 | } | ||
708 | |||
709 | |||
710 | static void | ||
711 | progressmeter(int flag) | ||
712 | { | ||
713 | static struct timeval lastupdate; | ||
714 | static off_t lastsize, totalsize; | ||
715 | |||
716 | struct timeval now, td, tvwait; | ||
717 | off_t abbrevsize; | ||
718 | int elapsed, ratio, barlength, i; | ||
719 | char buf[256]; | ||
720 | |||
721 | if (flag == -1) { /* first call to progressmeter */ | ||
722 | gettimeofday(&start, (struct timezone *) 0); | ||
723 | lastupdate = start; | ||
724 | lastsize = 0; | ||
725 | totalsize = content_len + beg_range; /* as content_len changes.. */ | ||
726 | } | ||
727 | |||
728 | gettimeofday(&now, (struct timezone *) 0); | ||
729 | ratio = 100; | ||
730 | if (totalsize != 0 && !chunked) { | ||
731 | /* long long helps to have working ETA even if !LFS */ | ||
732 | ratio = (int) (100 * (unsigned long long)(transferred+beg_range) / totalsize); | ||
733 | ratio = MIN(ratio, 100); | ||
734 | } | ||
735 | |||
736 | fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); | ||
737 | |||
738 | barlength = getttywidth() - 51; | ||
739 | if (barlength > 0 && barlength < sizeof(buf)) { | ||
740 | i = barlength * ratio / 100; | ||
741 | memset(buf, '*', i); | ||
742 | memset(buf + i, ' ', barlength - i); | ||
743 | buf[barlength] = '\0'; | ||
744 | fprintf(stderr, "|%s|", buf); | ||
745 | } | ||
746 | i = 0; | ||
747 | abbrevsize = transferred + beg_range; | ||
748 | while (abbrevsize >= 100000) { | ||
749 | i++; | ||
750 | abbrevsize >>= 10; | ||
751 | } | ||
752 | /* see http://en.wikipedia.org/wiki/Tera */ | ||
753 | fprintf(stderr, "%6d %c%c ", (int)abbrevsize, " KMGTPEZY"[i], i?'B':' '); | ||
754 | |||
755 | timersub(&now, &lastupdate, &tvwait); | ||
756 | if (transferred > lastsize) { | ||
757 | lastupdate = now; | ||
758 | lastsize = transferred; | ||
759 | if (tvwait.tv_sec >= STALLTIME) | ||
760 | timeradd(&start, &tvwait, &start); | ||
761 | tvwait.tv_sec = 0; | ||
762 | } | ||
763 | timersub(&now, &start, &td); | ||
764 | elapsed = td.tv_sec; | ||
765 | |||
766 | if (tvwait.tv_sec >= STALLTIME) { | ||
767 | fprintf(stderr, " - stalled -"); | ||
768 | } else { | ||
769 | off_t to_download = totalsize - beg_range; | ||
770 | if (transferred <= 0 || elapsed <= 0 || transferred > to_download || chunked) { | ||
771 | fprintf(stderr, "--:--:-- ETA"); | ||
772 | } else { | ||
773 | /* to_download / (transferred/elapsed) - elapsed: */ | ||
774 | int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed); | ||
775 | /* (long long helps to have working ETA even if !LFS) */ | ||
776 | i = eta % 3600; | ||
777 | fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60); | ||
778 | } | ||
779 | } | ||
780 | |||
781 | if (flag == -1) { /* first call to progressmeter */ | ||
782 | struct sigaction sa; | ||
783 | sa.sa_handler = updateprogressmeter; | ||
784 | sigemptyset(&sa.sa_mask); | ||
785 | sa.sa_flags = SA_RESTART; | ||
786 | sigaction(SIGALRM, &sa, NULL); | ||
787 | alarmtimer(1); | ||
788 | } else if (flag == 1) { /* last call to progressmeter */ | ||
789 | alarmtimer(0); | ||
790 | transferred = 0; | ||
791 | putc('\n', stderr); | ||
792 | } | ||
793 | } | ||
794 | #endif | ||
795 | |||
796 | /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, | ||
797 | * much of which was blatantly stolen from openssh. */ | ||
798 | |||
799 | /*- | ||
800 | * Copyright (c) 1992, 1993 | ||
801 | * The Regents of the University of California. All rights reserved. | ||
802 | * | ||
803 | * Redistribution and use in source and binary forms, with or without | ||
804 | * modification, are permitted provided that the following conditions | ||
805 | * are met: | ||
806 | * 1. Redistributions of source code must retain the above copyright | ||
807 | * notice, this list of conditions and the following disclaimer. | ||
808 | * 2. Redistributions in binary form must reproduce the above copyright | ||
809 | * notice, this list of conditions and the following disclaimer in the | ||
810 | * documentation and/or other materials provided with the distribution. | ||
811 | * | ||
812 | * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change | ||
813 | * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> | ||
814 | * | ||
815 | * 4. Neither the name of the University nor the names of its contributors | ||
816 | * may be used to endorse or promote products derived from this software | ||
817 | * without specific prior written permission. | ||
818 | * | ||
819 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | ||
820 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
821 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
822 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | ||
823 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
824 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||
825 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
826 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
827 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||
828 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||
829 | * SUCH DAMAGE. | ||
830 | * | ||
831 | * $Id: wget.c,v 1.75 2004/10/08 08:27:40 andersen Exp $ | ||
832 | */ | ||