Magellan Linux

Annotation of /trunk/mkinitrd-magellan/busybox/networking/wget.c

Parent Directory Parent Directory | Revision Log Revision Log


Revision 984 - (hide annotations) (download)
Sun May 30 11:32:42 2010 UTC (14 years ago) by niro
File MIME type: text/plain
File size: 22533 byte(s)
-updated to busybox-1.16.1 and enabled blkid/uuid support in default config
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 niro 984 * Licensed under GPLv2, see file LICENSE in this tarball for details.
8 niro 532 */
9 niro 816 #include "libbb.h"
10 niro 532
11     struct host_info {
12     // May be used if we ever will want to free() all xstrdup()s...
13     /* char *allocated; */
14 niro 816 const char *path;
15     const char *user;
16     char *host;
17     int port;
18     smallint is_ftp;
19 niro 532 };
20    
21    
22 niro 816 /* Globals (can be accessed from signal handlers) */
23     struct globals {
24     off_t content_len; /* Content-length of the file */
25     off_t beg_range; /* Range at which continue begins */
26 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
27 niro 816 off_t transferred; /* Number of bytes transferred so far */
28     const char *curfile; /* Name of current file being transferred */
29 niro 984 bb_progress_t pmt;
30 niro 532 #endif
31 niro 984 smallint chunked; /* chunked transfer encoding */
32     smallint got_clen; /* got content-length: from server */
33 niro 816 };
34     #define G (*(struct globals*)&bb_common_bufsiz1)
35     struct BUG_G_too_big {
36     char BUG_G_too_big[sizeof(G) <= COMMON_BUFSIZE ? 1 : -1];
37     };
38     #define INIT_G() do { } while (0)
39    
40    
41 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
42 niro 816
43 niro 984 static void progress_meter(int flag)
44 niro 816 {
45     /* We can be called from signal handler */
46     int save_errno = errno;
47    
48 niro 984 if (flag == -1) { /* first call to progress_meter */
49     bb_progress_init(&G.pmt);
50 niro 816 }
51    
52 niro 984 bb_progress_update(&G.pmt, G.curfile, G.beg_range, G.transferred,
53     G.chunked ? 0 : G.content_len + G.beg_range);
54 niro 816
55     if (flag == 0) {
56 niro 984 /* last call to progress_meter */
57 niro 816 alarm(0);
58     fputc('\n', stderr);
59 niro 984 G.transferred = 0;
60 niro 816 } else {
61 niro 984 if (flag == -1) { /* first call to progress_meter */
62     signal_SA_RESTART_empty_mask(SIGALRM, progress_meter);
63 niro 816 }
64     alarm(1);
65     }
66    
67     errno = save_errno;
68     }
69 niro 984
70 niro 816 #else /* FEATURE_WGET_STATUSBAR */
71    
72 niro 984 static ALWAYS_INLINE void progress_meter(int flag UNUSED_PARAM) { }
73 niro 816
74 niro 532 #endif
75    
76 niro 816
77 niro 984 /* 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     */
87     static 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    
113 niro 816 /* 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. */
115     static size_t safe_fread(void *ptr, size_t nmemb, FILE *stream)
116 niro 532 {
117 niro 816 size_t ret;
118     char *p = (char*)ptr;
119 niro 532
120     do {
121     clearerr(stream);
122 niro 984 errno = 0;
123 niro 816 ret = fread(p, 1, nmemb, stream);
124     p += ret;
125     nmemb -= ret;
126     } while (nmemb && ferror(stream) && errno == EINTR);
127 niro 532
128 niro 816 return p - (char*)ptr;
129 niro 532 }
130    
131 niro 816 /* Read a line or SIZE-1 bytes into S, whichever is less, from STREAM.
132 niro 532 * Returns S, or NULL if an eof or non-interrupt error is encountered. */
133     static char *safe_fgets(char *s, int size, FILE *stream)
134     {
135     char *ret;
136    
137     do {
138     clearerr(stream);
139 niro 984 errno = 0;
140 niro 532 ret = fgets(s, size, stream);
141     } while (ret == NULL && ferror(stream) && errno == EINTR);
142    
143     return ret;
144     }
145    
146     #if ENABLE_FEATURE_WGET_AUTHENTICATION
147 niro 816 /* Base64-encode character string. buf is assumed to be char buf[512]. */
148     static char *base64enc_512(char buf[512], const char *str)
149 niro 532 {
150 niro 816 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);
154 niro 532 return buf;
155     }
156     #endif
157    
158 niro 984 static 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 niro 816
167     static FILE *open_socket(len_and_sockaddr *lsa)
168 niro 532 {
169 niro 816 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    
180     static 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    
207     static 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
222 niro 984 bb_error_msg_and_die("not an http or ftp url: %s", sanitize_string(url));
223 niro 816
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) {
240     h->path = "";
241     } 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...
248     memmove(h->host - 1, h->host, sp - h->host);
249     h->host--;
250     sp[-1] = '\0';
251     h->path = sp;
252     }
253    
254 niro 984 // We used to set h->user to NULL here, but this interferes
255     // with handling of code 302 ("object was moved")
256    
257 niro 816 sp = strrchr(h->host, '@');
258     if (sp != NULL) {
259     h->user = h->host;
260     *sp = '\0';
261     h->host = sp + 1;
262     }
263    
264     sp = h->host;
265     }
266    
267     static 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 != ':')
290 niro 984 bb_error_msg_and_die("bad header line: %s", sanitize_string(buf));
291 niro 816
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    
306 niro 984 /* Rats! The buffer isn't big enough to hold the entire header value */
307 niro 816 while (c = getc(fp), c != EOF && c != '\n')
308     continue;
309     /* *istrunc = 1; */
310     return hdrval;
311     }
312    
313 niro 984 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
314     static 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 niro 816
321 niro 984 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
350    
351     static 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)
363     bb_error_msg_and_die("%s", sanitize_string(buf+4));
364    
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:
380     bb_error_msg_and_die("ftp login: %s", sanitize_string(buf+4));
381     }
382    
383     ftpcmd("TYPE I", NULL, sfp, buf);
384    
385     /*
386     * Querying file size
387     */
388     if (ftpcmd("SIZE ", target->path, sfp, buf) == 213) {
389     G.content_len = BB_STRTOOFF(buf+4, NULL, 10);
390     if (G.content_len < 0 || errno) {
391     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:
401     bb_error_msg_and_die("bad response to %s: %s", "PASV", sanitize_string(buf));
402     }
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    
419     if (G.beg_range) {
420     sprintf(buf, "REST %"OFF_FMT"u", G.beg_range);
421     if (ftpcmd(buf, NULL, sfp, buf) == 350)
422     G.content_len -= G.beg_range;
423     }
424    
425     if (ftpcmd("RETR ", target->path, sfp, buf) > 150)
426     bb_error_msg_and_die("bad response to %s: %s", "RETR", sanitize_string(buf));
427    
428     return sfp;
429     }
430    
431     /* Must match option string! */
432     enum {
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    
447     static 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) {
459     while (1) {
460     int n;
461     unsigned rdsz;
462    
463     rdsz = sizeof(buf);
464     if (G.got_clen) {
465     if (G.content_len < (off_t)sizeof(buf)) {
466     if ((int)G.content_len <= 0)
467     break;
468     rdsz = (unsigned)G.content_len;
469     }
470     }
471     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
481     G.transferred += n;
482     #endif
483     if (G.got_clen)
484     G.content_len -= n;
485     }
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);
493     G.content_len = STRTOOFF(buf, NULL, 16);
494     /* FIXME: error check? */
495     if (G.content_len == 0)
496     break; /* all done! */
497     G.got_clen = 1;
498     }
499    
500     if (!(option_mask32 & WGET_OPT_QUIET))
501     progress_meter(0);
502     }
503    
504 niro 816 int wget_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE;
505     int wget_main(int argc UNUSED_PARAM, char **argv)
506     {
507 niro 532 char buf[512];
508     struct host_info server, target;
509     len_and_sockaddr *lsa;
510     unsigned opt;
511 niro 984 int redir_limit;
512     char *proxy = NULL;
513 niro 532 char *dir_prefix = NULL;
514     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
515 niro 984 char *post_data;
516 niro 532 char *extra_headers = NULL;
517     llist_t *headers_llist = NULL;
518     #endif
519 niro 984 FILE *sfp; /* socket to web/ftp server */
520 niro 816 FILE *dfp; /* socket to ftp server (data) */
521     char *fname_out; /* where to direct output (-O) */
522 niro 532 int output_fd = -1;
523 niro 984 bool use_proxy; /* Use proxies if env vars are set */
524 niro 532 const char *proxy_flag = "on"; /* Use proxies if env vars are set */
525 niro 816 const char *user_agent = "Wget";/* "User-Agent" header field */
526 niro 532
527 niro 816 static const char keywords[] ALIGN1 =
528     "content-length\0""transfer-encoding\0""chunked\0""location\0";
529 niro 532 enum {
530 niro 816 KEY_content_length = 1, KEY_transfer_encoding, KEY_chunked, KEY_location
531 niro 532 };
532     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
533 niro 816 static const char wget_longopts[] ALIGN1 =
534     /* 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"
542     /* Ignored: */
543     // "tries\0" Required_argument "t"
544     // "timeout\0" Required_argument "T"
545     /* Ignored (we always use PASV): */
546     "passive-ftp\0" No_argument "\xff"
547     "header\0" Required_argument "\xfe"
548 niro 984 "post-data\0" Required_argument "\xfd"
549     /* Ignored (we don't do ssl) */
550     "no-check-certificate\0" No_argument "\xfc"
551 niro 816 ;
552 niro 532 #endif
553 niro 816
554     INIT_G();
555    
556     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
557     applet_long_options = wget_longopts;
558     #endif
559     /* server.allocated = target.allocated = NULL; */
560 niro 984 opt_complementary = "-1" IF_FEATURE_WGET_LONG_OPTIONS(":\xfe::");
561 niro 816 opt = getopt32(argv, "csqO:P:Y:U:" /*ignored:*/ "t:T:",
562 niro 532 &fname_out, &dir_prefix,
563 niro 816 &proxy_flag, &user_agent,
564     NULL, /* -t RETRIES */
565     NULL /* -T NETWORK_READ_TIMEOUT */
566 niro 984 IF_FEATURE_WGET_LONG_OPTIONS(, &headers_llist)
567     IF_FEATURE_WGET_LONG_OPTIONS(, &post_data)
568 niro 532 );
569     #if ENABLE_FEATURE_WGET_LONG_OPTIONS
570     if (headers_llist) {
571     int size = 1;
572     char *cp;
573 niro 816 llist_t *ll = headers_llist;
574 niro 532 while (ll) {
575     size += strlen(ll->data) + 2;
576     ll = ll->link;
577     }
578     extra_headers = cp = xmalloc(size);
579     while (headers_llist) {
580 niro 816 cp += sprintf(cp, "%s\r\n", (char*)llist_pop(&headers_llist));
581 niro 532 }
582     }
583     #endif
584    
585 niro 984 /* TODO: compat issue: should handle "wget URL1 URL2..." */
586    
587     target.user = NULL;
588 niro 532 parse_url(argv[optind], &target);
589    
590 niro 816 /* Use the proxy if necessary */
591 niro 984 use_proxy = (strcmp(proxy_flag, "off") != 0);
592 niro 532 if (use_proxy) {
593     proxy = getenv(target.is_ftp ? "ftp_proxy" : "http_proxy");
594 niro 984 if (proxy && proxy[0]) {
595     server.user = NULL;
596 niro 532 parse_url(proxy, &server);
597     } else {
598     use_proxy = 0;
599     }
600     }
601 niro 984 if (!use_proxy) {
602     server.port = target.port;
603     if (ENABLE_FEATURE_IPV6) {
604     server.host = xstrdup(target.host);
605     } else {
606     server.host = target.host;
607     }
608     }
609 niro 532
610 niro 984 if (ENABLE_FEATURE_IPV6)
611     strip_ipv6_scope_id(target.host);
612    
613 niro 816 /* Guess an output filename, if there was no -O FILE */
614     if (!(opt & WGET_OPT_OUTNAME)) {
615     fname_out = bb_get_last_path_component_nostrip(target.path);
616     /* handle "wget http://kernel.org//" */
617     if (fname_out[0] == '/' || !fname_out[0])
618     fname_out = (char*)"index.html";
619     /* -P DIR is considered only if there was no -O FILE */
620     if (dir_prefix)
621     fname_out = concat_path_file(dir_prefix, fname_out);
622     } else {
623     if (LONE_DASH(fname_out)) {
624     /* -O - */
625     output_fd = 1;
626     opt &= ~WGET_OPT_CONTINUE;
627 niro 532 }
628 niro 816 }
629 niro 532 #if ENABLE_FEATURE_WGET_STATUSBAR
630 niro 984 G.curfile = bb_get_last_path_component_nostrip(fname_out);
631 niro 532 #endif
632 niro 816
633 niro 532 /* Impossible?
634     if ((opt & WGET_OPT_CONTINUE) && !fname_out)
635 niro 984 bb_error_msg_and_die("can't specify continue (-c) without a filename (-O)");
636     */
637 niro 532
638 niro 816 /* Determine where to start transfer */
639 niro 532 if (opt & WGET_OPT_CONTINUE) {
640     output_fd = open(fname_out, O_WRONLY);
641     if (output_fd >= 0) {
642 niro 984 G.beg_range = xlseek(output_fd, 0, SEEK_END);
643 niro 532 }
644     /* File doesn't exist. We do not create file here yet.
645 niro 984 * We are not sure it exists on remove side */
646 niro 532 }
647    
648 niro 984 redir_limit = 5;
649     resolve_lsa:
650 niro 816 lsa = xhost2sockaddr(server.host, server.port);
651 niro 532 if (!(opt & WGET_OPT_QUIET)) {
652 niro 984 char *s = xmalloc_sockaddr2dotted(&lsa->u.sa);
653     fprintf(stderr, "Connecting to %s (%s)\n", server.host, s);
654     free(s);
655 niro 532 }
656 niro 984 establish_session:
657 niro 532 if (use_proxy || !target.is_ftp) {
658     /*
659     * HTTP session
660     */
661 niro 984 char *str;
662     int status;
663 niro 532
664 niro 984 /* Open socket to http server */
665     sfp = open_socket(lsa);
666 niro 532
667 niro 984 /* Send HTTP request */
668     if (use_proxy) {
669     fprintf(sfp, "GET %stp://%s/%s HTTP/1.1\r\n",
670     target.is_ftp ? "f" : "ht", target.host,
671     target.path);
672     } else {
673     if (opt & WGET_OPT_POST_DATA)
674     fprintf(sfp, "POST /%s HTTP/1.1\r\n", target.path);
675     else
676 niro 532 fprintf(sfp, "GET /%s HTTP/1.1\r\n", target.path);
677 niro 984 }
678 niro 532
679 niro 984 fprintf(sfp, "Host: %s\r\nUser-Agent: %s\r\n",
680     target.host, user_agent);
681 niro 532
682     #if ENABLE_FEATURE_WGET_AUTHENTICATION
683 niro 984 if (target.user) {
684     fprintf(sfp, "Proxy-Authorization: Basic %s\r\n"+6,
685     base64enc_512(buf, target.user));
686     }
687     if (use_proxy && server.user) {
688     fprintf(sfp, "Proxy-Authorization: Basic %s\r\n",
689     base64enc_512(buf, server.user));
690     }
691 niro 532 #endif
692    
693 niro 984 if (G.beg_range)
694     fprintf(sfp, "Range: bytes=%"OFF_FMT"u-\r\n", G.beg_range);
695 niro 532 #if ENABLE_FEATURE_WGET_LONG_OPTIONS
696 niro 984 if (extra_headers)
697     fputs(extra_headers, sfp);
698    
699     if (opt & WGET_OPT_POST_DATA) {
700     char *estr = URL_escape(post_data);
701     fprintf(sfp, "Content-Type: application/x-www-form-urlencoded\r\n");
702     fprintf(sfp, "Content-Length: %u\r\n" "\r\n" "%s",
703     (int) strlen(estr), estr);
704     /*fprintf(sfp, "Connection: Keep-Alive\r\n\r\n");*/
705     /*fprintf(sfp, "%s\r\n", estr);*/
706     free(estr);
707     } else
708 niro 532 #endif
709 niro 984 { /* If "Connection:" is needed, document why */
710     fprintf(sfp, /* "Connection: close\r\n" */ "\r\n");
711     }
712 niro 532
713 niro 984 /*
714     * Retrieve HTTP response line and check for "200" status code.
715     */
716 niro 532 read_response:
717 niro 984 if (fgets(buf, sizeof(buf), sfp) == NULL)
718     bb_error_msg_and_die("no response from server");
719 niro 532
720 niro 984 str = buf;
721     str = skip_non_whitespace(str);
722     str = skip_whitespace(str);
723     // FIXME: no error check
724     // xatou wouldn't work: "200 OK"
725     status = atoi(str);
726     switch (status) {
727     case 0:
728     case 100:
729     while (gethdr(buf, sizeof(buf), sfp /*, &n*/) != NULL)
730     /* eat all remaining headers */;
731     goto read_response;
732     case 200:
733 niro 816 /*
734     Response 204 doesn't say "null file", it says "metadata
735     has changed but data didn't":
736    
737     "10.2.5 204 No Content
738     The server has fulfilled the request but does not need to return
739     an entity-body, and might want to return updated metainformation.
740     The response MAY include new or updated metainformation in the form
741     of entity-headers, which if present SHOULD be associated with
742     the requested variant.
743    
744     If the client is a user agent, it SHOULD NOT change its document
745     view from that which caused the request to be sent. This response
746     is primarily intended to allow input for actions to take place
747     without causing a change to the user agent's active document view,
748     although any new or updated metainformation SHOULD be applied
749     to the document currently in the user agent's active view.
750    
751     The 204 response MUST NOT include a message-body, and thus
752     is always terminated by the first empty line after the header fields."
753    
754     However, in real world it was observed that some web servers
755     (e.g. Boa/0.94.14rc21) simply use code 204 when file size is zero.
756     */
757 niro 984 case 204:
758     break;
759     case 300: /* redirection */
760     case 301:
761     case 302:
762     case 303:
763     break;
764     case 206:
765     if (G.beg_range)
766 niro 532 break;
767 niro 984 /* fall through */
768     default:
769     bb_error_msg_and_die("server returned error: %s", sanitize_string(buf));
770     }
771    
772     /*
773     * Retrieve HTTP headers.
774     */
775     while ((str = gethdr(buf, sizeof(buf), sfp /*, &n*/)) != NULL) {
776     /* gethdr converted "FOO:" string to lowercase */
777     smalluint key;
778     /* strip trailing whitespace */
779     char *s = strchrnul(str, '\0') - 1;
780     while (s >= str && (*s == ' ' || *s == '\t')) {
781     *s = '\0';
782     s--;
783 niro 532 }
784 niro 984 key = index_in_strings(keywords, buf) + 1;
785     if (key == KEY_content_length) {
786     G.content_len = BB_STRTOOFF(str, NULL, 10);
787     if (G.content_len < 0 || errno) {
788     bb_error_msg_and_die("content-length %s is garbage", sanitize_string(str));
789 niro 532 }
790 niro 984 G.got_clen = 1;
791     continue;
792     }
793     if (key == KEY_transfer_encoding) {
794     if (index_in_strings(keywords, str_tolower(str)) + 1 != KEY_chunked)
795     bb_error_msg_and_die("transfer encoding '%s' is not supported", sanitize_string(str));
796     G.chunked = G.got_clen = 1;
797     }
798     if (key == KEY_location && status >= 300) {
799     if (--redir_limit == 0)
800     bb_error_msg_and_die("too many redirections");
801     fclose(sfp);
802     G.got_clen = 0;
803     G.chunked = 0;
804     if (str[0] == '/')
805     /* free(target.allocated); */
806     target.path = /* target.allocated = */ xstrdup(str+1);
807     /* lsa stays the same: it's on the same server */
808     else {
809     parse_url(str, &target);
810     if (!use_proxy) {
811     server.host = target.host;
812     /* strip_ipv6_scope_id(target.host); - no! */
813     /* we assume remote never gives us IPv6 addr with scope id */
814     server.port = target.port;
815 niro 532 free(lsa);
816 niro 984 goto resolve_lsa;
817     } /* else: lsa stays the same: we use proxy */
818 niro 532 }
819 niro 984 goto establish_session;
820 niro 532 }
821 niro 984 }
822     // if (status >= 300)
823     // bb_error_msg_and_die("bad redirection (no Location: header from server)");
824 niro 532
825 niro 984 /* For HTTP, data is pumped over the same connection */
826 niro 532 dfp = sfp;
827    
828     } else {
829     /*
830     * FTP session
831     */
832 niro 984 sfp = prepare_ftp_session(&dfp, &target, lsa);
833 niro 532 }
834    
835 niro 816 if (opt & WGET_OPT_SPIDER) {
836     if (ENABLE_FEATURE_CLEAN_UP)
837     fclose(sfp);
838     return EXIT_SUCCESS;
839     }
840 niro 532
841 niro 816 if (output_fd < 0) {
842     int o_flags = O_WRONLY | O_CREAT | O_TRUNC | O_EXCL;
843     /* compat with wget: -O FILE can overwrite */
844     if (opt & WGET_OPT_OUTNAME)
845     o_flags = O_WRONLY | O_CREAT | O_TRUNC;
846     output_fd = xopen(fname_out, o_flags);
847     }
848 niro 532
849 niro 984 retrieve_file_data(dfp, output_fd);
850     xclose(output_fd);
851 niro 532
852 niro 984 if (dfp != sfp) {
853     /* It's ftp. Close it properly */
854 niro 532 fclose(dfp);
855     if (ftpcmd(NULL, NULL, sfp, buf) != 226)
856 niro 984 bb_error_msg_and_die("ftp error: %s", sanitize_string(buf+4));
857     /* ftpcmd("QUIT", NULL, sfp, buf); - why bother? */
858 niro 532 }
859    
860 niro 816 return EXIT_SUCCESS;
861 niro 532 }