http.c revision 186124
137535Sdes/*-
2135546Sdes * Copyright (c) 2000-2004 Dag-Erling Co�dan Sm�rgrav
337535Sdes * All rights reserved.
437535Sdes *
537535Sdes * Redistribution and use in source and binary forms, with or without
637535Sdes * modification, are permitted provided that the following conditions
737535Sdes * are met:
837535Sdes * 1. Redistributions of source code must retain the above copyright
937535Sdes *    notice, this list of conditions and the following disclaimer
1037535Sdes *    in this position and unchanged.
1137535Sdes * 2. Redistributions in binary form must reproduce the above copyright
1237535Sdes *    notice, this list of conditions and the following disclaimer in the
1337535Sdes *    documentation and/or other materials provided with the distribution.
1437535Sdes * 3. The name of the author may not be used to endorse or promote products
1563012Sdes *    derived from this software without specific prior written permission.
1637535Sdes *
1737535Sdes * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1837535Sdes * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1937535Sdes * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2037535Sdes * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2137535Sdes * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2237535Sdes * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2337535Sdes * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2437535Sdes * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2537535Sdes * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2637535Sdes * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2737535Sdes */
2837535Sdes
2984203Sdillon#include <sys/cdefs.h>
3084203Sdillon__FBSDID("$FreeBSD: head/lib/libfetch/http.c 186124 2008-12-15 08:27:44Z murray $");
3184203Sdillon
3263236Sdes/*
3363236Sdes * The following copyright applies to the base64 code:
3463236Sdes *
3563236Sdes *-
3663236Sdes * Copyright 1997 Massachusetts Institute of Technology
3763236Sdes *
3863236Sdes * Permission to use, copy, modify, and distribute this software and
3963236Sdes * its documentation for any purpose and without fee is hereby
4063236Sdes * granted, provided that both the above copyright notice and this
4163236Sdes * permission notice appear in all copies, that both the above
4263236Sdes * copyright notice and this permission notice appear in all
4363236Sdes * supporting documentation, and that the name of M.I.T. not be used
4463236Sdes * in advertising or publicity pertaining to distribution of the
4563236Sdes * software without specific, written prior permission.  M.I.T. makes
4663236Sdes * no representations about the suitability of this software for any
4763236Sdes * purpose.  It is provided "as is" without express or implied
4863236Sdes * warranty.
4990267Sdes *
5063236Sdes * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
5163236Sdes * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
5263236Sdes * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5363236Sdes * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
5463236Sdes * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
5563236Sdes * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
5663236Sdes * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
5763236Sdes * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
5863236Sdes * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
5963236Sdes * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
6063236Sdes * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6163236Sdes * SUCH DAMAGE.
6263236Sdes */
6363236Sdes
6437535Sdes#include <sys/param.h>
6560737Sume#include <sys/socket.h>
66186124Smurray#include <sys/time.h>
6737535Sdes
6863012Sdes#include <ctype.h>
6937535Sdes#include <err.h>
7063012Sdes#include <errno.h>
7160376Sdes#include <locale.h>
7260189Sdes#include <netdb.h>
7337608Sdes#include <stdarg.h>
7437535Sdes#include <stdio.h>
7537535Sdes#include <stdlib.h>
7637535Sdes#include <string.h>
7760376Sdes#include <time.h>
7837535Sdes#include <unistd.h>
7937535Sdes
80141958Skbyanc#include <netinet/in.h>
81141958Skbyanc#include <netinet/tcp.h>
82141958Skbyanc
8337535Sdes#include "fetch.h"
8440939Sdes#include "common.h"
8541862Sdes#include "httperr.h"
8637535Sdes
8763012Sdes/* Maximum number of redirects to follow */
8863012Sdes#define MAX_REDIRECT 5
8937535Sdes
9063012Sdes/* Symbolic names for reply codes we care about */
9163012Sdes#define HTTP_OK			200
9263012Sdes#define HTTP_PARTIAL		206
9363012Sdes#define HTTP_MOVED_PERM		301
9463012Sdes#define HTTP_MOVED_TEMP		302
9563012Sdes#define HTTP_SEE_OTHER		303
96186124Smurray#define HTTP_NOT_MODIFIED	304
97169386Sdes#define HTTP_TEMP_REDIRECT	307
9863012Sdes#define HTTP_NEED_AUTH		401
9987317Sdes#define HTTP_NEED_PROXY_AUTH	407
100125696Sdes#define HTTP_BAD_RANGE		416
10163012Sdes#define HTTP_PROTOCOL_ERROR	999
10260196Sdes
10363012Sdes#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
10490267Sdes			    || (xyz) == HTTP_MOVED_TEMP \
105169386Sdes			    || (xyz) == HTTP_TEMP_REDIRECT \
10690267Sdes			    || (xyz) == HTTP_SEE_OTHER)
10763012Sdes
10888771Sdes#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
10963012Sdes
11090267Sdes
11163012Sdes/*****************************************************************************
11263012Sdes * I/O functions for decoding chunked streams
11363012Sdes */
11463012Sdes
11597859Sdesstruct httpio
11637535Sdes{
11797858Sdes	conn_t		*conn;		/* connection */
11897866Sdes	int		 chunked;	/* chunked mode */
11997858Sdes	char		*buf;		/* chunk buffer */
12097866Sdes	size_t		 bufsize;	/* size of chunk buffer */
12197866Sdes	ssize_t		 buflen;	/* amount of data currently in buffer */
12297866Sdes	int		 bufpos;	/* current read offset in buffer */
12397858Sdes	int		 eof;		/* end-of-file flag */
12497858Sdes	int		 error;		/* error flag */
12597858Sdes	size_t		 chunksize;	/* remaining size of current chunk */
12663281Sdes#ifndef NDEBUG
12790267Sdes	size_t		 total;
12863012Sdes#endif
12937535Sdes};
13037535Sdes
13137608Sdes/*
13263012Sdes * Get next chunk header
13337608Sdes */
13437608Sdesstatic int
135174588Sdeshttp_new_chunk(struct httpio *io)
13637608Sdes{
13790267Sdes	char *p;
13890267Sdes
139174588Sdes	if (fetch_getln(io->conn) == -1)
14090267Sdes		return (-1);
14190267Sdes
142174761Sdes	if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
14390267Sdes		return (-1);
14490267Sdes
145174761Sdes	for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
14690267Sdes		if (*p == ';')
14790267Sdes			break;
148174761Sdes		if (!isxdigit((unsigned char)*p))
14990267Sdes			return (-1);
150174761Sdes		if (isdigit((unsigned char)*p)) {
15197859Sdes			io->chunksize = io->chunksize * 16 +
15290267Sdes			    *p - '0';
15390267Sdes		} else {
15497859Sdes			io->chunksize = io->chunksize * 16 +
155176036Sdes			    10 + tolower((unsigned char)*p) - 'a';
15690267Sdes		}
15790267Sdes	}
15890267Sdes
15963281Sdes#ifndef NDEBUG
16090267Sdes	if (fetchDebug) {
16197859Sdes		io->total += io->chunksize;
16297859Sdes		if (io->chunksize == 0)
163106207Sdes			fprintf(stderr, "%s(): end of last chunk\n", __func__);
16490267Sdes		else
165106207Sdes			fprintf(stderr, "%s(): new chunk: %lu (%lu)\n",
166106207Sdes			    __func__, (unsigned long)io->chunksize,
167106207Sdes			    (unsigned long)io->total);
16890267Sdes	}
16963012Sdes#endif
17090267Sdes
17197859Sdes	return (io->chunksize);
17237608Sdes}
17337608Sdes
17437608Sdes/*
17597866Sdes * Grow the input buffer to at least len bytes
17697866Sdes */
17797866Sdesstatic inline int
178174588Sdeshttp_growbuf(struct httpio *io, size_t len)
17997866Sdes{
18097866Sdes	char *tmp;
18197866Sdes
18297866Sdes	if (io->bufsize >= len)
18397866Sdes		return (0);
18497866Sdes
18597866Sdes	if ((tmp = realloc(io->buf, len)) == NULL)
18697866Sdes		return (-1);
18797866Sdes	io->buf = tmp;
18897866Sdes	io->bufsize = len;
189106044Sdes	return (0);
19097866Sdes}
19197866Sdes
19297866Sdes/*
19337608Sdes * Fill the input buffer, do chunk decoding on the fly
19437608Sdes */
19563012Sdesstatic int
196174588Sdeshttp_fillbuf(struct httpio *io, size_t len)
19737535Sdes{
19897859Sdes	if (io->error)
19990267Sdes		return (-1);
20097859Sdes	if (io->eof)
20190267Sdes		return (0);
20290267Sdes
20397866Sdes	if (io->chunked == 0) {
204174588Sdes		if (http_growbuf(io, len) == -1)
20597866Sdes			return (-1);
206174588Sdes		if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
207106185Sdes			io->error = 1;
20897866Sdes			return (-1);
209106185Sdes		}
21097866Sdes		io->bufpos = 0;
21197866Sdes		return (io->buflen);
21297866Sdes	}
21397866Sdes
21497859Sdes	if (io->chunksize == 0) {
215174588Sdes		switch (http_new_chunk(io)) {
21690267Sdes		case -1:
21797859Sdes			io->error = 1;
21890267Sdes			return (-1);
21990267Sdes		case 0:
22097859Sdes			io->eof = 1;
22190267Sdes			return (0);
22290267Sdes		}
22337535Sdes	}
22463012Sdes
22597866Sdes	if (len > io->chunksize)
22697866Sdes		len = io->chunksize;
227174588Sdes	if (http_growbuf(io, len) == -1)
22890267Sdes		return (-1);
229174588Sdes	if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
230106185Sdes		io->error = 1;
23197866Sdes		return (-1);
232106185Sdes	}
23397866Sdes	io->chunksize -= io->buflen;
23490267Sdes
23597859Sdes	if (io->chunksize == 0) {
23697856Sdes		char endl[2];
23797856Sdes
238174588Sdes		if (fetch_read(io->conn, endl, 2) != 2 ||
23997856Sdes		    endl[0] != '\r' || endl[1] != '\n')
24090267Sdes			return (-1);
24190267Sdes	}
24290267Sdes
24397866Sdes	io->bufpos = 0;
24490267Sdes
24597866Sdes	return (io->buflen);
24637535Sdes}
24737535Sdes
24837608Sdes/*
24937608Sdes * Read function
25037608Sdes */
25137535Sdesstatic int
252174588Sdeshttp_readfn(void *v, char *buf, int len)
25337535Sdes{
25497859Sdes	struct httpio *io = (struct httpio *)v;
25590267Sdes	int l, pos;
25663012Sdes
25797859Sdes	if (io->error)
25890267Sdes		return (-1);
25997859Sdes	if (io->eof)
26090267Sdes		return (0);
26163012Sdes
26290267Sdes	for (pos = 0; len > 0; pos += l, len -= l) {
26390267Sdes		/* empty buffer */
26497866Sdes		if (!io->buf || io->bufpos == io->buflen)
265174588Sdes			if (http_fillbuf(io, len) < 1)
26690267Sdes				break;
26797866Sdes		l = io->buflen - io->bufpos;
26890267Sdes		if (len < l)
26990267Sdes			l = len;
270176105Sdes		memcpy(buf + pos, io->buf + io->bufpos, l);
27197866Sdes		io->bufpos += l;
27290267Sdes	}
27337535Sdes
27497859Sdes	if (!pos && io->error)
27590267Sdes		return (-1);
27690267Sdes	return (pos);
27737535Sdes}
27837535Sdes
27937608Sdes/*
28037608Sdes * Write function
28137608Sdes */
28237535Sdesstatic int
283174588Sdeshttp_writefn(void *v, const char *buf, int len)
28437535Sdes{
28597859Sdes	struct httpio *io = (struct httpio *)v;
28690267Sdes
287174588Sdes	return (fetch_write(io->conn, buf, len));
28837535Sdes}
28937535Sdes
29037608Sdes/*
29137608Sdes * Close function
29237608Sdes */
29337535Sdesstatic int
294174588Sdeshttp_closefn(void *v)
29537535Sdes{
29697859Sdes	struct httpio *io = (struct httpio *)v;
29790267Sdes	int r;
29863012Sdes
299174588Sdes	r = fetch_close(io->conn);
30097859Sdes	if (io->buf)
30197859Sdes		free(io->buf);
30297859Sdes	free(io);
30390267Sdes	return (r);
30437535Sdes}
30537535Sdes
30637608Sdes/*
30763012Sdes * Wrap a file descriptor up
30837608Sdes */
30963012Sdesstatic FILE *
310174588Sdeshttp_funopen(conn_t *conn, int chunked)
31137535Sdes{
31297859Sdes	struct httpio *io;
31390267Sdes	FILE *f;
31463012Sdes
315109967Sdes	if ((io = calloc(1, sizeof(*io))) == NULL) {
316174588Sdes		fetch_syserr();
31790267Sdes		return (NULL);
31890267Sdes	}
31997859Sdes	io->conn = conn;
32097866Sdes	io->chunked = chunked;
321174588Sdes	f = funopen(io, http_readfn, http_writefn, NULL, http_closefn);
32290267Sdes	if (f == NULL) {
323174588Sdes		fetch_syserr();
32497859Sdes		free(io);
32590267Sdes		return (NULL);
32690267Sdes	}
32790267Sdes	return (f);
32863012Sdes}
32963012Sdes
33090267Sdes
33163012Sdes/*****************************************************************************
33263012Sdes * Helper functions for talking to the server and parsing its replies
33363012Sdes */
33463012Sdes
33563012Sdes/* Header types */
33663012Sdestypedef enum {
33790267Sdes	hdr_syserror = -2,
33890267Sdes	hdr_error = -1,
33990267Sdes	hdr_end = 0,
34090267Sdes	hdr_unknown = 1,
34190267Sdes	hdr_content_length,
34290267Sdes	hdr_content_range,
34390267Sdes	hdr_last_modified,
34490267Sdes	hdr_location,
34590267Sdes	hdr_transfer_encoding,
34690267Sdes	hdr_www_authenticate
34785093Sdes} hdr_t;
34863012Sdes
34963012Sdes/* Names of interesting headers */
35063012Sdesstatic struct {
35190267Sdes	hdr_t		 num;
35290267Sdes	const char	*name;
35363012Sdes} hdr_names[] = {
35490267Sdes	{ hdr_content_length,		"Content-Length" },
35590267Sdes	{ hdr_content_range,		"Content-Range" },
35690267Sdes	{ hdr_last_modified,		"Last-Modified" },
35790267Sdes	{ hdr_location,			"Location" },
35890267Sdes	{ hdr_transfer_encoding,	"Transfer-Encoding" },
35990267Sdes	{ hdr_www_authenticate,		"WWW-Authenticate" },
36090267Sdes	{ hdr_unknown,			NULL },
36163012Sdes};
36263012Sdes
36363012Sdes/*
36463012Sdes * Send a formatted line; optionally echo to terminal
36563012Sdes */
36663012Sdesstatic int
367174588Sdeshttp_cmd(conn_t *conn, const char *fmt, ...)
36863012Sdes{
36990267Sdes	va_list ap;
37090267Sdes	size_t len;
37190267Sdes	char *msg;
37290267Sdes	int r;
37363012Sdes
37490267Sdes	va_start(ap, fmt);
37590267Sdes	len = vasprintf(&msg, fmt, ap);
37690267Sdes	va_end(ap);
37790267Sdes
37890267Sdes	if (msg == NULL) {
37990267Sdes		errno = ENOMEM;
380174588Sdes		fetch_syserr();
38190267Sdes		return (-1);
38290267Sdes	}
38390267Sdes
384174588Sdes	r = fetch_putln(conn, msg, len);
38590267Sdes	free(msg);
38690267Sdes
38790267Sdes	if (r == -1) {
388174588Sdes		fetch_syserr();
38990267Sdes		return (-1);
39090267Sdes	}
39190267Sdes
39290267Sdes	return (0);
39363012Sdes}
39463012Sdes
39563012Sdes/*
39663012Sdes * Get and parse status line
39763012Sdes */
39863012Sdesstatic int
399174588Sdeshttp_get_reply(conn_t *conn)
40063012Sdes{
40190267Sdes	char *p;
40290267Sdes
403174588Sdes	if (fetch_getln(conn) == -1)
40490267Sdes		return (-1);
40590267Sdes	/*
40690267Sdes	 * A valid status line looks like "HTTP/m.n xyz reason" where m
40790267Sdes	 * and n are the major and minor protocol version numbers and xyz
40890267Sdes	 * is the reply code.
40990267Sdes	 * Unfortunately, there are servers out there (NCSA 1.5.1, to name
41090267Sdes	 * just one) that do not send a version number, so we can't rely
41190267Sdes	 * on finding one, but if we do, insist on it being 1.0 or 1.1.
41290267Sdes	 * We don't care about the reason phrase.
41390267Sdes	 */
41497856Sdes	if (strncmp(conn->buf, "HTTP", 4) != 0)
41590267Sdes		return (HTTP_PROTOCOL_ERROR);
41697856Sdes	p = conn->buf + 4;
41790267Sdes	if (*p == '/') {
41890267Sdes		if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
41990267Sdes			return (HTTP_PROTOCOL_ERROR);
42090267Sdes		p += 4;
42190267Sdes	}
422174761Sdes	if (*p != ' ' ||
423174761Sdes	    !isdigit((unsigned char)p[1]) ||
424174761Sdes	    !isdigit((unsigned char)p[2]) ||
425174761Sdes	    !isdigit((unsigned char)p[3]))
42690267Sdes		return (HTTP_PROTOCOL_ERROR);
42790267Sdes
42897856Sdes	conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
42997856Sdes	return (conn->err);
43037535Sdes}
43137535Sdes
43237608Sdes/*
43390267Sdes * Check a header; if the type matches the given string, return a pointer
43490267Sdes * to the beginning of the value.
43563012Sdes */
43675891Sarchiestatic const char *
437174588Sdeshttp_match(const char *str, const char *hdr)
43863012Sdes{
439176036Sdes	while (*str && *hdr &&
440176036Sdes	    tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
44190267Sdes		/* nothing */;
44290267Sdes	if (*str || *hdr != ':')
44390267Sdes		return (NULL);
444174761Sdes	while (*hdr && isspace((unsigned char)*++hdr))
44590267Sdes		/* nothing */;
44690267Sdes	return (hdr);
44763012Sdes}
44863012Sdes
44963012Sdes/*
45063012Sdes * Get the next header and return the appropriate symbolic code.
45163012Sdes */
45285093Sdesstatic hdr_t
453174588Sdeshttp_next_header(conn_t *conn, const char **p)
45463012Sdes{
45590267Sdes	int i;
45690267Sdes
457174588Sdes	if (fetch_getln(conn) == -1)
45890267Sdes		return (hdr_syserror);
459174761Sdes	while (conn->buflen && isspace((unsigned char)conn->buf[conn->buflen - 1]))
46097856Sdes		conn->buflen--;
46197856Sdes	conn->buf[conn->buflen] = '\0';
46297856Sdes	if (conn->buflen == 0)
46397856Sdes		return (hdr_end);
46490267Sdes	/*
46590267Sdes	 * We could check for malformed headers but we don't really care.
46690267Sdes	 * A valid header starts with a token immediately followed by a
46790267Sdes	 * colon; a token is any sequence of non-control, non-whitespace
46890267Sdes	 * characters except "()<>@,;:\\\"{}".
46990267Sdes	 */
47090267Sdes	for (i = 0; hdr_names[i].num != hdr_unknown; i++)
471174588Sdes		if ((*p = http_match(hdr_names[i].name, conn->buf)) != NULL)
47290267Sdes			return (hdr_names[i].num);
47390267Sdes	return (hdr_unknown);
47463012Sdes}
47563012Sdes
47663012Sdes/*
47763012Sdes * Parse a last-modified header
47863012Sdes */
47963716Sdesstatic int
480174588Sdeshttp_parse_mtime(const char *p, time_t *mtime)
48163012Sdes{
48290267Sdes	char locale[64], *r;
48390267Sdes	struct tm tm;
48463012Sdes
485109967Sdes	strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
48690267Sdes	setlocale(LC_TIME, "C");
48790267Sdes	r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
48890267Sdes	/* XXX should add support for date-2 and date-3 */
48990267Sdes	setlocale(LC_TIME, locale);
49090267Sdes	if (r == NULL)
49190267Sdes		return (-1);
49290267Sdes	DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d "
49388769Sdes		  "%02d:%02d:%02d]\n",
49463012Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
49563012Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
49690267Sdes	*mtime = timegm(&tm);
49790267Sdes	return (0);
49863012Sdes}
49963012Sdes
50063012Sdes/*
50163012Sdes * Parse a content-length header
50263012Sdes */
50363716Sdesstatic int
504174588Sdeshttp_parse_length(const char *p, off_t *length)
50563012Sdes{
50690267Sdes	off_t len;
50790267Sdes
508174761Sdes	for (len = 0; *p && isdigit((unsigned char)*p); ++p)
50990267Sdes		len = len * 10 + (*p - '0');
51090267Sdes	if (*p)
51190267Sdes		return (-1);
51290267Sdes	DEBUG(fprintf(stderr, "content length: [%lld]\n",
51390267Sdes	    (long long)len));
51490267Sdes	*length = len;
51590267Sdes	return (0);
51663012Sdes}
51763012Sdes
51863012Sdes/*
51963012Sdes * Parse a content-range header
52063012Sdes */
52163716Sdesstatic int
522174588Sdeshttp_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
52363012Sdes{
52490267Sdes	off_t first, last, len;
52563716Sdes
52690267Sdes	if (strncasecmp(p, "bytes ", 6) != 0)
52790267Sdes		return (-1);
528125696Sdes	p += 6;
529125696Sdes	if (*p == '*') {
530125696Sdes		first = last = -1;
531125696Sdes		++p;
532125696Sdes	} else {
533174761Sdes		for (first = 0; *p && isdigit((unsigned char)*p); ++p)
534125696Sdes			first = first * 10 + *p - '0';
535125696Sdes		if (*p != '-')
536125696Sdes			return (-1);
537174761Sdes		for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
538125696Sdes			last = last * 10 + *p - '0';
539125696Sdes	}
54090267Sdes	if (first > last || *p != '/')
54190267Sdes		return (-1);
542174761Sdes	for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
54390267Sdes		len = len * 10 + *p - '0';
54490267Sdes	if (*p || len < last - first + 1)
54590267Sdes		return (-1);
546125696Sdes	if (first == -1) {
547125696Sdes		DEBUG(fprintf(stderr, "content range: [*/%lld]\n",
548125696Sdes		    (long long)len));
549125696Sdes		*length = 0;
550125696Sdes	} else {
551125696Sdes		DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n",
552125696Sdes		    (long long)first, (long long)last, (long long)len));
553125696Sdes		*length = last - first + 1;
554125696Sdes	}
55590267Sdes	*offset = first;
55690267Sdes	*size = len;
55790267Sdes	return (0);
55863012Sdes}
55963012Sdes
56090267Sdes
56163012Sdes/*****************************************************************************
56263012Sdes * Helper functions for authorization
56363012Sdes */
56463012Sdes
56563012Sdes/*
56637608Sdes * Base64 encoding
56737608Sdes */
56862965Sdesstatic char *
569174588Sdeshttp_base64(const char *src)
57037608Sdes{
57190267Sdes	static const char base64[] =
57290267Sdes	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
57390267Sdes	    "abcdefghijklmnopqrstuvwxyz"
57490267Sdes	    "0123456789+/";
57590267Sdes	char *str, *dst;
57690267Sdes	size_t l;
57790267Sdes	int t, r;
57862965Sdes
57990267Sdes	l = strlen(src);
580133280Sdes	if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
58190267Sdes		return (NULL);
58290267Sdes	dst = str;
58390267Sdes	r = 0;
58437608Sdes
58590267Sdes	while (l >= 3) {
58690267Sdes		t = (src[0] << 16) | (src[1] << 8) | src[2];
58790267Sdes		dst[0] = base64[(t >> 18) & 0x3f];
58890267Sdes		dst[1] = base64[(t >> 12) & 0x3f];
58990267Sdes		dst[2] = base64[(t >> 6) & 0x3f];
59090267Sdes		dst[3] = base64[(t >> 0) & 0x3f];
59190267Sdes		src += 3; l -= 3;
59290267Sdes		dst += 4; r += 4;
59390267Sdes	}
59437608Sdes
59590267Sdes	switch (l) {
59690267Sdes	case 2:
59790267Sdes		t = (src[0] << 16) | (src[1] << 8);
59890267Sdes		dst[0] = base64[(t >> 18) & 0x3f];
59990267Sdes		dst[1] = base64[(t >> 12) & 0x3f];
60090267Sdes		dst[2] = base64[(t >> 6) & 0x3f];
60190267Sdes		dst[3] = '=';
60290267Sdes		dst += 4;
60390267Sdes		r += 4;
60490267Sdes		break;
60590267Sdes	case 1:
60690267Sdes		t = src[0] << 16;
60790267Sdes		dst[0] = base64[(t >> 18) & 0x3f];
60890267Sdes		dst[1] = base64[(t >> 12) & 0x3f];
60990267Sdes		dst[2] = dst[3] = '=';
61090267Sdes		dst += 4;
61190267Sdes		r += 4;
61290267Sdes		break;
61390267Sdes	case 0:
61490267Sdes		break;
61590267Sdes	}
61690267Sdes
61790267Sdes	*dst = 0;
61890267Sdes	return (str);
61937608Sdes}
62037608Sdes
62137608Sdes/*
62237608Sdes * Encode username and password
62337608Sdes */
62462965Sdesstatic int
625174588Sdeshttp_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
62637608Sdes{
62790267Sdes	char *upw, *auth;
62890267Sdes	int r;
62937608Sdes
63090267Sdes	DEBUG(fprintf(stderr, "usr: [%s]\n", usr));
63190267Sdes	DEBUG(fprintf(stderr, "pwd: [%s]\n", pwd));
63290267Sdes	if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
63390267Sdes		return (-1);
634174588Sdes	auth = http_base64(upw);
63590267Sdes	free(upw);
63690267Sdes	if (auth == NULL)
63790267Sdes		return (-1);
638174588Sdes	r = http_cmd(conn, "%s: Basic %s", hdr, auth);
63990267Sdes	free(auth);
64090267Sdes	return (r);
64162965Sdes}
64262965Sdes
64362965Sdes/*
64462965Sdes * Send an authorization header
64562965Sdes */
64662965Sdesstatic int
647174588Sdeshttp_authorize(conn_t *conn, const char *hdr, const char *p)
64862965Sdes{
64990267Sdes	/* basic authorization */
65090267Sdes	if (strncasecmp(p, "basic:", 6) == 0) {
65190267Sdes		char *user, *pwd, *str;
65290267Sdes		int r;
65362965Sdes
65490267Sdes		/* skip realm */
65590267Sdes		for (p += 6; *p && *p != ':'; ++p)
65690267Sdes			/* nothing */ ;
65790267Sdes		if (!*p || strchr(++p, ':') == NULL)
65890267Sdes			return (-1);
65990267Sdes		if ((str = strdup(p)) == NULL)
66090267Sdes			return (-1); /* XXX */
66190267Sdes		user = str;
66290267Sdes		pwd = strchr(str, ':');
66390267Sdes		*pwd++ = '\0';
664174588Sdes		r = http_basic_auth(conn, hdr, user, pwd);
66590267Sdes		free(str);
66690267Sdes		return (r);
66790267Sdes	}
66890267Sdes	return (-1);
66937608Sdes}
67037608Sdes
67190267Sdes
67263012Sdes/*****************************************************************************
67363012Sdes * Helper functions for connecting to a server or proxy
67463012Sdes */
67563012Sdes
67637608Sdes/*
67790267Sdes * Connect to the correct HTTP server or proxy.
67863012Sdes */
67997856Sdesstatic conn_t *
680174588Sdeshttp_connect(struct url *URL, struct url *purl, const char *flags)
68163012Sdes{
68297856Sdes	conn_t *conn;
68390267Sdes	int verbose;
684141958Skbyanc	int af, val;
68590267Sdes
68663012Sdes#ifdef INET6
68790267Sdes	af = AF_UNSPEC;
68860737Sume#else
68990267Sdes	af = AF_INET;
69060737Sume#endif
69190267Sdes
69290267Sdes	verbose = CHECK_FLAG('v');
69390267Sdes	if (CHECK_FLAG('4'))
69490267Sdes		af = AF_INET;
69567043Sdes#ifdef INET6
69690267Sdes	else if (CHECK_FLAG('6'))
69790267Sdes		af = AF_INET6;
69867043Sdes#endif
69967043Sdes
70097868Sdes	if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
70190267Sdes		URL = purl;
70290267Sdes	} else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
70390267Sdes		/* can't talk http to an ftp server */
70490267Sdes		/* XXX should set an error code */
70597856Sdes		return (NULL);
70690267Sdes	}
70790267Sdes
708174588Sdes	if ((conn = fetch_connect(URL->host, URL->port, af, verbose)) == NULL)
709174588Sdes		/* fetch_connect() has already set an error code */
71097856Sdes		return (NULL);
71197868Sdes	if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
712174588Sdes	    fetch_ssl(conn, verbose) == -1) {
713174588Sdes		fetch_close(conn);
71497891Sdes		/* grrr */
71597891Sdes		errno = EAUTH;
716174588Sdes		fetch_syserr();
71797868Sdes		return (NULL);
71897868Sdes	}
719141958Skbyanc
720141958Skbyanc	val = 1;
721141958Skbyanc	setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
722141958Skbyanc
72397856Sdes	return (conn);
72467043Sdes}
72567043Sdes
72667043Sdesstatic struct url *
727174752Sdeshttp_get_proxy(struct url * url, const char *flags)
72867043Sdes{
72990267Sdes	struct url *purl;
73090267Sdes	char *p;
73190267Sdes
732112797Sdes	if (flags != NULL && strchr(flags, 'd') != NULL)
733112081Sdes		return (NULL);
734174752Sdes	if (fetch_no_proxy_match(url->host))
735174752Sdes		return (NULL);
73690267Sdes	if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
737149414Sdes	    *p && (purl = fetchParseURL(p))) {
73890267Sdes		if (!*purl->scheme)
73990267Sdes			strcpy(purl->scheme, SCHEME_HTTP);
74090267Sdes		if (!purl->port)
741174588Sdes			purl->port = fetch_default_proxy_port(purl->scheme);
74290267Sdes		if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
74390267Sdes			return (purl);
74490267Sdes		fetchFreeURL(purl);
74590267Sdes	}
74690267Sdes	return (NULL);
74760376Sdes}
74860376Sdes
74988771Sdesstatic void
750174588Sdeshttp_print_html(FILE *out, FILE *in)
75188771Sdes{
75290267Sdes	size_t len;
75390267Sdes	char *line, *p, *q;
75490267Sdes	int comment, tag;
75588771Sdes
75690267Sdes	comment = tag = 0;
75790267Sdes	while ((line = fgetln(in, &len)) != NULL) {
758174761Sdes		while (len && isspace((unsigned char)line[len - 1]))
75990267Sdes			--len;
76090267Sdes		for (p = q = line; q < line + len; ++q) {
76190267Sdes			if (comment && *q == '-') {
76290267Sdes				if (q + 2 < line + len &&
76390267Sdes				    strcmp(q, "-->") == 0) {
76490267Sdes					tag = comment = 0;
76590267Sdes					q += 2;
76690267Sdes				}
76790267Sdes			} else if (tag && !comment && *q == '>') {
76890267Sdes				p = q + 1;
76990267Sdes				tag = 0;
77090267Sdes			} else if (!tag && *q == '<') {
77190267Sdes				if (q > p)
77290267Sdes					fwrite(p, q - p, 1, out);
77390267Sdes				tag = 1;
77490267Sdes				if (q + 3 < line + len &&
77590267Sdes				    strcmp(q, "<!--") == 0) {
77690267Sdes					comment = 1;
77790267Sdes					q += 3;
77890267Sdes				}
77990267Sdes			}
78088771Sdes		}
78190267Sdes		if (!tag && q > p)
78290267Sdes			fwrite(p, q - p, 1, out);
78390267Sdes		fputc('\n', out);
78488771Sdes	}
78588771Sdes}
78688771Sdes
78790267Sdes
78863012Sdes/*****************************************************************************
78963012Sdes * Core
79060954Sdes */
79160954Sdes
79260954Sdes/*
79363012Sdes * Send a request and process the reply
79497866Sdes *
79597866Sdes * XXX This function is way too long, the do..while loop should be split
79697866Sdes * XXX off into a separate function.
79760376Sdes */
79867043SdesFILE *
799174588Sdeshttp_request(struct url *URL, const char *op, struct url_stat *us,
80090267Sdes    struct url *purl, const char *flags)
80160376Sdes{
802186124Smurray	char timebuf[80];
803186124Smurray	char hbuf[MAXHOSTNAMELEN + 7], *host;
80497856Sdes	conn_t *conn;
80590267Sdes	struct url *url, *new;
806186124Smurray	int chunked, direct, ims, need_auth, noredirect, verbose;
807143049Skbyanc	int e, i, n, val;
80890267Sdes	off_t offset, clength, length, size;
80990267Sdes	time_t mtime;
81090267Sdes	const char *p;
81190267Sdes	FILE *f;
81290267Sdes	hdr_t h;
813186124Smurray	struct tm *timestruct;
81463012Sdes
81590267Sdes	direct = CHECK_FLAG('d');
81690267Sdes	noredirect = CHECK_FLAG('A');
81790267Sdes	verbose = CHECK_FLAG('v');
818186124Smurray	ims = CHECK_FLAG('i');
81960737Sume
82090267Sdes	if (direct && purl) {
82190267Sdes		fetchFreeURL(purl);
82290267Sdes		purl = NULL;
82390267Sdes	}
82463716Sdes
82590267Sdes	/* try the provided URL first */
82690267Sdes	url = URL;
82763012Sdes
82890267Sdes	/* if the A flag is set, we only get one try */
82990267Sdes	n = noredirect ? 1 : MAX_REDIRECT;
83090267Sdes	i = 0;
83163012Sdes
83298422Sdes	e = HTTP_PROTOCOL_ERROR;
83390267Sdes	need_auth = 0;
83490267Sdes	do {
83590267Sdes		new = NULL;
83690267Sdes		chunked = 0;
83790267Sdes		offset = 0;
83890267Sdes		clength = -1;
83990267Sdes		length = -1;
84090267Sdes		size = -1;
84190267Sdes		mtime = 0;
84290267Sdes
84390267Sdes		/* check port */
84490267Sdes		if (!url->port)
845174588Sdes			url->port = fetch_default_port(url->scheme);
84690267Sdes
84790267Sdes		/* were we redirected to an FTP URL? */
84890267Sdes		if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
84990267Sdes			if (strcmp(op, "GET") == 0)
850174588Sdes				return (ftp_request(url, "RETR", us, purl, flags));
85190267Sdes			else if (strcmp(op, "HEAD") == 0)
852174588Sdes				return (ftp_request(url, "STAT", us, purl, flags));
85390267Sdes		}
85490267Sdes
85590267Sdes		/* connect to server or proxy */
856174588Sdes		if ((conn = http_connect(url, purl, flags)) == NULL)
85790267Sdes			goto ouch;
85890267Sdes
85990267Sdes		host = url->host;
86060737Sume#ifdef INET6
86190267Sdes		if (strchr(url->host, ':')) {
86290267Sdes			snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
86390267Sdes			host = hbuf;
86490267Sdes		}
86560737Sume#endif
866174588Sdes		if (url->port != fetch_default_port(url->scheme)) {
867107372Sdes			if (host != hbuf) {
868107372Sdes				strcpy(hbuf, host);
869107372Sdes				host = hbuf;
870107372Sdes			}
871107372Sdes			snprintf(hbuf + strlen(hbuf),
872107372Sdes			    sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
873107372Sdes		}
87437535Sdes
87590267Sdes		/* send request */
87690267Sdes		if (verbose)
877174588Sdes			fetch_info("requesting %s://%s%s",
878107372Sdes			    url->scheme, host, url->doc);
87990267Sdes		if (purl) {
880174588Sdes			http_cmd(conn, "%s %s://%s%s HTTP/1.1",
881107372Sdes			    op, url->scheme, host, url->doc);
88290267Sdes		} else {
883174588Sdes			http_cmd(conn, "%s %s HTTP/1.1",
88490267Sdes			    op, url->doc);
88590267Sdes		}
88637535Sdes
887186124Smurray		if (ims && url->ims_time) {
888186124Smurray			timestruct = gmtime((time_t *)&url->ims_time);
889186124Smurray			(void)strftime(timebuf, 80, "%a, %d %b %Y %T GMT",
890186124Smurray			    timestruct);
891186124Smurray			if (verbose)
892186124Smurray				fetch_info("If-Modified-Since: %s", timebuf);
893186124Smurray			http_cmd(conn, "If-Modified-Since: %s", timebuf);
894186124Smurray		}
89590267Sdes		/* virtual host */
896174588Sdes		http_cmd(conn, "Host: %s", host);
89790267Sdes
89890267Sdes		/* proxy authorization */
89990267Sdes		if (purl) {
90090267Sdes			if (*purl->user || *purl->pwd)
901174588Sdes				http_basic_auth(conn, "Proxy-Authorization",
90290267Sdes				    purl->user, purl->pwd);
90390267Sdes			else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
904174588Sdes				http_authorize(conn, "Proxy-Authorization", p);
90590267Sdes		}
90690267Sdes
90790267Sdes		/* server authorization */
90890267Sdes		if (need_auth || *url->user || *url->pwd) {
90990267Sdes			if (*url->user || *url->pwd)
910174588Sdes				http_basic_auth(conn, "Authorization", url->user, url->pwd);
91190267Sdes			else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
912174588Sdes				http_authorize(conn, "Authorization", p);
91390267Sdes			else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
914174588Sdes				http_basic_auth(conn, "Authorization", url->user, url->pwd);
91590267Sdes			} else {
916174588Sdes				http_seterr(HTTP_NEED_AUTH);
91790267Sdes				goto ouch;
91890267Sdes			}
91990267Sdes		}
92090267Sdes
92190267Sdes		/* other headers */
922107372Sdes		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
923107372Sdes			if (strcasecmp(p, "auto") == 0)
924174588Sdes				http_cmd(conn, "Referer: %s://%s%s",
925107372Sdes				    url->scheme, host, url->doc);
926107372Sdes			else
927174588Sdes				http_cmd(conn, "Referer: %s", p);
928107372Sdes		}
92990267Sdes		if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
930174588Sdes			http_cmd(conn, "User-Agent: %s", p);
93190267Sdes		else
932174588Sdes			http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname());
933109693Sdes		if (url->offset > 0)
934174588Sdes			http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset);
935174588Sdes		http_cmd(conn, "Connection: close");
936174588Sdes		http_cmd(conn, "");
93790267Sdes
938143049Skbyanc		/*
939143049Skbyanc		 * Force the queued request to be dispatched.  Normally, one
940143049Skbyanc		 * would do this with shutdown(2) but squid proxies can be
941143049Skbyanc		 * configured to disallow such half-closed connections.  To
942143049Skbyanc		 * be compatible with such configurations, fiddle with socket
943143049Skbyanc		 * options to force the pending data to be written.
944143049Skbyanc		 */
945143049Skbyanc		val = 0;
946143049Skbyanc		setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
947143049Skbyanc			   sizeof(val));
948143049Skbyanc		val = 1;
949143049Skbyanc		setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
950143049Skbyanc			   sizeof(val));
951143049Skbyanc
95290267Sdes		/* get reply */
953174588Sdes		switch (http_get_reply(conn)) {
95490267Sdes		case HTTP_OK:
95590267Sdes		case HTTP_PARTIAL:
956186124Smurray		case HTTP_NOT_MODIFIED:
95790267Sdes			/* fine */
95890267Sdes			break;
95990267Sdes		case HTTP_MOVED_PERM:
96090267Sdes		case HTTP_MOVED_TEMP:
96190267Sdes		case HTTP_SEE_OTHER:
96290267Sdes			/*
963125695Sdes			 * Not so fine, but we still have to read the
964125695Sdes			 * headers to get the new location.
96590267Sdes			 */
96690267Sdes			break;
96790267Sdes		case HTTP_NEED_AUTH:
96890267Sdes			if (need_auth) {
96990267Sdes				/*
970125695Sdes				 * We already sent out authorization code,
971125695Sdes				 * so there's nothing more we can do.
97290267Sdes				 */
973174588Sdes				http_seterr(conn->err);
97490267Sdes				goto ouch;
97590267Sdes			}
97690267Sdes			/* try again, but send the password this time */
97790267Sdes			if (verbose)
978174588Sdes				fetch_info("server requires authorization");
97990267Sdes			break;
98090267Sdes		case HTTP_NEED_PROXY_AUTH:
98190267Sdes			/*
982125695Sdes			 * If we're talking to a proxy, we already sent
983125695Sdes			 * our proxy authorization code, so there's
984125695Sdes			 * nothing more we can do.
98590267Sdes			 */
986174588Sdes			http_seterr(conn->err);
98790267Sdes			goto ouch;
988125696Sdes		case HTTP_BAD_RANGE:
989125696Sdes			/*
990125696Sdes			 * This can happen if we ask for 0 bytes because
991125696Sdes			 * we already have the whole file.  Consider this
992125696Sdes			 * a success for now, and check sizes later.
993125696Sdes			 */
994125696Sdes			break;
99590267Sdes		case HTTP_PROTOCOL_ERROR:
99690267Sdes			/* fall through */
99790267Sdes		case -1:
998174588Sdes			fetch_syserr();
99990267Sdes			goto ouch;
100090267Sdes		default:
1001174588Sdes			http_seterr(conn->err);
100290267Sdes			if (!verbose)
100390267Sdes				goto ouch;
100490267Sdes			/* fall through so we can get the full error message */
100590267Sdes		}
100690267Sdes
100790267Sdes		/* get headers */
100890267Sdes		do {
1009174588Sdes			switch ((h = http_next_header(conn, &p))) {
101090267Sdes			case hdr_syserror:
1011174588Sdes				fetch_syserr();
101290267Sdes				goto ouch;
101390267Sdes			case hdr_error:
1014174588Sdes				http_seterr(HTTP_PROTOCOL_ERROR);
101590267Sdes				goto ouch;
101690267Sdes			case hdr_content_length:
1017174588Sdes				http_parse_length(p, &clength);
101890267Sdes				break;
101990267Sdes			case hdr_content_range:
1020174588Sdes				http_parse_range(p, &offset, &length, &size);
102190267Sdes				break;
102290267Sdes			case hdr_last_modified:
1023174588Sdes				http_parse_mtime(p, &mtime);
102490267Sdes				break;
102590267Sdes			case hdr_location:
102697856Sdes				if (!HTTP_REDIRECT(conn->err))
102790267Sdes					break;
102890267Sdes				if (new)
102990267Sdes					free(new);
103090267Sdes				if (verbose)
1031174588Sdes					fetch_info("%d redirect to %s", conn->err, p);
103290267Sdes				if (*p == '/')
103390267Sdes					/* absolute path */
103490267Sdes					new = fetchMakeURL(url->scheme, url->host, url->port, p,
103590267Sdes					    url->user, url->pwd);
103690267Sdes				else
103790267Sdes					new = fetchParseURL(p);
103890267Sdes				if (new == NULL) {
103990267Sdes					/* XXX should set an error code */
104090267Sdes					DEBUG(fprintf(stderr, "failed to parse new URL\n"));
104190267Sdes					goto ouch;
104290267Sdes				}
104390267Sdes				if (!*new->user && !*new->pwd) {
104490267Sdes					strcpy(new->user, url->user);
104590267Sdes					strcpy(new->pwd, url->pwd);
104690267Sdes				}
104790267Sdes				new->offset = url->offset;
104890267Sdes				new->length = url->length;
104990267Sdes				break;
105090267Sdes			case hdr_transfer_encoding:
105190267Sdes				/* XXX weak test*/
105290267Sdes				chunked = (strcasecmp(p, "chunked") == 0);
105390267Sdes				break;
105490267Sdes			case hdr_www_authenticate:
105597856Sdes				if (conn->err != HTTP_NEED_AUTH)
105690267Sdes					break;
105790267Sdes				/* if we were smarter, we'd check the method and realm */
105890267Sdes				break;
105990267Sdes			case hdr_end:
106090267Sdes				/* fall through */
106190267Sdes			case hdr_unknown:
106290267Sdes				/* ignore */
106390267Sdes				break;
106490267Sdes			}
106590267Sdes		} while (h > hdr_end);
106690267Sdes
106790267Sdes		/* we need to provide authentication */
106897856Sdes		if (conn->err == HTTP_NEED_AUTH) {
106998422Sdes			e = conn->err;
107090267Sdes			need_auth = 1;
1071174588Sdes			fetch_close(conn);
107297856Sdes			conn = NULL;
107390267Sdes			continue;
107490267Sdes		}
107590267Sdes
1076125696Sdes		/* requested range not satisfiable */
1077125696Sdes		if (conn->err == HTTP_BAD_RANGE) {
1078125696Sdes			if (url->offset == size && url->length == 0) {
1079125696Sdes				/* asked for 0 bytes; fake it */
1080125696Sdes				offset = url->offset;
1081184222Sru				clength = -1;
1082125696Sdes				conn->err = HTTP_OK;
1083125696Sdes				break;
1084125696Sdes			} else {
1085174588Sdes				http_seterr(conn->err);
1086125696Sdes				goto ouch;
1087125696Sdes			}
1088125696Sdes		}
1089125696Sdes
1090104404Sru		/* we have a hit or an error */
1091186124Smurray		if (conn->err == HTTP_OK
1092186124Smurray		    || conn->err == HTTP_NOT_MODIFIED
1093186124Smurray		    || conn->err == HTTP_PARTIAL
1094186124Smurray		    || HTTP_ERROR(conn->err))
1095104404Sru			break;
1096104404Sru
109790267Sdes		/* all other cases: we got a redirect */
109898422Sdes		e = conn->err;
109990267Sdes		need_auth = 0;
1100174588Sdes		fetch_close(conn);
110197856Sdes		conn = NULL;
110290267Sdes		if (!new) {
110390267Sdes			DEBUG(fprintf(stderr, "redirect with no new location\n"));
110490267Sdes			break;
110590267Sdes		}
110690267Sdes		if (url != URL)
110790267Sdes			fetchFreeURL(url);
110890267Sdes		url = new;
110990267Sdes	} while (++i < n);
111090267Sdes
111190267Sdes	/* we failed, or ran out of retries */
111297856Sdes	if (conn == NULL) {
1113174588Sdes		http_seterr(e);
111463012Sdes		goto ouch;
111563012Sdes	}
111660376Sdes
111790267Sdes	DEBUG(fprintf(stderr, "offset %lld, length %lld,"
111890267Sdes		  " size %lld, clength %lld\n",
111990267Sdes		  (long long)offset, (long long)length,
112090267Sdes		  (long long)size, (long long)clength));
112160376Sdes
1122186124Smurray	if (conn->err == HTTP_NOT_MODIFIED) {
1123186124Smurray		http_seterr(HTTP_NOT_MODIFIED);
1124186124Smurray		return (NULL);
1125186124Smurray	}
1126186124Smurray
112790267Sdes	/* check for inconsistencies */
112890267Sdes	if (clength != -1 && length != -1 && clength != length) {
1129174588Sdes		http_seterr(HTTP_PROTOCOL_ERROR);
113063012Sdes		goto ouch;
113163012Sdes	}
113290267Sdes	if (clength == -1)
113390267Sdes		clength = length;
113490267Sdes	if (clength != -1)
113590267Sdes		length = offset + clength;
113690267Sdes	if (length != -1 && size != -1 && length != size) {
1137174588Sdes		http_seterr(HTTP_PROTOCOL_ERROR);
113863012Sdes		goto ouch;
113990267Sdes	}
114090267Sdes	if (size == -1)
114190267Sdes		size = length;
114260376Sdes
114390267Sdes	/* fill in stats */
114490267Sdes	if (us) {
114590267Sdes		us->size = size;
114690267Sdes		us->atime = us->mtime = mtime;
114790267Sdes	}
114863069Sdes
114990267Sdes	/* too far? */
1150109693Sdes	if (URL->offset > 0 && offset > URL->offset) {
1151174588Sdes		http_seterr(HTTP_PROTOCOL_ERROR);
115290267Sdes		goto ouch;
115377238Sdes	}
115460376Sdes
115590267Sdes	/* report back real offset and size */
115690267Sdes	URL->offset = offset;
115790267Sdes	URL->length = clength;
115837535Sdes
115990267Sdes	/* wrap it up in a FILE */
1160174588Sdes	if ((f = http_funopen(conn, chunked)) == NULL) {
1161174588Sdes		fetch_syserr();
116290267Sdes		goto ouch;
116390267Sdes	}
116463716Sdes
116590267Sdes	if (url != URL)
116690267Sdes		fetchFreeURL(url);
116790267Sdes	if (purl)
116890267Sdes		fetchFreeURL(purl);
116963567Sdes
117097856Sdes	if (HTTP_ERROR(conn->err)) {
1171174588Sdes		http_print_html(stderr, f);
117290267Sdes		fclose(f);
117390267Sdes		f = NULL;
117490267Sdes	}
117563012Sdes
117690267Sdes	return (f);
117788771Sdes
117890267Sdesouch:
117990267Sdes	if (url != URL)
118090267Sdes		fetchFreeURL(url);
118190267Sdes	if (purl)
118290267Sdes		fetchFreeURL(purl);
118397856Sdes	if (conn != NULL)
1184174588Sdes		fetch_close(conn);
118590267Sdes	return (NULL);
118663012Sdes}
118760189Sdes
118890267Sdes
118963012Sdes/*****************************************************************************
119063012Sdes * Entry points
119163012Sdes */
119263012Sdes
119363012Sdes/*
119463340Sdes * Retrieve and stat a file by HTTP
119563340Sdes */
119663340SdesFILE *
119775891SarchiefetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
119863340Sdes{
1199174752Sdes	return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
120063340Sdes}
120163340Sdes
120263340Sdes/*
120363012Sdes * Retrieve a file by HTTP
120463012Sdes */
120563012SdesFILE *
120675891SarchiefetchGetHTTP(struct url *URL, const char *flags)
120763012Sdes{
120890267Sdes	return (fetchXGetHTTP(URL, NULL, flags));
120937535Sdes}
121037535Sdes
121163340Sdes/*
121263340Sdes * Store a file by HTTP
121363340Sdes */
121437535SdesFILE *
121585093SdesfetchPutHTTP(struct url *URL __unused, const char *flags __unused)
121637535Sdes{
121790267Sdes	warnx("fetchPutHTTP(): not implemented");
121890267Sdes	return (NULL);
121937535Sdes}
122040975Sdes
122140975Sdes/*
122240975Sdes * Get an HTTP document's metadata
122340975Sdes */
122440975Sdesint
122575891SarchiefetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
122640975Sdes{
122790267Sdes	FILE *f;
122890267Sdes
1229174752Sdes	f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
1230112081Sdes	if (f == NULL)
123190267Sdes		return (-1);
123290267Sdes	fclose(f);
123390267Sdes	return (0);
123440975Sdes}
123541989Sdes
123641989Sdes/*
123741989Sdes * List a directory
123841989Sdes */
123941989Sdesstruct url_ent *
124085093SdesfetchListHTTP(struct url *url __unused, const char *flags __unused)
124141989Sdes{
124290267Sdes	warnx("fetchListHTTP(): not implemented");
124390267Sdes	return (NULL);
124441989Sdes}
1245