xref: /freebsd/lib/libfetch/http.c (revision a85978584cc37b468a8f24e79fd1bd5bc0edf478)
1 /*-
2  * Copyright (c) 2000 Dag-Erling Co�dan Sm�rgrav
3  * All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  * 1. Redistributions of source code must retain the above copyright
9  *    notice, this list of conditions and the following disclaimer
10  *    in this position and unchanged.
11  * 2. Redistributions in binary form must reproduce the above copyright
12  *    notice, this list of conditions and the following disclaimer in the
13  *    documentation and/or other materials provided with the distribution.
14  * 3. The name of the author may not be used to endorse or promote products
15  *    derived from this software without specific prior written permission.
16  *
17  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  *
28  *      $FreeBSD$
29  */
30 
31 #include <sys/param.h>
32 #include <sys/socket.h>
33 
34 #include <ctype.h>
35 #include <err.h>
36 #include <errno.h>
37 #include <locale.h>
38 #include <netdb.h>
39 #include <stdarg.h>
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <string.h>
43 #include <time.h>
44 #include <unistd.h>
45 
46 #include "fetch.h"
47 #include "common.h"
48 #include "httperr.h"
49 
50 extern char *__progname; /* XXX not portable */
51 
52 /* Maximum number of redirects to follow */
53 #define MAX_REDIRECT 5
54 
55 /* Symbolic names for reply codes we care about */
56 #define HTTP_OK			200
57 #define HTTP_PARTIAL		206
58 #define HTTP_MOVED_PERM		301
59 #define HTTP_MOVED_TEMP		302
60 #define HTTP_SEE_OTHER		303
61 #define HTTP_NEED_AUTH		401
62 #define HTTP_NEED_PROXY_AUTH	403
63 #define HTTP_PROTOCOL_ERROR	999
64 
65 #define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
66                             || (xyz) == HTTP_MOVED_TEMP \
67                             || (xyz) == HTTP_SEE_OTHER)
68 
69 
70 
71 /*****************************************************************************
72  * I/O functions for decoding chunked streams
73  */
74 
75 struct cookie
76 {
77     int		 fd;
78     char	*buf;
79     size_t	 b_size;
80     size_t	 b_len;
81     int		 b_pos;
82     int		 eof;
83     int		 error;
84     long	 chunksize;
85 #ifdef DEBUG
86     long	 total;
87 #endif
88 };
89 
90 /*
91  * Get next chunk header
92  */
93 static int
94 _http_new_chunk(struct cookie *c)
95 {
96     char *p;
97 
98     if (_fetch_getln(c->fd, &c->buf, &c->b_size, &c->b_len) == -1)
99 	return -1;
100 
101     if (c->b_len < 2 || !ishexnumber(*c->buf))
102 	return -1;
103 
104     for (p = c->buf; !isspace(*p) && *p != ';' && p < c->buf + c->b_len; ++p)
105 	if (!ishexnumber(*p))
106 	    return -1;
107 	else if (isdigit(*p))
108 	    c->chunksize = c->chunksize * 16 + *p - '0';
109 	else
110 	    c->chunksize = c->chunksize * 16 + 10 + tolower(*p) - 'a';
111 
112 #ifdef DEBUG
113     c->total += c->chunksize;
114     if (c->chunksize == 0)
115 	fprintf(stderr, "\033[1m_http_fillbuf(): "
116 		"end of last chunk\033[m\n");
117     else
118 	fprintf(stderr, "\033[1m_http_fillbuf(): "
119 		"new chunk: %ld (%ld)\033[m\n", c->chunksize, c->total);
120 #endif
121 
122     return c->chunksize;
123 }
124 
125 /*
126  * Fill the input buffer, do chunk decoding on the fly
127  */
128 static int
129 _http_fillbuf(struct cookie *c)
130 {
131     if (c->error)
132 	return -1;
133     if (c->eof)
134 	return 0;
135 
136     if (c->chunksize == 0) {
137 	switch (_http_new_chunk(c)) {
138 	case -1:
139 	    c->error = 1;
140 	    return -1;
141 	case 0:
142 	    c->eof = 1;
143 	    return 0;
144 	}
145     }
146 
147     if (c->b_size < c->chunksize) {
148 	char *tmp;
149 
150 	if ((tmp = realloc(c->buf, c->chunksize)) == NULL)
151 	    return -1;
152 	c->buf = tmp;
153 	c->b_size = c->chunksize;
154     }
155 
156     if ((c->b_len = read(c->fd, c->buf, c->chunksize)) == -1)
157 	return -1;
158     c->chunksize -= c->b_len;
159 
160     if (c->chunksize == 0) {
161 	char endl[2];
162 	read(c->fd, endl, 2);
163     }
164 
165     c->b_pos = 0;
166 
167     return c->b_len;
168 }
169 
170 /*
171  * Read function
172  */
173 static int
174 _http_readfn(void *v, char *buf, int len)
175 {
176     struct cookie *c = (struct cookie *)v;
177     int l, pos;
178 
179     if (c->error)
180 	return -1;
181     if (c->eof)
182 	return 0;
183 
184     for (pos = 0; len > 0; pos += l, len -= l) {
185 	/* empty buffer */
186 	if (!c->buf || c->b_pos == c->b_len)
187 	    if (_http_fillbuf(c) < 1)
188 		break;
189 	l = c->b_len - c->b_pos;
190 	if (len < l)
191 	    l = len;
192 	bcopy(c->buf + c->b_pos, buf + pos, l);
193 	c->b_pos += l;
194     }
195 
196     if (!pos && c->error)
197 	return -1;
198     return pos;
199 }
200 
201 /*
202  * Write function
203  */
204 static int
205 _http_writefn(void *v, const char *buf, int len)
206 {
207     struct cookie *c = (struct cookie *)v;
208 
209     return write(c->fd, buf, len);
210 }
211 
212 /*
213  * Close function
214  */
215 static int
216 _http_closefn(void *v)
217 {
218     struct cookie *c = (struct cookie *)v;
219     int r;
220 
221     r = close(c->fd);
222     if (c->buf)
223 	free(c->buf);
224     free(c);
225     return r;
226 }
227 
228 /*
229  * Wrap a file descriptor up
230  */
231 static FILE *
232 _http_funopen(int fd)
233 {
234     struct cookie *c;
235     FILE *f;
236 
237     if ((c = calloc(1, sizeof *c)) == NULL) {
238 	_fetch_syserr();
239 	return NULL;
240     }
241     c->fd = fd;
242     if (!(f = funopen(c, _http_readfn, _http_writefn, NULL, _http_closefn))) {
243 	_fetch_syserr();
244 	free(c);
245 	return NULL;
246     }
247     return f;
248 }
249 
250 
251 /*****************************************************************************
252  * Helper functions for talking to the server and parsing its replies
253  */
254 
255 /* Header types */
256 typedef enum {
257     hdr_syserror = -2,
258     hdr_error = -1,
259     hdr_end = 0,
260     hdr_unknown = 1,
261     hdr_content_length,
262     hdr_content_range,
263     hdr_last_modified,
264     hdr_location,
265     hdr_transfer_encoding
266 } hdr;
267 
268 /* Names of interesting headers */
269 static struct {
270     hdr		 num;
271     char	*name;
272 } hdr_names[] = {
273     { hdr_content_length,	"Content-Length" },
274     { hdr_content_range,	"Content-Range" },
275     { hdr_last_modified,	"Last-Modified" },
276     { hdr_location,		"Location" },
277     { hdr_transfer_encoding,	"Transfer-Encoding" },
278     { hdr_unknown,		NULL },
279 };
280 
281 static char	*reply_buf;
282 static size_t	 reply_size;
283 static size_t	 reply_length;
284 
285 /*
286  * Send a formatted line; optionally echo to terminal
287  */
288 static int
289 _http_cmd(int fd, char *fmt, ...)
290 {
291     va_list ap;
292     size_t len;
293     char *msg;
294     int r;
295 
296     va_start(ap, fmt);
297     len = vasprintf(&msg, fmt, ap);
298     va_end(ap);
299 
300     if (msg == NULL) {
301 	errno = ENOMEM;
302 	_fetch_syserr();
303 	return -1;
304     }
305 
306     r = _fetch_putln(fd, msg, len);
307     free(msg);
308 
309     if (r == -1) {
310 	_fetch_syserr();
311 	return -1;
312     }
313 
314     return 0;
315 }
316 
317 /*
318  * Get and parse status line
319  */
320 static int
321 _http_get_reply(int fd)
322 {
323     if (_fetch_getln(fd, &reply_buf, &reply_size, &reply_length) == -1)
324 	return -1;
325     /*
326      * A valid status line looks like "HTTP/m.n xyz reason" where m
327      * and n are the major and minor protocol version numbers and xyz
328      * is the reply code.
329      * We grok HTTP 1.0 and 1.1, so m must be 1 and n must be 0 or 1.
330      * We don't care about the reason phrase.
331      */
332     if (strncmp(reply_buf, "HTTP/1.", 7) != 0
333 	|| (reply_buf[7] != '0' && reply_buf[7] != '1') || reply_buf[8] != ' '
334 	|| !isdigit(reply_buf[9])
335 	|| !isdigit(reply_buf[10])
336 	|| !isdigit(reply_buf[11]))
337 	return HTTP_PROTOCOL_ERROR;
338 
339     return ((reply_buf[9] - '0') * 100
340 	    + (reply_buf[10] - '0') * 10
341 	    + (reply_buf[11] - '0'));
342 }
343 
344 /*
345  * Check a header; if the type matches the given string, return a
346  * pointer to the beginning of the value.
347  */
348 static char *
349 _http_match(char *str, char *hdr)
350 {
351     while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
352 	/* nothing */;
353     if (*str || *hdr != ':')
354 	return NULL;
355     while (*hdr && isspace(*++hdr))
356 	/* nothing */;
357     return hdr;
358 }
359 
360 /*
361  * Get the next header and return the appropriate symbolic code.
362  */
363 static hdr
364 _http_next_header(int fd, char **p)
365 {
366     int i;
367 
368     if (_fetch_getln(fd, &reply_buf, &reply_size, &reply_length) == -1)
369 	return hdr_syserror;
370     while (reply_length && isspace(reply_buf[reply_length-1]))
371 	reply_length--;
372     reply_buf[reply_length] = 0;
373     if (reply_length == 0)
374 	return hdr_end;
375     /*
376      * We could check for malformed headers but we don't really care.
377      * A valid header starts with a token immediately followed by a
378      * colon; a token is any sequence of non-control, non-whitespace
379      * characters except "()<>@,;:\\\"{}".
380      */
381     for (i = 0; hdr_names[i].num != hdr_unknown; i++)
382 	if ((*p = _http_match(hdr_names[i].name, reply_buf)) != NULL)
383 	    return hdr_names[i].num;
384     return hdr_unknown;
385 }
386 
387 /*
388  * Parse a last-modified header
389  */
390 static time_t
391 _http_parse_mtime(char *p)
392 {
393     char locale[64];
394     struct tm tm;
395 
396     strncpy(locale, setlocale(LC_TIME, NULL), sizeof locale);
397     setlocale(LC_TIME, "C");
398     strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
399     /* XXX should add support for date-2 and date-3 */
400     setlocale(LC_TIME, locale);
401     DEBUG(fprintf(stderr, "last modified: [\033[1m%04d-%02d-%02d "
402 		  "%02d:%02d:%02d\033[m]\n",
403 		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
404 		  tm.tm_hour, tm.tm_min, tm.tm_sec));
405     return timegm(&tm);
406 }
407 
408 /*
409  * Parse a content-length header
410  */
411 static off_t
412 _http_parse_length(char *p)
413 {
414     off_t len;
415 
416     for (len = 0; *p && isdigit(*p); ++p)
417 	len = len * 10 + (*p - '0');
418     DEBUG(fprintf(stderr, "content length: [\033[1m%lld\033[m]\n", len));
419     return len;
420 }
421 
422 /*
423  * Parse a content-range header
424  */
425 static off_t
426 _http_parse_range(char *p)
427 {
428     off_t off;
429 
430     if (strncasecmp(p, "bytes ", 6) != 0)
431 	return -1;
432     for (p += 6, off = 0; *p && isdigit(*p); ++p)
433 	off = off * 10 + *p - '0';
434     if (*p != '-')
435 	return -1;
436     DEBUG(fprintf(stderr, "content range: [\033[1m%lld-\033[m]\n", off));
437     return off;
438 }
439 
440 
441 /*****************************************************************************
442  * Helper functions for authorization
443  */
444 
445 /*
446  * Base64 encoding
447  */
448 static char *
449 _http_base64(char *src)
450 {
451     static const char base64[] =
452 	"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
453 	"abcdefghijklmnopqrstuvwxyz"
454 	"0123456789+/";
455     char *str, *dst;
456     size_t l;
457     int t, r;
458 
459     l = strlen(src);
460     if ((str = malloc(((l + 2) / 3) * 4)) == NULL)
461 	return NULL;
462     dst = str;
463     r = 0;
464 
465     while (l >= 3) {
466 	t = (src[0] << 16) | (src[1] << 8) | src[2];
467 	dst[0] = base64[(t >> 18) & 0x3f];
468 	dst[1] = base64[(t >> 12) & 0x3f];
469 	dst[2] = base64[(t >> 6) & 0x3f];
470 	dst[3] = base64[(t >> 0) & 0x3f];
471 	src += 3; l -= 3;
472 	dst += 4; r += 4;
473     }
474 
475     switch (l) {
476     case 2:
477 	t = (src[0] << 16) | (src[1] << 8);
478 	dst[0] = base64[(t >> 18) & 0x3f];
479 	dst[1] = base64[(t >> 12) & 0x3f];
480 	dst[2] = base64[(t >> 6) & 0x3f];
481 	dst[3] = '=';
482 	dst += 4;
483 	r += 4;
484 	break;
485     case 1:
486 	t = src[0] << 16;
487 	dst[0] = base64[(t >> 18) & 0x3f];
488 	dst[1] = base64[(t >> 12) & 0x3f];
489 	dst[2] = dst[3] = '=';
490 	dst += 4;
491 	r += 4;
492 	break;
493     case 0:
494 	break;
495     }
496 
497     *dst = 0;
498     return str;
499 }
500 
501 /*
502  * Encode username and password
503  */
504 static int
505 _http_basic_auth(int fd, char *hdr, char *usr, char *pwd)
506 {
507     char *upw, *auth;
508     int r;
509 
510     if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
511 	return -1;
512     auth = _http_base64(upw);
513     free(upw);
514     if (auth == NULL)
515 	return -1;
516     r = _http_cmd(fd, "%s: Basic %s", hdr, auth);
517     free(auth);
518     return r;
519 }
520 
521 /*
522  * Send an authorization header
523  */
524 static int
525 _http_authorize(int fd, char *hdr, char *p)
526 {
527     /* basic authorization */
528     if (strncasecmp(p, "basic:", 6) == 0) {
529 	char *user, *pwd, *str;
530 	int r;
531 
532 	/* skip realm */
533 	for (p += 6; *p && *p != ':'; ++p)
534 	    /* nothing */ ;
535 	if (!*p || strchr(++p, ':') == NULL)
536 	    return -1;
537 	if ((str = strdup(p)) == NULL)
538 	    return -1; /* XXX */
539 	user = str;
540 	pwd = strchr(str, ':');
541 	*pwd++ = '\0';
542 	r = _http_basic_auth(fd, hdr, user, pwd);
543 	free(str);
544 	return r;
545     }
546     return -1;
547 }
548 
549 
550 /*****************************************************************************
551  * Helper functions for connecting to a server or proxy
552  */
553 
554 /*
555  * Connect to the specified HTTP proxy server.
556  */
557 static int
558 _http_proxy_connect(char *proxy, int af, int verbose)
559 {
560     char *hostname, *p;
561     int fd, port;
562 
563     /* get hostname */
564     hostname = NULL;
565 #ifdef INET6
566     /* host part can be an IPv6 address enclosed in square brackets */
567     if (*proxy == '[') {
568 	if ((p = strchr(proxy, ']')) == NULL) {
569 	    /* no terminating bracket */
570 	    /* XXX should set an error code */
571 	    goto ouch;
572 	}
573 	if (p[1] != '\0' && p[1] != ':') {
574 	    /* garbage after address */
575 	    /* XXX should set an error code */
576 	    goto ouch;
577 	}
578 	if ((hostname = malloc(p - proxy)) == NULL) {
579 	    errno = ENOMEM;
580 	    _fetch_syserr();
581 	    goto ouch;
582 	}
583 	strncpy(hostname, proxy + 1, p - proxy - 1);
584 	hostname[p - proxy - 1] = '\0';
585 	++p;
586     } else {
587 #endif /* INET6 */
588 	if ((p = strchr(proxy, ':')) == NULL)
589 	    p = strchr(proxy, '\0');
590 	if ((hostname = malloc(p - proxy + 1)) == NULL) {
591 	    errno = ENOMEM;
592 	    _fetch_syserr();
593 	    goto ouch;
594 	}
595 	strncpy(hostname, proxy, p - proxy);
596 	hostname[p - proxy] = '\0';
597 #ifdef INET6
598     }
599 #endif /* INET6 */
600     DEBUG(fprintf(stderr, "proxy name: [%s]\n", hostname));
601 
602     /* get port number */
603     port = 0;
604     if (*p == ':') {
605 	++p;
606 	if (strspn(p, "0123456789") != strlen(p) || strlen(p) > 5) {
607 	    /* port number is non-numeric or too long */
608 	    /* XXX should set an error code */
609 	    goto ouch;
610 	}
611 	port = atoi(p);
612 	if (port < 1 || port > 65535) {
613 	    /* port number is out of range */
614 	    /* XXX should set an error code */
615 	    goto ouch;
616 	}
617     }
618 
619     if (!port) {
620 #if 0
621 	/*
622 	 * commented out, since there is currently no service name
623 	 * for HTTP proxies
624 	 */
625 	struct servent *se;
626 
627 	if ((se = getservbyname("xxxx", "tcp")) != NULL)
628 	    port = ntohs(se->s_port);
629 	else
630 #endif
631 	    port = 3128;
632     }
633     DEBUG(fprintf(stderr, "proxy port: %d\n", port));
634 
635     /* connect */
636     if ((fd = _fetch_connect(hostname, port, af, verbose)) == -1)
637 	_fetch_syserr();
638     return fd;
639 
640  ouch:
641     if (hostname)
642 	free(hostname);
643     return -1;
644 }
645 
646 /*
647  * Connect to the correct HTTP server or proxy.
648  */
649 static int
650 _http_connect(struct url *URL, int *proxy, char *flags)
651 {
652     int direct, verbose;
653     int af, fd;
654     char *p;
655 
656 #ifdef INET6
657     af = AF_UNSPEC;
658 #else
659     af = AF_INET;
660 #endif
661 
662     direct = (flags && strchr(flags, 'd'));
663     verbose = (flags && strchr(flags, 'v'));
664     if (flags && strchr(flags, '4'))
665 	af = AF_INET;
666     else if (flags && strchr(flags, '6'))
667 	af = AF_INET6;
668 
669     /* check port */
670     if (!URL->port) {
671 	struct servent *se;
672 
673 	/* Scheme can be ftp if we're using a proxy */
674 	if (strcasecmp(URL->scheme, "ftp") == 0)
675 	    if ((se = getservbyname("ftp", "tcp")) != NULL)
676 		URL->port = ntohs(se->s_port);
677 	    else
678 		URL->port = 21;
679 	else
680 	    if ((se = getservbyname("http", "tcp")) != NULL)
681 		URL->port = ntohs(se->s_port);
682 	    else
683 		URL->port = 80;
684     }
685 
686     if (!direct && (p = getenv("HTTP_PROXY")) != NULL) {
687 	/* attempt to connect to proxy server */
688 	if ((fd = _http_proxy_connect(p, af, verbose)) == -1)
689 	    return -1;
690 	*proxy = 1;
691     } else {
692 	/* if no proxy is configured, try direct */
693 	if (strcasecmp(URL->scheme, "ftp") == 0) {
694 	    /* can't talk http to an ftp server */
695 	    /* XXX should set an error code */
696 	    return -1;
697 	}
698 	if ((fd = _fetch_connect(URL->host, URL->port, af, verbose)) == -1)
699 	    /* _fetch_connect() has already set an error code */
700 	    return -1;
701 	*proxy = 0;
702     }
703 
704     return fd;
705 }
706 
707 
708 /*****************************************************************************
709  * Core
710  */
711 
712 /*
713  * Send a request and process the reply
714  */
715 static FILE *
716 _http_request(struct url *URL, char *op, struct url_stat *us, char *flags)
717 {
718     struct url *url, *new;
719     int chunked, need_auth, noredirect, proxy, verbose;
720     int code, fd, i, n;
721     off_t offset;
722     char *p;
723     FILE *f;
724     hdr h;
725     char *host;
726 #ifdef INET6
727     char hbuf[MAXHOSTNAMELEN + 1];
728 #endif
729 
730     noredirect = (flags && strchr(flags, 'A'));
731     verbose = (flags && strchr(flags, 'v'));
732 
733     n = noredirect ? 1 : MAX_REDIRECT;
734 
735     /* just to appease compiler warnings */
736     code = HTTP_PROTOCOL_ERROR;
737     chunked = 0;
738     offset = 0;
739     fd = -1;
740 
741     for (url = URL, i = 0; i < n; ++i) {
742 	new = NULL;
743 	us->size = -1;
744 	us->atime = us->mtime = 0;
745 	chunked = 0;
746 	need_auth = 0;
747 	offset = 0;
748 	fd = -1;
749     retry:
750 	/* connect to server or proxy */
751 	if ((fd = _http_connect(url, &proxy, flags)) == -1)
752 	    goto ouch;
753 
754 	host = url->host;
755 #ifdef INET6
756 	if (strchr(url->host, ':')) {
757 	    snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
758 	    host = hbuf;
759 	}
760 #endif
761 
762 	/* send request */
763 	if (verbose)
764 	    _fetch_info("requesting %s://%s:%d%s",
765 			url->scheme, host, url->port, url->doc);
766 	if (proxy) {
767 	    _http_cmd(fd, "%s %s://%s:%d%s HTTP/1.1",
768 		      op, url->scheme, host, url->port, url->doc);
769 	} else {
770 	    _http_cmd(fd, "%s %s HTTP/1.1",
771 		      op, url->doc);
772 	}
773 
774 	/* proxy authorization */
775 	if (proxy && (p = getenv("HTTP_PROXY_AUTH")) != NULL)
776 	    _http_authorize(fd, "Proxy-Authorization", p);
777 
778 	/* server authorization */
779 	if (need_auth) {
780 	    if (*url->user || *url->pwd)
781 		_http_basic_auth(fd, "Authorization",
782 				 url->user ? url->user : "",
783 				 url->pwd ? url->pwd : "");
784 	    else if ((p = getenv("HTTP_AUTH")) != NULL)
785 		_http_authorize(fd, "Authorization", p);
786 	    else {
787 		_http_seterr(HTTP_NEED_AUTH);
788 		goto ouch;
789 	    }
790 	}
791 
792 	/* other headers */
793 	_http_cmd(fd, "Host: %s:%d", host, url->port);
794 	_http_cmd(fd, "User-Agent: %s " _LIBFETCH_VER, __progname);
795 	if (URL->offset)
796 	    _http_cmd(fd, "Range: bytes=%lld-", url->offset);
797 	_http_cmd(fd, "Connection: close");
798 	_http_cmd(fd, "");
799 
800 	/* get reply */
801 	switch ((code = _http_get_reply(fd))) {
802 	case HTTP_OK:
803 	case HTTP_PARTIAL:
804 	    /* fine */
805 	    break;
806 	case HTTP_MOVED_PERM:
807 	case HTTP_MOVED_TEMP:
808 	    /*
809 	     * Not so fine, but we still have to read the headers to
810 	     * get the new location.
811 	     */
812 	    break;
813 	case HTTP_NEED_AUTH:
814 	    if (need_auth) {
815 		/*
816 		 * We already sent out authorization code, so there's
817 		 * nothing more we can do.
818 		 */
819 		_http_seterr(code);
820 		goto ouch;
821 	    }
822 	    /* try again, but send the password this time */
823 	    if (verbose)
824 		_fetch_info("server requires authorization");
825 	    need_auth = 1;
826 	    close(fd);
827 	    goto retry;
828 	case HTTP_NEED_PROXY_AUTH:
829 	    /*
830 	     * If we're talking to a proxy, we already sent our proxy
831 	     * authorization code, so there's nothing more we can do.
832 	     */
833 	    _http_seterr(code);
834 	    goto ouch;
835 	case HTTP_PROTOCOL_ERROR:
836 	    /* fall through */
837 	case -1:
838 	    _fetch_syserr();
839 	    goto ouch;
840 	default:
841 	    _http_seterr(code);
842 	    goto ouch;
843 	}
844 
845 	/* get headers */
846 	do {
847 	    switch ((h = _http_next_header(fd, &p))) {
848 	    case hdr_syserror:
849 		_fetch_syserr();
850 		goto ouch;
851 	    case hdr_error:
852 		_http_seterr(HTTP_PROTOCOL_ERROR);
853 		goto ouch;
854 	    case hdr_content_length:
855 		us->size = _http_parse_length(p);
856 		break;
857 	    case hdr_content_range:
858 		offset = _http_parse_range(p);
859 		break;
860 	    case hdr_last_modified:
861 		us->atime = us->mtime = _http_parse_mtime(p);
862 		break;
863 	    case hdr_location:
864 		if (!HTTP_REDIRECT(code))
865 		    break;
866 		if (new)
867 		    free(new);
868 		if (verbose)
869 		    _fetch_info("%d redirect to %s", code, p);
870 		if (*p == '/')
871 		    /* absolute path */
872 		    new = fetchMakeURL(url->scheme, url->host, url->port, p,
873 				       url->user, url->pwd);
874 		else
875 		    new = fetchParseURL(p);
876 		if (new == NULL) {
877 		    /* XXX should set an error code */
878 		    DEBUG(fprintf(stderr, "failed to parse new URL\n"));
879 		    goto ouch;
880 		}
881 		if (!*new->user && !*new->pwd) {
882 		    strcpy(new->user, url->user);
883 		    strcpy(new->pwd, url->pwd);
884 		}
885 		new->offset = url->offset;
886 		new->length = url->length;
887 		break;
888 	    case hdr_transfer_encoding:
889 		/* XXX weak test*/
890 		chunked = (strcasecmp(p, "chunked") == 0);
891 		break;
892 	    case hdr_end:
893 		/* fall through */
894 	    case hdr_unknown:
895 		/* ignore */
896 		break;
897 	    }
898 	} while (h > hdr_end);
899 
900 	/* we either have a hit, or a redirect with no Location: header */
901 	if (code == HTTP_OK || code == HTTP_PARTIAL || !new)
902 	    break;
903 
904 	/* we have a redirect */
905 	close(fd);
906 	if (url != URL)
907 	    fetchFreeURL(url);
908 	url = new;
909     }
910 
911     /* no success */
912     if (fd == -1) {
913 	_http_seterr(code);
914 	goto ouch;
915     }
916 
917     /* wrap it up in a FILE */
918     if ((f = chunked ? _http_funopen(fd) : fdopen(fd, "r")) == NULL) {
919 	_fetch_syserr();
920 	goto ouch;
921     }
922 
923     while (offset++ < url->offset)
924 	if (fgetc(f) == EOF) {
925 	    _fetch_syserr();
926 	    fclose(f);
927 	    f = NULL;
928 	}
929 
930     if (url != URL)
931 	fetchFreeURL(url);
932 
933     return f;
934 
935  ouch:
936     if (url != URL)
937 	fetchFreeURL(url);
938     if (fd != -1)
939 	close(fd);
940     return NULL;
941 }
942 
943 
944 /*****************************************************************************
945  * Entry points
946  */
947 
948 /*
949  * Retrieve a file by HTTP
950  */
951 FILE *
952 fetchGetHTTP(struct url *URL, char *flags)
953 {
954     struct url_stat us;
955 
956     return _http_request(URL, "GET", &us, flags);
957 }
958 
959 FILE *
960 fetchPutHTTP(struct url *URL, char *flags)
961 {
962     warnx("fetchPutHTTP(): not implemented");
963     return NULL;
964 }
965 
966 /*
967  * Get an HTTP document's metadata
968  */
969 int
970 fetchStatHTTP(struct url *URL, struct url_stat *us, char *flags)
971 {
972     FILE *f;
973 
974     if ((f = _http_request(URL, "HEAD", us, flags)) == NULL)
975 	return -1;
976     fclose(f);
977     return 0;
978 }
979 
980 /*
981  * List a directory
982  */
983 struct url_ent *
984 fetchListHTTP(struct url *url, char *flags)
985 {
986     warnx("fetchListHTTP(): not implemented");
987     return NULL;
988 }
989