http.c revision 221821
133965Sjdp/*-
278828Sobrien * Copyright (c) 2000-2004 Dag-Erling Co�dan Sm�rgrav
3218822Sdim * All rights reserved.
438889Sjdp *
533965Sjdp * Redistribution and use in source and binary forms, with or without
633965Sjdp * modification, are permitted provided that the following conditions
733965Sjdp * are met:
833965Sjdp * 1. Redistributions of source code must retain the above copyright
933965Sjdp *    notice, this list of conditions and the following disclaimer
1033965Sjdp *    in this position and unchanged.
1133965Sjdp * 2. Redistributions in binary form must reproduce the above copyright
1233965Sjdp *    notice, this list of conditions and the following disclaimer in the
1333965Sjdp *    documentation and/or other materials provided with the distribution.
1433965Sjdp * 3. The name of the author may not be used to endorse or promote products
1533965Sjdp *    derived from this software without specific prior written permission.
1633965Sjdp *
1733965Sjdp * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
1833965Sjdp * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
1933965Sjdp * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20218822Sdim * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21218822Sdim * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
2233965Sjdp * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23218822Sdim * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
2433965Sjdp * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
2533965Sjdp * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
2633965Sjdp * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
2733965Sjdp */
28218822Sdim
2933965Sjdp#include <sys/cdefs.h>
3061843Sobrien__FBSDID("$FreeBSD: head/lib/libfetch/http.c 221821 2011-05-12 21:18:55Z des $");
31130561Sobrien
32130561Sobrien/*
3333965Sjdp * The following copyright applies to the base64 code:
34218822Sdim *
3533965Sjdp *-
3638889Sjdp * Copyright 1997 Massachusetts Institute of Technology
3738889Sjdp *
3838889Sjdp * Permission to use, copy, modify, and distribute this software and
39104834Sobrien * its documentation for any purpose and without fee is hereby
4038889Sjdp * granted, provided that both the above copyright notice and this
4138889Sjdp * permission notice appear in all copies, that both the above
4238889Sjdp * copyright notice and this permission notice appear in all
4338889Sjdp * supporting documentation, and that the name of M.I.T. not be used
4438889Sjdp * in advertising or publicity pertaining to distribution of the
4538889Sjdp * software without specific, written prior permission.  M.I.T. makes
4638889Sjdp * no representations about the suitability of this software for any
4760484Sobrien * purpose.  It is provided "as is" without express or implied
4860484Sobrien * warranty.
4960484Sobrien *
5060484Sobrien * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
5160484Sobrien * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
5260484Sobrien * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
5360484Sobrien * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
5460484Sobrien * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
5589857Sobrien * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
5689857Sobrien * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
5789857Sobrien * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
5889857Sobrien * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
5989857Sobrien * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
6089857Sobrien * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
6189857Sobrien * SUCH DAMAGE.
6289857Sobrien */
6389857Sobrien
6489857Sobrien#include <sys/param.h>
65130561Sobrien#include <sys/socket.h>
6689857Sobrien#include <sys/time.h>
6760484Sobrien
6833965Sjdp#include <ctype.h>
69130561Sobrien#include <err.h>
70130561Sobrien#include <errno.h>
7133965Sjdp#include <locale.h>
7233965Sjdp#include <netdb.h>
7333965Sjdp#include <stdarg.h>
7433965Sjdp#include <stdio.h>
7533965Sjdp#include <stdlib.h>
76130561Sobrien#include <string.h>
77130561Sobrien#include <time.h>
7833965Sjdp#include <unistd.h>
7933965Sjdp#include <md5.h>
8033965Sjdp
8133965Sjdp#include <netinet/in.h>
8260484Sobrien#include <netinet/tcp.h>
83130561Sobrien
84130561Sobrien#include "fetch.h"
85130561Sobrien#include "common.h"
86130561Sobrien#include "httperr.h"
87130561Sobrien
8833965Sjdp/* Maximum number of redirects to follow */
8933965Sjdp#define MAX_REDIRECT 5
90104834Sobrien
9133965Sjdp/* Symbolic names for reply codes we care about */
9233965Sjdp#define HTTP_OK			200
9333965Sjdp#define HTTP_PARTIAL		206
9433965Sjdp#define HTTP_MOVED_PERM		301
9560484Sobrien#define HTTP_MOVED_TEMP		302
96130561Sobrien#define HTTP_SEE_OTHER		303
97130561Sobrien#define HTTP_NOT_MODIFIED	304
9833965Sjdp#define HTTP_TEMP_REDIRECT	307
9933965Sjdp#define HTTP_NEED_AUTH		401
10060484Sobrien#define HTTP_NEED_PROXY_AUTH	407
10133965Sjdp#define HTTP_BAD_RANGE		416
10233965Sjdp#define HTTP_PROTOCOL_ERROR	999
10360484Sobrien
10460484Sobrien#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
10560484Sobrien			    || (xyz) == HTTP_MOVED_TEMP \
10660484Sobrien			    || (xyz) == HTTP_TEMP_REDIRECT \
10760484Sobrien			    || (xyz) == HTTP_SEE_OTHER)
10860484Sobrien
10960484Sobrien#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
11060484Sobrien
11133965Sjdp
11233965Sjdp/*****************************************************************************
11333965Sjdp * I/O functions for decoding chunked streams
114130561Sobrien */
115130561Sobrien
116130561Sobrienstruct httpio
117130561Sobrien{
118130561Sobrien	conn_t		*conn;		/* connection */
119130561Sobrien	int		 chunked;	/* chunked mode */
120130561Sobrien	char		*buf;		/* chunk buffer */
121130561Sobrien	size_t		 bufsize;	/* size of chunk buffer */
122130561Sobrien	ssize_t		 buflen;	/* amount of data currently in buffer */
123130561Sobrien	int		 bufpos;	/* current read offset in buffer */
124130561Sobrien	int		 eof;		/* end-of-file flag */
12533965Sjdp	int		 error;		/* error flag */
12633965Sjdp	size_t		 chunksize;	/* remaining size of current chunk */
12760484Sobrien#ifndef NDEBUG
12889857Sobrien	size_t		 total;
129130561Sobrien#endif
130130561Sobrien};
13189857Sobrien
132130561Sobrien/*
133130561Sobrien * Get next chunk header
13433965Sjdp */
13560484Sobrienstatic int
13660484Sobrienhttp_new_chunk(struct httpio *io)
137130561Sobrien{
13833965Sjdp	char *p;
13933965Sjdp
14060484Sobrien	if (fetch_getln(io->conn) == -1)
14160484Sobrien		return (-1);
14233965Sjdp
14333965Sjdp	if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
144130561Sobrien		return (-1);
14533965Sjdp
14633965Sjdp	for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
14733965Sjdp		if (*p == ';')
148130561Sobrien			break;
14933965Sjdp		if (!isxdigit((unsigned char)*p))
15033965Sjdp			return (-1);
151218822Sdim		if (isdigit((unsigned char)*p)) {
152218822Sdim			io->chunksize = io->chunksize * 16 +
15389857Sobrien			    *p - '0';
154130561Sobrien		} else {
155130561Sobrien			io->chunksize = io->chunksize * 16 +
156130561Sobrien			    10 + tolower((unsigned char)*p) - 'a';
157130561Sobrien		}
15833965Sjdp	}
15933965Sjdp
16033965Sjdp#ifndef NDEBUG
16133965Sjdp	if (fetchDebug) {
16233965Sjdp		io->total += io->chunksize;
16333965Sjdp		if (io->chunksize == 0)
16433965Sjdp			fprintf(stderr, "%s(): end of last chunk\n", __func__);
16533965Sjdp		else
16633965Sjdp			fprintf(stderr, "%s(): new chunk: %lu (%lu)\n",
16733965Sjdp			    __func__, (unsigned long)io->chunksize,
16833965Sjdp			    (unsigned long)io->total);
16933965Sjdp	}
17033965Sjdp#endif
17133965Sjdp
17233965Sjdp	return (io->chunksize);
17333965Sjdp}
17433965Sjdp
17589857Sobrien/*
17633965Sjdp * Grow the input buffer to at least len bytes
17733965Sjdp */
178130561Sobrienstatic inline int
179130561Sobrienhttp_growbuf(struct httpio *io, size_t len)
180130561Sobrien{
181130561Sobrien	char *tmp;
18233965Sjdp
183130561Sobrien	if (io->bufsize >= len)
18433965Sjdp		return (0);
18533965Sjdp
186130561Sobrien	if ((tmp = realloc(io->buf, len)) == NULL)
18733965Sjdp		return (-1);
18833965Sjdp	io->buf = tmp;
189130561Sobrien	io->bufsize = len;
19033965Sjdp	return (0);
191130561Sobrien}
192130561Sobrien
193130561Sobrien/*
194218822Sdim * Fill the input buffer, do chunk decoding on the fly
195218822Sdim */
196218822Sdimstatic int
19778828Sobrienhttp_fillbuf(struct httpio *io, size_t len)
19878828Sobrien{
19938889Sjdp	if (io->error)
200218822Sdim		return (-1);
20138889Sjdp	if (io->eof)
20238889Sjdp		return (0);
203218822Sdim
20478828Sobrien	if (io->chunked == 0) {
20538889Sjdp		if (http_growbuf(io, len) == -1)
20660484Sobrien			return (-1);
20738889Sjdp		if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
208130561Sobrien			io->error = 1;
209130561Sobrien			return (-1);
21038889Sjdp		}
211218822Sdim		io->bufpos = 0;
212218822Sdim		return (io->buflen);
213218822Sdim	}
214130561Sobrien
215130561Sobrien	if (io->chunksize == 0) {
216130561Sobrien		switch (http_new_chunk(io)) {
217130561Sobrien		case -1:
218130561Sobrien			io->error = 1;
219218822Sdim			return (-1);
220218822Sdim		case 0:
221218822Sdim			io->eof = 1;
222218822Sdim			return (0);
223218822Sdim		}
224218822Sdim	}
225218822Sdim
226218822Sdim	if (len > io->chunksize)
22733965Sjdp		len = io->chunksize;
228130561Sobrien	if (http_growbuf(io, len) == -1)
229130561Sobrien		return (-1);
230130561Sobrien	if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
231130561Sobrien		io->error = 1;
232130561Sobrien		return (-1);
233130561Sobrien	}
234130561Sobrien	io->chunksize -= io->buflen;
235130561Sobrien
236130561Sobrien	if (io->chunksize == 0) {
237130561Sobrien		char endl[2];
238130561Sobrien
239130561Sobrien		if (fetch_read(io->conn, endl, 2) != 2 ||
240130561Sobrien		    endl[0] != '\r' || endl[1] != '\n')
241130561Sobrien			return (-1);
242130561Sobrien	}
243130561Sobrien
244130561Sobrien	io->bufpos = 0;
245130561Sobrien
246130561Sobrien	return (io->buflen);
247130561Sobrien}
248130561Sobrien
249130561Sobrien/*
250130561Sobrien * Read function
251130561Sobrien */
252218822Sdimstatic int
253218822Sdimhttp_readfn(void *v, char *buf, int len)
254130561Sobrien{
255218822Sdim	struct httpio *io = (struct httpio *)v;
256130561Sobrien	int l, pos;
257218822Sdim
258218822Sdim	if (io->error)
259130561Sobrien		return (-1);
260130561Sobrien	if (io->eof)
261130561Sobrien		return (0);
262130561Sobrien
263130561Sobrien	for (pos = 0; len > 0; pos += l, len -= l) {
264130561Sobrien		/* empty buffer */
265130561Sobrien		if (!io->buf || io->bufpos == io->buflen)
266130561Sobrien			if (http_fillbuf(io, len) < 1)
267130561Sobrien				break;
268130561Sobrien		l = io->buflen - io->bufpos;
269218822Sdim		if (len < l)
270130561Sobrien			l = len;
271130561Sobrien		memcpy(buf + pos, io->buf + io->bufpos, l);
272130561Sobrien		io->bufpos += l;
273218822Sdim	}
274218822Sdim
275218822Sdim	if (!pos && io->error)
276130561Sobrien		return (-1);
27733965Sjdp	return (pos);
27833965Sjdp}
27933965Sjdp
28033965Sjdp/*
28133965Sjdp * Write function
28233965Sjdp */
28333965Sjdpstatic int
28433965Sjdphttp_writefn(void *v, const char *buf, int len)
28533965Sjdp{
286130561Sobrien	struct httpio *io = (struct httpio *)v;
28733965Sjdp
28833965Sjdp	return (fetch_write(io->conn, buf, len));
289218822Sdim}
29033965Sjdp
291130561Sobrien/*
29233965Sjdp * Close function
29333965Sjdp */
29480016Sobrienstatic int
29533965Sjdphttp_closefn(void *v)
29633965Sjdp{
29733965Sjdp	struct httpio *io = (struct httpio *)v;
29833965Sjdp	int r;
29933965Sjdp
30033965Sjdp	r = fetch_close(io->conn);
30133965Sjdp	if (io->buf)
30233965Sjdp		free(io->buf);
30333965Sjdp	free(io);
304130561Sobrien	return (r);
30533965Sjdp}
30633965Sjdp
30733965Sjdp/*
30833965Sjdp * Wrap a file descriptor up
30933965Sjdp */
31033965Sjdpstatic FILE *
31133965Sjdphttp_funopen(conn_t *conn, int chunked)
312130561Sobrien{
31333965Sjdp	struct httpio *io;
31460484Sobrien	FILE *f;
31560484Sobrien
31660484Sobrien	if ((io = calloc(1, sizeof(*io))) == NULL) {
31760484Sobrien		fetch_syserr();
318130561Sobrien		return (NULL);
31989857Sobrien	}
32033965Sjdp	io->conn = conn;
32160484Sobrien	io->chunked = chunked;
32233965Sjdp	f = funopen(io, http_readfn, http_writefn, NULL, http_closefn);
32360484Sobrien	if (f == NULL) {
32460484Sobrien		fetch_syserr();
32560484Sobrien		free(io);
32660484Sobrien		return (NULL);
32760484Sobrien	}
32833965Sjdp	return (f);
32933965Sjdp}
33033965Sjdp
331218822Sdim
33233965Sjdp/*****************************************************************************
33333965Sjdp * Helper functions for talking to the server and parsing its replies
334218822Sdim */
335218822Sdim
33633965Sjdp/* Header types */
337130561Sobrientypedef enum {
338130561Sobrien	hdr_syserror = -2,
33933965Sjdp	hdr_error = -1,
34033965Sjdp	hdr_end = 0,
34133965Sjdp	hdr_unknown = 1,
342218822Sdim	hdr_content_length,
343130561Sobrien	hdr_content_range,
344130561Sobrien	hdr_last_modified,
34533965Sjdp	hdr_location,
346130561Sobrien	hdr_transfer_encoding,
347218822Sdim	hdr_www_authenticate,
348130561Sobrien	hdr_proxy_authenticate,
349130561Sobrien} hdr_t;
35060484Sobrien
35160484Sobrien/* Names of interesting headers */
352130561Sobrienstatic struct {
353130561Sobrien	hdr_t		 num;
35433965Sjdp	const char	*name;
35533965Sjdp} hdr_names[] = {
35633965Sjdp	{ hdr_content_length,		"Content-Length" },
357130561Sobrien	{ hdr_content_range,		"Content-Range" },
358130561Sobrien	{ hdr_last_modified,		"Last-Modified" },
359130561Sobrien	{ hdr_location,			"Location" },
36033965Sjdp	{ hdr_transfer_encoding,	"Transfer-Encoding" },
361130561Sobrien	{ hdr_www_authenticate,		"WWW-Authenticate" },
362130561Sobrien	{ hdr_proxy_authenticate,	"Proxy-Authenticate" },
363130561Sobrien	{ hdr_unknown,			NULL },
364130561Sobrien};
36533965Sjdp
36633965Sjdp/*
36789857Sobrien * Send a formatted line; optionally echo to terminal
368218822Sdim */
36933965Sjdpstatic int
37033965Sjdphttp_cmd(conn_t *conn, const char *fmt, ...)
371130561Sobrien{
372130561Sobrien	va_list ap;
37333965Sjdp	size_t len;
37433965Sjdp	char *msg;
37533965Sjdp	int r;
376218822Sdim
377218822Sdim	va_start(ap, fmt);
37833965Sjdp	len = vasprintf(&msg, fmt, ap);
379130561Sobrien	va_end(ap);
38033965Sjdp
38133965Sjdp	if (msg == NULL) {
38233965Sjdp		errno = ENOMEM;
38333965Sjdp		fetch_syserr();
38438889Sjdp		return (-1);
38578828Sobrien	}
386130561Sobrien
387130561Sobrien	r = fetch_putln(conn, msg, len);
38833965Sjdp	free(msg);
38933965Sjdp
39033965Sjdp	if (r == -1) {
39133965Sjdp		fetch_syserr();
39233965Sjdp		return (-1);
39333965Sjdp	}
39433965Sjdp
39533965Sjdp	return (0);
396104834Sobrien}
39733965Sjdp
39833965Sjdp/*
39977298Sobrien * Get and parse status line
40077298Sobrien */
40177298Sobrienstatic int
40233965Sjdphttp_get_reply(conn_t *conn)
40377298Sobrien{
40477298Sobrien	char *p;
40577298Sobrien
406130561Sobrien	if (fetch_getln(conn) == -1)
40777298Sobrien		return (-1);
408130561Sobrien	/*
409130561Sobrien	 * A valid status line looks like "HTTP/m.n xyz reason" where m
410130561Sobrien	 * and n are the major and minor protocol version numbers and xyz
411130561Sobrien	 * is the reply code.
41289857Sobrien	 * Unfortunately, there are servers out there (NCSA 1.5.1, to name
413130561Sobrien	 * just one) that do not send a version number, so we can't rely
414130561Sobrien	 * on finding one, but if we do, insist on it being 1.0 or 1.1.
415218822Sdim	 * We don't care about the reason phrase.
416130561Sobrien	 */
417130561Sobrien	if (strncmp(conn->buf, "HTTP", 4) != 0)
418130561Sobrien		return (HTTP_PROTOCOL_ERROR);
419130561Sobrien	p = conn->buf + 4;
420130561Sobrien	if (*p == '/') {
421130561Sobrien		if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
42289857Sobrien			return (HTTP_PROTOCOL_ERROR);
42333965Sjdp		p += 4;
424130561Sobrien	}
42533965Sjdp	if (*p != ' ' ||
42689857Sobrien	    !isdigit((unsigned char)p[1]) ||
42789857Sobrien	    !isdigit((unsigned char)p[2]) ||
42889857Sobrien	    !isdigit((unsigned char)p[3]))
42960484Sobrien		return (HTTP_PROTOCOL_ERROR);
43060484Sobrien
43160484Sobrien	conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
43289857Sobrien	return (conn->err);
43360484Sobrien}
43460484Sobrien
43560484Sobrien/*
43660484Sobrien * Check a header; if the type matches the given string, return a pointer
437130561Sobrien * to the beginning of the value.
43860484Sobrien */
43960484Sobrienstatic const char *
440130561Sobrienhttp_match(const char *str, const char *hdr)
44160484Sobrien{
44260484Sobrien	while (*str && *hdr &&
443218822Sdim	    tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
444218822Sdim		/* nothing */;
445218822Sdim	if (*str || *hdr != ':')
446130561Sobrien		return (NULL);
447218822Sdim	while (*hdr && isspace((unsigned char)*++hdr))
448218822Sdim		/* nothing */;
449218822Sdim	return (hdr);
450218822Sdim}
45160484Sobrien
452218822Sdim
45378828Sobrien/*
45460484Sobrien * Get the next header and return the appropriate symbolic code.  We
45560484Sobrien * need to read one line ahead for checking for a continuation line
456218822Sdim * belonging to the current header (continuation lines start with
45760484Sobrien * white space).
45860484Sobrien *
45960484Sobrien * We get called with a fresh line already in the conn buffer, either
46060484Sobrien * from the previous http_next_header() invocation, or, the first
46160484Sobrien * time, from a fetch_getln() performed by our caller.
46260484Sobrien *
46360484Sobrien * This stops when we encounter an empty line (we dont read beyond the header
46460484Sobrien * area).
46560484Sobrien *
46660484Sobrien * Note that the "headerbuf" is just a place to return the result. Its
46760484Sobrien * contents are not used for the next call. This means that no cleanup
46860484Sobrien * is needed when ie doing another connection, just call the cleanup when
46960484Sobrien * fully done to deallocate memory.
47060484Sobrien */
47160484Sobrien
47260484Sobrien/* Limit the max number of continuation lines to some reasonable value */
47360484Sobrien#define HTTP_MAX_CONT_LINES 10
47460484Sobrien
47560484Sobrien/* Place into which to build a header from one or several lines */
47660484Sobrientypedef struct {
47760484Sobrien	char	*buf;		/* buffer */
47860484Sobrien	size_t	 bufsize;	/* buffer size */
47989857Sobrien	size_t	 buflen;	/* length of buffer contents */
48060484Sobrien} http_headerbuf_t;
48160484Sobrien
482218822Sdimstatic void
48360484Sobrieninit_http_headerbuf(http_headerbuf_t *buf)
484130561Sobrien{
485130561Sobrien	buf->buf = NULL;
48677298Sobrien	buf->bufsize = 0;
48777298Sobrien	buf->buflen = 0;
48878828Sobrien}
489218822Sdim
490218822Sdimstatic void
491218822Sdimclean_http_headerbuf(http_headerbuf_t *buf)
49278828Sobrien{
49378828Sobrien	if (buf->buf)
494218822Sdim		free(buf->buf);
49578828Sobrien	init_http_headerbuf(buf);
49678828Sobrien}
497218822Sdim
498130561Sobrien/* Remove whitespace at the end of the buffer */
499130561Sobrienstatic void
500130561Sobrienhttp_conn_trimright(conn_t *conn)
501130561Sobrien{
502130561Sobrien	while (conn->buflen &&
503130561Sobrien	       isspace((unsigned char)conn->buf[conn->buflen - 1]))
504130561Sobrien		conn->buflen--;
505130561Sobrien	conn->buf[conn->buflen] = '\0';
506130561Sobrien}
50760484Sobrien
508218822Sdimstatic hdr_t
50960484Sobrienhttp_next_header(conn_t *conn, http_headerbuf_t *hbuf, const char **p)
51060484Sobrien{
511130561Sobrien	unsigned int i, len;
51260484Sobrien
51333965Sjdp	/*
514218822Sdim	 * Have to do the stripping here because of the first line. So
51560484Sobrien	 * it's done twice for the subsequent lines. No big deal
51633965Sjdp	 */
51733965Sjdp	http_conn_trimright(conn);
51833965Sjdp	if (conn->buflen == 0)
51933965Sjdp		return (hdr_end);
520130561Sobrien
52133965Sjdp	/* Copy the line to the headerbuf */
52289857Sobrien	if (hbuf->bufsize < conn->buflen + 1) {
52389857Sobrien		if ((hbuf->buf = realloc(hbuf->buf, conn->buflen + 1)) == NULL)
52489857Sobrien			return (hdr_syserror);
52560484Sobrien		hbuf->bufsize = conn->buflen + 1;
52689857Sobrien	}
52789857Sobrien	strcpy(hbuf->buf, conn->buf);
52889857Sobrien	hbuf->buflen = conn->buflen;
52960484Sobrien
53089857Sobrien	/*
53160484Sobrien	 * Fetch possible continuation lines. Stop at 1st non-continuation
532130561Sobrien	 * and leave it in the conn buffer
53360484Sobrien	 */
534130561Sobrien	for (i = 0; i < HTTP_MAX_CONT_LINES; i++) {
53589857Sobrien		if (fetch_getln(conn) == -1)
536218822Sdim			return (hdr_syserror);
537218822Sdim
538218822Sdim		/*
53960484Sobrien		 * Note: we carry on the idea from the previous version
54060484Sobrien		 * that a pure whitespace line is equivalent to an empty
54160484Sobrien		 * one (so it's not continuation and will be handled when
54260484Sobrien		 * we are called next)
54360484Sobrien		 */
544130561Sobrien		http_conn_trimright(conn);
54560484Sobrien		if (conn->buf[0] != ' ' && conn->buf[0] != "\t"[0])
54660484Sobrien			break;
54760484Sobrien
54833965Sjdp		/* Got a continuation line. Concatenate to previous */
549218822Sdim		len = hbuf->buflen + conn->buflen;
55060484Sobrien		if (hbuf->bufsize < len + 1) {
55133965Sjdp			len *= 2;
55233965Sjdp			if ((hbuf->buf = realloc(hbuf->buf, len + 1)) == NULL)
55333965Sjdp				return (hdr_syserror);
55433965Sjdp			hbuf->bufsize = len + 1;
55533965Sjdp		}
55633965Sjdp		strcpy(hbuf->buf + hbuf->buflen, conn->buf);
55733965Sjdp		hbuf->buflen += conn->buflen;
558130561Sobrien	}
55933965Sjdp
56033965Sjdp	/*
56133965Sjdp	 * We could check for malformed headers but we don't really care.
56233965Sjdp	 * A valid header starts with a token immediately followed by a
56333965Sjdp	 * colon; a token is any sequence of non-control, non-whitespace
56433965Sjdp	 * characters except "()<>@,;:\\\"{}".
56533965Sjdp	 */
56633965Sjdp	for (i = 0; hdr_names[i].num != hdr_unknown; i++)
56733965Sjdp		if ((*p = http_match(hdr_names[i].name, hbuf->buf)) != NULL)
56833965Sjdp			return (hdr_names[i].num);
56933965Sjdp
57033965Sjdp	return (hdr_unknown);
57133965Sjdp}
57233965Sjdp
57333965Sjdp/**************************
57433965Sjdp * [Proxy-]Authenticate header parsing
57533965Sjdp */
57633965Sjdp
57738889Sjdp/*
57838889Sjdp * Read doublequote-delimited string into output buffer obuf (allocated
57938889Sjdp * by caller, whose responsibility it is to ensure that it's big enough)
58033965Sjdp * cp points to the first char after the initial '"'
58133965Sjdp * Handles \ quoting
58260484Sobrien * Returns pointer to the first char after the terminating double quote, or
58333965Sjdp * NULL for error.
58460484Sobrien */
58533965Sjdpstatic const char *
58633965Sjdphttp_parse_headerstring(const char *cp, char *obuf)
58733965Sjdp{
588218822Sdim	for (;;) {
58938889Sjdp		switch (*cp) {
59033965Sjdp		case 0: /* Unterminated string */
59138889Sjdp			*obuf = 0;
59238889Sjdp			return (NULL);
59338889Sjdp		case '"': /* Ending quote */
59433965Sjdp			*obuf = 0;
59538889Sjdp			return (++cp);
59638889Sjdp		case '\\':
59738889Sjdp			if (*++cp == 0) {
59860484Sobrien				*obuf = 0;
59960484Sobrien				return (NULL);
60060484Sobrien			}
60138889Sjdp			/* FALLTHROUGH */
60238889Sjdp		default:
60333965Sjdp			*obuf++ = *cp++;
60433965Sjdp		}
60533965Sjdp	}
60633965Sjdp}
60733965Sjdp
60833965Sjdp/* Http auth challenge schemes */
60933965Sjdptypedef enum {HTTPAS_UNKNOWN, HTTPAS_BASIC,HTTPAS_DIGEST} http_auth_schemes_t;
61060484Sobrien
61133965Sjdp/* Data holder for a Basic or Digest challenge. */
61233965Sjdptypedef struct {
613130561Sobrien	http_auth_schemes_t scheme;
61433965Sjdp	char	*realm;
615130561Sobrien	char	*qop;
61633965Sjdp	char	*nonce;
61760484Sobrien	char	*opaque;
61833965Sjdp	char	*algo;
61933965Sjdp	int	 stale;
62033965Sjdp	int	 nc; /* Nonce count */
62133965Sjdp} http_auth_challenge_t;
62233965Sjdp
62333965Sjdpstatic void
624130561Sobrieninit_http_auth_challenge(http_auth_challenge_t *b)
62533965Sjdp{
626130561Sobrien	b->scheme = HTTPAS_UNKNOWN;
627130561Sobrien	b->realm = b->qop = b->nonce = b->opaque = b->algo = NULL;
628130561Sobrien	b->stale = b->nc = 0;
62960484Sobrien}
63060484Sobrien
63160484Sobrienstatic void
63260484Sobrienclean_http_auth_challenge(http_auth_challenge_t *b)
633130561Sobrien{
63433965Sjdp	if (b->realm)
63533965Sjdp		free(b->realm);
63660484Sobrien	if (b->qop)
63760484Sobrien		free(b->qop);
63833965Sjdp	if (b->nonce)
63933965Sjdp		free(b->nonce);
64033965Sjdp	if (b->opaque)
64133965Sjdp		free(b->opaque);
64233965Sjdp	if (b->algo)
64333965Sjdp		free(b->algo);
64460484Sobrien	init_http_auth_challenge(b);
645130561Sobrien}
64633965Sjdp
64733965Sjdp/* Data holder for an array of challenges offered in an http response. */
64833965Sjdp#define MAX_CHALLENGES 10
649130561Sobrientypedef struct {
65033965Sjdp	http_auth_challenge_t *challenges[MAX_CHALLENGES];
65138889Sjdp	int	count; /* Number of parsed challenges in the array */
65238889Sjdp	int	valid; /* We did parse an authenticate header */
65333965Sjdp} http_auth_challenges_t;
65433965Sjdp
655104834Sobrienstatic void
65678828Sobrieninit_http_auth_challenges(http_auth_challenges_t *cs)
65778828Sobrien{
65878828Sobrien	int i;
65978828Sobrien	for (i = 0; i < MAX_CHALLENGES; i++)
66078828Sobrien		cs->challenges[i] = NULL;
661130561Sobrien	cs->count = cs->valid = 0;
66278828Sobrien}
663130561Sobrien
66478828Sobrienstatic void
66578828Sobrienclean_http_auth_challenges(http_auth_challenges_t *cs)
66678828Sobrien{
66778828Sobrien	int i;
668104834Sobrien	/* We rely on non-zero pointers being allocated, not on the count */
669130561Sobrien	for (i = 0; i < MAX_CHALLENGES; i++) {
670130561Sobrien		if (cs->challenges[i] != NULL) {
671218822Sdim			clean_http_auth_challenge(cs->challenges[i]);
672218822Sdim			free(cs->challenges[i]);
673218822Sdim		}
674218822Sdim	}
67578828Sobrien	init_http_auth_challenges(cs);
676130561Sobrien}
67778828Sobrien
67878828Sobrien/*
679130561Sobrien * Enumeration for lexical elements. Separators will be returned as their own
68078828Sobrien * ascii value
681130561Sobrien */
68278828Sobrientypedef enum {HTTPHL_WORD=256, HTTPHL_STRING=257, HTTPHL_END=258,
68378828Sobrien	      HTTPHL_ERROR = 259} http_header_lex_t;
68478828Sobrien
685130561Sobrien/*
686130561Sobrien * Determine what kind of token comes next and return possible value
68778828Sobrien * in buf, which is supposed to have been allocated big enough by
68878828Sobrien * caller. Advance input pointer and return element type.
689104834Sobrien */
69078828Sobrienstatic int
69178828Sobrienhttp_header_lex(const char **cpp, char *buf)
69278828Sobrien{
69378828Sobrien	size_t l;
69478828Sobrien	/* Eat initial whitespace */
695130561Sobrien	*cpp += strspn(*cpp, " \t");
69678828Sobrien	if (**cpp == 0)
69778828Sobrien		return (HTTPHL_END);
69878828Sobrien
69978828Sobrien	/* Separator ? */
70078828Sobrien	if (**cpp == ',' || **cpp == '=')
70178828Sobrien		return (*((*cpp)++));
70278828Sobrien
70378828Sobrien	/* String ? */
70478828Sobrien	if (**cpp == '"') {
70578828Sobrien		*cpp = http_parse_headerstring(++*cpp, buf);
706130561Sobrien		if (*cpp == NULL)
70778828Sobrien			return (HTTPHL_ERROR);
708104834Sobrien		return (HTTPHL_STRING);
70978828Sobrien	}
71078828Sobrien
71178828Sobrien	/* Read other token, until separator or whitespace */
71278828Sobrien	l = strcspn(*cpp, " \t,=");
71378828Sobrien	memcpy(buf, *cpp, l);
714130561Sobrien	buf[l] = 0;
71578828Sobrien	*cpp += l;
716104834Sobrien	return (HTTPHL_WORD);
71778828Sobrien}
718130561Sobrien
71978828Sobrien/*
720104834Sobrien * Read challenges from http xxx-authenticate header and accumulate them
72178828Sobrien * in the challenges list structure.
72278828Sobrien *
72378828Sobrien * Headers with multiple challenges are specified by rfc2617, but
72478828Sobrien * servers (ie: squid) often send them in separate headers instead,
72578828Sobrien * which in turn is forbidden by the http spec (multiple headers with
72678828Sobrien * the same name are only allowed for pure comma-separated lists, see
727104834Sobrien * rfc2616 sec 4.2).
72878828Sobrien *
72978828Sobrien * We support both approaches anyway
73078828Sobrien */
73178828Sobrienstatic int
73278828Sobrienhttp_parse_authenticate(const char *cp, http_auth_challenges_t *cs)
73378828Sobrien{
73478828Sobrien	int ret = -1;
73578828Sobrien	http_header_lex_t lex;
73678828Sobrien	char *key = malloc(strlen(cp) + 1);
73778828Sobrien	char *value = malloc(strlen(cp) + 1);
73878828Sobrien	char *buf = malloc(strlen(cp) + 1);
73978828Sobrien
74078828Sobrien	if (key == NULL || value == NULL || buf == NULL) {
74178828Sobrien		fetch_syserr();
74278828Sobrien		goto out;
74378828Sobrien	}
744104834Sobrien
745104834Sobrien	/* In any case we've seen the header and we set the valid bit */
74678828Sobrien	cs->valid = 1;
74778828Sobrien
74878828Sobrien	/* Need word first */
74978828Sobrien	lex = http_header_lex(&cp, key);
75078828Sobrien	if (lex != HTTPHL_WORD)
75178828Sobrien		goto out;
75278828Sobrien
75378828Sobrien	/* Loop on challenges */
75478828Sobrien	for (; cs->count < MAX_CHALLENGES; cs->count++) {
755218822Sdim		cs->challenges[cs->count] =
756218822Sdim			malloc(sizeof(http_auth_challenge_t));
75778828Sobrien		if (cs->challenges[cs->count] == NULL) {
758104834Sobrien			fetch_syserr();
75978828Sobrien			goto out;
76078828Sobrien		}
76178828Sobrien		init_http_auth_challenge(cs->challenges[cs->count]);
76278828Sobrien		if (!strcasecmp(key, "basic")) {
76378828Sobrien			cs->challenges[cs->count]->scheme = HTTPAS_BASIC;
76478828Sobrien		} else if (!strcasecmp(key, "digest")) {
76578828Sobrien			cs->challenges[cs->count]->scheme = HTTPAS_DIGEST;
76678828Sobrien		} else {
76778828Sobrien			cs->challenges[cs->count]->scheme = HTTPAS_UNKNOWN;
76878828Sobrien			/*
76978828Sobrien			 * Continue parsing as basic or digest may
77078828Sobrien			 * follow, and the syntax is the same for
77133965Sjdp			 * all. We'll just ignore this one when
77233965Sjdp			 * looking at the list
77333965Sjdp			 */
774130561Sobrien		}
775130561Sobrien
77633965Sjdp		/* Loop on attributes */
77733965Sjdp		for (;;) {
77833965Sjdp			/* Key */
779130561Sobrien			lex = http_header_lex(&cp, key);
780130561Sobrien			if (lex != HTTPHL_WORD)
781130561Sobrien				goto out;
782130561Sobrien
783130561Sobrien			/* Equal sign */
784130561Sobrien			lex = http_header_lex(&cp, buf);
785130561Sobrien			if (lex != '=')
786130561Sobrien				goto out;
787130561Sobrien
788130561Sobrien			/* Value */
789130561Sobrien			lex = http_header_lex(&cp, value);
790130561Sobrien			if (lex != HTTPHL_WORD && lex != HTTPHL_STRING)
791130561Sobrien				goto out;
792130561Sobrien
793130561Sobrien			if (!strcasecmp(key, "realm"))
794130561Sobrien				cs->challenges[cs->count]->realm =
795130561Sobrien					strdup(value);
796130561Sobrien			else if (!strcasecmp(key, "qop"))
797130561Sobrien				cs->challenges[cs->count]->qop =
798130561Sobrien					strdup(value);
79989857Sobrien			else if (!strcasecmp(key, "nonce"))
800130561Sobrien				cs->challenges[cs->count]->nonce =
80133965Sjdp					strdup(value);
80233965Sjdp			else if (!strcasecmp(key, "opaque"))
803218822Sdim				cs->challenges[cs->count]->opaque =
804218822Sdim					strdup(value);
805218822Sdim			else if (!strcasecmp(key, "algorithm"))
806218822Sdim				cs->challenges[cs->count]->algo =
807218822Sdim					strdup(value);
808218822Sdim			else if (!strcasecmp(key, "stale"))
809218822Sdim				cs->challenges[cs->count]->stale =
810218822Sdim					strcasecmp(value, "no");
811218822Sdim			/* Else ignore unknown attributes */
812218822Sdim
813218822Sdim			/* Comma or Next challenge or End */
814218822Sdim			lex = http_header_lex(&cp, key);
815218822Sdim			/*
816218822Sdim			 * If we get a word here, this is the beginning of the
817218822Sdim			 * next challenge. Break the attributes loop
818218822Sdim			 */
819218822Sdim			if (lex == HTTPHL_WORD)
820218822Sdim				break;
821218822Sdim
822218822Sdim			if (lex == HTTPHL_END) {
823218822Sdim				/* End while looking for ',' is normal exit */
824218822Sdim				cs->count++;
825218822Sdim				ret = 0;
826218822Sdim				goto out;
82733965Sjdp			}
82833965Sjdp			/* Anything else is an error */
829130561Sobrien			if (lex != ',')
830130561Sobrien				goto out;
83133965Sjdp
832130561Sobrien		} /* End attributes loop */
833130561Sobrien	} /* End challenge loop */
834130561Sobrien
83533965Sjdp	/*
836130561Sobrien	 * Challenges max count exceeded. This really can't happen
837130561Sobrien	 * with normal data, something's fishy -> error
838130561Sobrien	 */
839130561Sobrien
840130561Sobrienout:
841130561Sobrien	if (key)
842130561Sobrien		free(key);
843130561Sobrien	if (value)
844130561Sobrien		free(value);
845130561Sobrien	if (buf)
846130561Sobrien		free(buf);
84760484Sobrien	return (ret);
84860484Sobrien}
84960484Sobrien
850130561Sobrien
851130561Sobrien/*
85233965Sjdp * Parse a last-modified header
853130561Sobrien */
854130561Sobrienstatic int
855130561Sobrienhttp_parse_mtime(const char *p, time_t *mtime)
85660484Sobrien{
857218822Sdim	char locale[64], *r;
858218822Sdim	struct tm tm;
859218822Sdim
860218822Sdim	strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
861218822Sdim	setlocale(LC_TIME, "C");
862218822Sdim	r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
863218822Sdim	/* XXX should add support for date-2 and date-3 */
864218822Sdim	setlocale(LC_TIME, locale);
865218822Sdim	if (r == NULL)
866218822Sdim		return (-1);
867218822Sdim	DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d "
868218822Sdim		  "%02d:%02d:%02d]\n",
869218822Sdim		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
870218822Sdim		  tm.tm_hour, tm.tm_min, tm.tm_sec));
871218822Sdim	*mtime = timegm(&tm);
872218822Sdim	return (0);
873218822Sdim}
874218822Sdim
875218822Sdim/*
876218822Sdim * Parse a content-length header
877218822Sdim */
878218822Sdimstatic int
879218822Sdimhttp_parse_length(const char *p, off_t *length)
880218822Sdim{
881218822Sdim	off_t len;
882130561Sobrien
88333965Sjdp	for (len = 0; *p && isdigit((unsigned char)*p); ++p)
88433965Sjdp		len = len * 10 + (*p - '0');
885218822Sdim	if (*p)
886218822Sdim		return (-1);
887218822Sdim	DEBUG(fprintf(stderr, "content length: [%lld]\n",
888218822Sdim	    (long long)len));
889218822Sdim	*length = len;
890218822Sdim	return (0);
891218822Sdim}
892218822Sdim
893218822Sdim/*
894218822Sdim * Parse a content-range header
895218822Sdim */
896218822Sdimstatic int
897218822Sdimhttp_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
898218822Sdim{
899218822Sdim	off_t first, last, len;
900218822Sdim
901218822Sdim	if (strncasecmp(p, "bytes ", 6) != 0)
902218822Sdim		return (-1);
90333965Sjdp	p += 6;
90433965Sjdp	if (*p == '*') {
90533965Sjdp		first = last = -1;
90633965Sjdp		++p;
90733965Sjdp	} else {
908130561Sobrien		for (first = 0; *p && isdigit((unsigned char)*p); ++p)
909130561Sobrien			first = first * 10 + *p - '0';
91033965Sjdp		if (*p != '-')
911130561Sobrien			return (-1);
91233965Sjdp		for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
913218822Sdim			last = last * 10 + *p - '0';
91433965Sjdp	}
91533965Sjdp	if (first > last || *p != '/')
91633965Sjdp		return (-1);
91733965Sjdp	for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
91833965Sjdp		len = len * 10 + *p - '0';
919130561Sobrien	if (*p || len < last - first + 1)
920218822Sdim		return (-1);
921218822Sdim	if (first == -1) {
922130561Sobrien		DEBUG(fprintf(stderr, "content range: [*/%lld]\n",
923130561Sobrien		    (long long)len));
924130561Sobrien		*length = 0;
92533965Sjdp	} else {
926130561Sobrien		DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n",
927130561Sobrien		    (long long)first, (long long)last, (long long)len));
92860484Sobrien		*length = last - first + 1;
92960484Sobrien	}
930130561Sobrien	*offset = first;
93160484Sobrien	*size = len;
932130561Sobrien	return (0);
933130561Sobrien}
934130561Sobrien
935130561Sobrien
93660484Sobrien/*****************************************************************************
93760484Sobrien * Helper functions for authorization
938130561Sobrien */
939130561Sobrien
940130561Sobrien/*
941130561Sobrien * Base64 encoding
942130561Sobrien */
943130561Sobrienstatic char *
944130561Sobrienhttp_base64(const char *src)
945130561Sobrien{
94633965Sjdp	static const char base64[] =
947130561Sobrien	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
948130561Sobrien	    "abcdefghijklmnopqrstuvwxyz"
949130561Sobrien	    "0123456789+/";
950130561Sobrien	char *str, *dst;
951130561Sobrien	size_t l;
952130561Sobrien	int t, r;
95333965Sjdp
954130561Sobrien	l = strlen(src);
955130561Sobrien	if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
956130561Sobrien		return (NULL);
957130561Sobrien	dst = str;
958130561Sobrien	r = 0;
959130561Sobrien
960130561Sobrien	while (l >= 3) {
961130561Sobrien		t = (src[0] << 16) | (src[1] << 8) | src[2];
96289857Sobrien		dst[0] = base64[(t >> 18) & 0x3f];
963130561Sobrien		dst[1] = base64[(t >> 12) & 0x3f];
964130561Sobrien		dst[2] = base64[(t >> 6) & 0x3f];
965130561Sobrien		dst[3] = base64[(t >> 0) & 0x3f];
96633965Sjdp		src += 3; l -= 3;
967130561Sobrien		dst += 4; r += 4;
968130561Sobrien	}
969130561Sobrien
970130561Sobrien	switch (l) {
971130561Sobrien	case 2:
972130561Sobrien		t = (src[0] << 16) | (src[1] << 8);
973130561Sobrien		dst[0] = base64[(t >> 18) & 0x3f];
974130561Sobrien		dst[1] = base64[(t >> 12) & 0x3f];
975130561Sobrien		dst[2] = base64[(t >> 6) & 0x3f];
976130561Sobrien		dst[3] = '=';
977130561Sobrien		dst += 4;
978130561Sobrien		r += 4;
979130561Sobrien		break;
980130561Sobrien	case 1:
981130561Sobrien		t = src[0] << 16;
982130561Sobrien		dst[0] = base64[(t >> 18) & 0x3f];
983130561Sobrien		dst[1] = base64[(t >> 12) & 0x3f];
984130561Sobrien		dst[2] = dst[3] = '=';
985130561Sobrien		dst += 4;
986130561Sobrien		r += 4;
987130561Sobrien		break;
98860484Sobrien	case 0:
989218822Sdim		break;
99060484Sobrien	}
99160484Sobrien
99260484Sobrien	*dst = 0;
99360484Sobrien	return (str);
994218822Sdim}
995218822Sdim
996218822Sdim
997218822Sdim/*
998104834Sobrien * Extract authorization parameters from environment value.
99977298Sobrien * The value is like scheme:realm:user:pass
1000218822Sdim */
100178828Sobrientypedef struct {
100278828Sobrien	char	*scheme;
1003130561Sobrien	char	*realm;
100478828Sobrien	char	*user;
100578828Sobrien	char	*password;
1006218822Sdim} http_auth_params_t;
100733965Sjdp
100833965Sjdpstatic void
100989857Sobrieninit_http_auth_params(http_auth_params_t *s)
101033965Sjdp{
101160484Sobrien	s->scheme = s->realm = s->user = s->password = 0;
101233965Sjdp}
101360484Sobrien
101460484Sobrienstatic void
101533965Sjdpclean_http_auth_params(http_auth_params_t *s)
1016218822Sdim{
101780016Sobrien	if (s->scheme)
101880016Sobrien		free(s->scheme);
1019218822Sdim	if (s->realm)
102033965Sjdp		free(s->realm);
102160484Sobrien	if (s->user)
102260484Sobrien		free(s->user);
102360484Sobrien	if (s->password)
102433965Sjdp		free(s->password);
102533965Sjdp	init_http_auth_params(s);
102638889Sjdp}
1027218822Sdim
1028218822Sdimstatic int
1029218822Sdimhttp_authfromenv(const char *p, http_auth_params_t *parms)
1030218822Sdim{
1031218822Sdim	int ret = -1;
1032218822Sdim	char *v, *ve;
1033218822Sdim	char *str = strdup(p);
1034218822Sdim
1035218822Sdim	if (str == NULL) {
1036218822Sdim		fetch_syserr();
1037218822Sdim		return (-1);
1038218822Sdim	}
1039218822Sdim	v = str;
1040218822Sdim
1041218822Sdim	if ((ve = strchr(v, ':')) == NULL)
1042218822Sdim		goto out;
1043218822Sdim
1044218822Sdim	*ve = 0;
1045218822Sdim	if ((parms->scheme = strdup(v)) == NULL) {
1046218822Sdim		fetch_syserr();
1047218822Sdim		goto out;
1048218822Sdim	}
104933965Sjdp	v = ve + 1;
1050218822Sdim
105133965Sjdp	if ((ve = strchr(v, ':')) == NULL)
1052218822Sdim		goto out;
105333965Sjdp
1054218822Sdim	*ve = 0;
1055218822Sdim	if ((parms->realm = strdup(v)) == NULL) {
1056218822Sdim		fetch_syserr();
1057218822Sdim		goto out;
1058218822Sdim	}
1059218822Sdim	v = ve + 1;
1060218822Sdim
1061218822Sdim	if ((ve = strchr(v, ':')) == NULL)
1062218822Sdim		goto out;
1063218822Sdim
1064218822Sdim	*ve = 0;
1065218822Sdim	if ((parms->user = strdup(v)) == NULL) {
1066218822Sdim		fetch_syserr();
1067218822Sdim		goto out;
1068218822Sdim	}
1069218822Sdim	v = ve + 1;
1070218822Sdim
1071218822Sdim
1072218822Sdim	if ((parms->password = strdup(v)) == NULL) {
1073218822Sdim		fetch_syserr();
1074218822Sdim		goto out;
1075218822Sdim	}
1076218822Sdim	ret = 0;
1077218822Sdimout:
1078218822Sdim	if (ret == -1)
1079218822Sdim		clean_http_auth_params(parms);
1080218822Sdim	if (str)
108133965Sjdp		free(str);
108233965Sjdp	return (ret);
108333965Sjdp}
108433965Sjdp
108533965Sjdp
108633965Sjdp/*
108733965Sjdp * Digest response: the code to compute the digest is taken from the
108833965Sjdp * sample implementation in RFC2616
108989857Sobrien */
109089857Sobrien#define IN
109160484Sobrien#define OUT
1092130561Sobrien
109360484Sobrien#define HASHLEN 16
109460484Sobrientypedef char HASH[HASHLEN];
109560484Sobrien#define HASHHEXLEN 32
109689857Sobrientypedef char HASHHEX[HASHHEXLEN+1];
109789857Sobrien
109889857Sobrienstatic const char *hexchars = "0123456789abcdef";
109960484Sobrienstatic void
110089857SobrienCvtHex(IN HASH Bin, OUT HASHHEX Hex)
110160484Sobrien{
110260484Sobrien	unsigned short i;
110389857Sobrien	unsigned char j;
110460484Sobrien
110560484Sobrien	for (i = 0; i < HASHLEN; i++) {
1106130561Sobrien		j = (Bin[i] >> 4) & 0xf;
110760484Sobrien		Hex[i*2] = hexchars[j];
110860484Sobrien		j = Bin[i] & 0xf;
110960484Sobrien		Hex[i*2+1] = hexchars[j];
111060484Sobrien	};
111160484Sobrien	Hex[HASHHEXLEN] = '\0';
111260484Sobrien};
111360484Sobrien
111460484Sobrien/* calculate H(A1) as per spec */
111589857Sobrienstatic void
1116130561SobrienDigestCalcHA1(
111760484Sobrien	IN char * pszAlg,
111860484Sobrien	IN char * pszUserName,
111989857Sobrien	IN char * pszRealm,
1120130561Sobrien	IN char * pszPassword,
112160484Sobrien	IN char * pszNonce,
112260484Sobrien	IN char * pszCNonce,
1123130561Sobrien	OUT HASHHEX SessionKey
112460484Sobrien	)
112560484Sobrien{
112660484Sobrien	MD5_CTX Md5Ctx;
112760484Sobrien	HASH HA1;
112860484Sobrien
112960484Sobrien	MD5Init(&Md5Ctx);
113060484Sobrien	MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
113160484Sobrien	MD5Update(&Md5Ctx, ":", 1);
1132130561Sobrien	MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
1133130561Sobrien	MD5Update(&Md5Ctx, ":", 1);
113433965Sjdp	MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
113533965Sjdp	MD5Final(HA1, &Md5Ctx);
1136130561Sobrien	if (strcasecmp(pszAlg, "md5-sess") == 0) {
113733965Sjdp
1138130561Sobrien		MD5Init(&Md5Ctx);
1139130561Sobrien		MD5Update(&Md5Ctx, HA1, HASHLEN);
1140130561Sobrien		MD5Update(&Md5Ctx, ":", 1);
1141130561Sobrien		MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
1142130561Sobrien		MD5Update(&Md5Ctx, ":", 1);
1143130561Sobrien		MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
114433965Sjdp		MD5Final(HA1, &Md5Ctx);
1145130561Sobrien	};
1146130561Sobrien	CvtHex(HA1, SessionKey);
1147130561Sobrien}
1148130561Sobrien
114989857Sobrien/* calculate request-digest/response-digest as per HTTP Digest spec */
1150130561Sobrienstatic void
1151130561SobrienDigestCalcResponse(
1152130561Sobrien	IN HASHHEX HA1,           /* H(A1) */
1153130561Sobrien	IN char * pszNonce,       /* nonce from server */
1154130561Sobrien	IN char * pszNonceCount,  /* 8 hex digits */
1155130561Sobrien	IN char * pszCNonce,      /* client nonce */
1156130561Sobrien	IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
1157130561Sobrien	IN char * pszMethod,      /* method from the request */
1158130561Sobrien	IN char * pszDigestUri,   /* requested URL */
1159130561Sobrien	IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
1160130561Sobrien	OUT HASHHEX Response      /* request-digest or response-digest */
1161130561Sobrien	)
1162130561Sobrien{
1163130561Sobrien/*	DEBUG(fprintf(stderr,
1164130561Sobrien		      "Calc: HA1[%s] Nonce[%s] qop[%s] method[%s] URI[%s]\n",
1165130561Sobrien		      HA1, pszNonce, pszQop, pszMethod, pszDigestUri));*/
1166130561Sobrien	MD5_CTX Md5Ctx;
1167130561Sobrien	HASH HA2;
1168130561Sobrien	HASH RespHash;
1169130561Sobrien	HASHHEX HA2Hex;
1170130561Sobrien
1171130561Sobrien	// calculate H(A2)
1172130561Sobrien	MD5Init(&Md5Ctx);
1173130561Sobrien	MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
1174130561Sobrien	MD5Update(&Md5Ctx, ":", 1);
1175130561Sobrien	MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
1176130561Sobrien	if (strcasecmp(pszQop, "auth-int") == 0) {
1177130561Sobrien		MD5Update(&Md5Ctx, ":", 1);
1178130561Sobrien		MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
1179130561Sobrien	};
1180130561Sobrien	MD5Final(HA2, &Md5Ctx);
1181130561Sobrien	CvtHex(HA2, HA2Hex);
1182130561Sobrien
1183130561Sobrien	// calculate response
1184130561Sobrien	MD5Init(&Md5Ctx);
1185130561Sobrien	MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
1186130561Sobrien	MD5Update(&Md5Ctx, ":", 1);
1187130561Sobrien	MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
1188130561Sobrien	MD5Update(&Md5Ctx, ":", 1);
1189130561Sobrien	if (*pszQop) {
1190130561Sobrien		MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
1191130561Sobrien		MD5Update(&Md5Ctx, ":", 1);
1192130561Sobrien		MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
1193130561Sobrien		MD5Update(&Md5Ctx, ":", 1);
1194130561Sobrien		MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
1195130561Sobrien		MD5Update(&Md5Ctx, ":", 1);
1196130561Sobrien	};
1197130561Sobrien	MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
1198130561Sobrien	MD5Final(RespHash, &Md5Ctx);
1199130561Sobrien	CvtHex(RespHash, Response);
1200130561Sobrien}
1201130561Sobrien
1202130561Sobrien/*
1203130561Sobrien * Generate/Send a Digest authorization header
1204130561Sobrien * This looks like: [Proxy-]Authorization: credentials
1205130561Sobrien *
1206130561Sobrien *  credentials      = "Digest" digest-response
1207130561Sobrien *  digest-response  = 1#( username | realm | nonce | digest-uri
1208130561Sobrien *                      | response | [ algorithm ] | [cnonce] |
1209130561Sobrien *                      [opaque] | [message-qop] |
1210130561Sobrien *                          [nonce-count]  | [auth-param] )
1211130561Sobrien *  username         = "username" "=" username-value
1212130561Sobrien *  username-value   = quoted-string
1213130561Sobrien *  digest-uri       = "uri" "=" digest-uri-value
1214130561Sobrien *  digest-uri-value = request-uri   ; As specified by HTTP/1.1
1215130561Sobrien *  message-qop      = "qop" "=" qop-value
1216218822Sdim *  cnonce           = "cnonce" "=" cnonce-value
1217130561Sobrien *  cnonce-value     = nonce-value
1218130561Sobrien *  nonce-count      = "nc" "=" nc-value
1219130561Sobrien *  nc-value         = 8LHEX
1220130561Sobrien *  response         = "response" "=" request-digest
1221130561Sobrien *  request-digest = <"> 32LHEX <">
1222130561Sobrien */
1223130561Sobrienstatic int
1224130561Sobrienhttp_digest_auth(conn_t *conn, const char *hdr, http_auth_challenge_t *c,
1225218822Sdim		 http_auth_params_t *parms, struct url *url)
1226130561Sobrien{
1227130561Sobrien	int r;
1228218822Sdim	char noncecount[10];
1229130561Sobrien	char cnonce[40];
1230130561Sobrien	char *options = 0;
1231130561Sobrien
1232130561Sobrien	if (!c->realm || !c->nonce) {
1233130561Sobrien		DEBUG(fprintf(stderr, "realm/nonce not set in challenge\n"));
1234130561Sobrien		return(-1);
1235130561Sobrien	}
1236130561Sobrien	if (!c->algo)
1237130561Sobrien		c->algo = strdup("");
1238218822Sdim
1239130561Sobrien	if (asprintf(&options, "%s%s%s%s",
1240130561Sobrien		     *c->algo? ",algorithm=" : "", c->algo,
124133965Sjdp		     c->opaque? ",opaque=" : "", c->opaque?c->opaque:"")== -1)
124233965Sjdp		return (-1);
1243218822Sdim
1244218822Sdim	if (!c->qop) {
1245218822Sdim		c->qop = strdup("");
1246218822Sdim		*noncecount = 0;
1247218822Sdim		*cnonce = 0;
1248218822Sdim	} else {
1249218822Sdim		c->nc++;
1250218822Sdim		sprintf(noncecount, "%08x", c->nc);
1251218822Sdim		/* We don't try very hard with the cnonce ... */
1252218822Sdim		sprintf(cnonce, "%x%lx", getpid(), (unsigned long)time(0));
1253218822Sdim	}
1254218822Sdim
1255218822Sdim	HASHHEX HA1;
1256218822Sdim	DigestCalcHA1(c->algo, parms->user, c->realm,
1257218822Sdim		      parms->password, c->nonce, cnonce, HA1);
1258218822Sdim	DEBUG(fprintf(stderr, "HA1: [%s]\n", HA1));
1259218822Sdim	HASHHEX digest;
1260218822Sdim	DigestCalcResponse(HA1, c->nonce, noncecount, cnonce, c->qop,
1261218822Sdim			   "GET", url->doc, "", digest);
1262218822Sdim
1263218822Sdim	if (c->qop[0]) {
1264218822Sdim		r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
1265218822Sdim			     "nonce=\"%s\",uri=\"%s\",response=\"%s\","
1266218822Sdim			     "qop=\"auth\", cnonce=\"%s\", nc=%s%s",
1267218822Sdim			     hdr, parms->user, c->realm,
1268218822Sdim			     c->nonce, url->doc, digest,
1269218822Sdim			     cnonce, noncecount, options);
1270218822Sdim	} else {
1271218822Sdim		r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
1272218822Sdim			     "nonce=\"%s\",uri=\"%s\",response=\"%s\"%s",
1273218822Sdim			     hdr, parms->user, c->realm,
1274218822Sdim			     c->nonce, url->doc, digest, options);
1275218822Sdim	}
1276218822Sdim	if (options)
1277218822Sdim		free(options);
1278218822Sdim	return (r);
1279218822Sdim}
1280218822Sdim
1281218822Sdim/*
1282218822Sdim * Encode username and password
1283218822Sdim */
1284218822Sdimstatic int
1285218822Sdimhttp_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
1286218822Sdim{
1287218822Sdim	char *upw, *auth;
1288218822Sdim	int r;
1289218822Sdim
1290218822Sdim	DEBUG(fprintf(stderr, "basic: usr: [%s]\n", usr));
1291218822Sdim	DEBUG(fprintf(stderr, "basic: pwd: [%s]\n", pwd));
1292218822Sdim	if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
1293218822Sdim		return (-1);
1294218822Sdim	auth = http_base64(upw);
1295218822Sdim	free(upw);
1296218822Sdim	if (auth == NULL)
1297218822Sdim		return (-1);
1298218822Sdim	r = http_cmd(conn, "%s: Basic %s", hdr, auth);
1299218822Sdim	free(auth);
1300218822Sdim	return (r);
1301218822Sdim}
1302218822Sdim
1303218822Sdim/*
1304218822Sdim * Chose the challenge to answer and call the appropriate routine to
1305218822Sdim * produce the header.
1306218822Sdim */
1307218822Sdimstatic int
1308218822Sdimhttp_authorize(conn_t *conn, const char *hdr, http_auth_challenges_t *cs,
1309218822Sdim	       http_auth_params_t *parms, struct url *url)
1310218822Sdim{
1311130561Sobrien	http_auth_challenge_t *basic = NULL;
1312130561Sobrien	http_auth_challenge_t *digest = NULL;
131333965Sjdp	int i;
1314130561Sobrien
1315130561Sobrien	/* If user or pass are null we're not happy */
131633965Sjdp	if (!parms->user || !parms->password) {
131733965Sjdp		DEBUG(fprintf(stderr, "NULL usr or pass\n"));
131833965Sjdp		return (-1);
131933965Sjdp	}
1320130561Sobrien
132133965Sjdp	/* Look for a Digest and a Basic challenge */
132233965Sjdp	for (i = 0; i < cs->count; i++) {
132360484Sobrien		if (cs->challenges[i]->scheme == HTTPAS_BASIC)
1324130561Sobrien			basic = cs->challenges[i];
1325130561Sobrien		if (cs->challenges[i]->scheme == HTTPAS_DIGEST)
1326130561Sobrien			digest = cs->challenges[i];
132733965Sjdp	}
132877298Sobrien
132977298Sobrien	/* Error if "Digest" was specified and there is no Digest challenge */
133077298Sobrien	if (!digest && (parms->scheme &&
1331130561Sobrien			!strcasecmp(parms->scheme, "digest"))) {
1332130561Sobrien		DEBUG(fprintf(stderr,
1333130561Sobrien			      "Digest auth in env, not supported by peer\n"));
133477298Sobrien		return (-1);
1335130561Sobrien	}
1336130561Sobrien	/*
133777298Sobrien	 * If "basic" was specified in the environment, or there is no Digest
133860484Sobrien	 * challenge, do the basic thing. Don't need a challenge for this,
133933965Sjdp	 * so no need to check basic!=NULL
1340218822Sdim	 */
1341218822Sdim	if (!digest || (parms->scheme && !strcasecmp(parms->scheme,"basic")))
134260484Sobrien		return (http_basic_auth(conn,hdr,parms->user,parms->password));
134333965Sjdp
1344218822Sdim	/* Else, prefer digest. We just checked that it's not NULL */
1345218822Sdim	return (http_digest_auth(conn, hdr, digest, parms, url));
134633965Sjdp}
1347218822Sdim
1348218822Sdim/*****************************************************************************
1349218822Sdim * Helper functions for connecting to a server or proxy
1350218822Sdim */
1351218822Sdim
1352218822Sdim/*
1353218822Sdim * Connect to the correct HTTP server or proxy.
135433965Sjdp */
1355104834Sobrienstatic conn_t *
1356104834Sobrienhttp_connect(struct url *URL, struct url *purl, const char *flags)
135789857Sobrien{
135889857Sobrien	conn_t *conn;
1359130561Sobrien	int verbose;
1360130561Sobrien	int af, val;
1361130561Sobrien
1362130561Sobrien#ifdef INET6
1363130561Sobrien	af = AF_UNSPEC;
1364130561Sobrien#else
1365130561Sobrien	af = AF_INET;
136689857Sobrien#endif
1367130561Sobrien
1368130561Sobrien	verbose = CHECK_FLAG('v');
1369218822Sdim	if (CHECK_FLAG('4'))
1370130561Sobrien		af = AF_INET;
1371130561Sobrien#ifdef INET6
137289857Sobrien	else if (CHECK_FLAG('6'))
137333965Sjdp		af = AF_INET6;
137489857Sobrien#endif
1375130561Sobrien
1376130561Sobrien	if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
1377130561Sobrien		URL = purl;
1378130561Sobrien	} else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
1379130561Sobrien		/* can't talk http to an ftp server */
1380130561Sobrien		/* XXX should set an error code */
1381130561Sobrien		return (NULL);
1382218822Sdim	}
1383218822Sdim
1384130561Sobrien	if ((conn = fetch_connect(URL->host, URL->port, af, verbose)) == NULL)
1385218822Sdim		/* fetch_connect() has already set an error code */
1386218822Sdim		return (NULL);
1387218822Sdim	if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
1388218822Sdim	    fetch_ssl(conn, verbose) == -1) {
1389130561Sobrien		fetch_close(conn);
139060484Sobrien		/* grrr */
139133965Sjdp		errno = EAUTH;
1392130561Sobrien		fetch_syserr();
1393218822Sdim		return (NULL);
1394130561Sobrien	}
1395130561Sobrien
139633965Sjdp	val = 1;
139733965Sjdp	setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
139833965Sjdp
139960484Sobrien	return (conn);
140033965Sjdp}
140133965Sjdp
140233965Sjdpstatic struct url *
1403218822Sdimhttp_get_proxy(struct url * url, const char *flags)
1404218822Sdim{
1405218822Sdim	struct url *purl;
1406218822Sdim	char *p;
1407218822Sdim
1408218822Sdim	if (flags != NULL && strchr(flags, 'd') != NULL)
1409218822Sdim		return (NULL);
1410218822Sdim	if (fetch_no_proxy_match(url->host))
1411218822Sdim		return (NULL);
1412218822Sdim	if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
1413218822Sdim	    *p && (purl = fetchParseURL(p))) {
1414218822Sdim		if (!*purl->scheme)
1415218822Sdim			strcpy(purl->scheme, SCHEME_HTTP);
1416218822Sdim		if (!purl->port)
1417218822Sdim			purl->port = fetch_default_proxy_port(purl->scheme);
1418218822Sdim		if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
1419218822Sdim			return (purl);
1420218822Sdim		fetchFreeURL(purl);
142160484Sobrien	}
142233965Sjdp	return (NULL);
1423130561Sobrien}
142433965Sjdp
1425218822Sdimstatic void
1426218822Sdimhttp_print_html(FILE *out, FILE *in)
142733965Sjdp{
142833965Sjdp	size_t len;
142933965Sjdp	char *line, *p, *q;
143033965Sjdp	int comment, tag;
143133965Sjdp
143233965Sjdp	comment = tag = 0;
143333965Sjdp	while ((line = fgetln(in, &len)) != NULL) {
1434130561Sobrien		while (len && isspace((unsigned char)line[len - 1]))
1435130561Sobrien			--len;
1436130561Sobrien		for (p = q = line; q < line + len; ++q) {
1437130561Sobrien			if (comment && *q == '-') {
1438130561Sobrien				if (q + 2 < line + len &&
143933965Sjdp				    strcmp(q, "-->") == 0) {
1440218822Sdim					tag = comment = 0;
1441130561Sobrien					q += 2;
1442130561Sobrien				}
1443218822Sdim			} else if (tag && !comment && *q == '>') {
1444218822Sdim				p = q + 1;
1445218822Sdim				tag = 0;
1446218822Sdim			} else if (!tag && *q == '<') {
1447218822Sdim				if (q > p)
1448218822Sdim					fwrite(p, q - p, 1, out);
1449218822Sdim				tag = 1;
1450218822Sdim				if (q + 3 < line + len &&
1451130561Sobrien				    strcmp(q, "<!--") == 0) {
1452218822Sdim					comment = 1;
1453218822Sdim					q += 3;
1454218822Sdim				}
1455218822Sdim			}
1456218822Sdim		}
1457218822Sdim		if (!tag && q > p)
1458218822Sdim			fwrite(p, q - p, 1, out);
1459218822Sdim		fputc('\n', out);
1460218822Sdim	}
146133965Sjdp}
1462218822Sdim
1463130561Sobrien
1464130561Sobrien/*****************************************************************************
1465130561Sobrien * Core
1466130561Sobrien */
146760484Sobrien
1468130561Sobrien/*
1469130561Sobrien * Send a request and process the reply
1470130561Sobrien *
1471130561Sobrien * XXX This function is way too long, the do..while loop should be split
1472130561Sobrien * XXX off into a separate function.
1473130561Sobrien */
1474130561SobrienFILE *
1475130561Sobrienhttp_request(struct url *URL, const char *op, struct url_stat *us,
1476130561Sobrien	struct url *purl, const char *flags)
147733965Sjdp{
1478130561Sobrien	char timebuf[80];
147933965Sjdp	char hbuf[MAXHOSTNAMELEN + 7], *host;
1480130561Sobrien	conn_t *conn;
148160484Sobrien	struct url *url, *new;
1482130561Sobrien	int chunked, direct, ims, noredirect, verbose;
1483130561Sobrien	int e, i, n, val;
1484130561Sobrien	off_t offset, clength, length, size;
148560484Sobrien	time_t mtime;
1486130561Sobrien	const char *p;
1487130561Sobrien	FILE *f;
148860484Sobrien	hdr_t h;
148933965Sjdp	struct tm *timestruct;
149033965Sjdp	http_headerbuf_t headerbuf;
149133965Sjdp	http_auth_challenges_t server_challenges;
149233965Sjdp	http_auth_challenges_t proxy_challenges;
149333965Sjdp
1494130561Sobrien	/* The following calls don't allocate anything */
1495130561Sobrien	init_http_headerbuf(&headerbuf);
1496130561Sobrien	init_http_auth_challenges(&server_challenges);
1497130561Sobrien	init_http_auth_challenges(&proxy_challenges);
1498130561Sobrien
1499130561Sobrien	direct = CHECK_FLAG('d');
1500130561Sobrien	noredirect = CHECK_FLAG('A');
1501130561Sobrien	verbose = CHECK_FLAG('v');
1502130561Sobrien	ims = CHECK_FLAG('i');
1503130561Sobrien
1504130561Sobrien	if (direct && purl) {
1505218822Sdim		fetchFreeURL(purl);
1506218822Sdim		purl = NULL;
1507218822Sdim	}
1508218822Sdim
1509218822Sdim	/* try the provided URL first */
1510218822Sdim	url = URL;
1511218822Sdim
1512218822Sdim	/* if the A flag is set, we only get one try */
1513218822Sdim	n = noredirect ? 1 : MAX_REDIRECT;
1514218822Sdim	i = 0;
1515218822Sdim
1516218822Sdim	e = HTTP_PROTOCOL_ERROR;
1517218822Sdim	do {
1518218822Sdim		new = NULL;
1519218822Sdim		chunked = 0;
1520218822Sdim		offset = 0;
1521218822Sdim		clength = -1;
1522218822Sdim		length = -1;
1523218822Sdim		size = -1;
1524218822Sdim		mtime = 0;
1525218822Sdim
1526218822Sdim		/* check port */
1527218822Sdim		if (!url->port)
1528218822Sdim			url->port = fetch_default_port(url->scheme);
1529218822Sdim
1530218822Sdim		/* were we redirected to an FTP URL? */
1531218822Sdim		if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
1532218822Sdim			if (strcmp(op, "GET") == 0)
1533218822Sdim				return (ftp_request(url, "RETR", us, purl, flags));
1534218822Sdim			else if (strcmp(op, "HEAD") == 0)
1535218822Sdim				return (ftp_request(url, "STAT", us, purl, flags));
1536218822Sdim		}
1537218822Sdim
1538218822Sdim		/* connect to server or proxy */
1539218822Sdim		if ((conn = http_connect(url, purl, flags)) == NULL)
1540218822Sdim			goto ouch;
1541218822Sdim
1542218822Sdim		host = url->host;
1543218822Sdim#ifdef INET6
1544218822Sdim		if (strchr(url->host, ':')) {
1545222204Sbenl			snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
1546222204Sbenl			host = hbuf;
1547218822Sdim		}
1548130561Sobrien#endif
1549130561Sobrien		if (url->port != fetch_default_port(url->scheme)) {
1550218822Sdim			if (host != hbuf) {
1551218822Sdim				strcpy(hbuf, host);
155233965Sjdp				host = hbuf;
155333965Sjdp			}
155433965Sjdp			snprintf(hbuf + strlen(hbuf),
155533965Sjdp			    sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
155633965Sjdp		}
155733965Sjdp
155833965Sjdp		/* send request */
155933965Sjdp		if (verbose)
156033965Sjdp			fetch_info("requesting %s://%s%s",
156133965Sjdp			    url->scheme, host, url->doc);
156233965Sjdp		if (purl) {
1563130561Sobrien			http_cmd(conn, "%s %s://%s%s HTTP/1.1",
156433965Sjdp			    op, url->scheme, host, url->doc);
1565130561Sobrien		} else {
156633965Sjdp			http_cmd(conn, "%s %s HTTP/1.1",
156738889Sjdp			    op, url->doc);
156833965Sjdp		}
1569130561Sobrien
157033965Sjdp		if (ims && url->ims_time) {
157133965Sjdp			timestruct = gmtime((time_t *)&url->ims_time);
157233965Sjdp			(void)strftime(timebuf, 80, "%a, %d %b %Y %T GMT",
157333965Sjdp			    timestruct);
157433965Sjdp			if (verbose)
157533965Sjdp				fetch_info("If-Modified-Since: %s", timebuf);
157633965Sjdp			http_cmd(conn, "If-Modified-Since: %s", timebuf);
157733965Sjdp		}
157833965Sjdp		/* virtual host */
157933965Sjdp		http_cmd(conn, "Host: %s", host);
158033965Sjdp
158133965Sjdp		/*
158233965Sjdp		 * Proxy authorization: we only send auth after we received
158333965Sjdp		 * a 407 error. We do not first try basic anyway (changed
158433965Sjdp		 * when support was added for digest-auth)
158533965Sjdp		 */
158638889Sjdp		if (purl && proxy_challenges.valid) {
158738889Sjdp			http_auth_params_t aparams;
158833965Sjdp			init_http_auth_params(&aparams);
158933965Sjdp			if (*purl->user || *purl->pwd) {
159033965Sjdp				aparams.user = purl->user ?
159133965Sjdp					strdup(purl->user) : strdup("");
159233965Sjdp				aparams.password = purl->pwd?
159360484Sobrien					strdup(purl->pwd) : strdup("");
1594104834Sobrien			} else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL &&
1595104834Sobrien				   *p != '\0') {
159633965Sjdp				if (http_authfromenv(p, &aparams) < 0) {
159733965Sjdp					http_seterr(HTTP_NEED_PROXY_AUTH);
159833965Sjdp					goto ouch;
159933965Sjdp				}
160033965Sjdp			}
160133965Sjdp			http_authorize(conn, "Proxy-Authorization",
160233965Sjdp				       &proxy_challenges, &aparams, url);
160333965Sjdp			clean_http_auth_params(&aparams);
160433965Sjdp		}
160533965Sjdp
160633965Sjdp		/*
160733965Sjdp		 * Server authorization: we never send "a priori"
160838889Sjdp		 * Basic auth, which used to be done if user/pass were
160933965Sjdp		 * set in the url. This would be weird because we'd send the
161033965Sjdp		 * password in the clear even if Digest is finally to be
161138889Sjdp		 * used (it would have made more sense for the
161233965Sjdp		 * pre-digest version to do this when Basic was specified
161338889Sjdp		 * in the environment)
161433965Sjdp		 */
161533965Sjdp		if (server_challenges.valid) {
161638889Sjdp			http_auth_params_t aparams;
161733965Sjdp			init_http_auth_params(&aparams);
161860484Sobrien			if (*url->user || *url->pwd) {
1619104834Sobrien				aparams.user = url->user ?
1620104834Sobrien					strdup(url->user) : strdup("");
162133965Sjdp				aparams.password = url->pwd ?
162233965Sjdp					strdup(url->pwd) : strdup("");
162333965Sjdp			} else if ((p = getenv("HTTP_AUTH")) != NULL &&
162433965Sjdp				   *p != '\0') {
162538889Sjdp				if (http_authfromenv(p, &aparams) < 0) {
162638889Sjdp					http_seterr(HTTP_NEED_AUTH);
162738889Sjdp					goto ouch;
162833965Sjdp				}
162933965Sjdp			} else if (fetchAuthMethod &&
163038889Sjdp				   fetchAuthMethod(url) == 0) {
163133965Sjdp				aparams.user = url->user ?
163233965Sjdp					strdup(url->user) : strdup("");
163389857Sobrien				aparams.password = url->pwd ?
163489857Sobrien					strdup(url->pwd) : strdup("");
163560484Sobrien			} else {
163660484Sobrien				http_seterr(HTTP_NEED_AUTH);
163760484Sobrien				goto ouch;
163833965Sjdp			}
163960484Sobrien			http_authorize(conn, "Authorization",
164060484Sobrien				       &server_challenges, &aparams, url);
164160484Sobrien			clean_http_auth_params(&aparams);
1642130561Sobrien		}
164360484Sobrien
1644218822Sdim		/* other headers */
164560484Sobrien		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
164660484Sobrien			if (strcasecmp(p, "auto") == 0)
164760484Sobrien				http_cmd(conn, "Referer: %s://%s%s",
1648218822Sdim				    url->scheme, host, url->doc);
164978828Sobrien			else
165060484Sobrien				http_cmd(conn, "Referer: %s", p);
1651130561Sobrien		}
165260484Sobrien		if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
165360484Sobrien			http_cmd(conn, "User-Agent: %s", p);
165460484Sobrien		else
165560484Sobrien			http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname());
165660484Sobrien		if (url->offset > 0)
165760484Sobrien			http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset);
165860484Sobrien		http_cmd(conn, "Connection: close");
165960484Sobrien		http_cmd(conn, "");
166060484Sobrien
166160484Sobrien		/*
166233965Sjdp		 * Force the queued request to be dispatched.  Normally, one
166360484Sobrien		 * would do this with shutdown(2) but squid proxies can be
166460484Sobrien		 * configured to disallow such half-closed connections.  To
166560484Sobrien		 * be compatible with such configurations, fiddle with socket
166660484Sobrien		 * options to force the pending data to be written.
166760484Sobrien		 */
166860484Sobrien		val = 0;
166960484Sobrien		setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
167060484Sobrien			   sizeof(val));
1671130561Sobrien		val = 1;
1672130561Sobrien		setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
167360484Sobrien			   sizeof(val));
167460484Sobrien
167533965Sjdp		/* get reply */
167660484Sobrien		switch (http_get_reply(conn)) {
167760484Sobrien		case HTTP_OK:
167860484Sobrien		case HTTP_PARTIAL:
167933965Sjdp		case HTTP_NOT_MODIFIED:
168060484Sobrien			/* fine */
1681130561Sobrien			break;
168233965Sjdp		case HTTP_MOVED_PERM:
168333965Sjdp		case HTTP_MOVED_TEMP:
168433965Sjdp		case HTTP_SEE_OTHER:
168533965Sjdp			/*
168633965Sjdp			 * Not so fine, but we still have to read the
168733965Sjdp			 * headers to get the new location.
1688130561Sobrien			 */
168933965Sjdp			break;
169033965Sjdp		case HTTP_NEED_AUTH:
169133965Sjdp			if (server_challenges.valid) {
169233965Sjdp				/*
169333965Sjdp				 * We already sent out authorization code,
169433965Sjdp				 * so there's nothing more we can do.
169533965Sjdp				 */
1696130561Sobrien				http_seterr(conn->err);
1697130561Sobrien				goto ouch;
1698130561Sobrien			}
1699130561Sobrien			/* try again, but send the password this time */
1700130561Sobrien			if (verbose)
1701130561Sobrien				fetch_info("server requires authorization");
170233965Sjdp			break;
170333965Sjdp		case HTTP_NEED_PROXY_AUTH:
170433965Sjdp			if (proxy_challenges.valid) {
1705130561Sobrien				/*
1706130561Sobrien				 * We already sent our proxy
1707130561Sobrien				 * authorization code, so there's
1708130561Sobrien				 * nothing more we can do. */
1709130561Sobrien				http_seterr(conn->err);
1710130561Sobrien				goto ouch;
1711130561Sobrien			}
1712130561Sobrien			/* try again, but send the password this time */
1713130561Sobrien			if (verbose)
1714130561Sobrien				fetch_info("proxy requires authorization");
171533965Sjdp			break;
171633965Sjdp		case HTTP_BAD_RANGE:
171733965Sjdp			/*
171833965Sjdp			 * This can happen if we ask for 0 bytes because
171933965Sjdp			 * we already have the whole file.  Consider this
172033965Sjdp			 * a success for now, and check sizes later.
172133965Sjdp			 */
172233965Sjdp			break;
1723130561Sobrien		case HTTP_PROTOCOL_ERROR:
1724130561Sobrien			/* fall through */
172533965Sjdp		case -1:
172633965Sjdp			fetch_syserr();
172733965Sjdp			goto ouch;
172833965Sjdp		default:
172933965Sjdp			http_seterr(conn->err);
173033965Sjdp			if (!verbose)
173133965Sjdp				goto ouch;
173233965Sjdp			/* fall through so we can get the full error message */
173333965Sjdp		}
173433965Sjdp
173533965Sjdp		/* get headers. http_next_header expects one line readahead */
173689857Sobrien		if (fetch_getln(conn) == -1) {
173733965Sjdp		    fetch_syserr();
173833965Sjdp		    goto ouch;
173933965Sjdp		}
174033965Sjdp		do {
174133965Sjdp		    switch ((h = http_next_header(conn, &headerbuf, &p))) {
174233965Sjdp			case hdr_syserror:
174333965Sjdp				fetch_syserr();
174433965Sjdp				goto ouch;
174560484Sobrien			case hdr_error:
174633965Sjdp				http_seterr(HTTP_PROTOCOL_ERROR);
174733965Sjdp				goto ouch;
1748130561Sobrien			case hdr_content_length:
1749130561Sobrien				http_parse_length(p, &clength);
1750130561Sobrien				break;
1751130561Sobrien			case hdr_content_range:
175260484Sobrien				http_parse_range(p, &offset, &length, &size);
175333965Sjdp				break;
175433965Sjdp			case hdr_last_modified:
175533965Sjdp				http_parse_mtime(p, &mtime);
175633965Sjdp				break;
175733965Sjdp			case hdr_location:
175833965Sjdp				if (!HTTP_REDIRECT(conn->err))
175933965Sjdp					break;
1760218822Sdim				if (new)
1761218822Sdim					free(new);
1762218822Sdim				if (verbose)
1763218822Sdim					fetch_info("%d redirect to %s", conn->err, p);
1764218822Sdim				if (*p == '/')
176533965Sjdp					/* absolute path */
176633965Sjdp					new = fetchMakeURL(url->scheme, url->host, url->port, p,
176733965Sjdp					    url->user, url->pwd);
176833965Sjdp				else
1769218822Sdim					new = fetchParseURL(p);
177033965Sjdp				if (new == NULL) {
177160484Sobrien					/* XXX should set an error code */
177260484Sobrien					DEBUG(fprintf(stderr, "failed to parse new URL\n"));
177360484Sobrien					goto ouch;
1774130561Sobrien				}
177533965Sjdp				if (!*new->user && !*new->pwd) {
177689857Sobrien					strcpy(new->user, url->user);
177789857Sobrien					strcpy(new->pwd, url->pwd);
177889857Sobrien				}
177989857Sobrien				new->offset = url->offset;
1780218822Sdim				new->length = url->length;
1781218822Sdim				break;
1782218822Sdim			case hdr_transfer_encoding:
1783218822Sdim				/* XXX weak test*/
1784218822Sdim				chunked = (strcasecmp(p, "chunked") == 0);
1785218822Sdim				break;
1786218822Sdim			case hdr_www_authenticate:
1787218822Sdim				if (conn->err != HTTP_NEED_AUTH)
1788218822Sdim					break;
1789218822Sdim				if (http_parse_authenticate(p, &server_challenges) == 0)
1790218822Sdim					++n;
1791218822Sdim				break;
1792218822Sdim			case hdr_proxy_authenticate:
1793218822Sdim				if (conn->err != HTTP_NEED_PROXY_AUTH)
1794218822Sdim					break;
1795130561Sobrien				if (http_parse_authenticate(p, &proxy_challenges) == 0)
1796130561Sobrien					++n;
179733965Sjdp				break;
179833965Sjdp			case hdr_end:
179933965Sjdp				/* fall through */
1800218822Sdim			case hdr_unknown:
1801218822Sdim				/* ignore */
1802218822Sdim				break;
1803218822Sdim			}
180433965Sjdp		} while (h > hdr_end);
180533965Sjdp
1806218822Sdim		/* we need to provide authentication */
1807218822Sdim		if (conn->err == HTTP_NEED_AUTH ||
180833965Sjdp		    conn->err == HTTP_NEED_PROXY_AUTH) {
180933965Sjdp			e = conn->err;
181033965Sjdp			if ((conn->err == HTTP_NEED_AUTH &&
181133965Sjdp			     !server_challenges.valid) ||
181291041Sobrien			    (conn->err == HTTP_NEED_PROXY_AUTH &&
181333965Sjdp			     !proxy_challenges.valid)) {
181433965Sjdp				/* 401/7 but no www/proxy-authenticate ?? */
181533965Sjdp				DEBUG(fprintf(stderr, "401/7 and no auth header\n"));
181633965Sjdp				goto ouch;
1817218822Sdim			}
181833965Sjdp			fetch_close(conn);
181933965Sjdp			conn = NULL;
1820218822Sdim			continue;
1821218822Sdim		}
1822218822Sdim
1823218822Sdim		/* requested range not satisfiable */
1824130561Sobrien		if (conn->err == HTTP_BAD_RANGE) {
182533965Sjdp			if (url->offset == size && url->length == 0) {
182633965Sjdp				/* asked for 0 bytes; fake it */
182733965Sjdp				offset = url->offset;
182833965Sjdp				clength = -1;
182933965Sjdp				conn->err = HTTP_OK;
183089857Sobrien				break;
183189857Sobrien			} else {
183289857Sobrien				http_seterr(conn->err);
183389857Sobrien				goto ouch;
1834130561Sobrien			}
183533965Sjdp		}
183691041Sobrien
183791041Sobrien		/* we have a hit or an error */
183833965Sjdp		if (conn->err == HTTP_OK
183960484Sobrien		    || conn->err == HTTP_NOT_MODIFIED
184060484Sobrien		    || conn->err == HTTP_PARTIAL
1841130561Sobrien		    || HTTP_ERROR(conn->err))
184233965Sjdp			break;
184391041Sobrien
184491041Sobrien		/* all other cases: we got a redirect */
184591041Sobrien		e = conn->err;
184691041Sobrien		clean_http_auth_challenges(&server_challenges);
184791041Sobrien		fetch_close(conn);
184891041Sobrien		conn = NULL;
184991041Sobrien		if (!new) {
1850218822Sdim			DEBUG(fprintf(stderr, "redirect with no new location\n"));
1851218822Sdim			break;
1852218822Sdim		}
1853218822Sdim		if (url != URL)
1854130561Sobrien			fetchFreeURL(url);
1855130561Sobrien		url = new;
185691041Sobrien	} while (++i < n);
185791041Sobrien
185891041Sobrien	/* we failed, or ran out of retries */
185991041Sobrien	if (conn == NULL) {
186091041Sobrien		http_seterr(e);
186191041Sobrien		goto ouch;
186291041Sobrien	}
186391041Sobrien
186460484Sobrien	DEBUG(fprintf(stderr, "offset %lld, length %lld,"
186560484Sobrien		  " size %lld, clength %lld\n",
186660484Sobrien		  (long long)offset, (long long)length,
186789857Sobrien		  (long long)size, (long long)clength));
186860484Sobrien
186960484Sobrien	if (conn->err == HTTP_NOT_MODIFIED) {
187060484Sobrien		http_seterr(HTTP_NOT_MODIFIED);
187160484Sobrien		return (NULL);
187260484Sobrien	}
1873130561Sobrien
187433965Sjdp	/* check for inconsistencies */
187533965Sjdp	if (clength != -1 && length != -1 && clength != length) {
1876218822Sdim		http_seterr(HTTP_PROTOCOL_ERROR);
187733965Sjdp		goto ouch;
187833965Sjdp	}
1879130561Sobrien	if (clength == -1)
1880218822Sdim		clength = length;
1881218822Sdim	if (clength != -1)
1882218822Sdim		length = offset + clength;
1883218822Sdim	if (length != -1 && size != -1 && length != size) {
1884218822Sdim		http_seterr(HTTP_PROTOCOL_ERROR);
1885218822Sdim		goto ouch;
1886218822Sdim	}
188760484Sobrien	if (size == -1)
1888218822Sdim		size = length;
1889218822Sdim
1890218822Sdim	/* fill in stats */
1891218822Sdim	if (us) {
1892218822Sdim		us->size = size;
1893218822Sdim		us->atime = us->mtime = mtime;
1894218822Sdim	}
1895218822Sdim
1896218822Sdim	/* too far? */
1897218822Sdim	if (URL->offset > 0 && offset > URL->offset) {
1898218822Sdim		http_seterr(HTTP_PROTOCOL_ERROR);
1899218822Sdim		goto ouch;
1900218822Sdim	}
1901218822Sdim
1902218822Sdim	/* report back real offset and size */
1903218822Sdim	URL->offset = offset;
1904218822Sdim	URL->length = clength;
1905218822Sdim
1906218822Sdim	/* wrap it up in a FILE */
190733965Sjdp	if ((f = http_funopen(conn, chunked)) == NULL) {
1908218822Sdim		fetch_syserr();
1909218822Sdim		goto ouch;
1910218822Sdim	}
1911218822Sdim
1912218822Sdim	if (url != URL)
1913218822Sdim		fetchFreeURL(url);
1914218822Sdim	if (purl)
1915218822Sdim		fetchFreeURL(purl);
1916218822Sdim
1917218822Sdim	if (HTTP_ERROR(conn->err)) {
1918218822Sdim		http_print_html(stderr, f);
1919218822Sdim		fclose(f);
192033965Sjdp		f = NULL;
192133965Sjdp	}
1922130561Sobrien	clean_http_headerbuf(&headerbuf);
1923130561Sobrien	clean_http_auth_challenges(&server_challenges);
1924130561Sobrien	clean_http_auth_challenges(&proxy_challenges);
1925130561Sobrien	return (f);
1926130561Sobrien
1927130561Sobrienouch:
1928130561Sobrien	if (url != URL)
1929130561Sobrien		fetchFreeURL(url);
1930130561Sobrien	if (purl)
193133965Sjdp		fetchFreeURL(purl);
1932130561Sobrien	if (conn != NULL)
1933130561Sobrien		fetch_close(conn);
193433965Sjdp	clean_http_headerbuf(&headerbuf);
1935130561Sobrien	clean_http_auth_challenges(&server_challenges);
193633965Sjdp	clean_http_auth_challenges(&proxy_challenges);
1937130561Sobrien	return (NULL);
1938218822Sdim}
193933965Sjdp
1940130561Sobrien
194133965Sjdp/*****************************************************************************
1942130561Sobrien * Entry points
194333965Sjdp */
1944130561Sobrien
1945130561Sobrien/*
194633965Sjdp * Retrieve and stat a file by HTTP
1947130561Sobrien */
194833965SjdpFILE *
194933965SjdpfetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
195060484Sobrien{
195133965Sjdp	return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
195233965Sjdp}
195360484Sobrien
195433965Sjdp/*
195533965Sjdp * Retrieve a file by HTTP
195633965Sjdp */
195733965SjdpFILE *
195891041SobrienfetchGetHTTP(struct url *URL, const char *flags)
195991041Sobrien{
196091041Sobrien	return (fetchXGetHTTP(URL, NULL, flags));
196191041Sobrien}
196291041Sobrien
196391041Sobrien/*
196491041Sobrien * Store a file by HTTP
196533965Sjdp */
196633965SjdpFILE *
196733965SjdpfetchPutHTTP(struct url *URL __unused, const char *flags __unused)
196833965Sjdp{
196933965Sjdp	warnx("fetchPutHTTP(): not implemented");
197033965Sjdp	return (NULL);
197133965Sjdp}
1972130561Sobrien
1973130561Sobrien/*
197433965Sjdp * Get an HTTP document's metadata
197533965Sjdp */
1976130561Sobrienint
1977130561SobrienfetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
197833965Sjdp{
1979130561Sobrien	FILE *f;
1980130561Sobrien
1981130561Sobrien	f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
1982130561Sobrien	if (f == NULL)
1983130561Sobrien		return (-1);
1984130561Sobrien	fclose(f);
198533965Sjdp	return (0);
198633965Sjdp}
198733965Sjdp
198833965Sjdp/*
198960484Sobrien * List a directory
199033965Sjdp */
199133965Sjdpstruct url_ent *
199233965SjdpfetchListHTTP(struct url *url __unused, const char *flags __unused)
1993218822Sdim{
199433965Sjdp	warnx("fetchListHTTP(): not implemented");
199533965Sjdp	return (NULL);
199633965Sjdp}
199733965Sjdp