http.c revision 141958
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 141958 2005-02-16 00:22:20Z kbyanc $");
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>
6637535Sdes
6763012Sdes#include <ctype.h>
6837535Sdes#include <err.h>
6963012Sdes#include <errno.h>
7060376Sdes#include <locale.h>
7160189Sdes#include <netdb.h>
7237608Sdes#include <stdarg.h>
7337535Sdes#include <stdio.h>
7437535Sdes#include <stdlib.h>
7537535Sdes#include <string.h>
7660376Sdes#include <time.h>
7737535Sdes#include <unistd.h>
7837535Sdes
79141958Skbyanc#include <netinet/in.h>
80141958Skbyanc#include <netinet/tcp.h>
81141958Skbyanc
8237535Sdes#include "fetch.h"
8340939Sdes#include "common.h"
8441862Sdes#include "httperr.h"
8537535Sdes
8663012Sdes/* Maximum number of redirects to follow */
8763012Sdes#define MAX_REDIRECT 5
8837535Sdes
8963012Sdes/* Symbolic names for reply codes we care about */
9063012Sdes#define HTTP_OK			200
9163012Sdes#define HTTP_PARTIAL		206
9263012Sdes#define HTTP_MOVED_PERM		301
9363012Sdes#define HTTP_MOVED_TEMP		302
9463012Sdes#define HTTP_SEE_OTHER		303
9563012Sdes#define HTTP_NEED_AUTH		401
9687317Sdes#define HTTP_NEED_PROXY_AUTH	407
97125696Sdes#define HTTP_BAD_RANGE		416
9863012Sdes#define HTTP_PROTOCOL_ERROR	999
9960196Sdes
10063012Sdes#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
10190267Sdes			    || (xyz) == HTTP_MOVED_TEMP \
10290267Sdes			    || (xyz) == HTTP_SEE_OTHER)
10363012Sdes
10488771Sdes#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
10563012Sdes
10690267Sdes
10763012Sdes/*****************************************************************************
10863012Sdes * I/O functions for decoding chunked streams
10963012Sdes */
11063012Sdes
11197859Sdesstruct httpio
11237535Sdes{
11397858Sdes	conn_t		*conn;		/* connection */
11497866Sdes	int		 chunked;	/* chunked mode */
11597858Sdes	char		*buf;		/* chunk buffer */
11697866Sdes	size_t		 bufsize;	/* size of chunk buffer */
11797866Sdes	ssize_t		 buflen;	/* amount of data currently in buffer */
11897866Sdes	int		 bufpos;	/* current read offset in buffer */
11997858Sdes	int		 eof;		/* end-of-file flag */
12097858Sdes	int		 error;		/* error flag */
12197858Sdes	size_t		 chunksize;	/* remaining size of current chunk */
12263281Sdes#ifndef NDEBUG
12390267Sdes	size_t		 total;
12463012Sdes#endif
12537535Sdes};
12637535Sdes
12737608Sdes/*
12863012Sdes * Get next chunk header
12937608Sdes */
13037608Sdesstatic int
13197859Sdes_http_new_chunk(struct httpio *io)
13237608Sdes{
13390267Sdes	char *p;
13490267Sdes
13597859Sdes	if (_fetch_getln(io->conn) == -1)
13690267Sdes		return (-1);
13790267Sdes
13897859Sdes	if (io->conn->buflen < 2 || !ishexnumber(*io->conn->buf))
13990267Sdes		return (-1);
14090267Sdes
14197859Sdes	for (p = io->conn->buf; *p && !isspace(*p); ++p) {
14290267Sdes		if (*p == ';')
14390267Sdes			break;
14490267Sdes		if (!ishexnumber(*p))
14590267Sdes			return (-1);
14690267Sdes		if (isdigit(*p)) {
14797859Sdes			io->chunksize = io->chunksize * 16 +
14890267Sdes			    *p - '0';
14990267Sdes		} else {
15097859Sdes			io->chunksize = io->chunksize * 16 +
15190267Sdes			    10 + tolower(*p) - 'a';
15290267Sdes		}
15390267Sdes	}
15490267Sdes
15563281Sdes#ifndef NDEBUG
15690267Sdes	if (fetchDebug) {
15797859Sdes		io->total += io->chunksize;
15897859Sdes		if (io->chunksize == 0)
159106207Sdes			fprintf(stderr, "%s(): end of last chunk\n", __func__);
16090267Sdes		else
161106207Sdes			fprintf(stderr, "%s(): new chunk: %lu (%lu)\n",
162106207Sdes			    __func__, (unsigned long)io->chunksize,
163106207Sdes			    (unsigned long)io->total);
16490267Sdes	}
16563012Sdes#endif
16690267Sdes
16797859Sdes	return (io->chunksize);
16837608Sdes}
16937608Sdes
17037608Sdes/*
17197866Sdes * Grow the input buffer to at least len bytes
17297866Sdes */
17397866Sdesstatic inline int
17497866Sdes_http_growbuf(struct httpio *io, size_t len)
17597866Sdes{
17697866Sdes	char *tmp;
17797866Sdes
17897866Sdes	if (io->bufsize >= len)
17997866Sdes		return (0);
18097866Sdes
18197866Sdes	if ((tmp = realloc(io->buf, len)) == NULL)
18297866Sdes		return (-1);
18397866Sdes	io->buf = tmp;
18497866Sdes	io->bufsize = len;
185106044Sdes	return (0);
18697866Sdes}
18797866Sdes
18897866Sdes/*
18937608Sdes * Fill the input buffer, do chunk decoding on the fly
19037608Sdes */
19163012Sdesstatic int
19297866Sdes_http_fillbuf(struct httpio *io, size_t len)
19337535Sdes{
19497859Sdes	if (io->error)
19590267Sdes		return (-1);
19697859Sdes	if (io->eof)
19790267Sdes		return (0);
19890267Sdes
19997866Sdes	if (io->chunked == 0) {
20097866Sdes		if (_http_growbuf(io, len) == -1)
20197866Sdes			return (-1);
202106185Sdes		if ((io->buflen = _fetch_read(io->conn, io->buf, len)) == -1) {
203106185Sdes			io->error = 1;
20497866Sdes			return (-1);
205106185Sdes		}
20697866Sdes		io->bufpos = 0;
20797866Sdes		return (io->buflen);
20897866Sdes	}
20997866Sdes
21097859Sdes	if (io->chunksize == 0) {
21197859Sdes		switch (_http_new_chunk(io)) {
21290267Sdes		case -1:
21397859Sdes			io->error = 1;
21490267Sdes			return (-1);
21590267Sdes		case 0:
21697859Sdes			io->eof = 1;
21790267Sdes			return (0);
21890267Sdes		}
21937535Sdes	}
22063012Sdes
22197866Sdes	if (len > io->chunksize)
22297866Sdes		len = io->chunksize;
22397866Sdes	if (_http_growbuf(io, len) == -1)
22490267Sdes		return (-1);
225106185Sdes	if ((io->buflen = _fetch_read(io->conn, io->buf, len)) == -1) {
226106185Sdes		io->error = 1;
22797866Sdes		return (-1);
228106185Sdes	}
22997866Sdes	io->chunksize -= io->buflen;
23090267Sdes
23197859Sdes	if (io->chunksize == 0) {
23297856Sdes		char endl[2];
23397856Sdes
23497866Sdes		if (_fetch_read(io->conn, endl, 2) != 2 ||
23597856Sdes		    endl[0] != '\r' || endl[1] != '\n')
23690267Sdes			return (-1);
23790267Sdes	}
23890267Sdes
23997866Sdes	io->bufpos = 0;
24090267Sdes
24197866Sdes	return (io->buflen);
24237535Sdes}
24337535Sdes
24437608Sdes/*
24537608Sdes * Read function
24637608Sdes */
24737535Sdesstatic int
24863012Sdes_http_readfn(void *v, char *buf, int len)
24937535Sdes{
25097859Sdes	struct httpio *io = (struct httpio *)v;
25190267Sdes	int l, pos;
25263012Sdes
25397859Sdes	if (io->error)
25490267Sdes		return (-1);
25597859Sdes	if (io->eof)
25690267Sdes		return (0);
25763012Sdes
25890267Sdes	for (pos = 0; len > 0; pos += l, len -= l) {
25990267Sdes		/* empty buffer */
26097866Sdes		if (!io->buf || io->bufpos == io->buflen)
26197866Sdes			if (_http_fillbuf(io, len) < 1)
26290267Sdes				break;
26397866Sdes		l = io->buflen - io->bufpos;
26490267Sdes		if (len < l)
26590267Sdes			l = len;
26697866Sdes		bcopy(io->buf + io->bufpos, buf + pos, l);
26797866Sdes		io->bufpos += l;
26890267Sdes	}
26937535Sdes
27097859Sdes	if (!pos && io->error)
27190267Sdes		return (-1);
27290267Sdes	return (pos);
27337535Sdes}
27437535Sdes
27537608Sdes/*
27637608Sdes * Write function
27737608Sdes */
27837535Sdesstatic int
27963012Sdes_http_writefn(void *v, const char *buf, int len)
28037535Sdes{
28197859Sdes	struct httpio *io = (struct httpio *)v;
28290267Sdes
28397866Sdes	return (_fetch_write(io->conn, buf, len));
28437535Sdes}
28537535Sdes
28637608Sdes/*
28737608Sdes * Close function
28837608Sdes */
28937535Sdesstatic int
29063012Sdes_http_closefn(void *v)
29137535Sdes{
29297859Sdes	struct httpio *io = (struct httpio *)v;
29390267Sdes	int r;
29463012Sdes
29597859Sdes	r = _fetch_close(io->conn);
29697859Sdes	if (io->buf)
29797859Sdes		free(io->buf);
29897859Sdes	free(io);
29990267Sdes	return (r);
30037535Sdes}
30137535Sdes
30237608Sdes/*
30363012Sdes * Wrap a file descriptor up
30437608Sdes */
30563012Sdesstatic FILE *
30697866Sdes_http_funopen(conn_t *conn, int chunked)
30737535Sdes{
30897859Sdes	struct httpio *io;
30990267Sdes	FILE *f;
31063012Sdes
311109967Sdes	if ((io = calloc(1, sizeof(*io))) == NULL) {
31290267Sdes		_fetch_syserr();
31390267Sdes		return (NULL);
31490267Sdes	}
31597859Sdes	io->conn = conn;
31697866Sdes	io->chunked = chunked;
31797859Sdes	f = funopen(io, _http_readfn, _http_writefn, NULL, _http_closefn);
31890267Sdes	if (f == NULL) {
31990267Sdes		_fetch_syserr();
32097859Sdes		free(io);
32190267Sdes		return (NULL);
32290267Sdes	}
32390267Sdes	return (f);
32463012Sdes}
32563012Sdes
32690267Sdes
32763012Sdes/*****************************************************************************
32863012Sdes * Helper functions for talking to the server and parsing its replies
32963012Sdes */
33063012Sdes
33163012Sdes/* Header types */
33263012Sdestypedef enum {
33390267Sdes	hdr_syserror = -2,
33490267Sdes	hdr_error = -1,
33590267Sdes	hdr_end = 0,
33690267Sdes	hdr_unknown = 1,
33790267Sdes	hdr_content_length,
33890267Sdes	hdr_content_range,
33990267Sdes	hdr_last_modified,
34090267Sdes	hdr_location,
34190267Sdes	hdr_transfer_encoding,
34290267Sdes	hdr_www_authenticate
34385093Sdes} hdr_t;
34463012Sdes
34563012Sdes/* Names of interesting headers */
34663012Sdesstatic struct {
34790267Sdes	hdr_t		 num;
34890267Sdes	const char	*name;
34963012Sdes} hdr_names[] = {
35090267Sdes	{ hdr_content_length,		"Content-Length" },
35190267Sdes	{ hdr_content_range,		"Content-Range" },
35290267Sdes	{ hdr_last_modified,		"Last-Modified" },
35390267Sdes	{ hdr_location,			"Location" },
35490267Sdes	{ hdr_transfer_encoding,	"Transfer-Encoding" },
35590267Sdes	{ hdr_www_authenticate,		"WWW-Authenticate" },
35690267Sdes	{ hdr_unknown,			NULL },
35763012Sdes};
35863012Sdes
35963012Sdes/*
36063012Sdes * Send a formatted line; optionally echo to terminal
36163012Sdes */
36263012Sdesstatic int
36397856Sdes_http_cmd(conn_t *conn, const char *fmt, ...)
36463012Sdes{
36590267Sdes	va_list ap;
36690267Sdes	size_t len;
36790267Sdes	char *msg;
36890267Sdes	int r;
36963012Sdes
37090267Sdes	va_start(ap, fmt);
37190267Sdes	len = vasprintf(&msg, fmt, ap);
37290267Sdes	va_end(ap);
37390267Sdes
37490267Sdes	if (msg == NULL) {
37590267Sdes		errno = ENOMEM;
37690267Sdes		_fetch_syserr();
37790267Sdes		return (-1);
37890267Sdes	}
37990267Sdes
38097856Sdes	r = _fetch_putln(conn, msg, len);
38190267Sdes	free(msg);
38290267Sdes
38390267Sdes	if (r == -1) {
38490267Sdes		_fetch_syserr();
38590267Sdes		return (-1);
38690267Sdes	}
38790267Sdes
38890267Sdes	return (0);
38963012Sdes}
39063012Sdes
39163012Sdes/*
39263012Sdes * Get and parse status line
39363012Sdes */
39463012Sdesstatic int
39597856Sdes_http_get_reply(conn_t *conn)
39663012Sdes{
39790267Sdes	char *p;
39890267Sdes
39997856Sdes	if (_fetch_getln(conn) == -1)
40090267Sdes		return (-1);
40190267Sdes	/*
40290267Sdes	 * A valid status line looks like "HTTP/m.n xyz reason" where m
40390267Sdes	 * and n are the major and minor protocol version numbers and xyz
40490267Sdes	 * is the reply code.
40590267Sdes	 * Unfortunately, there are servers out there (NCSA 1.5.1, to name
40690267Sdes	 * just one) that do not send a version number, so we can't rely
40790267Sdes	 * on finding one, but if we do, insist on it being 1.0 or 1.1.
40890267Sdes	 * We don't care about the reason phrase.
40990267Sdes	 */
41097856Sdes	if (strncmp(conn->buf, "HTTP", 4) != 0)
41190267Sdes		return (HTTP_PROTOCOL_ERROR);
41297856Sdes	p = conn->buf + 4;
41390267Sdes	if (*p == '/') {
41490267Sdes		if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
41590267Sdes			return (HTTP_PROTOCOL_ERROR);
41690267Sdes		p += 4;
41790267Sdes	}
41890267Sdes	if (*p != ' ' || !isdigit(p[1]) || !isdigit(p[2]) || !isdigit(p[3]))
41990267Sdes		return (HTTP_PROTOCOL_ERROR);
42090267Sdes
42197856Sdes	conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
42297856Sdes	return (conn->err);
42337535Sdes}
42437535Sdes
42537608Sdes/*
42690267Sdes * Check a header; if the type matches the given string, return a pointer
42790267Sdes * to the beginning of the value.
42863012Sdes */
42975891Sarchiestatic const char *
43075891Sarchie_http_match(const char *str, const char *hdr)
43163012Sdes{
43290267Sdes	while (*str && *hdr && tolower(*str++) == tolower(*hdr++))
43390267Sdes		/* nothing */;
43490267Sdes	if (*str || *hdr != ':')
43590267Sdes		return (NULL);
43690267Sdes	while (*hdr && isspace(*++hdr))
43790267Sdes		/* nothing */;
43890267Sdes	return (hdr);
43963012Sdes}
44063012Sdes
44163012Sdes/*
44263012Sdes * Get the next header and return the appropriate symbolic code.
44363012Sdes */
44485093Sdesstatic hdr_t
44597856Sdes_http_next_header(conn_t *conn, const char **p)
44663012Sdes{
44790267Sdes	int i;
44890267Sdes
44997856Sdes	if (_fetch_getln(conn) == -1)
45090267Sdes		return (hdr_syserror);
45197856Sdes	while (conn->buflen && isspace(conn->buf[conn->buflen - 1]))
45297856Sdes		conn->buflen--;
45397856Sdes	conn->buf[conn->buflen] = '\0';
45497856Sdes	if (conn->buflen == 0)
45597856Sdes		return (hdr_end);
45690267Sdes	/*
45790267Sdes	 * We could check for malformed headers but we don't really care.
45890267Sdes	 * A valid header starts with a token immediately followed by a
45990267Sdes	 * colon; a token is any sequence of non-control, non-whitespace
46090267Sdes	 * characters except "()<>@,;:\\\"{}".
46190267Sdes	 */
46290267Sdes	for (i = 0; hdr_names[i].num != hdr_unknown; i++)
46397856Sdes		if ((*p = _http_match(hdr_names[i].name, conn->buf)) != NULL)
46490267Sdes			return (hdr_names[i].num);
46590267Sdes	return (hdr_unknown);
46663012Sdes}
46763012Sdes
46863012Sdes/*
46963012Sdes * Parse a last-modified header
47063012Sdes */
47163716Sdesstatic int
47275891Sarchie_http_parse_mtime(const char *p, time_t *mtime)
47363012Sdes{
47490267Sdes	char locale[64], *r;
47590267Sdes	struct tm tm;
47663012Sdes
477109967Sdes	strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
47890267Sdes	setlocale(LC_TIME, "C");
47990267Sdes	r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
48090267Sdes	/* XXX should add support for date-2 and date-3 */
48190267Sdes	setlocale(LC_TIME, locale);
48290267Sdes	if (r == NULL)
48390267Sdes		return (-1);
48490267Sdes	DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d "
48588769Sdes		  "%02d:%02d:%02d]\n",
48663012Sdes		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
48763012Sdes		  tm.tm_hour, tm.tm_min, tm.tm_sec));
48890267Sdes	*mtime = timegm(&tm);
48990267Sdes	return (0);
49063012Sdes}
49163012Sdes
49263012Sdes/*
49363012Sdes * Parse a content-length header
49463012Sdes */
49563716Sdesstatic int
49675891Sarchie_http_parse_length(const char *p, off_t *length)
49763012Sdes{
49890267Sdes	off_t len;
49990267Sdes
50090267Sdes	for (len = 0; *p && isdigit(*p); ++p)
50190267Sdes		len = len * 10 + (*p - '0');
50290267Sdes	if (*p)
50390267Sdes		return (-1);
50490267Sdes	DEBUG(fprintf(stderr, "content length: [%lld]\n",
50590267Sdes	    (long long)len));
50690267Sdes	*length = len;
50790267Sdes	return (0);
50863012Sdes}
50963012Sdes
51063012Sdes/*
51163012Sdes * Parse a content-range header
51263012Sdes */
51363716Sdesstatic int
51475891Sarchie_http_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
51563012Sdes{
51690267Sdes	off_t first, last, len;
51763716Sdes
51890267Sdes	if (strncasecmp(p, "bytes ", 6) != 0)
51990267Sdes		return (-1);
520125696Sdes	p += 6;
521125696Sdes	if (*p == '*') {
522125696Sdes		first = last = -1;
523125696Sdes		++p;
524125696Sdes	} else {
525125696Sdes		for (first = 0; *p && isdigit(*p); ++p)
526125696Sdes			first = first * 10 + *p - '0';
527125696Sdes		if (*p != '-')
528125696Sdes			return (-1);
529125696Sdes		for (last = 0, ++p; *p && isdigit(*p); ++p)
530125696Sdes			last = last * 10 + *p - '0';
531125696Sdes	}
53290267Sdes	if (first > last || *p != '/')
53390267Sdes		return (-1);
53490267Sdes	for (len = 0, ++p; *p && isdigit(*p); ++p)
53590267Sdes		len = len * 10 + *p - '0';
53690267Sdes	if (*p || len < last - first + 1)
53790267Sdes		return (-1);
538125696Sdes	if (first == -1) {
539125696Sdes		DEBUG(fprintf(stderr, "content range: [*/%lld]\n",
540125696Sdes		    (long long)len));
541125696Sdes		*length = 0;
542125696Sdes	} else {
543125696Sdes		DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n",
544125696Sdes		    (long long)first, (long long)last, (long long)len));
545125696Sdes		*length = last - first + 1;
546125696Sdes	}
54790267Sdes	*offset = first;
54890267Sdes	*size = len;
54990267Sdes	return (0);
55063012Sdes}
55163012Sdes
55290267Sdes
55363012Sdes/*****************************************************************************
55463012Sdes * Helper functions for authorization
55563012Sdes */
55663012Sdes
55763012Sdes/*
55837608Sdes * Base64 encoding
55937608Sdes */
56062965Sdesstatic char *
56190267Sdes_http_base64(const char *src)
56237608Sdes{
56390267Sdes	static const char base64[] =
56490267Sdes	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
56590267Sdes	    "abcdefghijklmnopqrstuvwxyz"
56690267Sdes	    "0123456789+/";
56790267Sdes	char *str, *dst;
56890267Sdes	size_t l;
56990267Sdes	int t, r;
57062965Sdes
57190267Sdes	l = strlen(src);
572133280Sdes	if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
57390267Sdes		return (NULL);
57490267Sdes	dst = str;
57590267Sdes	r = 0;
57637608Sdes
57790267Sdes	while (l >= 3) {
57890267Sdes		t = (src[0] << 16) | (src[1] << 8) | src[2];
57990267Sdes		dst[0] = base64[(t >> 18) & 0x3f];
58090267Sdes		dst[1] = base64[(t >> 12) & 0x3f];
58190267Sdes		dst[2] = base64[(t >> 6) & 0x3f];
58290267Sdes		dst[3] = base64[(t >> 0) & 0x3f];
58390267Sdes		src += 3; l -= 3;
58490267Sdes		dst += 4; r += 4;
58590267Sdes	}
58637608Sdes
58790267Sdes	switch (l) {
58890267Sdes	case 2:
58990267Sdes		t = (src[0] << 16) | (src[1] << 8);
59090267Sdes		dst[0] = base64[(t >> 18) & 0x3f];
59190267Sdes		dst[1] = base64[(t >> 12) & 0x3f];
59290267Sdes		dst[2] = base64[(t >> 6) & 0x3f];
59390267Sdes		dst[3] = '=';
59490267Sdes		dst += 4;
59590267Sdes		r += 4;
59690267Sdes		break;
59790267Sdes	case 1:
59890267Sdes		t = src[0] << 16;
59990267Sdes		dst[0] = base64[(t >> 18) & 0x3f];
60090267Sdes		dst[1] = base64[(t >> 12) & 0x3f];
60190267Sdes		dst[2] = dst[3] = '=';
60290267Sdes		dst += 4;
60390267Sdes		r += 4;
60490267Sdes		break;
60590267Sdes	case 0:
60690267Sdes		break;
60790267Sdes	}
60890267Sdes
60990267Sdes	*dst = 0;
61090267Sdes	return (str);
61137608Sdes}
61237608Sdes
61337608Sdes/*
61437608Sdes * Encode username and password
61537608Sdes */
61662965Sdesstatic int
61797856Sdes_http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
61837608Sdes{
61990267Sdes	char *upw, *auth;
62090267Sdes	int r;
62137608Sdes
62290267Sdes	DEBUG(fprintf(stderr, "usr: [%s]\n", usr));
62390267Sdes	DEBUG(fprintf(stderr, "pwd: [%s]\n", pwd));
62490267Sdes	if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
62590267Sdes		return (-1);
62690267Sdes	auth = _http_base64(upw);
62790267Sdes	free(upw);
62890267Sdes	if (auth == NULL)
62990267Sdes		return (-1);
63097856Sdes	r = _http_cmd(conn, "%s: Basic %s", hdr, auth);
63190267Sdes	free(auth);
63290267Sdes	return (r);
63362965Sdes}
63462965Sdes
63562965Sdes/*
63662965Sdes * Send an authorization header
63762965Sdes */
63862965Sdesstatic int
63997856Sdes_http_authorize(conn_t *conn, const char *hdr, const char *p)
64062965Sdes{
64190267Sdes	/* basic authorization */
64290267Sdes	if (strncasecmp(p, "basic:", 6) == 0) {
64390267Sdes		char *user, *pwd, *str;
64490267Sdes		int r;
64562965Sdes
64690267Sdes		/* skip realm */
64790267Sdes		for (p += 6; *p && *p != ':'; ++p)
64890267Sdes			/* nothing */ ;
64990267Sdes		if (!*p || strchr(++p, ':') == NULL)
65090267Sdes			return (-1);
65190267Sdes		if ((str = strdup(p)) == NULL)
65290267Sdes			return (-1); /* XXX */
65390267Sdes		user = str;
65490267Sdes		pwd = strchr(str, ':');
65590267Sdes		*pwd++ = '\0';
65697856Sdes		r = _http_basic_auth(conn, hdr, user, pwd);
65790267Sdes		free(str);
65890267Sdes		return (r);
65990267Sdes	}
66090267Sdes	return (-1);
66137608Sdes}
66237608Sdes
66390267Sdes
66463012Sdes/*****************************************************************************
66563012Sdes * Helper functions for connecting to a server or proxy
66663012Sdes */
66763012Sdes
66837608Sdes/*
66990267Sdes * Connect to the correct HTTP server or proxy.
67063012Sdes */
67197856Sdesstatic conn_t *
67275891Sarchie_http_connect(struct url *URL, struct url *purl, const char *flags)
67363012Sdes{
67497856Sdes	conn_t *conn;
67590267Sdes	int verbose;
676141958Skbyanc	int af, val;
67790267Sdes
67863012Sdes#ifdef INET6
67990267Sdes	af = AF_UNSPEC;
68060737Sume#else
68190267Sdes	af = AF_INET;
68260737Sume#endif
68390267Sdes
68490267Sdes	verbose = CHECK_FLAG('v');
68590267Sdes	if (CHECK_FLAG('4'))
68690267Sdes		af = AF_INET;
68767043Sdes#ifdef INET6
68890267Sdes	else if (CHECK_FLAG('6'))
68990267Sdes		af = AF_INET6;
69067043Sdes#endif
69167043Sdes
69297868Sdes	if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
69390267Sdes		URL = purl;
69490267Sdes	} else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
69590267Sdes		/* can't talk http to an ftp server */
69690267Sdes		/* XXX should set an error code */
69797856Sdes		return (NULL);
69890267Sdes	}
69990267Sdes
70097856Sdes	if ((conn = _fetch_connect(URL->host, URL->port, af, verbose)) == NULL)
70190267Sdes		/* _fetch_connect() has already set an error code */
70297856Sdes		return (NULL);
70397868Sdes	if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
70497868Sdes	    _fetch_ssl(conn, verbose) == -1) {
70597868Sdes		_fetch_close(conn);
70697891Sdes		/* grrr */
70797891Sdes		errno = EAUTH;
70897891Sdes		_fetch_syserr();
70997868Sdes		return (NULL);
71097868Sdes	}
711141958Skbyanc
712141958Skbyanc	val = 1;
713141958Skbyanc	setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
714141958Skbyanc
71597856Sdes	return (conn);
71667043Sdes}
71767043Sdes
71867043Sdesstatic struct url *
719112081Sdes_http_get_proxy(const char *flags)
72067043Sdes{
72190267Sdes	struct url *purl;
72290267Sdes	char *p;
72390267Sdes
724112797Sdes	if (flags != NULL && strchr(flags, 'd') != NULL)
725112081Sdes		return (NULL);
72690267Sdes	if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
72790267Sdes	    (purl = fetchParseURL(p))) {
72890267Sdes		if (!*purl->scheme)
72990267Sdes			strcpy(purl->scheme, SCHEME_HTTP);
73090267Sdes		if (!purl->port)
73190267Sdes			purl->port = _fetch_default_proxy_port(purl->scheme);
73290267Sdes		if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
73390267Sdes			return (purl);
73490267Sdes		fetchFreeURL(purl);
73590267Sdes	}
73690267Sdes	return (NULL);
73760376Sdes}
73860376Sdes
73988771Sdesstatic void
74088771Sdes_http_print_html(FILE *out, FILE *in)
74188771Sdes{
74290267Sdes	size_t len;
74390267Sdes	char *line, *p, *q;
74490267Sdes	int comment, tag;
74588771Sdes
74690267Sdes	comment = tag = 0;
74790267Sdes	while ((line = fgetln(in, &len)) != NULL) {
74890267Sdes		while (len && isspace(line[len - 1]))
74990267Sdes			--len;
75090267Sdes		for (p = q = line; q < line + len; ++q) {
75190267Sdes			if (comment && *q == '-') {
75290267Sdes				if (q + 2 < line + len &&
75390267Sdes				    strcmp(q, "-->") == 0) {
75490267Sdes					tag = comment = 0;
75590267Sdes					q += 2;
75690267Sdes				}
75790267Sdes			} else if (tag && !comment && *q == '>') {
75890267Sdes				p = q + 1;
75990267Sdes				tag = 0;
76090267Sdes			} else if (!tag && *q == '<') {
76190267Sdes				if (q > p)
76290267Sdes					fwrite(p, q - p, 1, out);
76390267Sdes				tag = 1;
76490267Sdes				if (q + 3 < line + len &&
76590267Sdes				    strcmp(q, "<!--") == 0) {
76690267Sdes					comment = 1;
76790267Sdes					q += 3;
76890267Sdes				}
76990267Sdes			}
77088771Sdes		}
77190267Sdes		if (!tag && q > p)
77290267Sdes			fwrite(p, q - p, 1, out);
77390267Sdes		fputc('\n', out);
77488771Sdes	}
77588771Sdes}
77688771Sdes
77790267Sdes
77863012Sdes/*****************************************************************************
77963012Sdes * Core
78060954Sdes */
78160954Sdes
78260954Sdes/*
78363012Sdes * Send a request and process the reply
78497866Sdes *
78597866Sdes * XXX This function is way too long, the do..while loop should be split
78697866Sdes * XXX off into a separate function.
78760376Sdes */
78867043SdesFILE *
78975891Sarchie_http_request(struct url *URL, const char *op, struct url_stat *us,
79090267Sdes    struct url *purl, const char *flags)
79160376Sdes{
79297856Sdes	conn_t *conn;
79390267Sdes	struct url *url, *new;
79490267Sdes	int chunked, direct, need_auth, noredirect, verbose;
79598422Sdes	int e, i, n;
79690267Sdes	off_t offset, clength, length, size;
79790267Sdes	time_t mtime;
79890267Sdes	const char *p;
79990267Sdes	FILE *f;
80090267Sdes	hdr_t h;
801107372Sdes	char hbuf[MAXHOSTNAMELEN + 7], *host;
80263012Sdes
80390267Sdes	direct = CHECK_FLAG('d');
80490267Sdes	noredirect = CHECK_FLAG('A');
80590267Sdes	verbose = CHECK_FLAG('v');
80660737Sume
80790267Sdes	if (direct && purl) {
80890267Sdes		fetchFreeURL(purl);
80990267Sdes		purl = NULL;
81090267Sdes	}
81163716Sdes
81290267Sdes	/* try the provided URL first */
81390267Sdes	url = URL;
81463012Sdes
81590267Sdes	/* if the A flag is set, we only get one try */
81690267Sdes	n = noredirect ? 1 : MAX_REDIRECT;
81790267Sdes	i = 0;
81863012Sdes
81998422Sdes	e = HTTP_PROTOCOL_ERROR;
82090267Sdes	need_auth = 0;
82190267Sdes	do {
82290267Sdes		new = NULL;
82390267Sdes		chunked = 0;
82490267Sdes		offset = 0;
82590267Sdes		clength = -1;
82690267Sdes		length = -1;
82790267Sdes		size = -1;
82890267Sdes		mtime = 0;
82990267Sdes
83090267Sdes		/* check port */
83190267Sdes		if (!url->port)
83290267Sdes			url->port = _fetch_default_port(url->scheme);
83390267Sdes
83490267Sdes		/* were we redirected to an FTP URL? */
83590267Sdes		if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
83690267Sdes			if (strcmp(op, "GET") == 0)
83790267Sdes				return (_ftp_request(url, "RETR", us, purl, flags));
83890267Sdes			else if (strcmp(op, "HEAD") == 0)
83990267Sdes				return (_ftp_request(url, "STAT", us, purl, flags));
84090267Sdes		}
84190267Sdes
84290267Sdes		/* connect to server or proxy */
84397856Sdes		if ((conn = _http_connect(url, purl, flags)) == NULL)
84490267Sdes			goto ouch;
84590267Sdes
84690267Sdes		host = url->host;
84760737Sume#ifdef INET6
84890267Sdes		if (strchr(url->host, ':')) {
84990267Sdes			snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
85090267Sdes			host = hbuf;
85190267Sdes		}
85260737Sume#endif
853107372Sdes		if (url->port != _fetch_default_port(url->scheme)) {
854107372Sdes			if (host != hbuf) {
855107372Sdes				strcpy(hbuf, host);
856107372Sdes				host = hbuf;
857107372Sdes			}
858107372Sdes			snprintf(hbuf + strlen(hbuf),
859107372Sdes			    sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
860107372Sdes		}
86137535Sdes
86290267Sdes		/* send request */
86390267Sdes		if (verbose)
864107372Sdes			_fetch_info("requesting %s://%s%s",
865107372Sdes			    url->scheme, host, url->doc);
86690267Sdes		if (purl) {
867107372Sdes			_http_cmd(conn, "%s %s://%s%s HTTP/1.1",
868107372Sdes			    op, url->scheme, host, url->doc);
86990267Sdes		} else {
87097856Sdes			_http_cmd(conn, "%s %s HTTP/1.1",
87190267Sdes			    op, url->doc);
87290267Sdes		}
87337535Sdes
87490267Sdes		/* virtual host */
875107372Sdes		_http_cmd(conn, "Host: %s", host);
87690267Sdes
87790267Sdes		/* proxy authorization */
87890267Sdes		if (purl) {
87990267Sdes			if (*purl->user || *purl->pwd)
88097856Sdes				_http_basic_auth(conn, "Proxy-Authorization",
88190267Sdes				    purl->user, purl->pwd);
88290267Sdes			else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
88397856Sdes				_http_authorize(conn, "Proxy-Authorization", p);
88490267Sdes		}
88590267Sdes
88690267Sdes		/* server authorization */
88790267Sdes		if (need_auth || *url->user || *url->pwd) {
88890267Sdes			if (*url->user || *url->pwd)
88997856Sdes				_http_basic_auth(conn, "Authorization", url->user, url->pwd);
89090267Sdes			else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
89197856Sdes				_http_authorize(conn, "Authorization", p);
89290267Sdes			else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
89397856Sdes				_http_basic_auth(conn, "Authorization", url->user, url->pwd);
89490267Sdes			} else {
89590267Sdes				_http_seterr(HTTP_NEED_AUTH);
89690267Sdes				goto ouch;
89790267Sdes			}
89890267Sdes		}
89990267Sdes
90090267Sdes		/* other headers */
901107372Sdes		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
902107372Sdes			if (strcasecmp(p, "auto") == 0)
903107372Sdes				_http_cmd(conn, "Referer: %s://%s%s",
904107372Sdes				    url->scheme, host, url->doc);
905107372Sdes			else
906107372Sdes				_http_cmd(conn, "Referer: %s", p);
907107372Sdes		}
90890267Sdes		if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
90997856Sdes			_http_cmd(conn, "User-Agent: %s", p);
91090267Sdes		else
91197856Sdes			_http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname());
912109693Sdes		if (url->offset > 0)
91397856Sdes			_http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset);
91497856Sdes		_http_cmd(conn, "Connection: close");
91597856Sdes		_http_cmd(conn, "");
916141958Skbyanc		shutdown(conn->sd, SHUT_WR);
91790267Sdes
91890267Sdes		/* get reply */
91997856Sdes		switch (_http_get_reply(conn)) {
92090267Sdes		case HTTP_OK:
92190267Sdes		case HTTP_PARTIAL:
92290267Sdes			/* fine */
92390267Sdes			break;
92490267Sdes		case HTTP_MOVED_PERM:
92590267Sdes		case HTTP_MOVED_TEMP:
92690267Sdes		case HTTP_SEE_OTHER:
92790267Sdes			/*
928125695Sdes			 * Not so fine, but we still have to read the
929125695Sdes			 * headers to get the new location.
93090267Sdes			 */
93190267Sdes			break;
93290267Sdes		case HTTP_NEED_AUTH:
93390267Sdes			if (need_auth) {
93490267Sdes				/*
935125695Sdes				 * We already sent out authorization code,
936125695Sdes				 * so there's nothing more we can do.
93790267Sdes				 */
93897856Sdes				_http_seterr(conn->err);
93990267Sdes				goto ouch;
94090267Sdes			}
94190267Sdes			/* try again, but send the password this time */
94290267Sdes			if (verbose)
94390267Sdes				_fetch_info("server requires authorization");
94490267Sdes			break;
94590267Sdes		case HTTP_NEED_PROXY_AUTH:
94690267Sdes			/*
947125695Sdes			 * If we're talking to a proxy, we already sent
948125695Sdes			 * our proxy authorization code, so there's
949125695Sdes			 * nothing more we can do.
95090267Sdes			 */
95197856Sdes			_http_seterr(conn->err);
95290267Sdes			goto ouch;
953125696Sdes		case HTTP_BAD_RANGE:
954125696Sdes			/*
955125696Sdes			 * This can happen if we ask for 0 bytes because
956125696Sdes			 * we already have the whole file.  Consider this
957125696Sdes			 * a success for now, and check sizes later.
958125696Sdes			 */
959125696Sdes			break;
96090267Sdes		case HTTP_PROTOCOL_ERROR:
96190267Sdes			/* fall through */
96290267Sdes		case -1:
96390267Sdes			_fetch_syserr();
96490267Sdes			goto ouch;
96590267Sdes		default:
96697856Sdes			_http_seterr(conn->err);
96790267Sdes			if (!verbose)
96890267Sdes				goto ouch;
96990267Sdes			/* fall through so we can get the full error message */
97090267Sdes		}
97190267Sdes
97290267Sdes		/* get headers */
97390267Sdes		do {
97497856Sdes			switch ((h = _http_next_header(conn, &p))) {
97590267Sdes			case hdr_syserror:
97690267Sdes				_fetch_syserr();
97790267Sdes				goto ouch;
97890267Sdes			case hdr_error:
97990267Sdes				_http_seterr(HTTP_PROTOCOL_ERROR);
98090267Sdes				goto ouch;
98190267Sdes			case hdr_content_length:
98290267Sdes				_http_parse_length(p, &clength);
98390267Sdes				break;
98490267Sdes			case hdr_content_range:
98590267Sdes				_http_parse_range(p, &offset, &length, &size);
98690267Sdes				break;
98790267Sdes			case hdr_last_modified:
98890267Sdes				_http_parse_mtime(p, &mtime);
98990267Sdes				break;
99090267Sdes			case hdr_location:
99197856Sdes				if (!HTTP_REDIRECT(conn->err))
99290267Sdes					break;
99390267Sdes				if (new)
99490267Sdes					free(new);
99590267Sdes				if (verbose)
99697856Sdes					_fetch_info("%d redirect to %s", conn->err, p);
99790267Sdes				if (*p == '/')
99890267Sdes					/* absolute path */
99990267Sdes					new = fetchMakeURL(url->scheme, url->host, url->port, p,
100090267Sdes					    url->user, url->pwd);
100190267Sdes				else
100290267Sdes					new = fetchParseURL(p);
100390267Sdes				if (new == NULL) {
100490267Sdes					/* XXX should set an error code */
100590267Sdes					DEBUG(fprintf(stderr, "failed to parse new URL\n"));
100690267Sdes					goto ouch;
100790267Sdes				}
100890267Sdes				if (!*new->user && !*new->pwd) {
100990267Sdes					strcpy(new->user, url->user);
101090267Sdes					strcpy(new->pwd, url->pwd);
101190267Sdes				}
101290267Sdes				new->offset = url->offset;
101390267Sdes				new->length = url->length;
101490267Sdes				break;
101590267Sdes			case hdr_transfer_encoding:
101690267Sdes				/* XXX weak test*/
101790267Sdes				chunked = (strcasecmp(p, "chunked") == 0);
101890267Sdes				break;
101990267Sdes			case hdr_www_authenticate:
102097856Sdes				if (conn->err != HTTP_NEED_AUTH)
102190267Sdes					break;
102290267Sdes				/* if we were smarter, we'd check the method and realm */
102390267Sdes				break;
102490267Sdes			case hdr_end:
102590267Sdes				/* fall through */
102690267Sdes			case hdr_unknown:
102790267Sdes				/* ignore */
102890267Sdes				break;
102990267Sdes			}
103090267Sdes		} while (h > hdr_end);
103190267Sdes
103290267Sdes		/* we need to provide authentication */
103397856Sdes		if (conn->err == HTTP_NEED_AUTH) {
103498422Sdes			e = conn->err;
103590267Sdes			need_auth = 1;
103697856Sdes			_fetch_close(conn);
103797856Sdes			conn = NULL;
103890267Sdes			continue;
103990267Sdes		}
104090267Sdes
1041125696Sdes		/* requested range not satisfiable */
1042125696Sdes		if (conn->err == HTTP_BAD_RANGE) {
1043125696Sdes			if (url->offset == size && url->length == 0) {
1044125696Sdes				/* asked for 0 bytes; fake it */
1045125696Sdes				offset = url->offset;
1046125696Sdes				conn->err = HTTP_OK;
1047125696Sdes				break;
1048125696Sdes			} else {
1049125697Sdes				_http_seterr(conn->err);
1050125696Sdes				goto ouch;
1051125696Sdes			}
1052125696Sdes		}
1053125696Sdes
1054104404Sru		/* we have a hit or an error */
1055104404Sru		if (conn->err == HTTP_OK || conn->err == HTTP_PARTIAL || HTTP_ERROR(conn->err))
1056104404Sru			break;
1057104404Sru
105890267Sdes		/* all other cases: we got a redirect */
105998422Sdes		e = conn->err;
106090267Sdes		need_auth = 0;
106197856Sdes		_fetch_close(conn);
106297856Sdes		conn = NULL;
106390267Sdes		if (!new) {
106490267Sdes			DEBUG(fprintf(stderr, "redirect with no new location\n"));
106590267Sdes			break;
106690267Sdes		}
106790267Sdes		if (url != URL)
106890267Sdes			fetchFreeURL(url);
106990267Sdes		url = new;
107090267Sdes	} while (++i < n);
107190267Sdes
107290267Sdes	/* we failed, or ran out of retries */
107397856Sdes	if (conn == NULL) {
107498422Sdes		_http_seterr(e);
107563012Sdes		goto ouch;
107663012Sdes	}
107760376Sdes
107890267Sdes	DEBUG(fprintf(stderr, "offset %lld, length %lld,"
107990267Sdes		  " size %lld, clength %lld\n",
108090267Sdes		  (long long)offset, (long long)length,
108190267Sdes		  (long long)size, (long long)clength));
108260376Sdes
108390267Sdes	/* check for inconsistencies */
108490267Sdes	if (clength != -1 && length != -1 && clength != length) {
108590267Sdes		_http_seterr(HTTP_PROTOCOL_ERROR);
108663012Sdes		goto ouch;
108763012Sdes	}
108890267Sdes	if (clength == -1)
108990267Sdes		clength = length;
109090267Sdes	if (clength != -1)
109190267Sdes		length = offset + clength;
109290267Sdes	if (length != -1 && size != -1 && length != size) {
109363012Sdes		_http_seterr(HTTP_PROTOCOL_ERROR);
109463012Sdes		goto ouch;
109590267Sdes	}
109690267Sdes	if (size == -1)
109790267Sdes		size = length;
109860376Sdes
109990267Sdes	/* fill in stats */
110090267Sdes	if (us) {
110190267Sdes		us->size = size;
110290267Sdes		us->atime = us->mtime = mtime;
110390267Sdes	}
110463069Sdes
110590267Sdes	/* too far? */
1106109693Sdes	if (URL->offset > 0 && offset > URL->offset) {
110790267Sdes		_http_seterr(HTTP_PROTOCOL_ERROR);
110890267Sdes		goto ouch;
110977238Sdes	}
111060376Sdes
111190267Sdes	/* report back real offset and size */
111290267Sdes	URL->offset = offset;
111390267Sdes	URL->length = clength;
111437535Sdes
111590267Sdes	/* wrap it up in a FILE */
111697866Sdes	if ((f = _http_funopen(conn, chunked)) == NULL) {
111790267Sdes		_fetch_syserr();
111890267Sdes		goto ouch;
111990267Sdes	}
112063716Sdes
112190267Sdes	if (url != URL)
112290267Sdes		fetchFreeURL(url);
112390267Sdes	if (purl)
112490267Sdes		fetchFreeURL(purl);
112563567Sdes
112697856Sdes	if (HTTP_ERROR(conn->err)) {
112790267Sdes		_http_print_html(stderr, f);
112890267Sdes		fclose(f);
112990267Sdes		f = NULL;
113090267Sdes	}
113163012Sdes
113290267Sdes	return (f);
113388771Sdes
113490267Sdesouch:
113590267Sdes	if (url != URL)
113690267Sdes		fetchFreeURL(url);
113790267Sdes	if (purl)
113890267Sdes		fetchFreeURL(purl);
113997856Sdes	if (conn != NULL)
114097856Sdes		_fetch_close(conn);
114190267Sdes	return (NULL);
114263012Sdes}
114360189Sdes
114490267Sdes
114563012Sdes/*****************************************************************************
114663012Sdes * Entry points
114763012Sdes */
114863012Sdes
114963012Sdes/*
115063340Sdes * Retrieve and stat a file by HTTP
115163340Sdes */
115263340SdesFILE *
115375891SarchiefetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
115463340Sdes{
1155112081Sdes	return (_http_request(URL, "GET", us, _http_get_proxy(flags), flags));
115663340Sdes}
115763340Sdes
115863340Sdes/*
115963012Sdes * Retrieve a file by HTTP
116063012Sdes */
116163012SdesFILE *
116275891SarchiefetchGetHTTP(struct url *URL, const char *flags)
116363012Sdes{
116490267Sdes	return (fetchXGetHTTP(URL, NULL, flags));
116537535Sdes}
116637535Sdes
116763340Sdes/*
116863340Sdes * Store a file by HTTP
116963340Sdes */
117037535SdesFILE *
117185093SdesfetchPutHTTP(struct url *URL __unused, const char *flags __unused)
117237535Sdes{
117390267Sdes	warnx("fetchPutHTTP(): not implemented");
117490267Sdes	return (NULL);
117537535Sdes}
117640975Sdes
117740975Sdes/*
117840975Sdes * Get an HTTP document's metadata
117940975Sdes */
118040975Sdesint
118175891SarchiefetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
118240975Sdes{
118390267Sdes	FILE *f;
118490267Sdes
1185112081Sdes	f = _http_request(URL, "HEAD", us, _http_get_proxy(flags), flags);
1186112081Sdes	if (f == NULL)
118790267Sdes		return (-1);
118890267Sdes	fclose(f);
118990267Sdes	return (0);
119040975Sdes}
119141989Sdes
119241989Sdes/*
119341989Sdes * List a directory
119441989Sdes */
119541989Sdesstruct url_ent *
119685093SdesfetchListHTTP(struct url *url __unused, const char *flags __unused)
119741989Sdes{
119890267Sdes	warnx("fetchListHTTP(): not implemented");
119990267Sdes	return (NULL);
120041989Sdes}
1201