Annotation of /trunk/mkinitrd-magellan/busybox/networking/wget.c
Parent Directory | Revision Log
Revision 532 -
(hide annotations)
(download)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 22131 byte(s)
Sat Sep 1 22:45:15 2007 UTC (16 years, 8 months ago) by niro
File MIME type: text/plain
File size: 22131 byte(s)
-import if magellan mkinitrd; it is a fork of redhats mkinitrd-5.0.8 with all magellan patches and features; deprecates magellan-src/mkinitrd
1 | niro | 532 | /* 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(len_and_sockaddr *lsa); | ||
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 | len_and_sockaddr *lsa; | ||
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 (!last_char_is(target.path, '/')) { | ||
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 (LONE_DASH(fname_out)) { | ||
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 | lsa = host2sockaddr(server.host, server.port); | ||
237 | if (!(opt & WGET_OPT_QUIET)) { | ||
238 | fprintf(stderr, "Connecting to %s [%s]\n", server.host, | ||
239 | xmalloc_sockaddr2dotted(&lsa->sa, lsa->len)); | ||
240 | /* We leak xmalloc_sockaddr2dotted result */ | ||
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(lsa); | ||
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 | fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n", | ||
272 | target.is_ftp ? "f" : "ht", target.host, | ||
273 | target.path); | ||
274 | } else { | ||
275 | fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path); | ||
276 | } | ||
277 | |||
278 | fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n", | ||
279 | target.host, user_agent); | ||
280 | |||
281 | #if ENABLE_FEATURE_WGET_AUTHENTICATION | ||
282 | if (target.user) { | ||
283 | fprintf(sfp, "Authorization: Basic %s\r\n", | ||
284 | base64enc((unsigned char*)target.user, buf, sizeof(buf))); | ||
285 | } | ||
286 | if (use_proxy && server.user) { | ||
287 | fprintf(sfp, "Proxy-Authorization: Basic %s\r\n", | ||
288 | base64enc((unsigned char*)server.user, buf, sizeof(buf))); | ||
289 | } | ||
290 | #endif | ||
291 | |||
292 | if (beg_range) | ||
293 | fprintf(sfp, "Range: bytes=%"OFF_FMT"d-\r\n", beg_range); | ||
294 | #if ENABLE_FEATURE_WGET_LONG_OPTIONS | ||
295 | if (extra_headers) | ||
296 | fputs(extra_headers, sfp); | ||
297 | #endif | ||
298 | fprintf(sfp, "Connection: close\r\n\r\n"); | ||
299 | |||
300 | /* | ||
301 | * Retrieve HTTP response line and check for "200" status code. | ||
302 | */ | ||
303 | read_response: | ||
304 | if (fgets(buf, sizeof(buf), sfp) == NULL) | ||
305 | bb_error_msg_and_die("no response from server"); | ||
306 | |||
307 | s = buf; | ||
308 | while (*s != '\0' && !isspace(*s)) ++s; | ||
309 | s = skip_whitespace(s); | ||
310 | // FIXME: no error check | ||
311 | // xatou wouldn't work: "200 OK" | ||
312 | status = atoi(s); | ||
313 | switch (status) { | ||
314 | case 0: | ||
315 | case 100: | ||
316 | while (gethdr(buf, sizeof(buf), sfp, &n) != NULL) | ||
317 | /* eat all remaining headers */; | ||
318 | goto read_response; | ||
319 | case 200: | ||
320 | break; | ||
321 | case 300: /* redirection */ | ||
322 | case 301: | ||
323 | case 302: | ||
324 | case 303: | ||
325 | break; | ||
326 | case 206: | ||
327 | if (beg_range) | ||
328 | break; | ||
329 | /*FALLTHRU*/ | ||
330 | default: | ||
331 | /* Show first line only and kill any ESC tricks */ | ||
332 | buf[strcspn(buf, "\n\r\x1b")] = '\0'; | ||
333 | bb_error_msg_and_die("server returned error: %s", buf); | ||
334 | } | ||
335 | |||
336 | /* | ||
337 | * Retrieve HTTP headers. | ||
338 | */ | ||
339 | while ((s = gethdr(buf, sizeof(buf), sfp, &n)) != NULL) { | ||
340 | if (strcasecmp(buf, "content-length") == 0) { | ||
341 | content_len = BB_STRTOOFF(s, NULL, 10); | ||
342 | if (errno || content_len < 0) { | ||
343 | bb_error_msg_and_die("content-length %s is garbage", s); | ||
344 | } | ||
345 | got_clen = 1; | ||
346 | continue; | ||
347 | } | ||
348 | if (strcasecmp(buf, "transfer-encoding") == 0) { | ||
349 | if (strcasecmp(s, "chunked") != 0) | ||
350 | bb_error_msg_and_die("server wants to do %s transfer encoding", s); | ||
351 | chunked = got_clen = 1; | ||
352 | } | ||
353 | if (strcasecmp(buf, "location") == 0) { | ||
354 | if (s[0] == '/') | ||
355 | /* free(target.allocated); */ | ||
356 | target.path = /* target.allocated = */ xstrdup(s+1); | ||
357 | else { | ||
358 | parse_url(s, &target); | ||
359 | if (use_proxy == 0) { | ||
360 | server.host = target.host; | ||
361 | server.port = target.port; | ||
362 | } | ||
363 | free(lsa); | ||
364 | lsa = host2sockaddr(server.host, server.port); | ||
365 | break; | ||
366 | } | ||
367 | } | ||
368 | } | ||
369 | } while (status >= 300); | ||
370 | |||
371 | dfp = sfp; | ||
372 | |||
373 | } else { | ||
374 | |||
375 | /* | ||
376 | * FTP session | ||
377 | */ | ||
378 | if (!target.user) | ||
379 | target.user = xstrdup("anonymous:busybox@"); | ||
380 | |||
381 | sfp = open_socket(lsa); | ||
382 | if (ftpcmd(NULL, NULL, sfp, buf) != 220) | ||
383 | bb_error_msg_and_die("%s", buf+4); | ||
384 | |||
385 | /* | ||
386 | * Splitting username:password pair, | ||
387 | * trying to log in | ||
388 | */ | ||
389 | s = strchr(target.user, ':'); | ||
390 | if (s) | ||
391 | *(s++) = '\0'; | ||
392 | switch (ftpcmd("USER ", target.user, sfp, buf)) { | ||
393 | case 230: | ||
394 | break; | ||
395 | case 331: | ||
396 | if (ftpcmd("PASS ", s, sfp, buf) == 230) | ||
397 | break; | ||
398 | /* FALLTHRU (failed login) */ | ||
399 | default: | ||
400 | bb_error_msg_and_die("ftp login: %s", buf+4); | ||
401 | } | ||
402 | |||
403 | ftpcmd("TYPE I", NULL, sfp, buf); | ||
404 | |||
405 | /* | ||
406 | * Querying file size | ||
407 | */ | ||
408 | if (ftpcmd("SIZE ", target.path, sfp, buf) == 213) { | ||
409 | content_len = BB_STRTOOFF(buf+4, NULL, 10); | ||
410 | if (errno || content_len < 0) { | ||
411 | bb_error_msg_and_die("SIZE value is garbage"); | ||
412 | } | ||
413 | got_clen = 1; | ||
414 | } | ||
415 | |||
416 | /* | ||
417 | * Entering passive mode | ||
418 | */ | ||
419 | if (ftpcmd("PASV", NULL, sfp, buf) != 227) { | ||
420 | pasv_error: | ||
421 | bb_error_msg_and_die("bad response to %s: %s", "PASV", buf); | ||
422 | } | ||
423 | // Response is "227 garbageN1,N2,N3,N4,P1,P2[)garbage] | ||
424 | // Server's IP is N1.N2.N3.N4 (we ignore it) | ||
425 | // Server's port for data connection is P1*256+P2 | ||
426 | s = strrchr(buf, ')'); | ||
427 | if (s) s[0] = '\0'; | ||
428 | s = strrchr(buf, ','); | ||
429 | if (!s) goto pasv_error; | ||
430 | port = xatou_range(s+1, 0, 255); | ||
431 | *s = '\0'; | ||
432 | s = strrchr(buf, ','); | ||
433 | if (!s) goto pasv_error; | ||
434 | port += xatou_range(s+1, 0, 255) * 256; | ||
435 | set_nport(lsa, htons(port)); | ||
436 | dfp = open_socket(lsa); | ||
437 | |||
438 | if (beg_range) { | ||
439 | sprintf(buf, "REST %"OFF_FMT"d", beg_range); | ||
440 | if (ftpcmd(buf, NULL, sfp, buf) == 350) | ||
441 | content_len -= beg_range; | ||
442 | } | ||
443 | |||
444 | if (ftpcmd("RETR ", target.path, sfp, buf) > 150) | ||
445 | bb_error_msg_and_die("bad response to RETR: %s", buf); | ||
446 | } | ||
447 | |||
448 | |||
449 | /* | ||
450 | * Retrieve file | ||
451 | */ | ||
452 | if (chunked) { | ||
453 | fgets(buf, sizeof(buf), dfp); | ||
454 | content_len = STRTOOFF(buf, NULL, 16); | ||
455 | /* FIXME: error check?? */ | ||
456 | } | ||
457 | |||
458 | /* Do it before progressmeter (want to have nice error message) */ | ||
459 | if (output_fd < 0) | ||
460 | output_fd = xopen(fname_out, | ||
461 | O_WRONLY|O_CREAT|O_EXCL|O_TRUNC); | ||
462 | |||
463 | if (!(opt & WGET_OPT_QUIET)) | ||
464 | progressmeter(-1); | ||
465 | |||
466 | do { | ||
467 | while (content_len > 0 || !got_clen) { | ||
468 | unsigned rdsz = sizeof(buf); | ||
469 | if (content_len < sizeof(buf) && (chunked || got_clen)) | ||
470 | rdsz = (unsigned)content_len; | ||
471 | n = safe_fread(buf, 1, rdsz, dfp); | ||
472 | if (n <= 0) | ||
473 | break; | ||
474 | if (full_write(output_fd, buf, n) != n) { | ||
475 | bb_perror_msg_and_die(bb_msg_write_error); | ||
476 | } | ||
477 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
478 | transferred += n; | ||
479 | #endif | ||
480 | if (got_clen) { | ||
481 | content_len -= n; | ||
482 | } | ||
483 | } | ||
484 | |||
485 | if (chunked) { | ||
486 | safe_fgets(buf, sizeof(buf), dfp); /* This is a newline */ | ||
487 | safe_fgets(buf, sizeof(buf), dfp); | ||
488 | content_len = STRTOOFF(buf, NULL, 16); | ||
489 | /* FIXME: error check? */ | ||
490 | if (content_len == 0) { | ||
491 | chunked = 0; /* all done! */ | ||
492 | } | ||
493 | } | ||
494 | |||
495 | if (n == 0 && ferror(dfp)) { | ||
496 | bb_perror_msg_and_die(bb_msg_read_error); | ||
497 | } | ||
498 | } while (chunked); | ||
499 | |||
500 | if (!(opt & WGET_OPT_QUIET)) | ||
501 | progressmeter(1); | ||
502 | |||
503 | if ((use_proxy == 0) && target.is_ftp) { | ||
504 | fclose(dfp); | ||
505 | if (ftpcmd(NULL, NULL, sfp, buf) != 226) | ||
506 | bb_error_msg_and_die("ftp error: %s", buf+4); | ||
507 | ftpcmd("QUIT", NULL, sfp, buf); | ||
508 | } | ||
509 | exit(EXIT_SUCCESS); | ||
510 | } | ||
511 | |||
512 | |||
513 | static void parse_url(char *src_url, struct host_info *h) | ||
514 | { | ||
515 | char *url, *p, *sp; | ||
516 | |||
517 | /* h->allocated = */ url = xstrdup(src_url); | ||
518 | |||
519 | if (strncmp(url, "http://", 7) == 0) { | ||
520 | h->port = bb_lookup_port("http", "tcp", 80); | ||
521 | h->host = url + 7; | ||
522 | h->is_ftp = 0; | ||
523 | } else if (strncmp(url, "ftp://", 6) == 0) { | ||
524 | h->port = bb_lookup_port("ftp", "tcp", 21); | ||
525 | h->host = url + 6; | ||
526 | h->is_ftp = 1; | ||
527 | } else | ||
528 | bb_error_msg_and_die("not an http or ftp url: %s", url); | ||
529 | |||
530 | // FYI: | ||
531 | // "Real" wget 'http://busybox.net?var=a/b' sends this request: | ||
532 | // 'GET /?var=a/b HTTP 1.0' | ||
533 | // and saves 'index.html?var=a%2Fb' (we save 'b') | ||
534 | // wget 'http://busybox.net?login=john@doe': | ||
535 | // request: 'GET /?login=john@doe HTTP/1.0' | ||
536 | // saves: 'index.html?login=john@doe' (we save '?login=john@doe') | ||
537 | // wget 'http://busybox.net#test/test': | ||
538 | // request: 'GET / HTTP/1.0' | ||
539 | // saves: 'index.html' (we save 'test') | ||
540 | // | ||
541 | // We also don't add unique .N suffix if file exists... | ||
542 | sp = strchr(h->host, '/'); | ||
543 | p = strchr(h->host, '?'); if (!sp || (p && sp > p)) sp = p; | ||
544 | p = strchr(h->host, '#'); if (!sp || (p && sp > p)) sp = p; | ||
545 | if (!sp) { | ||
546 | /* must be writable because of bb_get_last_path_component() */ | ||
547 | static char nullstr[] = ""; | ||
548 | h->path = nullstr; | ||
549 | } else if (*sp == '/') { | ||
550 | *sp = '\0'; | ||
551 | h->path = sp + 1; | ||
552 | } else { // '#' or '?' | ||
553 | // http://busybox.net?login=john@doe is a valid URL | ||
554 | // memmove converts to: | ||
555 | // http:/busybox.nett?login=john@doe... | ||
556 | memmove(h->host-1, h->host, sp - h->host); | ||
557 | h->host--; | ||
558 | sp[-1] = '\0'; | ||
559 | h->path = sp; | ||
560 | } | ||
561 | |||
562 | sp = strrchr(h->host, '@'); | ||
563 | h->user = NULL; | ||
564 | if (sp != NULL) { | ||
565 | h->user = h->host; | ||
566 | *sp = '\0'; | ||
567 | h->host = sp + 1; | ||
568 | } | ||
569 | |||
570 | sp = h->host; | ||
571 | |||
572 | //host2sockaddr does this itself | ||
573 | //#if ENABLE_FEATURE_WGET_IP6_LITERAL | ||
574 | // if (sp[0] == '[') { | ||
575 | // char *ep; | ||
576 | // | ||
577 | // ep = sp + 1; | ||
578 | // while (*ep == ':' || isxdigit(*ep)) | ||
579 | // ep++; | ||
580 | // if (*ep == ']') { | ||
581 | // h->host++; | ||
582 | // *ep = '\0'; | ||
583 | // sp = ep + 1; | ||
584 | // } | ||
585 | // } | ||
586 | //#endif | ||
587 | // | ||
588 | // p = strchr(sp, ':'); | ||
589 | // if (p != NULL) { | ||
590 | // *p = '\0'; | ||
591 | // h->port = htons(xatou16(p + 1)); | ||
592 | // } | ||
593 | } | ||
594 | |||
595 | |||
596 | static FILE *open_socket(len_and_sockaddr *lsa) | ||
597 | { | ||
598 | FILE *fp; | ||
599 | |||
600 | /* glibc 2.4 seems to try seeking on it - ??! */ | ||
601 | /* hopefully it understands what ESPIPE means... */ | ||
602 | fp = fdopen(xconnect_stream(lsa), "r+"); | ||
603 | if (fp == NULL) | ||
604 | bb_perror_msg_and_die("fdopen"); | ||
605 | |||
606 | return fp; | ||
607 | } | ||
608 | |||
609 | |||
610 | static char *gethdr(char *buf, size_t bufsiz, FILE *fp, int *istrunc) | ||
611 | { | ||
612 | char *s, *hdrval; | ||
613 | int c; | ||
614 | |||
615 | *istrunc = 0; | ||
616 | |||
617 | /* retrieve header line */ | ||
618 | if (fgets(buf, bufsiz, fp) == NULL) | ||
619 | return NULL; | ||
620 | |||
621 | /* see if we are at the end of the headers */ | ||
622 | for (s = buf; *s == '\r'; ++s) | ||
623 | ; | ||
624 | if (s[0] == '\n') | ||
625 | return NULL; | ||
626 | |||
627 | /* convert the header name to lower case */ | ||
628 | for (s = buf; isalnum(*s) || *s == '-'; ++s) | ||
629 | *s = tolower(*s); | ||
630 | |||
631 | /* verify we are at the end of the header name */ | ||
632 | if (*s != ':') | ||
633 | bb_error_msg_and_die("bad header line: %s", buf); | ||
634 | |||
635 | /* locate the start of the header value */ | ||
636 | for (*s++ = '\0'; *s == ' ' || *s == '\t'; ++s) | ||
637 | ; | ||
638 | hdrval = s; | ||
639 | |||
640 | /* locate the end of header */ | ||
641 | while (*s != '\0' && *s != '\r' && *s != '\n') | ||
642 | ++s; | ||
643 | |||
644 | /* end of header found */ | ||
645 | if (*s != '\0') { | ||
646 | *s = '\0'; | ||
647 | return hdrval; | ||
648 | } | ||
649 | |||
650 | /* Rats! The buffer isn't big enough to hold the entire header value. */ | ||
651 | while (c = getc(fp), c != EOF && c != '\n') | ||
652 | ; | ||
653 | *istrunc = 1; | ||
654 | return hdrval; | ||
655 | } | ||
656 | |||
657 | static int ftpcmd(char *s1, char *s2, FILE *fp, char *buf) | ||
658 | { | ||
659 | int result; | ||
660 | if (s1) { | ||
661 | if (!s2) s2 = ""; | ||
662 | fprintf(fp, "%s%s\r\n", s1, s2); | ||
663 | fflush(fp); | ||
664 | } | ||
665 | |||
666 | do { | ||
667 | char *buf_ptr; | ||
668 | |||
669 | if (fgets(buf, 510, fp) == NULL) { | ||
670 | bb_perror_msg_and_die("error getting response"); | ||
671 | } | ||
672 | buf_ptr = strstr(buf, "\r\n"); | ||
673 | if (buf_ptr) { | ||
674 | *buf_ptr = '\0'; | ||
675 | } | ||
676 | } while (!isdigit(buf[0]) || buf[3] != ' '); | ||
677 | |||
678 | buf[3] = '\0'; | ||
679 | result = xatoi_u(buf); | ||
680 | buf[3] = ' '; | ||
681 | return result; | ||
682 | } | ||
683 | |||
684 | #if ENABLE_FEATURE_WGET_STATUSBAR | ||
685 | /* Stuff below is from BSD rcp util.c, as added to openshh. | ||
686 | * Original copyright notice is retained at the end of this file. | ||
687 | */ | ||
688 | static int | ||
689 | getttywidth(void) | ||
690 | { | ||
691 | int width; | ||
692 | get_terminal_width_height(0, &width, NULL); | ||
693 | return width; | ||
694 | } | ||
695 | |||
696 | static void | ||
697 | updateprogressmeter(int ignore) | ||
698 | { | ||
699 | int save_errno = errno; | ||
700 | |||
701 | progressmeter(0); | ||
702 | errno = save_errno; | ||
703 | } | ||
704 | |||
705 | static void alarmtimer(int iwait) | ||
706 | { | ||
707 | struct itimerval itv; | ||
708 | |||
709 | itv.it_value.tv_sec = iwait; | ||
710 | itv.it_value.tv_usec = 0; | ||
711 | itv.it_interval = itv.it_value; | ||
712 | setitimer(ITIMER_REAL, &itv, NULL); | ||
713 | } | ||
714 | |||
715 | |||
716 | static void | ||
717 | progressmeter(int flag) | ||
718 | { | ||
719 | static struct timeval lastupdate; | ||
720 | static off_t lastsize, totalsize; | ||
721 | |||
722 | struct timeval now, td, tvwait; | ||
723 | off_t abbrevsize; | ||
724 | int elapsed, ratio, barlength, i; | ||
725 | char buf[256]; | ||
726 | |||
727 | if (flag == -1) { /* first call to progressmeter */ | ||
728 | gettimeofday(&start, (struct timezone *) 0); | ||
729 | lastupdate = start; | ||
730 | lastsize = 0; | ||
731 | totalsize = content_len + beg_range; /* as content_len changes.. */ | ||
732 | } | ||
733 | |||
734 | gettimeofday(&now, (struct timezone *) 0); | ||
735 | ratio = 100; | ||
736 | if (totalsize != 0 && !chunked) { | ||
737 | /* long long helps to have working ETA even if !LFS */ | ||
738 | ratio = (int) (100 * (unsigned long long)(transferred+beg_range) / totalsize); | ||
739 | ratio = MIN(ratio, 100); | ||
740 | } | ||
741 | |||
742 | fprintf(stderr, "\r%-20.20s%4d%% ", curfile, ratio); | ||
743 | |||
744 | barlength = getttywidth() - 51; | ||
745 | if (barlength > 0 && barlength < sizeof(buf)) { | ||
746 | i = barlength * ratio / 100; | ||
747 | memset(buf, '*', i); | ||
748 | memset(buf + i, ' ', barlength - i); | ||
749 | buf[barlength] = '\0'; | ||
750 | fprintf(stderr, "|%s|", buf); | ||
751 | } | ||
752 | i = 0; | ||
753 | abbrevsize = transferred + beg_range; | ||
754 | while (abbrevsize >= 100000) { | ||
755 | i++; | ||
756 | abbrevsize >>= 10; | ||
757 | } | ||
758 | /* see http://en.wikipedia.org/wiki/Tera */ | ||
759 | fprintf(stderr, "%6d %c%c ", (int)abbrevsize, " KMGTPEZY"[i], i?'B':' '); | ||
760 | |||
761 | timersub(&now, &lastupdate, &tvwait); | ||
762 | if (transferred > lastsize) { | ||
763 | lastupdate = now; | ||
764 | lastsize = transferred; | ||
765 | if (tvwait.tv_sec >= STALLTIME) | ||
766 | timeradd(&start, &tvwait, &start); | ||
767 | tvwait.tv_sec = 0; | ||
768 | } | ||
769 | timersub(&now, &start, &td); | ||
770 | elapsed = td.tv_sec; | ||
771 | |||
772 | if (tvwait.tv_sec >= STALLTIME) { | ||
773 | fprintf(stderr, " - stalled -"); | ||
774 | } else { | ||
775 | off_t to_download = totalsize - beg_range; | ||
776 | if (transferred <= 0 || elapsed <= 0 || transferred > to_download || chunked) { | ||
777 | fprintf(stderr, "--:--:-- ETA"); | ||
778 | } else { | ||
779 | /* to_download / (transferred/elapsed) - elapsed: */ | ||
780 | int eta = (int) ((unsigned long long)to_download*elapsed/transferred - elapsed); | ||
781 | /* (long long helps to have working ETA even if !LFS) */ | ||
782 | i = eta % 3600; | ||
783 | fprintf(stderr, "%02d:%02d:%02d ETA", eta / 3600, i / 60, i % 60); | ||
784 | } | ||
785 | } | ||
786 | |||
787 | if (flag == -1) { /* first call to progressmeter */ | ||
788 | struct sigaction sa; | ||
789 | sa.sa_handler = updateprogressmeter; | ||
790 | sigemptyset(&sa.sa_mask); | ||
791 | sa.sa_flags = SA_RESTART; | ||
792 | sigaction(SIGALRM, &sa, NULL); | ||
793 | alarmtimer(1); | ||
794 | } else if (flag == 1) { /* last call to progressmeter */ | ||
795 | alarmtimer(0); | ||
796 | transferred = 0; | ||
797 | putc('\n', stderr); | ||
798 | } | ||
799 | } | ||
800 | #endif | ||
801 | |||
802 | /* Original copyright notice which applies to the CONFIG_FEATURE_WGET_STATUSBAR stuff, | ||
803 | * much of which was blatantly stolen from openssh. */ | ||
804 | |||
805 | /*- | ||
806 | * Copyright (c) 1992, 1993 | ||
807 | * The Regents of the University of California. All rights reserved. | ||
808 | * | ||
809 | * Redistribution and use in source and binary forms, with or without | ||
810 | * modification, are permitted provided that the following conditions | ||
811 | * are met: | ||
812 | * 1. Redistributions of source code must retain the above copyright | ||
813 | * notice, this list of conditions and the following disclaimer. | ||
814 | * 2. Redistributions in binary form must reproduce the above copyright | ||
815 | * notice, this list of conditions and the following disclaimer in the | ||
816 | * documentation and/or other materials provided with the distribution. | ||
817 | * | ||
818 | * 3. <BSD Advertising Clause omitted per the July 22, 1999 licensing change | ||
819 | * ftp://ftp.cs.berkeley.edu/pub/4bsd/README.Impt.License.Change> | ||
820 | * | ||
821 | * 4. Neither the name of the University nor the names of its contributors | ||
822 | * may be used to endorse or promote products derived from this software | ||
823 | * without specific prior written permission. | ||
824 | * | ||
825 | * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND | ||
826 | * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE | ||
827 | * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE | ||
828 | * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE | ||
829 | * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL | ||
830 | * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS | ||
831 | * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) | ||
832 | * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT | ||
833 | * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY | ||
834 | * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF | ||
835 | * SUCH DAMAGE. | ||
836 | * | ||
837 | * $Id: wget.c,v 1.1 2007-09-01 22:43:54 niro Exp $ | ||
838 | */ |