http.c revision 184222
118316Swollman/*-
218316Swollman * Copyright (c) 2000-2004 Dag-Erling Co�dan Sm�rgrav
318316Swollman * All rights reserved.
418316Swollman *
518316Swollman * Redistribution and use in source and binary forms, with or without
618316Swollman * modification, are permitted provided that the following conditions
718316Swollman * are met:
818316Swollman * 1. Redistributions of source code must retain the above copyright
918316Swollman *    notice, this list of conditions and the following disclaimer
1018316Swollman *    in this position and unchanged.
1118316Swollman * 2. Redistributions in binary form must reproduce the above copyright
1218316Swollman *    notice, this list of conditions and the following disclaimer in the
1318316Swollman *    documentation and/or other materials provided with the distribution.
1418316Swollman * 3. The name of the author may not be used to endorse or promote products
1518316Swollman *    derived from this software without specific prior written permission.
1618316Swollman *
1718316Swollman * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1818316Swollman * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1918316Swollman * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
2018316Swollman * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
2118316Swollman * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2218316Swollman * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
2318316Swollman * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2418316Swollman * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2518316Swollman * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2618316Swollman * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2718316Swollman */
2846303Smarkm
2950476Speter#include <sys/cdefs.h>
3018316Swollman__FBSDID("$FreeBSD: head/lib/libfetch/http.c 184222 2008-10-24 07:56:01Z ru $");
3118316Swollman
3218316Swollman/*
3318316Swollman * The following copyright applies to the base64 code:
3418316Swollman *
3518316Swollman *-
3646303Smarkm * Copyright 1997 Massachusetts Institute of Technology
3718316Swollman *
3818316Swollman * Permission to use, copy, modify, and distribute this software and
39126250Sbms * its documentation for any purpose and without fee is hereby
4046303Smarkm * granted, provided that both the above copyright notice and this
41126250Sbms * permission notice appear in all copies, that both the above
42126250Sbms * copyright notice and this permission notice appear in all
43126250Sbms * supporting documentation, and that the name of M.I.T. not be used
44126250Sbms * in advertising or publicity pertaining to distribution of the
45126250Sbms * software without specific, written prior permission.  M.I.T. makes
4646303Smarkm * no representations about the suitability of this software for any
4746303Smarkm * purpose.  It is provided "as is" without express or implied
4846303Smarkm * warranty.
4918316Swollman *
50102231Strhodes * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
5118316Swollman * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
5218316Swollman * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5318316Swollman * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
5420339Swollman * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
5581604Speter * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
5646303Smarkm * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
57118582Simp * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
58118582Simp * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
59190715Sphk * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
6018316Swollman * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6118316Swollman * SUCH DAMAGE.
6246303Smarkm */
6318316Swollman
6418316Swollman#include <sys/param.h>
6519880Swollman#include <sys/socket.h>
6619880Swollman
6719880Swollman#include <ctype.h>
6819880Swollman#include <err.h>
6919880Swollman#include <errno.h>
7019880Swollman#include <locale.h>
7119880Swollman#include <netdb.h>
7219880Swollman#include <stdarg.h>
7319880Swollman#include <stdio.h>
7419880Swollman#include <stdlib.h>
7519880Swollman#include <string.h>
7619880Swollman#include <time.h>
7719880Swollman#include <unistd.h>
7819880Swollman
7919880Swollman#include <netinet/in.h>
8019880Swollman#include <netinet/tcp.h>
8119880Swollman
8219880Swollman#include "fetch.h"
8319880Swollman#include "common.h"
8419880Swollman#include "httperr.h"
8519880Swollman
8619880Swollman/* Maximum number of redirects to follow */
8719880Swollman#define MAX_REDIRECT 5
8819880Swollman
8919880Swollman/* Symbolic names for reply codes we care about */
9019880Swollman#define HTTP_OK			200
9119880Swollman#define HTTP_PARTIAL		206
9219880Swollman#define HTTP_MOVED_PERM		301
9319880Swollman#define HTTP_MOVED_TEMP		302
9419880Swollman#define HTTP_SEE_OTHER		303
9519880Swollman#define HTTP_TEMP_REDIRECT	307
9619880Swollman#define HTTP_NEED_AUTH		401
9719880Swollman#define HTTP_NEED_PROXY_AUTH	407
9819880Swollman#define HTTP_BAD_RANGE		416
9919880Swollman#define HTTP_PROTOCOL_ERROR	999
10019880Swollman
10119880Swollman#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
10219880Swollman			    || (xyz) == HTTP_MOVED_TEMP \
10319880Swollman			    || (xyz) == HTTP_TEMP_REDIRECT \
10419880Swollman			    || (xyz) == HTTP_SEE_OTHER)
10519880Swollman
10619880Swollman#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
10719880Swollman
10819880Swollman
10919880Swollman/*****************************************************************************
11019880Swollman * I/O functions for decoding chunked streams
11119880Swollman */
11219880Swollman
11319880Swollmanstruct httpio
11419880Swollman{
11519880Swollman	conn_t		*conn;		/* connection */
11619880Swollman	int		 chunked;	/* chunked mode */
11719880Swollman	char		*buf;		/* chunk buffer */
11818316Swollman	size_t		 bufsize;	/* size of chunk buffer */
11918316Swollman	ssize_t		 buflen;	/* amount of data currently in buffer */
12018316Swollman	int		 bufpos;	/* current read offset in buffer */
12118316Swollman	int		 eof;		/* end-of-file flag */
12218316Swollman	int		 error;		/* error flag */
12318316Swollman	size_t		 chunksize;	/* remaining size of current chunk */
12418316Swollman#ifndef NDEBUG
12518316Swollman	size_t		 total;
12618316Swollman#endif
12718316Swollman};
12818316Swollman
12918316Swollman/*
13018316Swollman * Get next chunk header
13118316Swollman */
13218316Swollmanstatic int
13318316Swollmanhttp_new_chunk(struct httpio *io)
13418316Swollman{
13518316Swollman	char *p;
13618316Swollman
13718316Swollman	if (fetch_getln(io->conn) == -1)
13818316Swollman		return (-1);
13946303Smarkm
14018316Swollman	if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
14118316Swollman		return (-1);
14218316Swollman
14318316Swollman	for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
14418316Swollman		if (*p == ';')
14518316Swollman			break;
14618316Swollman		if (!isxdigit((unsigned char)*p))
14718316Swollman			return (-1);
14818316Swollman		if (isdigit((unsigned char)*p)) {
14918316Swollman			io->chunksize = io->chunksize * 16 +
15018316Swollman			    *p - '0';
15118316Swollman		} else {
15218316Swollman			io->chunksize = io->chunksize * 16 +
15318316Swollman			    10 + tolower((unsigned char)*p) - 'a';
15446303Smarkm		}
15518316Swollman	}
15618316Swollman
15718316Swollman#ifndef NDEBUG
15818316Swollman	if (fetchDebug) {
15918316Swollman		io->total += io->chunksize;
16018316Swollman		if (io->chunksize == 0)
16118316Swollman			fprintf(stderr, "%s(): end of last chunk\n", __func__);
16218316Swollman		else
16318316Swollman			fprintf(stderr, "%s(): new chunk: %lu (%lu)\n",
16418316Swollman			    __func__, (unsigned long)io->chunksize,
16520339Swollman			    (unsigned long)io->total);
16620339Swollman	}
16718316Swollman#endif
16818316Swollman
16918316Swollman	return (io->chunksize);
17020339Swollman}
17120339Swollman
17218316Swollman/*
17320339Swollman * Grow the input buffer to at least len bytes
17418316Swollman */
17518316Swollmanstatic inline int
17618316Swollmanhttp_growbuf(struct httpio *io, size_t len)
17718316Swollman{
17818316Swollman	char *tmp;
17946303Smarkm
18018316Swollman	if (io->bufsize >= len)
18118316Swollman		return (0);
18218316Swollman
18318316Swollman	if ((tmp = realloc(io->buf, len)) == NULL)
18418316Swollman		return (-1);
18518316Swollman	io->buf = tmp;
18618316Swollman	io->bufsize = len;
187126250Sbms	return (0);
18846303Smarkm}
18918316Swollman
19018316Swollman/*
19118316Swollman * Fill the input buffer, do chunk decoding on the fly
19218316Swollman */
19318316Swollmanstatic int
19446303Smarkmhttp_fillbuf(struct httpio *io, size_t len)
19546303Smarkm{
19618316Swollman	if (io->error)
19718316Swollman		return (-1);
19818316Swollman	if (io->eof)
19918316Swollman		return (0);
20018316Swollman
20118316Swollman	if (io->chunked == 0) {
20218316Swollman		if (http_growbuf(io, len) == -1)
20346303Smarkm			return (-1);
20420339Swollman		if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
20520339Swollman			io->error = 1;
20620339Swollman			return (-1);
20718316Swollman		}
20846303Smarkm		io->bufpos = 0;
20946303Smarkm		return (io->buflen);
21046303Smarkm	}
21146303Smarkm
21246303Smarkm	if (io->chunksize == 0) {
21346303Smarkm		switch (http_new_chunk(io)) {
21418316Swollman		case -1:
21518316Swollman			io->error = 1;
21620339Swollman			return (-1);
21718316Swollman		case 0:
21818316Swollman			io->eof = 1;
21918316Swollman			return (0);
22018316Swollman		}
22118316Swollman	}
22218316Swollman
22318316Swollman	if (len > io->chunksize)
22418316Swollman		len = io->chunksize;
22518316Swollman	if (http_growbuf(io, len) == -1)
22646303Smarkm		return (-1);
22718316Swollman	if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
22818316Swollman		io->error = 1;
22918316Swollman		return (-1);
23018316Swollman	}
23118316Swollman	io->chunksize -= io->buflen;
23246303Smarkm
23318316Swollman	if (io->chunksize == 0) {
23418316Swollman		char endl[2];
23518316Swollman
23618316Swollman		if (fetch_read(io->conn, endl, 2) != 2 ||
23718316Swollman		    endl[0] != '\r' || endl[1] != '\n')
23818316Swollman			return (-1);
23918316Swollman	}
24018316Swollman
241126250Sbms	io->bufpos = 0;
24246303Smarkm
24318316Swollman	return (io->buflen);
24446303Smarkm}
24518316Swollman
24618316Swollman/*
24718316Swollman * Read function
24818316Swollman */
24918316Swollmanstatic int
25020339Swollmanhttp_readfn(void *v, char *buf, int len)
25120339Swollman{
25218316Swollman	struct httpio *io = (struct httpio *)v;
25346303Smarkm	int l, pos;
25420339Swollman
25518316Swollman	if (io->error)
256190718Sphk		return (-1);
25720339Swollman	if (io->eof)
25820339Swollman		return (0);
25920339Swollman
26020339Swollman	for (pos = 0; len > 0; pos += l, len -= l) {
26120339Swollman		/* empty buffer */
262190718Sphk		if (!io->buf || io->bufpos == io->buflen)
26320339Swollman			if (http_fillbuf(io, len) < 1)
26420339Swollman				break;
26520339Swollman		l = io->buflen - io->bufpos;
26620339Swollman		if (len < l)
26720339Swollman			l = len;
26820339Swollman		memcpy(buf + pos, io->buf + io->bufpos, l);
26920339Swollman		io->bufpos += l;
27020339Swollman	}
27120339Swollman
27220339Swollman	if (!pos && io->error)
27320339Swollman		return (-1);
27420339Swollman	return (pos);
27520339Swollman}
27620339Swollman
27720339Swollman/*
27820339Swollman * Write function
27920339Swollman */
28020339Swollmanstatic int
28120339Swollmanhttp_writefn(void *v, const char *buf, int len)
28220339Swollman{
28320339Swollman	struct httpio *io = (struct httpio *)v;
28420339Swollman
28520339Swollman	return (fetch_write(io->conn, buf, len));
28620339Swollman}
28720339Swollman
28820339Swollman/*
28920339Swollman * Close function
29020339Swollman */
29120339Swollmanstatic int
29220339Swollmanhttp_closefn(void *v)
29320339Swollman{
29420339Swollman	struct httpio *io = (struct httpio *)v;
29520339Swollman	int r;
29620339Swollman
29746303Smarkm	r = fetch_close(io->conn);
29846303Smarkm	if (io->buf)
29920339Swollman		free(io->buf);
30020339Swollman	free(io);
30118316Swollman	return (r);
30218316Swollman}
30346303Smarkm
30418316Swollman/*
30518316Swollman * Wrap a file descriptor up
30620339Swollman */
30720339Swollmanstatic FILE *
30820339Swollmanhttp_funopen(conn_t *conn, int chunked)
30918316Swollman{
31020339Swollman	struct httpio *io;
31120339Swollman	FILE *f;
31220339Swollman
31320339Swollman	if ((io = calloc(1, sizeof(*io))) == NULL) {
31420339Swollman		fetch_syserr();
31520339Swollman		return (NULL);
31620339Swollman	}
31720339Swollman	io->conn = conn;
31820339Swollman	io->chunked = chunked;
31920339Swollman	f = funopen(io, http_readfn, http_writefn, NULL, http_closefn);
32018316Swollman	if (f == NULL) {
32118316Swollman		fetch_syserr();
32218316Swollman		free(io);
32318316Swollman		return (NULL);
32418316Swollman	}
32518316Swollman	return (f);
32618316Swollman}
32720339Swollman
32820339Swollman
32920339Swollman/*****************************************************************************
33020339Swollman * Helper functions for talking to the server and parsing its replies
33120339Swollman */
33220339Swollman
33320339Swollman/* Header types */
33420339Swollmantypedef enum {
33520339Swollman	hdr_syserror = -2,
33620339Swollman	hdr_error = -1,
33720339Swollman	hdr_end = 0,
33820339Swollman	hdr_unknown = 1,
33920339Swollman	hdr_content_length,
34020339Swollman	hdr_content_range,
34120339Swollman	hdr_last_modified,
34220339Swollman	hdr_location,
34320339Swollman	hdr_transfer_encoding,
34420339Swollman	hdr_www_authenticate
34546303Smarkm} hdr_t;
34618316Swollman
34718316Swollman/* Names of interesting headers */
34818316Swollmanstatic struct {
34918316Swollman	hdr_t		 num;
35018316Swollman	const char	*name;
35120339Swollman} hdr_names[] = {
35220339Swollman	{ hdr_content_length,		"Content-Length" },
35320339Swollman	{ hdr_content_range,		"Content-Range" },
35420339Swollman	{ hdr_last_modified,		"Last-Modified" },
35520339Swollman	{ hdr_location,			"Location" },
35620339Swollman	{ hdr_transfer_encoding,	"Transfer-Encoding" },
35720339Swollman	{ hdr_www_authenticate,		"WWW-Authenticate" },
35820339Swollman	{ hdr_unknown,			NULL },
35920339Swollman};
36020339Swollman
36118316Swollman/*
36218316Swollman * Send a formatted line; optionally echo to terminal
36318316Swollman */
36446303Smarkmstatic int
36518316Swollmanhttp_cmd(conn_t *conn, const char *fmt, ...)
36646303Smarkm{
36718316Swollman	va_list ap;
36820339Swollman	size_t len;
36920339Swollman	char *msg;
37020339Swollman	int r;
37118316Swollman
37220339Swollman	va_start(ap, fmt);
37320339Swollman	len = vasprintf(&msg, fmt, ap);
37420339Swollman	va_end(ap);
37520339Swollman
37620339Swollman	if (msg == NULL) {
37719880Swollman		errno = ENOMEM;
37820339Swollman		fetch_syserr();
37920339Swollman		return (-1);
38020339Swollman	}
38118316Swollman
38218316Swollman	r = fetch_putln(conn, msg, len);
38318316Swollman	free(msg);
38418316Swollman
38518316Swollman	if (r == -1) {
38646303Smarkm		fetch_syserr();
38718316Swollman		return (-1);
38818316Swollman	}
38946303Smarkm
39018316Swollman	return (0);
39118316Swollman}
39218316Swollman
39318316Swollman/*
39418316Swollman * Get and parse status line
39546303Smarkm */
39618316Swollmanstatic int
39718316Swollmanhttp_get_reply(conn_t *conn)
39846303Smarkm{
39918316Swollman	char *p;
40018316Swollman
40118316Swollman	if (fetch_getln(conn) == -1)
40220339Swollman		return (-1);
40318316Swollman	/*
40418316Swollman	 * A valid status line looks like "HTTP/m.n xyz reason" where m
40520339Swollman	 * and n are the major and minor protocol version numbers and xyz
40618316Swollman	 * is the reply code.
40720339Swollman	 * Unfortunately, there are servers out there (NCSA 1.5.1, to name
40820339Swollman	 * just one) that do not send a version number, so we can't rely
40918316Swollman	 * on finding one, but if we do, insist on it being 1.0 or 1.1.
41020339Swollman	 * We don't care about the reason phrase.
41120339Swollman	 */
41220339Swollman	if (strncmp(conn->buf, "HTTP", 4) != 0)
41320339Swollman		return (HTTP_PROTOCOL_ERROR);
41420339Swollman	p = conn->buf + 4;
41520339Swollman	if (*p == '/') {
41620339Swollman		if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
41720339Swollman			return (HTTP_PROTOCOL_ERROR);
41820339Swollman		p += 4;
41920339Swollman	}
42018316Swollman	if (*p != ' ' ||
42118316Swollman	    !isdigit((unsigned char)p[1]) ||
42220339Swollman	    !isdigit((unsigned char)p[2]) ||
42320339Swollman	    !isdigit((unsigned char)p[3]))
42418316Swollman		return (HTTP_PROTOCOL_ERROR);
42518316Swollman
42618316Swollman	conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
42718316Swollman	return (conn->err);
42818316Swollman}
42918316Swollman
43018316Swollman/*
43118316Swollman * Check a header; if the type matches the given string, return a pointer
43218316Swollman * to the beginning of the value.
43318316Swollman */
43418316Swollmanstatic const char *
43518316Swollmanhttp_match(const char *str, const char *hdr)
43618316Swollman{
43718316Swollman	while (*str && *hdr &&
43818316Swollman	    tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
43918316Swollman		/* nothing */;
44018316Swollman	if (*str || *hdr != ':')
44118316Swollman		return (NULL);
44218316Swollman	while (*hdr && isspace((unsigned char)*++hdr))
44318316Swollman		/* nothing */;
44418316Swollman	return (hdr);
44518316Swollman}
44618316Swollman
44718316Swollman/*
44818316Swollman * Get the next header and return the appropriate symbolic code.
44918316Swollman */
45018316Swollmanstatic hdr_t
45118316Swollmanhttp_next_header(conn_t *conn, const char **p)
45218316Swollman{
45318316Swollman	int i;
45418316Swollman
45518316Swollman	if (fetch_getln(conn) == -1)
45618316Swollman		return (hdr_syserror);
45718316Swollman	while (conn->buflen && isspace((unsigned char)conn->buf[conn->buflen - 1]))
45818316Swollman		conn->buflen--;
45918316Swollman	conn->buf[conn->buflen] = '\0';
46018316Swollman	if (conn->buflen == 0)
46118316Swollman		return (hdr_end);
46218316Swollman	/*
46318316Swollman	 * We could check for malformed headers but we don't really care.
46418316Swollman	 * A valid header starts with a token immediately followed by a
46518316Swollman	 * colon; a token is any sequence of non-control, non-whitespace
46618316Swollman	 * characters except "()<>@,;:\\\"{}".
46718316Swollman	 */
46818316Swollman	for (i = 0; hdr_names[i].num != hdr_unknown; i++)
46946303Smarkm		if ((*p = http_match(hdr_names[i].name, conn->buf)) != NULL)
47046303Smarkm			return (hdr_names[i].num);
47146303Smarkm	return (hdr_unknown);
47218316Swollman}
47318316Swollman
474190718Sphk/*
47518316Swollman * Parse a last-modified header
47618316Swollman */
47718316Swollmanstatic int
47818316Swollmanhttp_parse_mtime(const char *p, time_t *mtime)
47918316Swollman{
480190718Sphk	char locale[64], *r;
48119880Swollman	struct tm tm;
48218316Swollman
48319880Swollman	strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
48419880Swollman	setlocale(LC_TIME, "C");
48518316Swollman	r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
48618316Swollman	/* XXX should add support for date-2 and date-3 */
48718316Swollman	setlocale(LC_TIME, locale);
48818316Swollman	if (r == NULL)
48918316Swollman		return (-1);
49018316Swollman	DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d "
49118316Swollman		  "%02d:%02d:%02d]\n",
49218316Swollman		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
49318316Swollman		  tm.tm_hour, tm.tm_min, tm.tm_sec));
49419880Swollman	*mtime = timegm(&tm);
49518316Swollman	return (0);
49618316Swollman}
49719880Swollman
49820339Swollman/*
49918316Swollman * Parse a content-length header
50018316Swollman */
50118316Swollmanstatic int
50218316Swollmanhttp_parse_length(const char *p, off_t *length)
50318316Swollman{
50418316Swollman	off_t len;
50518316Swollman
50618316Swollman	for (len = 0; *p && isdigit((unsigned char)*p); ++p)
50718316Swollman		len = len * 10 + (*p - '0');
50818316Swollman	if (*p)
50918316Swollman		return (-1);
51018316Swollman	DEBUG(fprintf(stderr, "content length: [%lld]\n",
51118316Swollman	    (long long)len));
51218316Swollman	*length = len;
51318316Swollman	return (0);
51418316Swollman}
51518316Swollman
51618316Swollman/*
51718316Swollman * Parse a content-range header
51818316Swollman */
51918316Swollmanstatic int
52019880Swollmanhttp_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
52118316Swollman{
52218316Swollman	off_t first, last, len;
52318316Swollman
524190718Sphk	if (strncasecmp(p, "bytes ", 6) != 0)
52518316Swollman		return (-1);
52618316Swollman	p += 6;
52718316Swollman	if (*p == '*') {
52818316Swollman		first = last = -1;
52918316Swollman		++p;
53018316Swollman	} else {
53118316Swollman		for (first = 0; *p && isdigit((unsigned char)*p); ++p)
53218316Swollman			first = first * 10 + *p - '0';
53318316Swollman		if (*p != '-')
53418316Swollman			return (-1);
53518316Swollman		for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
53618316Swollman			last = last * 10 + *p - '0';
53718316Swollman	}
53846303Smarkm	if (first > last || *p != '/')
53918316Swollman		return (-1);
54018316Swollman	for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
54118316Swollman		len = len * 10 + *p - '0';
54246303Smarkm	if (*p || len < last - first + 1)
54318316Swollman		return (-1);
54418316Swollman	if (first == -1) {
54518316Swollman		DEBUG(fprintf(stderr, "content range: [*/%lld]\n",
54618316Swollman		    (long long)len));
54718316Swollman		*length = 0;
54818316Swollman	} else {
54918316Swollman		DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n",
55018316Swollman		    (long long)first, (long long)last, (long long)len));
55118316Swollman		*length = last - first + 1;
55218316Swollman	}
55318316Swollman	*offset = first;
55418316Swollman	*size = len;
55518316Swollman	return (0);
55618316Swollman}
55718316Swollman
55818316Swollman
55918316Swollman/*****************************************************************************
56018316Swollman * Helper functions for authorization
56118316Swollman */
56218316Swollman
56318316Swollman/*
56418316Swollman * Base64 encoding
56518316Swollman */
56618316Swollmanstatic char *
56718316Swollmanhttp_base64(const char *src)
56818316Swollman{
56918316Swollman	static const char base64[] =
57018316Swollman	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
57118316Swollman	    "abcdefghijklmnopqrstuvwxyz"
57218316Swollman	    "0123456789+/";
57318316Swollman	char *str, *dst;
57418316Swollman	size_t l;
57518316Swollman	int t, r;
57618316Swollman
57718316Swollman	l = strlen(src);
57846303Smarkm	if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
57946303Smarkm		return (NULL);
58046303Smarkm	dst = str;
58146303Smarkm	r = 0;
58218316Swollman
58318316Swollman	while (l >= 3) {
58418316Swollman		t = (src[0] << 16) | (src[1] << 8) | src[2];
58518316Swollman		dst[0] = base64[(t >> 18) & 0x3f];
58618316Swollman		dst[1] = base64[(t >> 12) & 0x3f];
58718316Swollman		dst[2] = base64[(t >> 6) & 0x3f];
58846303Smarkm		dst[3] = base64[(t >> 0) & 0x3f];
58918316Swollman		src += 3; l -= 3;
59018316Swollman		dst += 4; r += 4;
59118316Swollman	}
59218316Swollman
59320339Swollman	switch (l) {
59420339Swollman	case 2:
59520339Swollman		t = (src[0] << 16) | (src[1] << 8);
59620339Swollman		dst[0] = base64[(t >> 18) & 0x3f];
59720339Swollman		dst[1] = base64[(t >> 12) & 0x3f];
59820339Swollman		dst[2] = base64[(t >> 6) & 0x3f];
59920339Swollman		dst[3] = '=';
60020339Swollman		dst += 4;
60146303Smarkm		r += 4;
60246303Smarkm		break;
60346303Smarkm	case 1:
60420339Swollman		t = src[0] << 16;
60520339Swollman		dst[0] = base64[(t >> 18) & 0x3f];
60620339Swollman		dst[1] = base64[(t >> 12) & 0x3f];
60720339Swollman		dst[2] = dst[3] = '=';
60820339Swollman		dst += 4;
60920339Swollman		r += 4;
61020339Swollman		break;
61120339Swollman	case 0:
61220339Swollman		break;
61320339Swollman	}
61420339Swollman
61520339Swollman	*dst = 0;
61620339Swollman	return (str);
61746303Smarkm}
61846303Smarkm
61946303Smarkm/*
62046303Smarkm * Encode username and password
62146303Smarkm */
62246303Smarkmstatic int
62346303Smarkmhttp_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
62420339Swollman{
62520339Swollman	char *upw, *auth;
62620339Swollman	int r;
62718316Swollman
62846303Smarkm	DEBUG(fprintf(stderr, "usr: [%s]\n", usr));
62946303Smarkm	DEBUG(fprintf(stderr, "pwd: [%s]\n", pwd));
63018316Swollman	if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
63118316Swollman		return (-1);
63218316Swollman	auth = http_base64(upw);
63318316Swollman	free(upw);
63418316Swollman	if (auth == NULL)
63519880Swollman		return (-1);
63618316Swollman	r = http_cmd(conn, "%s: Basic %s", hdr, auth);
63718316Swollman	free(auth);
63819880Swollman	return (r);
63919880Swollman}
64019880Swollman
64118316Swollman/*
64218316Swollman * Send an authorization header
64318316Swollman */
644126250Sbmsstatic int
645126250Sbmshttp_authorize(conn_t *conn, const char *hdr, const char *p)
646126250Sbms{
647126250Sbms	/* basic authorization */
648126250Sbms	if (strncasecmp(p, "basic:", 6) == 0) {
649126250Sbms		char *user, *pwd, *str;
65019880Swollman		int r;
65119880Swollman
652126250Sbms		/* skip realm */
65318316Swollman		for (p += 6; *p && *p != ':'; ++p)
65418316Swollman			/* nothing */ ;
65518316Swollman		if (!*p || strchr(++p, ':') == NULL)
65618316Swollman			return (-1);
65718316Swollman		if ((str = strdup(p)) == NULL)
65818316Swollman			return (-1); /* XXX */
65918316Swollman		user = str;
66018316Swollman		pwd = strchr(str, ':');
66118316Swollman		*pwd++ = '\0';
66246303Smarkm		r = http_basic_auth(conn, hdr, user, pwd);
66318316Swollman		free(str);
66418316Swollman		return (r);
66518316Swollman	}
66620339Swollman	return (-1);
66746303Smarkm}
66846303Smarkm
66946303Smarkm
67046303Smarkm/*****************************************************************************
67146303Smarkm * Helper functions for connecting to a server or proxy
67218316Swollman */
67318316Swollman
67418316Swollman/*
67546303Smarkm * Connect to the correct HTTP server or proxy.
67646303Smarkm */
67746303Smarkmstatic conn_t *
67846303Smarkmhttp_connect(struct url *URL, struct url *purl, const char *flags)
67946303Smarkm{
68046303Smarkm	conn_t *conn;
68146303Smarkm	int verbose;
68246303Smarkm	int af, val;
68346303Smarkm
68418316Swollman#ifdef INET6
68546303Smarkm	af = AF_UNSPEC;
68646303Smarkm#else
68720339Swollman	af = AF_INET;
68846303Smarkm#endif
68946303Smarkm
69020339Swollman	verbose = CHECK_FLAG('v');
69120339Swollman	if (CHECK_FLAG('4'))
69218316Swollman		af = AF_INET;
69320339Swollman#ifdef INET6
69446303Smarkm	else if (CHECK_FLAG('6'))
69546303Smarkm		af = AF_INET6;
69646303Smarkm#endif
69746303Smarkm
69846303Smarkm	if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
69920339Swollman		URL = purl;
70046303Smarkm	} else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
70146303Smarkm		/* can't talk http to an ftp server */
70246303Smarkm		/* XXX should set an error code */
70346303Smarkm		return (NULL);
70418316Swollman	}
70518316Swollman
70618316Swollman	if ((conn = fetch_connect(URL->host, URL->port, af, verbose)) == NULL)
70746303Smarkm		/* fetch_connect() has already set an error code */
70846303Smarkm		return (NULL);
70946303Smarkm	if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
71046303Smarkm	    fetch_ssl(conn, verbose) == -1) {
71146303Smarkm		fetch_close(conn);
71218316Swollman		/* grrr */
71320339Swollman		errno = EAUTH;
71418316Swollman		fetch_syserr();
71518316Swollman		return (NULL);
71618316Swollman	}
71746303Smarkm
71818316Swollman	val = 1;
71918316Swollman	setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
72046303Smarkm
72118316Swollman	return (conn);
72218316Swollman}
72318316Swollman
72446303Smarkmstatic struct url *
72518316Swollmanhttp_get_proxy(struct url * url, const char *flags)
72618316Swollman{
72718316Swollman	struct url *purl;
72818316Swollman	char *p;
72918316Swollman
730126250Sbms	if (flags != NULL && strchr(flags, 'd') != NULL)
73146303Smarkm		return (NULL);
73218316Swollman	if (fetch_no_proxy_match(url->host))
73318316Swollman		return (NULL);
73418316Swollman	if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
73518316Swollman	    *p && (purl = fetchParseURL(p))) {
73618316Swollman		if (!*purl->scheme)
73718316Swollman			strcpy(purl->scheme, SCHEME_HTTP);
73846303Smarkm		if (!purl->port)
73918316Swollman			purl->port = fetch_default_proxy_port(purl->scheme);
74018316Swollman		if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
74118316Swollman			return (purl);
74218316Swollman		fetchFreeURL(purl);
74318316Swollman	}
74418316Swollman	return (NULL);
74518316Swollman}
74618316Swollman
74718316Swollmanstatic void
748126250Sbmshttp_print_html(FILE *out, FILE *in)
74919880Swollman{
75018316Swollman	size_t len;
75118316Swollman	char *line, *p, *q;
75218316Swollman	int comment, tag;
75318316Swollman
75418316Swollman	comment = tag = 0;
75518316Swollman	while ((line = fgetln(in, &len)) != NULL) {
75646303Smarkm		while (len && isspace((unsigned char)line[len - 1]))
75718316Swollman			--len;
75818316Swollman		for (p = q = line; q < line + len; ++q) {
75918316Swollman			if (comment && *q == '-') {
76018316Swollman				if (q + 2 < line + len &&
76118316Swollman				    strcmp(q, "-->") == 0) {
76218316Swollman					tag = comment = 0;
76318316Swollman					q += 2;
76418316Swollman				}
76518316Swollman			} else if (tag && !comment && *q == '>') {
766126250Sbms				p = q + 1;
76719880Swollman				tag = 0;
76818316Swollman			} else if (!tag && *q == '<') {
76918316Swollman				if (q > p)
77018316Swollman					fwrite(p, q - p, 1, out);
77118316Swollman				tag = 1;
77218316Swollman				if (q + 3 < line + len &&
77318316Swollman				    strcmp(q, "<!--") == 0) {
77446303Smarkm					comment = 1;
77546303Smarkm					q += 3;
77618316Swollman				}
77718316Swollman			}
77818316Swollman		}
77918316Swollman		if (!tag && q > p)
78046303Smarkm			fwrite(p, q - p, 1, out);
78146303Smarkm		fputc('\n', out);
78246303Smarkm	}
78318316Swollman}
78446303Smarkm
78546303Smarkm
78618316Swollman/*****************************************************************************
78718316Swollman * Core
78818316Swollman */
78920339Swollman
79018316Swollman/*
79146303Smarkm * Send a request and process the reply
79220339Swollman *
79320339Swollman * XXX This function is way too long, the do..while loop should be split
79418316Swollman * XXX off into a separate function.
79518316Swollman */
79620339SwollmanFILE *
79746303Smarkmhttp_request(struct url *URL, const char *op, struct url_stat *us,
79846303Smarkm    struct url *purl, const char *flags)
79946303Smarkm{
80046303Smarkm	conn_t *conn;
80146303Smarkm	struct url *url, *new;
80246303Smarkm	int chunked, direct, need_auth, noredirect, verbose;
80320339Swollman	int e, i, n, val;
80446303Smarkm	off_t offset, clength, length, size;
80546303Smarkm	time_t mtime;
80646303Smarkm	const char *p;
80718316Swollman	FILE *f;
80818316Swollman	hdr_t h;
80920339Swollman	char hbuf[MAXHOSTNAMELEN + 7], *host;
81018316Swollman
81118316Swollman	direct = CHECK_FLAG('d');
81218316Swollman	noredirect = CHECK_FLAG('A');
81318316Swollman	verbose = CHECK_FLAG('v');
81446303Smarkm
81518316Swollman	if (direct && purl) {
81618316Swollman		fetchFreeURL(purl);
81718316Swollman		purl = NULL;
81818316Swollman	}
81918316Swollman
82020339Swollman	/* try the provided URL first */
82118316Swollman	url = URL;
82246303Smarkm
82320339Swollman	/* if the A flag is set, we only get one try */
82420339Swollman	n = noredirect ? 1 : MAX_REDIRECT;
82520339Swollman	i = 0;
82618316Swollman
82718316Swollman	e = HTTP_PROTOCOL_ERROR;
82818316Swollman	need_auth = 0;
82918316Swollman	do {
83018316Swollman		new = NULL;
83118316Swollman		chunked = 0;
83246303Smarkm		offset = 0;
83318316Swollman		clength = -1;
83418316Swollman		length = -1;
83518316Swollman		size = -1;
83646303Smarkm		mtime = 0;
83718316Swollman
83846303Smarkm		/* check port */
83946303Smarkm		if (!url->port)
84046303Smarkm			url->port = fetch_default_port(url->scheme);
84118316Swollman
84220339Swollman		/* were we redirected to an FTP URL? */
84320339Swollman		if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
84420339Swollman			if (strcmp(op, "GET") == 0)
84546303Smarkm				return (ftp_request(url, "RETR", us, purl, flags));
84618316Swollman			else if (strcmp(op, "HEAD") == 0)
84718316Swollman				return (ftp_request(url, "STAT", us, purl, flags));
84818316Swollman		}
84920339Swollman
85020339Swollman		/* connect to server or proxy */
85120339Swollman		if ((conn = http_connect(url, purl, flags)) == NULL)
85220339Swollman			goto ouch;
85318316Swollman
85418316Swollman		host = url->host;
85518316Swollman#ifdef INET6
85618316Swollman		if (strchr(url->host, ':')) {
85718316Swollman			snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
85818316Swollman			host = hbuf;
85918316Swollman		}
86018316Swollman#endif
86118316Swollman		if (url->port != fetch_default_port(url->scheme)) {
86218316Swollman			if (host != hbuf) {
86318316Swollman				strcpy(hbuf, host);
86419880Swollman				host = hbuf;
86519880Swollman			}
86618316Swollman			snprintf(hbuf + strlen(hbuf),
86718316Swollman			    sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
86818316Swollman		}
86918316Swollman
87020339Swollman		/* send request */
871190711Sphk		if (verbose)
87219880Swollman			fetch_info("requesting %s://%s%s",
87318316Swollman			    url->scheme, host, url->doc);
87418316Swollman		if (purl) {
87518316Swollman			http_cmd(conn, "%s %s://%s%s HTTP/1.1",
87618316Swollman			    op, url->scheme, host, url->doc);
87718316Swollman		} else {
87846303Smarkm			http_cmd(conn, "%s %s HTTP/1.1",
87918316Swollman			    op, url->doc);
88018316Swollman		}
88118316Swollman
88218316Swollman		/* virtual host */
88318316Swollman		http_cmd(conn, "Host: %s", host);
88418316Swollman
88546303Smarkm		/* proxy authorization */
88619880Swollman		if (purl) {
88718316Swollman			if (*purl->user || *purl->pwd)
88818316Swollman				http_basic_auth(conn, "Proxy-Authorization",
88918316Swollman				    purl->user, purl->pwd);
89018316Swollman			else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
89118316Swollman				http_authorize(conn, "Proxy-Authorization", p);
89218316Swollman		}
89318316Swollman
89418316Swollman		/* server authorization */
89518316Swollman		if (need_auth || *url->user || *url->pwd) {
89618316Swollman			if (*url->user || *url->pwd)
89718316Swollman				http_basic_auth(conn, "Authorization", url->user, url->pwd);
89818316Swollman			else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
89918316Swollman				http_authorize(conn, "Authorization", p);
90018316Swollman			else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
90118316Swollman				http_basic_auth(conn, "Authorization", url->user, url->pwd);
90218316Swollman			} else {
90318316Swollman				http_seterr(HTTP_NEED_AUTH);
90418316Swollman				goto ouch;
90518316Swollman			}
90618316Swollman		}
90718316Swollman
90818316Swollman		/* other headers */
90918316Swollman		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
91019880Swollman			if (strcasecmp(p, "auto") == 0)
91118316Swollman				http_cmd(conn, "Referer: %s://%s%s",
91218316Swollman				    url->scheme, host, url->doc);
91318316Swollman			else
91418316Swollman				http_cmd(conn, "Referer: %s", p);
91518316Swollman		}
91618316Swollman		if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
91719880Swollman			http_cmd(conn, "User-Agent: %s", p);
91819880Swollman		else
91918316Swollman			http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname());
92019880Swollman		if (url->offset > 0)
92119880Swollman			http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset);
92219880Swollman		http_cmd(conn, "Connection: close");
92319880Swollman		http_cmd(conn, "");
92418316Swollman
92518316Swollman		/*
92618316Swollman		 * Force the queued request to be dispatched.  Normally, one
92718316Swollman		 * would do this with shutdown(2) but squid proxies can be
92818316Swollman		 * configured to disallow such half-closed connections.  To
92918316Swollman		 * be compatible with such configurations, fiddle with socket
93018316Swollman		 * options to force the pending data to be written.
93118316Swollman		 */
93219880Swollman		val = 0;
93318316Swollman		setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
93418316Swollman			   sizeof(val));
93519880Swollman		val = 1;
93618316Swollman		setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
93718316Swollman			   sizeof(val));
93818316Swollman
93918316Swollman		/* get reply */
94018316Swollman		switch (http_get_reply(conn)) {
94118316Swollman		case HTTP_OK:
94219880Swollman		case HTTP_PARTIAL:
94319880Swollman			/* fine */
94419880Swollman			break;
94519880Swollman		case HTTP_MOVED_PERM:
94619880Swollman		case HTTP_MOVED_TEMP:
94719880Swollman		case HTTP_SEE_OTHER:
94819880Swollman			/*
94919880Swollman			 * Not so fine, but we still have to read the
95019880Swollman			 * headers to get the new location.
95119880Swollman			 */
95219880Swollman			break;
95319880Swollman		case HTTP_NEED_AUTH:
95419880Swollman			if (need_auth) {
95546303Smarkm				/*
95646303Smarkm				 * We already sent out authorization code,
95746303Smarkm				 * so there's nothing more we can do.
95846303Smarkm				 */
95919880Swollman				http_seterr(conn->err);
96046303Smarkm				goto ouch;
96146303Smarkm			}
96246303Smarkm			/* try again, but send the password this time */
96346303Smarkm			if (verbose)
96446303Smarkm				fetch_info("server requires authorization");
96546303Smarkm			break;
96619880Swollman		case HTTP_NEED_PROXY_AUTH:
96719880Swollman			/*
96818316Swollman			 * If we're talking to a proxy, we already sent
96946303Smarkm			 * our proxy authorization code, so there's
97019880Swollman			 * nothing more we can do.
97118316Swollman			 */
97246303Smarkm			http_seterr(conn->err);
97318316Swollman			goto ouch;
97418316Swollman		case HTTP_BAD_RANGE:
97519880Swollman			/*
97618316Swollman			 * This can happen if we ask for 0 bytes because
97718316Swollman			 * we already have the whole file.  Consider this
97818316Swollman			 * a success for now, and check sizes later.
97918316Swollman			 */
98019880Swollman			break;
98118316Swollman		case HTTP_PROTOCOL_ERROR:
98218316Swollman			/* fall through */
98319880Swollman		case -1:
98418316Swollman			fetch_syserr();
98518316Swollman			goto ouch;
98618316Swollman		default:
98718316Swollman			http_seterr(conn->err);
98818316Swollman			if (!verbose)
98918316Swollman				goto ouch;
99018316Swollman			/* fall through so we can get the full error message */
99118316Swollman		}
99218316Swollman
99318316Swollman		/* get headers */
99418316Swollman		do {
99518316Swollman			switch ((h = http_next_header(conn, &p))) {
99618316Swollman			case hdr_syserror:
99718316Swollman				fetch_syserr();
99818316Swollman				goto ouch;
99918316Swollman			case hdr_error:
100018316Swollman				http_seterr(HTTP_PROTOCOL_ERROR);
100118316Swollman				goto ouch;
100218316Swollman			case hdr_content_length:
100318316Swollman				http_parse_length(p, &clength);
100418316Swollman				break;
100518316Swollman			case hdr_content_range:
100618316Swollman				http_parse_range(p, &offset, &length, &size);
100718316Swollman				break;
100818316Swollman			case hdr_last_modified:
100918316Swollman				http_parse_mtime(p, &mtime);
101018316Swollman				break;
101118316Swollman			case hdr_location:
101218316Swollman				if (!HTTP_REDIRECT(conn->err))
101318316Swollman					break;
101420339Swollman				if (new)
101520339Swollman					free(new);
101618316Swollman				if (verbose)
101718316Swollman					fetch_info("%d redirect to %s", conn->err, p);
101818316Swollman				if (*p == '/')
101918316Swollman					/* absolute path */
102018316Swollman					new = fetchMakeURL(url->scheme, url->host, url->port, p,
102118316Swollman					    url->user, url->pwd);
1022				else
1023					new = fetchParseURL(p);
1024				if (new == NULL) {
1025					/* XXX should set an error code */
1026					DEBUG(fprintf(stderr, "failed to parse new URL\n"));
1027					goto ouch;
1028				}
1029				if (!*new->user && !*new->pwd) {
1030					strcpy(new->user, url->user);
1031					strcpy(new->pwd, url->pwd);
1032				}
1033				new->offset = url->offset;
1034				new->length = url->length;
1035				break;
1036			case hdr_transfer_encoding:
1037				/* XXX weak test*/
1038				chunked = (strcasecmp(p, "chunked") == 0);
1039				break;
1040			case hdr_www_authenticate:
1041				if (conn->err != HTTP_NEED_AUTH)
1042					break;
1043				/* if we were smarter, we'd check the method and realm */
1044				break;
1045			case hdr_end:
1046				/* fall through */
1047			case hdr_unknown:
1048				/* ignore */
1049				break;
1050			}
1051		} while (h > hdr_end);
1052
1053		/* we need to provide authentication */
1054		if (conn->err == HTTP_NEED_AUTH) {
1055			e = conn->err;
1056			need_auth = 1;
1057			fetch_close(conn);
1058			conn = NULL;
1059			continue;
1060		}
1061
1062		/* requested range not satisfiable */
1063		if (conn->err == HTTP_BAD_RANGE) {
1064			if (url->offset == size && url->length == 0) {
1065				/* asked for 0 bytes; fake it */
1066				offset = url->offset;
1067				clength = -1;
1068				conn->err = HTTP_OK;
1069				break;
1070			} else {
1071				http_seterr(conn->err);
1072				goto ouch;
1073			}
1074		}
1075
1076		/* we have a hit or an error */
1077		if (conn->err == HTTP_OK || conn->err == HTTP_PARTIAL || HTTP_ERROR(conn->err))
1078			break;
1079
1080		/* all other cases: we got a redirect */
1081		e = conn->err;
1082		need_auth = 0;
1083		fetch_close(conn);
1084		conn = NULL;
1085		if (!new) {
1086			DEBUG(fprintf(stderr, "redirect with no new location\n"));
1087			break;
1088		}
1089		if (url != URL)
1090			fetchFreeURL(url);
1091		url = new;
1092	} while (++i < n);
1093
1094	/* we failed, or ran out of retries */
1095	if (conn == NULL) {
1096		http_seterr(e);
1097		goto ouch;
1098	}
1099
1100	DEBUG(fprintf(stderr, "offset %lld, length %lld,"
1101		  " size %lld, clength %lld\n",
1102		  (long long)offset, (long long)length,
1103		  (long long)size, (long long)clength));
1104
1105	/* check for inconsistencies */
1106	if (clength != -1 && length != -1 && clength != length) {
1107		http_seterr(HTTP_PROTOCOL_ERROR);
1108		goto ouch;
1109	}
1110	if (clength == -1)
1111		clength = length;
1112	if (clength != -1)
1113		length = offset + clength;
1114	if (length != -1 && size != -1 && length != size) {
1115		http_seterr(HTTP_PROTOCOL_ERROR);
1116		goto ouch;
1117	}
1118	if (size == -1)
1119		size = length;
1120
1121	/* fill in stats */
1122	if (us) {
1123		us->size = size;
1124		us->atime = us->mtime = mtime;
1125	}
1126
1127	/* too far? */
1128	if (URL->offset > 0 && offset > URL->offset) {
1129		http_seterr(HTTP_PROTOCOL_ERROR);
1130		goto ouch;
1131	}
1132
1133	/* report back real offset and size */
1134	URL->offset = offset;
1135	URL->length = clength;
1136
1137	/* wrap it up in a FILE */
1138	if ((f = http_funopen(conn, chunked)) == NULL) {
1139		fetch_syserr();
1140		goto ouch;
1141	}
1142
1143	if (url != URL)
1144		fetchFreeURL(url);
1145	if (purl)
1146		fetchFreeURL(purl);
1147
1148	if (HTTP_ERROR(conn->err)) {
1149		http_print_html(stderr, f);
1150		fclose(f);
1151		f = NULL;
1152	}
1153
1154	return (f);
1155
1156ouch:
1157	if (url != URL)
1158		fetchFreeURL(url);
1159	if (purl)
1160		fetchFreeURL(purl);
1161	if (conn != NULL)
1162		fetch_close(conn);
1163	return (NULL);
1164}
1165
1166
1167/*****************************************************************************
1168 * Entry points
1169 */
1170
1171/*
1172 * Retrieve and stat a file by HTTP
1173 */
1174FILE *
1175fetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
1176{
1177	return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
1178}
1179
1180/*
1181 * Retrieve a file by HTTP
1182 */
1183FILE *
1184fetchGetHTTP(struct url *URL, const char *flags)
1185{
1186	return (fetchXGetHTTP(URL, NULL, flags));
1187}
1188
1189/*
1190 * Store a file by HTTP
1191 */
1192FILE *
1193fetchPutHTTP(struct url *URL __unused, const char *flags __unused)
1194{
1195	warnx("fetchPutHTTP(): not implemented");
1196	return (NULL);
1197}
1198
1199/*
1200 * Get an HTTP document's metadata
1201 */
1202int
1203fetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
1204{
1205	FILE *f;
1206
1207	f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
1208	if (f == NULL)
1209		return (-1);
1210	fclose(f);
1211	return (0);
1212}
1213
1214/*
1215 * List a directory
1216 */
1217struct url_ent *
1218fetchListHTTP(struct url *url __unused, const char *flags __unused)
1219{
1220	warnx("fetchListHTTP(): not implemented");
1221	return (NULL);
1222}
1223