http.c revision 225814
1275970Scy/*-
2275970Scy * Copyright (c) 2000-2011 Dag-Erling Sm�rgrav
3275970Scy * All rights reserved.
4275970Scy *
5275970Scy * Redistribution and use in source and binary forms, with or without
6275970Scy * modification, are permitted provided that the following conditions
7275970Scy * are met:
8275970Scy * 1. Redistributions of source code must retain the above copyright
9275970Scy *    notice, this list of conditions and the following disclaimer
10275970Scy *    in this position and unchanged.
11275970Scy * 2. Redistributions in binary form must reproduce the above copyright
12275970Scy *    notice, this list of conditions and the following disclaimer in the
13275970Scy *    documentation and/or other materials provided with the distribution.
14275970Scy * 3. The name of the author may not be used to endorse or promote products
15275970Scy *    derived from this software without specific prior written permission.
16275970Scy *
17275970Scy * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18275970Scy * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19275970Scy * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20275970Scy * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21275970Scy * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22275970Scy * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23275970Scy * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24275970Scy * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25275970Scy * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26275970Scy * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27275970Scy */
28275970Scy
29275970Scy#include <sys/cdefs.h>
30275970Scy__FBSDID("$FreeBSD: head/lib/libfetch/http.c 225814 2011-09-27 18:57:26Z des $");
31275970Scy
32275970Scy/*
33275970Scy * The following copyright applies to the base64 code:
34275970Scy *
35275970Scy *-
36275970Scy * Copyright 1997 Massachusetts Institute of Technology
37275970Scy *
38275970Scy * Permission to use, copy, modify, and distribute this software and
39275970Scy * its documentation for any purpose and without fee is hereby
40275970Scy * granted, provided that both the above copyright notice and this
41275970Scy * permission notice appear in all copies, that both the above
42275970Scy * copyright notice and this permission notice appear in all
43275970Scy * supporting documentation, and that the name of M.I.T. not be used
44275970Scy * in advertising or publicity pertaining to distribution of the
45275970Scy * software without specific, written prior permission.  M.I.T. makes
46275970Scy * no representations about the suitability of this software for any
47275970Scy * purpose.  It is provided "as is" without express or implied
48275970Scy * warranty.
49275970Scy *
50275970Scy * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
51275970Scy * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
52275970Scy * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
53275970Scy * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
54275970Scy * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
55275970Scy * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
56275970Scy * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
57275970Scy * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
58275970Scy * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
59275970Scy * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
60275970Scy * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
61275970Scy * SUCH DAMAGE.
62275970Scy */
63275970Scy
64275970Scy#include <sys/param.h>
65275970Scy#include <sys/socket.h>
66275970Scy#include <sys/time.h>
67275970Scy
68275970Scy#include <ctype.h>
69275970Scy#include <err.h>
70275970Scy#include <errno.h>
71275970Scy#include <locale.h>
72275970Scy#include <netdb.h>
73275970Scy#include <stdarg.h>
74275970Scy#include <stdio.h>
75275970Scy#include <stdlib.h>
76275970Scy#include <string.h>
77275970Scy#include <time.h>
78275970Scy#include <unistd.h>
79275970Scy#include <md5.h>
80275970Scy
81275970Scy#include <netinet/in.h>
82275970Scy#include <netinet/tcp.h>
83275970Scy
84275970Scy#include "fetch.h"
85275970Scy#include "common.h"
86275970Scy#include "httperr.h"
87275970Scy
88275970Scy/* Maximum number of redirects to follow */
89275970Scy#define MAX_REDIRECT 5
90275970Scy
91275970Scy/* Symbolic names for reply codes we care about */
92275970Scy#define HTTP_OK			200
93275970Scy#define HTTP_PARTIAL		206
94275970Scy#define HTTP_MOVED_PERM		301
95275970Scy#define HTTP_MOVED_TEMP		302
96275970Scy#define HTTP_SEE_OTHER		303
97275970Scy#define HTTP_NOT_MODIFIED	304
98275970Scy#define HTTP_TEMP_REDIRECT	307
99275970Scy#define HTTP_NEED_AUTH		401
100275970Scy#define HTTP_NEED_PROXY_AUTH	407
101275970Scy#define HTTP_BAD_RANGE		416
102275970Scy#define HTTP_PROTOCOL_ERROR	999
103275970Scy
104275970Scy#define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
105275970Scy			    || (xyz) == HTTP_MOVED_TEMP \
106275970Scy			    || (xyz) == HTTP_TEMP_REDIRECT \
107275970Scy			    || (xyz) == HTTP_SEE_OTHER)
108275970Scy
109275970Scy#define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
110275970Scy
111275970Scy
112275970Scy/*****************************************************************************
113275970Scy * I/O functions for decoding chunked streams
114275970Scy */
115275970Scy
116275970Scystruct httpio
117275970Scy{
118275970Scy	conn_t		*conn;		/* connection */
119275970Scy	int		 chunked;	/* chunked mode */
120275970Scy	char		*buf;		/* chunk buffer */
121275970Scy	size_t		 bufsize;	/* size of chunk buffer */
122275970Scy	ssize_t		 buflen;	/* amount of data currently in buffer */
123275970Scy	int		 bufpos;	/* current read offset in buffer */
124275970Scy	int		 eof;		/* end-of-file flag */
125275970Scy	int		 error;		/* error flag */
126275970Scy	size_t		 chunksize;	/* remaining size of current chunk */
127275970Scy#ifndef NDEBUG
128275970Scy	size_t		 total;
129275970Scy#endif
130275970Scy};
131275970Scy
132275970Scy/*
133275970Scy * Get next chunk header
134275970Scy */
135275970Scystatic int
136275970Scyhttp_new_chunk(struct httpio *io)
137275970Scy{
138275970Scy	char *p;
139275970Scy
140275970Scy	if (fetch_getln(io->conn) == -1)
141275970Scy		return (-1);
142275970Scy
143275970Scy	if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
144275970Scy		return (-1);
145275970Scy
146275970Scy	for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
147275970Scy		if (*p == ';')
148275970Scy			break;
149275970Scy		if (!isxdigit((unsigned char)*p))
150275970Scy			return (-1);
151275970Scy		if (isdigit((unsigned char)*p)) {
152275970Scy			io->chunksize = io->chunksize * 16 +
153275970Scy			    *p - '0';
154275970Scy		} else {
155275970Scy			io->chunksize = io->chunksize * 16 +
156275970Scy			    10 + tolower((unsigned char)*p) - 'a';
157275970Scy		}
158275970Scy	}
159275970Scy
160275970Scy#ifndef NDEBUG
161275970Scy	if (fetchDebug) {
162275970Scy		io->total += io->chunksize;
163275970Scy		if (io->chunksize == 0)
164275970Scy			fprintf(stderr, "%s(): end of last chunk\n", __func__);
165275970Scy		else
166275970Scy			fprintf(stderr, "%s(): new chunk: %lu (%lu)\n",
167275970Scy			    __func__, (unsigned long)io->chunksize,
168275970Scy			    (unsigned long)io->total);
169275970Scy	}
170275970Scy#endif
171275970Scy
172275970Scy	return (io->chunksize);
173275970Scy}
174275970Scy
175275970Scy/*
176275970Scy * Grow the input buffer to at least len bytes
177275970Scy */
178275970Scystatic inline int
179275970Scyhttp_growbuf(struct httpio *io, size_t len)
180275970Scy{
181275970Scy	char *tmp;
182275970Scy
183275970Scy	if (io->bufsize >= len)
184275970Scy		return (0);
185275970Scy
186275970Scy	if ((tmp = realloc(io->buf, len)) == NULL)
187275970Scy		return (-1);
188275970Scy	io->buf = tmp;
189275970Scy	io->bufsize = len;
190275970Scy	return (0);
191275970Scy}
192275970Scy
193275970Scy/*
194275970Scy * Fill the input buffer, do chunk decoding on the fly
195275970Scy */
196275970Scystatic int
197275970Scyhttp_fillbuf(struct httpio *io, size_t len)
198275970Scy{
199275970Scy	if (io->error)
200275970Scy		return (-1);
201275970Scy	if (io->eof)
202275970Scy		return (0);
203275970Scy
204275970Scy	if (io->chunked == 0) {
205275970Scy		if (http_growbuf(io, len) == -1)
206275970Scy			return (-1);
207275970Scy		if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
208275970Scy			io->error = 1;
209275970Scy			return (-1);
210275970Scy		}
211275970Scy		io->bufpos = 0;
212275970Scy		return (io->buflen);
213275970Scy	}
214275970Scy
215275970Scy	if (io->chunksize == 0) {
216275970Scy		switch (http_new_chunk(io)) {
217275970Scy		case -1:
218275970Scy			io->error = 1;
219275970Scy			return (-1);
220275970Scy		case 0:
221275970Scy			io->eof = 1;
222275970Scy			return (0);
223275970Scy		}
224275970Scy	}
225275970Scy
226275970Scy	if (len > io->chunksize)
227275970Scy		len = io->chunksize;
228275970Scy	if (http_growbuf(io, len) == -1)
229275970Scy		return (-1);
230275970Scy	if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
231275970Scy		io->error = 1;
232275970Scy		return (-1);
233275970Scy	}
234275970Scy	io->chunksize -= io->buflen;
235275970Scy
236275970Scy	if (io->chunksize == 0) {
237275970Scy		char endl[2];
238275970Scy
239275970Scy		if (fetch_read(io->conn, endl, 2) != 2 ||
240275970Scy		    endl[0] != '\r' || endl[1] != '\n')
241275970Scy			return (-1);
242275970Scy	}
243275970Scy
244275970Scy	io->bufpos = 0;
245275970Scy
246275970Scy	return (io->buflen);
247275970Scy}
248275970Scy
249275970Scy/*
250275970Scy * Read function
251275970Scy */
252275970Scystatic int
253275970Scyhttp_readfn(void *v, char *buf, int len)
254275970Scy{
255275970Scy	struct httpio *io = (struct httpio *)v;
256275970Scy	int l, pos;
257275970Scy
258275970Scy	if (io->error)
259275970Scy		return (-1);
260275970Scy	if (io->eof)
261275970Scy		return (0);
262275970Scy
263275970Scy	for (pos = 0; len > 0; pos += l, len -= l) {
264275970Scy		/* empty buffer */
265275970Scy		if (!io->buf || io->bufpos == io->buflen)
266275970Scy			if (http_fillbuf(io, len) < 1)
267275970Scy				break;
268275970Scy		l = io->buflen - io->bufpos;
269275970Scy		if (len < l)
270275970Scy			l = len;
271275970Scy		memcpy(buf + pos, io->buf + io->bufpos, l);
272275970Scy		io->bufpos += l;
273275970Scy	}
274275970Scy
275275970Scy	if (!pos && io->error)
276275970Scy		return (-1);
277275970Scy	return (pos);
278275970Scy}
279275970Scy
280275970Scy/*
281275970Scy * Write function
282275970Scy */
283275970Scystatic int
284275970Scyhttp_writefn(void *v, const char *buf, int len)
285275970Scy{
286275970Scy	struct httpio *io = (struct httpio *)v;
287275970Scy
288275970Scy	return (fetch_write(io->conn, buf, len));
289275970Scy}
290275970Scy
291275970Scy/*
292275970Scy * Close function
293275970Scy */
294275970Scystatic int
295275970Scyhttp_closefn(void *v)
296275970Scy{
297275970Scy	struct httpio *io = (struct httpio *)v;
298275970Scy	int r;
299275970Scy
300275970Scy	r = fetch_close(io->conn);
301275970Scy	if (io->buf)
302275970Scy		free(io->buf);
303275970Scy	free(io);
304275970Scy	return (r);
305275970Scy}
306275970Scy
307275970Scy/*
308275970Scy * Wrap a file descriptor up
309275970Scy */
310275970Scystatic FILE *
311275970Scyhttp_funopen(conn_t *conn, int chunked)
312275970Scy{
313275970Scy	struct httpio *io;
314275970Scy	FILE *f;
315275970Scy
316275970Scy	if ((io = calloc(1, sizeof(*io))) == NULL) {
317275970Scy		fetch_syserr();
318275970Scy		return (NULL);
319275970Scy	}
320275970Scy	io->conn = conn;
321275970Scy	io->chunked = chunked;
322275970Scy	f = funopen(io, http_readfn, http_writefn, NULL, http_closefn);
323275970Scy	if (f == NULL) {
324275970Scy		fetch_syserr();
325275970Scy		free(io);
326275970Scy		return (NULL);
327275970Scy	}
328275970Scy	return (f);
329275970Scy}
330275970Scy
331275970Scy
332275970Scy/*****************************************************************************
333275970Scy * Helper functions for talking to the server and parsing its replies
334275970Scy */
335275970Scy
336275970Scy/* Header types */
337275970Scytypedef enum {
338275970Scy	hdr_syserror = -2,
339275970Scy	hdr_error = -1,
340275970Scy	hdr_end = 0,
341275970Scy	hdr_unknown = 1,
342275970Scy	hdr_content_length,
343275970Scy	hdr_content_range,
344275970Scy	hdr_last_modified,
345275970Scy	hdr_location,
346275970Scy	hdr_transfer_encoding,
347275970Scy	hdr_www_authenticate,
348275970Scy	hdr_proxy_authenticate,
349275970Scy} hdr_t;
350275970Scy
351275970Scy/* Names of interesting headers */
352275970Scystatic struct {
353275970Scy	hdr_t		 num;
354275970Scy	const char	*name;
355275970Scy} hdr_names[] = {
356275970Scy	{ hdr_content_length,		"Content-Length" },
357275970Scy	{ hdr_content_range,		"Content-Range" },
358275970Scy	{ hdr_last_modified,		"Last-Modified" },
359275970Scy	{ hdr_location,			"Location" },
360275970Scy	{ hdr_transfer_encoding,	"Transfer-Encoding" },
361275970Scy	{ hdr_www_authenticate,		"WWW-Authenticate" },
362275970Scy	{ hdr_proxy_authenticate,	"Proxy-Authenticate" },
363275970Scy	{ hdr_unknown,			NULL },
364275970Scy};
365275970Scy
366275970Scy/*
367275970Scy * Send a formatted line; optionally echo to terminal
368275970Scy */
369275970Scystatic int
370275970Scyhttp_cmd(conn_t *conn, const char *fmt, ...)
371275970Scy{
372275970Scy	va_list ap;
373275970Scy	size_t len;
374275970Scy	char *msg;
375275970Scy	int r;
376275970Scy
377275970Scy	va_start(ap, fmt);
378275970Scy	len = vasprintf(&msg, fmt, ap);
379275970Scy	va_end(ap);
380275970Scy
381275970Scy	if (msg == NULL) {
382275970Scy		errno = ENOMEM;
383275970Scy		fetch_syserr();
384275970Scy		return (-1);
385275970Scy	}
386275970Scy
387275970Scy	r = fetch_putln(conn, msg, len);
388275970Scy	free(msg);
389275970Scy
390275970Scy	if (r == -1) {
391275970Scy		fetch_syserr();
392275970Scy		return (-1);
393275970Scy	}
394275970Scy
395275970Scy	return (0);
396275970Scy}
397275970Scy
398275970Scy/*
399275970Scy * Get and parse status line
400275970Scy */
401275970Scystatic int
402275970Scyhttp_get_reply(conn_t *conn)
403275970Scy{
404275970Scy	char *p;
405275970Scy
406275970Scy	if (fetch_getln(conn) == -1)
407275970Scy		return (-1);
408275970Scy	/*
409275970Scy	 * A valid status line looks like "HTTP/m.n xyz reason" where m
410275970Scy	 * and n are the major and minor protocol version numbers and xyz
411275970Scy	 * is the reply code.
412275970Scy	 * Unfortunately, there are servers out there (NCSA 1.5.1, to name
413275970Scy	 * just one) that do not send a version number, so we can't rely
414275970Scy	 * on finding one, but if we do, insist on it being 1.0 or 1.1.
415275970Scy	 * We don't care about the reason phrase.
416275970Scy	 */
417275970Scy	if (strncmp(conn->buf, "HTTP", 4) != 0)
418275970Scy		return (HTTP_PROTOCOL_ERROR);
419275970Scy	p = conn->buf + 4;
420275970Scy	if (*p == '/') {
421275970Scy		if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
422275970Scy			return (HTTP_PROTOCOL_ERROR);
423275970Scy		p += 4;
424275970Scy	}
425275970Scy	if (*p != ' ' ||
426275970Scy	    !isdigit((unsigned char)p[1]) ||
427275970Scy	    !isdigit((unsigned char)p[2]) ||
428275970Scy	    !isdigit((unsigned char)p[3]))
429275970Scy		return (HTTP_PROTOCOL_ERROR);
430275970Scy
431275970Scy	conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
432275970Scy	return (conn->err);
433275970Scy}
434275970Scy
435275970Scy/*
436275970Scy * Check a header; if the type matches the given string, return a pointer
437275970Scy * to the beginning of the value.
438275970Scy */
439275970Scystatic const char *
440275970Scyhttp_match(const char *str, const char *hdr)
441275970Scy{
442275970Scy	while (*str && *hdr &&
443275970Scy	    tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
444275970Scy		/* nothing */;
445275970Scy	if (*str || *hdr != ':')
446275970Scy		return (NULL);
447275970Scy	while (*hdr && isspace((unsigned char)*++hdr))
448275970Scy		/* nothing */;
449275970Scy	return (hdr);
450275970Scy}
451275970Scy
452275970Scy
453275970Scy/*
454275970Scy * Get the next header and return the appropriate symbolic code.  We
455275970Scy * need to read one line ahead for checking for a continuation line
456275970Scy * belonging to the current header (continuation lines start with
457275970Scy * white space).
458275970Scy *
459275970Scy * We get called with a fresh line already in the conn buffer, either
460275970Scy * from the previous http_next_header() invocation, or, the first
461275970Scy * time, from a fetch_getln() performed by our caller.
462275970Scy *
463275970Scy * This stops when we encounter an empty line (we dont read beyond the header
464275970Scy * area).
465275970Scy *
466275970Scy * Note that the "headerbuf" is just a place to return the result. Its
467275970Scy * contents are not used for the next call. This means that no cleanup
468275970Scy * is needed when ie doing another connection, just call the cleanup when
469275970Scy * fully done to deallocate memory.
470275970Scy */
471275970Scy
472275970Scy/* Limit the max number of continuation lines to some reasonable value */
473275970Scy#define HTTP_MAX_CONT_LINES 10
474275970Scy
475275970Scy/* Place into which to build a header from one or several lines */
476275970Scytypedef struct {
477275970Scy	char	*buf;		/* buffer */
478275970Scy	size_t	 bufsize;	/* buffer size */
479275970Scy	size_t	 buflen;	/* length of buffer contents */
480275970Scy} http_headerbuf_t;
481275970Scy
482275970Scystatic void
483275970Scyinit_http_headerbuf(http_headerbuf_t *buf)
484275970Scy{
485275970Scy	buf->buf = NULL;
486275970Scy	buf->bufsize = 0;
487275970Scy	buf->buflen = 0;
488275970Scy}
489275970Scy
490275970Scystatic void
491275970Scyclean_http_headerbuf(http_headerbuf_t *buf)
492275970Scy{
493275970Scy	if (buf->buf)
494275970Scy		free(buf->buf);
495275970Scy	init_http_headerbuf(buf);
496275970Scy}
497275970Scy
498275970Scy/* Remove whitespace at the end of the buffer */
499275970Scystatic void
500275970Scyhttp_conn_trimright(conn_t *conn)
501275970Scy{
502275970Scy	while (conn->buflen &&
503275970Scy	       isspace((unsigned char)conn->buf[conn->buflen - 1]))
504275970Scy		conn->buflen--;
505275970Scy	conn->buf[conn->buflen] = '\0';
506275970Scy}
507275970Scy
508275970Scystatic hdr_t
509275970Scyhttp_next_header(conn_t *conn, http_headerbuf_t *hbuf, const char **p)
510275970Scy{
511275970Scy	unsigned int i, len;
512275970Scy
513275970Scy	/*
514275970Scy	 * Have to do the stripping here because of the first line. So
515275970Scy	 * it's done twice for the subsequent lines. No big deal
516275970Scy	 */
517275970Scy	http_conn_trimright(conn);
518275970Scy	if (conn->buflen == 0)
519275970Scy		return (hdr_end);
520275970Scy
521275970Scy	/* Copy the line to the headerbuf */
522275970Scy	if (hbuf->bufsize < conn->buflen + 1) {
523275970Scy		if ((hbuf->buf = realloc(hbuf->buf, conn->buflen + 1)) == NULL)
524275970Scy			return (hdr_syserror);
525275970Scy		hbuf->bufsize = conn->buflen + 1;
526275970Scy	}
527275970Scy	strcpy(hbuf->buf, conn->buf);
528275970Scy	hbuf->buflen = conn->buflen;
529275970Scy
530275970Scy	/*
531275970Scy	 * Fetch possible continuation lines. Stop at 1st non-continuation
532275970Scy	 * and leave it in the conn buffer
533275970Scy	 */
534275970Scy	for (i = 0; i < HTTP_MAX_CONT_LINES; i++) {
535275970Scy		if (fetch_getln(conn) == -1)
536275970Scy			return (hdr_syserror);
537275970Scy
538275970Scy		/*
539275970Scy		 * Note: we carry on the idea from the previous version
540275970Scy		 * that a pure whitespace line is equivalent to an empty
541275970Scy		 * one (so it's not continuation and will be handled when
542275970Scy		 * we are called next)
543275970Scy		 */
544275970Scy		http_conn_trimright(conn);
545275970Scy		if (conn->buf[0] != ' ' && conn->buf[0] != "\t"[0])
546275970Scy			break;
547275970Scy
548275970Scy		/* Got a continuation line. Concatenate to previous */
549275970Scy		len = hbuf->buflen + conn->buflen;
550275970Scy		if (hbuf->bufsize < len + 1) {
551275970Scy			len *= 2;
552275970Scy			if ((hbuf->buf = realloc(hbuf->buf, len + 1)) == NULL)
553275970Scy				return (hdr_syserror);
554275970Scy			hbuf->bufsize = len + 1;
555275970Scy		}
556275970Scy		strcpy(hbuf->buf + hbuf->buflen, conn->buf);
557275970Scy		hbuf->buflen += conn->buflen;
558275970Scy	}
559275970Scy
560275970Scy	/*
561275970Scy	 * We could check for malformed headers but we don't really care.
562275970Scy	 * A valid header starts with a token immediately followed by a
563275970Scy	 * colon; a token is any sequence of non-control, non-whitespace
564275970Scy	 * characters except "()<>@,;:\\\"{}".
565275970Scy	 */
566275970Scy	for (i = 0; hdr_names[i].num != hdr_unknown; i++)
567275970Scy		if ((*p = http_match(hdr_names[i].name, hbuf->buf)) != NULL)
568275970Scy			return (hdr_names[i].num);
569275970Scy
570275970Scy	return (hdr_unknown);
571275970Scy}
572275970Scy
573275970Scy/**************************
574275970Scy * [Proxy-]Authenticate header parsing
575275970Scy */
576275970Scy
577275970Scy/*
578275970Scy * Read doublequote-delimited string into output buffer obuf (allocated
579275970Scy * by caller, whose responsibility it is to ensure that it's big enough)
580275970Scy * cp points to the first char after the initial '"'
581275970Scy * Handles \ quoting
582275970Scy * Returns pointer to the first char after the terminating double quote, or
583275970Scy * NULL for error.
584275970Scy */
585275970Scystatic const char *
586275970Scyhttp_parse_headerstring(const char *cp, char *obuf)
587275970Scy{
588275970Scy	for (;;) {
589275970Scy		switch (*cp) {
590275970Scy		case 0: /* Unterminated string */
591275970Scy			*obuf = 0;
592275970Scy			return (NULL);
593275970Scy		case '"': /* Ending quote */
594275970Scy			*obuf = 0;
595275970Scy			return (++cp);
596275970Scy		case '\\':
597275970Scy			if (*++cp == 0) {
598275970Scy				*obuf = 0;
599275970Scy				return (NULL);
600275970Scy			}
601275970Scy			/* FALLTHROUGH */
602275970Scy		default:
603275970Scy			*obuf++ = *cp++;
604275970Scy		}
605275970Scy	}
606275970Scy}
607275970Scy
608275970Scy/* Http auth challenge schemes */
609275970Scytypedef enum {HTTPAS_UNKNOWN, HTTPAS_BASIC,HTTPAS_DIGEST} http_auth_schemes_t;
610275970Scy
611275970Scy/* Data holder for a Basic or Digest challenge. */
612275970Scytypedef struct {
613275970Scy	http_auth_schemes_t scheme;
614275970Scy	char	*realm;
615275970Scy	char	*qop;
616275970Scy	char	*nonce;
617275970Scy	char	*opaque;
618275970Scy	char	*algo;
619275970Scy	int	 stale;
620275970Scy	int	 nc; /* Nonce count */
621275970Scy} http_auth_challenge_t;
622275970Scy
623275970Scystatic void
624275970Scyinit_http_auth_challenge(http_auth_challenge_t *b)
625275970Scy{
626275970Scy	b->scheme = HTTPAS_UNKNOWN;
627275970Scy	b->realm = b->qop = b->nonce = b->opaque = b->algo = NULL;
628275970Scy	b->stale = b->nc = 0;
629275970Scy}
630275970Scy
631275970Scystatic void
632275970Scyclean_http_auth_challenge(http_auth_challenge_t *b)
633275970Scy{
634275970Scy	if (b->realm)
635275970Scy		free(b->realm);
636275970Scy	if (b->qop)
637275970Scy		free(b->qop);
638275970Scy	if (b->nonce)
639275970Scy		free(b->nonce);
640275970Scy	if (b->opaque)
641275970Scy		free(b->opaque);
642275970Scy	if (b->algo)
643275970Scy		free(b->algo);
644275970Scy	init_http_auth_challenge(b);
645275970Scy}
646275970Scy
647275970Scy/* Data holder for an array of challenges offered in an http response. */
648275970Scy#define MAX_CHALLENGES 10
649275970Scytypedef struct {
650275970Scy	http_auth_challenge_t *challenges[MAX_CHALLENGES];
651275970Scy	int	count; /* Number of parsed challenges in the array */
652275970Scy	int	valid; /* We did parse an authenticate header */
653275970Scy} http_auth_challenges_t;
654275970Scy
655275970Scystatic void
656275970Scyinit_http_auth_challenges(http_auth_challenges_t *cs)
657275970Scy{
658275970Scy	int i;
659275970Scy	for (i = 0; i < MAX_CHALLENGES; i++)
660275970Scy		cs->challenges[i] = NULL;
661275970Scy	cs->count = cs->valid = 0;
662275970Scy}
663275970Scy
664275970Scystatic void
665275970Scyclean_http_auth_challenges(http_auth_challenges_t *cs)
666275970Scy{
667275970Scy	int i;
668275970Scy	/* We rely on non-zero pointers being allocated, not on the count */
669275970Scy	for (i = 0; i < MAX_CHALLENGES; i++) {
670275970Scy		if (cs->challenges[i] != NULL) {
671275970Scy			clean_http_auth_challenge(cs->challenges[i]);
672275970Scy			free(cs->challenges[i]);
673275970Scy		}
674275970Scy	}
675275970Scy	init_http_auth_challenges(cs);
676275970Scy}
677275970Scy
678275970Scy/*
679275970Scy * Enumeration for lexical elements. Separators will be returned as their own
680275970Scy * ascii value
681275970Scy */
682275970Scytypedef enum {HTTPHL_WORD=256, HTTPHL_STRING=257, HTTPHL_END=258,
683275970Scy	      HTTPHL_ERROR = 259} http_header_lex_t;
684275970Scy
685275970Scy/*
686275970Scy * Determine what kind of token comes next and return possible value
687275970Scy * in buf, which is supposed to have been allocated big enough by
688275970Scy * caller. Advance input pointer and return element type.
689275970Scy */
690275970Scystatic int
691275970Scyhttp_header_lex(const char **cpp, char *buf)
692275970Scy{
693275970Scy	size_t l;
694275970Scy	/* Eat initial whitespace */
695275970Scy	*cpp += strspn(*cpp, " \t");
696275970Scy	if (**cpp == 0)
697275970Scy		return (HTTPHL_END);
698275970Scy
699275970Scy	/* Separator ? */
700275970Scy	if (**cpp == ',' || **cpp == '=')
701275970Scy		return (*((*cpp)++));
702275970Scy
703275970Scy	/* String ? */
704275970Scy	if (**cpp == '"') {
705275970Scy		*cpp = http_parse_headerstring(++*cpp, buf);
706275970Scy		if (*cpp == NULL)
707275970Scy			return (HTTPHL_ERROR);
708275970Scy		return (HTTPHL_STRING);
709275970Scy	}
710275970Scy
711275970Scy	/* Read other token, until separator or whitespace */
712275970Scy	l = strcspn(*cpp, " \t,=");
713275970Scy	memcpy(buf, *cpp, l);
714275970Scy	buf[l] = 0;
715275970Scy	*cpp += l;
716275970Scy	return (HTTPHL_WORD);
717275970Scy}
718275970Scy
719275970Scy/*
720275970Scy * Read challenges from http xxx-authenticate header and accumulate them
721275970Scy * in the challenges list structure.
722275970Scy *
723275970Scy * Headers with multiple challenges are specified by rfc2617, but
724275970Scy * servers (ie: squid) often send them in separate headers instead,
725275970Scy * which in turn is forbidden by the http spec (multiple headers with
726275970Scy * the same name are only allowed for pure comma-separated lists, see
727275970Scy * rfc2616 sec 4.2).
728275970Scy *
729275970Scy * We support both approaches anyway
730275970Scy */
731275970Scystatic int
732275970Scyhttp_parse_authenticate(const char *cp, http_auth_challenges_t *cs)
733275970Scy{
734275970Scy	int ret = -1;
735275970Scy	http_header_lex_t lex;
736275970Scy	char *key = malloc(strlen(cp) + 1);
737275970Scy	char *value = malloc(strlen(cp) + 1);
738275970Scy	char *buf = malloc(strlen(cp) + 1);
739275970Scy
740275970Scy	if (key == NULL || value == NULL || buf == NULL) {
741275970Scy		fetch_syserr();
742275970Scy		goto out;
743275970Scy	}
744275970Scy
745275970Scy	/* In any case we've seen the header and we set the valid bit */
746275970Scy	cs->valid = 1;
747275970Scy
748275970Scy	/* Need word first */
749275970Scy	lex = http_header_lex(&cp, key);
750275970Scy	if (lex != HTTPHL_WORD)
751275970Scy		goto out;
752275970Scy
753275970Scy	/* Loop on challenges */
754275970Scy	for (; cs->count < MAX_CHALLENGES; cs->count++) {
755275970Scy		cs->challenges[cs->count] =
756275970Scy			malloc(sizeof(http_auth_challenge_t));
757275970Scy		if (cs->challenges[cs->count] == NULL) {
758275970Scy			fetch_syserr();
759275970Scy			goto out;
760275970Scy		}
761275970Scy		init_http_auth_challenge(cs->challenges[cs->count]);
762275970Scy		if (!strcasecmp(key, "basic")) {
763275970Scy			cs->challenges[cs->count]->scheme = HTTPAS_BASIC;
764275970Scy		} else if (!strcasecmp(key, "digest")) {
765275970Scy			cs->challenges[cs->count]->scheme = HTTPAS_DIGEST;
766275970Scy		} else {
767275970Scy			cs->challenges[cs->count]->scheme = HTTPAS_UNKNOWN;
768275970Scy			/*
769275970Scy			 * Continue parsing as basic or digest may
770275970Scy			 * follow, and the syntax is the same for
771275970Scy			 * all. We'll just ignore this one when
772275970Scy			 * looking at the list
773275970Scy			 */
774275970Scy		}
775275970Scy
776275970Scy		/* Loop on attributes */
777275970Scy		for (;;) {
778275970Scy			/* Key */
779275970Scy			lex = http_header_lex(&cp, key);
780275970Scy			if (lex != HTTPHL_WORD)
781275970Scy				goto out;
782275970Scy
783275970Scy			/* Equal sign */
784275970Scy			lex = http_header_lex(&cp, buf);
785275970Scy			if (lex != '=')
786275970Scy				goto out;
787275970Scy
788275970Scy			/* Value */
789275970Scy			lex = http_header_lex(&cp, value);
790275970Scy			if (lex != HTTPHL_WORD && lex != HTTPHL_STRING)
791275970Scy				goto out;
792275970Scy
793275970Scy			if (!strcasecmp(key, "realm"))
794275970Scy				cs->challenges[cs->count]->realm =
795275970Scy					strdup(value);
796275970Scy			else if (!strcasecmp(key, "qop"))
797275970Scy				cs->challenges[cs->count]->qop =
798275970Scy					strdup(value);
799275970Scy			else if (!strcasecmp(key, "nonce"))
800275970Scy				cs->challenges[cs->count]->nonce =
801275970Scy					strdup(value);
802275970Scy			else if (!strcasecmp(key, "opaque"))
803275970Scy				cs->challenges[cs->count]->opaque =
804275970Scy					strdup(value);
805275970Scy			else if (!strcasecmp(key, "algorithm"))
806275970Scy				cs->challenges[cs->count]->algo =
807275970Scy					strdup(value);
808275970Scy			else if (!strcasecmp(key, "stale"))
809275970Scy				cs->challenges[cs->count]->stale =
810275970Scy					strcasecmp(value, "no");
811275970Scy			/* Else ignore unknown attributes */
812275970Scy
813275970Scy			/* Comma or Next challenge or End */
814275970Scy			lex = http_header_lex(&cp, key);
815275970Scy			/*
816275970Scy			 * If we get a word here, this is the beginning of the
817275970Scy			 * next challenge. Break the attributes loop
818275970Scy			 */
819275970Scy			if (lex == HTTPHL_WORD)
820275970Scy				break;
821275970Scy
822275970Scy			if (lex == HTTPHL_END) {
823275970Scy				/* End while looking for ',' is normal exit */
824275970Scy				cs->count++;
825275970Scy				ret = 0;
826275970Scy				goto out;
827275970Scy			}
828275970Scy			/* Anything else is an error */
829275970Scy			if (lex != ',')
830275970Scy				goto out;
831275970Scy
832275970Scy		} /* End attributes loop */
833275970Scy	} /* End challenge loop */
834275970Scy
835275970Scy	/*
836275970Scy	 * Challenges max count exceeded. This really can't happen
837275970Scy	 * with normal data, something's fishy -> error
838275970Scy	 */
839275970Scy
840275970Scyout:
841275970Scy	if (key)
842275970Scy		free(key);
843275970Scy	if (value)
844275970Scy		free(value);
845275970Scy	if (buf)
846275970Scy		free(buf);
847275970Scy	return (ret);
848275970Scy}
849275970Scy
850275970Scy
851275970Scy/*
852275970Scy * Parse a last-modified header
853275970Scy */
854275970Scystatic int
855275970Scyhttp_parse_mtime(const char *p, time_t *mtime)
856275970Scy{
857275970Scy	char locale[64], *r;
858275970Scy	struct tm tm;
859275970Scy
860275970Scy	strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
861275970Scy	setlocale(LC_TIME, "C");
862275970Scy	r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
863275970Scy	/* XXX should add support for date-2 and date-3 */
864275970Scy	setlocale(LC_TIME, locale);
865275970Scy	if (r == NULL)
866275970Scy		return (-1);
867275970Scy	DEBUG(fprintf(stderr, "last modified: [%04d-%02d-%02d "
868275970Scy		  "%02d:%02d:%02d]\n",
869275970Scy		  tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday,
870275970Scy		  tm.tm_hour, tm.tm_min, tm.tm_sec));
871275970Scy	*mtime = timegm(&tm);
872275970Scy	return (0);
873275970Scy}
874275970Scy
875275970Scy/*
876275970Scy * Parse a content-length header
877275970Scy */
878275970Scystatic int
879275970Scyhttp_parse_length(const char *p, off_t *length)
880275970Scy{
881275970Scy	off_t len;
882275970Scy
883275970Scy	for (len = 0; *p && isdigit((unsigned char)*p); ++p)
884275970Scy		len = len * 10 + (*p - '0');
885275970Scy	if (*p)
886275970Scy		return (-1);
887275970Scy	DEBUG(fprintf(stderr, "content length: [%lld]\n",
888275970Scy	    (long long)len));
889275970Scy	*length = len;
890275970Scy	return (0);
891275970Scy}
892275970Scy
893275970Scy/*
894275970Scy * Parse a content-range header
895275970Scy */
896275970Scystatic int
897275970Scyhttp_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
898275970Scy{
899275970Scy	off_t first, last, len;
900275970Scy
901275970Scy	if (strncasecmp(p, "bytes ", 6) != 0)
902275970Scy		return (-1);
903275970Scy	p += 6;
904275970Scy	if (*p == '*') {
905275970Scy		first = last = -1;
906275970Scy		++p;
907275970Scy	} else {
908275970Scy		for (first = 0; *p && isdigit((unsigned char)*p); ++p)
909275970Scy			first = first * 10 + *p - '0';
910275970Scy		if (*p != '-')
911275970Scy			return (-1);
912275970Scy		for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
913275970Scy			last = last * 10 + *p - '0';
914275970Scy	}
915275970Scy	if (first > last || *p != '/')
916275970Scy		return (-1);
917275970Scy	for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
918275970Scy		len = len * 10 + *p - '0';
919275970Scy	if (*p || len < last - first + 1)
920275970Scy		return (-1);
921275970Scy	if (first == -1) {
922275970Scy		DEBUG(fprintf(stderr, "content range: [*/%lld]\n",
923275970Scy		    (long long)len));
924275970Scy		*length = 0;
925275970Scy	} else {
926275970Scy		DEBUG(fprintf(stderr, "content range: [%lld-%lld/%lld]\n",
927275970Scy		    (long long)first, (long long)last, (long long)len));
928275970Scy		*length = last - first + 1;
929275970Scy	}
930275970Scy	*offset = first;
931275970Scy	*size = len;
932275970Scy	return (0);
933275970Scy}
934275970Scy
935275970Scy
936275970Scy/*****************************************************************************
937275970Scy * Helper functions for authorization
938275970Scy */
939275970Scy
940275970Scy/*
941275970Scy * Base64 encoding
942275970Scy */
943275970Scystatic char *
944275970Scyhttp_base64(const char *src)
945275970Scy{
946275970Scy	static const char base64[] =
947275970Scy	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
948275970Scy	    "abcdefghijklmnopqrstuvwxyz"
949275970Scy	    "0123456789+/";
950275970Scy	char *str, *dst;
951275970Scy	size_t l;
952275970Scy	int t, r;
953275970Scy
954275970Scy	l = strlen(src);
955275970Scy	if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
956275970Scy		return (NULL);
957275970Scy	dst = str;
958275970Scy	r = 0;
959275970Scy
960275970Scy	while (l >= 3) {
961275970Scy		t = (src[0] << 16) | (src[1] << 8) | src[2];
962275970Scy		dst[0] = base64[(t >> 18) & 0x3f];
963275970Scy		dst[1] = base64[(t >> 12) & 0x3f];
964275970Scy		dst[2] = base64[(t >> 6) & 0x3f];
965275970Scy		dst[3] = base64[(t >> 0) & 0x3f];
966275970Scy		src += 3; l -= 3;
967275970Scy		dst += 4; r += 4;
968275970Scy	}
969275970Scy
970275970Scy	switch (l) {
971275970Scy	case 2:
972275970Scy		t = (src[0] << 16) | (src[1] << 8);
973275970Scy		dst[0] = base64[(t >> 18) & 0x3f];
974275970Scy		dst[1] = base64[(t >> 12) & 0x3f];
975275970Scy		dst[2] = base64[(t >> 6) & 0x3f];
976275970Scy		dst[3] = '=';
977275970Scy		dst += 4;
978275970Scy		r += 4;
979275970Scy		break;
980275970Scy	case 1:
981275970Scy		t = src[0] << 16;
982275970Scy		dst[0] = base64[(t >> 18) & 0x3f];
983275970Scy		dst[1] = base64[(t >> 12) & 0x3f];
984275970Scy		dst[2] = dst[3] = '=';
985275970Scy		dst += 4;
986275970Scy		r += 4;
987275970Scy		break;
988275970Scy	case 0:
989275970Scy		break;
990275970Scy	}
991275970Scy
992275970Scy	*dst = 0;
993275970Scy	return (str);
994275970Scy}
995275970Scy
996275970Scy
997275970Scy/*
998275970Scy * Extract authorization parameters from environment value.
999275970Scy * The value is like scheme:realm:user:pass
1000275970Scy */
1001275970Scytypedef struct {
1002275970Scy	char	*scheme;
1003275970Scy	char	*realm;
1004275970Scy	char	*user;
1005275970Scy	char	*password;
1006275970Scy} http_auth_params_t;
1007275970Scy
1008275970Scystatic void
1009275970Scyinit_http_auth_params(http_auth_params_t *s)
1010275970Scy{
1011275970Scy	s->scheme = s->realm = s->user = s->password = 0;
1012275970Scy}
1013275970Scy
1014275970Scystatic void
1015275970Scyclean_http_auth_params(http_auth_params_t *s)
1016275970Scy{
1017275970Scy	if (s->scheme)
1018275970Scy		free(s->scheme);
1019275970Scy	if (s->realm)
1020275970Scy		free(s->realm);
1021275970Scy	if (s->user)
1022275970Scy		free(s->user);
1023275970Scy	if (s->password)
1024275970Scy		free(s->password);
1025275970Scy	init_http_auth_params(s);
1026275970Scy}
1027275970Scy
1028275970Scystatic int
1029275970Scyhttp_authfromenv(const char *p, http_auth_params_t *parms)
1030275970Scy{
1031275970Scy	int ret = -1;
1032275970Scy	char *v, *ve;
1033275970Scy	char *str = strdup(p);
1034275970Scy
1035275970Scy	if (str == NULL) {
1036275970Scy		fetch_syserr();
1037275970Scy		return (-1);
1038275970Scy	}
1039275970Scy	v = str;
1040275970Scy
1041275970Scy	if ((ve = strchr(v, ':')) == NULL)
1042275970Scy		goto out;
1043275970Scy
1044275970Scy	*ve = 0;
1045275970Scy	if ((parms->scheme = strdup(v)) == NULL) {
1046275970Scy		fetch_syserr();
1047275970Scy		goto out;
1048275970Scy	}
1049275970Scy	v = ve + 1;
1050275970Scy
1051275970Scy	if ((ve = strchr(v, ':')) == NULL)
1052275970Scy		goto out;
1053275970Scy
1054275970Scy	*ve = 0;
1055275970Scy	if ((parms->realm = strdup(v)) == NULL) {
1056275970Scy		fetch_syserr();
1057275970Scy		goto out;
1058275970Scy	}
1059275970Scy	v = ve + 1;
1060275970Scy
1061275970Scy	if ((ve = strchr(v, ':')) == NULL)
1062275970Scy		goto out;
1063275970Scy
1064275970Scy	*ve = 0;
1065275970Scy	if ((parms->user = strdup(v)) == NULL) {
1066275970Scy		fetch_syserr();
1067275970Scy		goto out;
1068275970Scy	}
1069275970Scy	v = ve + 1;
1070275970Scy
1071275970Scy
1072275970Scy	if ((parms->password = strdup(v)) == NULL) {
1073275970Scy		fetch_syserr();
1074275970Scy		goto out;
1075275970Scy	}
1076275970Scy	ret = 0;
1077275970Scyout:
1078275970Scy	if (ret == -1)
1079275970Scy		clean_http_auth_params(parms);
1080275970Scy	if (str)
1081275970Scy		free(str);
1082275970Scy	return (ret);
1083275970Scy}
1084275970Scy
1085275970Scy
1086275970Scy/*
1087275970Scy * Digest response: the code to compute the digest is taken from the
1088275970Scy * sample implementation in RFC2616
1089275970Scy */
1090275970Scy#define IN const
1091275970Scy#define OUT
1092275970Scy
1093275970Scy#define HASHLEN 16
1094275970Scytypedef char HASH[HASHLEN];
1095275970Scy#define HASHHEXLEN 32
1096275970Scytypedef char HASHHEX[HASHHEXLEN+1];
1097275970Scy
1098275970Scystatic const char *hexchars = "0123456789abcdef";
1099275970Scystatic void
1100275970ScyCvtHex(IN HASH Bin, OUT HASHHEX Hex)
1101275970Scy{
1102275970Scy	unsigned short i;
1103275970Scy	unsigned char j;
1104275970Scy
1105275970Scy	for (i = 0; i < HASHLEN; i++) {
1106275970Scy		j = (Bin[i] >> 4) & 0xf;
1107275970Scy		Hex[i*2] = hexchars[j];
1108275970Scy		j = Bin[i] & 0xf;
1109275970Scy		Hex[i*2+1] = hexchars[j];
1110275970Scy	};
1111275970Scy	Hex[HASHHEXLEN] = '\0';
1112275970Scy};
1113275970Scy
1114275970Scy/* calculate H(A1) as per spec */
1115275970Scystatic void
1116275970ScyDigestCalcHA1(
1117275970Scy	IN char * pszAlg,
1118275970Scy	IN char * pszUserName,
1119275970Scy	IN char * pszRealm,
1120275970Scy	IN char * pszPassword,
1121275970Scy	IN char * pszNonce,
1122275970Scy	IN char * pszCNonce,
1123275970Scy	OUT HASHHEX SessionKey
1124275970Scy	)
1125275970Scy{
1126275970Scy	MD5_CTX Md5Ctx;
1127275970Scy	HASH HA1;
1128275970Scy
1129275970Scy	MD5Init(&Md5Ctx);
1130275970Scy	MD5Update(&Md5Ctx, pszUserName, strlen(pszUserName));
1131275970Scy	MD5Update(&Md5Ctx, ":", 1);
1132275970Scy	MD5Update(&Md5Ctx, pszRealm, strlen(pszRealm));
1133275970Scy	MD5Update(&Md5Ctx, ":", 1);
1134275970Scy	MD5Update(&Md5Ctx, pszPassword, strlen(pszPassword));
1135275970Scy	MD5Final(HA1, &Md5Ctx);
1136275970Scy	if (strcasecmp(pszAlg, "md5-sess") == 0) {
1137275970Scy
1138275970Scy		MD5Init(&Md5Ctx);
1139275970Scy		MD5Update(&Md5Ctx, HA1, HASHLEN);
1140275970Scy		MD5Update(&Md5Ctx, ":", 1);
1141275970Scy		MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
1142275970Scy		MD5Update(&Md5Ctx, ":", 1);
1143275970Scy		MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
1144275970Scy		MD5Final(HA1, &Md5Ctx);
1145275970Scy	};
1146275970Scy	CvtHex(HA1, SessionKey);
1147275970Scy}
1148275970Scy
1149275970Scy/* calculate request-digest/response-digest as per HTTP Digest spec */
1150275970Scystatic void
1151275970ScyDigestCalcResponse(
1152275970Scy	IN HASHHEX HA1,           /* H(A1) */
1153275970Scy	IN char * pszNonce,       /* nonce from server */
1154275970Scy	IN char * pszNonceCount,  /* 8 hex digits */
1155275970Scy	IN char * pszCNonce,      /* client nonce */
1156275970Scy	IN char * pszQop,         /* qop-value: "", "auth", "auth-int" */
1157275970Scy	IN char * pszMethod,      /* method from the request */
1158275970Scy	IN char * pszDigestUri,   /* requested URL */
1159275970Scy	IN HASHHEX HEntity,       /* H(entity body) if qop="auth-int" */
1160275970Scy	OUT HASHHEX Response      /* request-digest or response-digest */
1161275970Scy	)
1162275970Scy{
1163275970Scy/*	DEBUG(fprintf(stderr,
1164275970Scy		      "Calc: HA1[%s] Nonce[%s] qop[%s] method[%s] URI[%s]\n",
1165275970Scy		      HA1, pszNonce, pszQop, pszMethod, pszDigestUri));*/
1166275970Scy	MD5_CTX Md5Ctx;
1167275970Scy	HASH HA2;
1168275970Scy	HASH RespHash;
1169275970Scy	HASHHEX HA2Hex;
1170275970Scy
1171275970Scy	// calculate H(A2)
1172275970Scy	MD5Init(&Md5Ctx);
1173275970Scy	MD5Update(&Md5Ctx, pszMethod, strlen(pszMethod));
1174275970Scy	MD5Update(&Md5Ctx, ":", 1);
1175275970Scy	MD5Update(&Md5Ctx, pszDigestUri, strlen(pszDigestUri));
1176275970Scy	if (strcasecmp(pszQop, "auth-int") == 0) {
1177275970Scy		MD5Update(&Md5Ctx, ":", 1);
1178275970Scy		MD5Update(&Md5Ctx, HEntity, HASHHEXLEN);
1179275970Scy	};
1180275970Scy	MD5Final(HA2, &Md5Ctx);
1181275970Scy	CvtHex(HA2, HA2Hex);
1182275970Scy
1183275970Scy	// calculate response
1184275970Scy	MD5Init(&Md5Ctx);
1185275970Scy	MD5Update(&Md5Ctx, HA1, HASHHEXLEN);
1186275970Scy	MD5Update(&Md5Ctx, ":", 1);
1187275970Scy	MD5Update(&Md5Ctx, pszNonce, strlen(pszNonce));
1188275970Scy	MD5Update(&Md5Ctx, ":", 1);
1189275970Scy	if (*pszQop) {
1190275970Scy		MD5Update(&Md5Ctx, pszNonceCount, strlen(pszNonceCount));
1191275970Scy		MD5Update(&Md5Ctx, ":", 1);
1192275970Scy		MD5Update(&Md5Ctx, pszCNonce, strlen(pszCNonce));
1193275970Scy		MD5Update(&Md5Ctx, ":", 1);
1194275970Scy		MD5Update(&Md5Ctx, pszQop, strlen(pszQop));
1195275970Scy		MD5Update(&Md5Ctx, ":", 1);
1196275970Scy	};
1197275970Scy	MD5Update(&Md5Ctx, HA2Hex, HASHHEXLEN);
1198275970Scy	MD5Final(RespHash, &Md5Ctx);
1199275970Scy	CvtHex(RespHash, Response);
1200275970Scy}
1201275970Scy
1202275970Scy/*
1203275970Scy * Generate/Send a Digest authorization header
1204275970Scy * This looks like: [Proxy-]Authorization: credentials
1205275970Scy *
1206275970Scy *  credentials      = "Digest" digest-response
1207275970Scy *  digest-response  = 1#( username | realm | nonce | digest-uri
1208275970Scy *                      | response | [ algorithm ] | [cnonce] |
1209275970Scy *                      [opaque] | [message-qop] |
1210275970Scy *                          [nonce-count]  | [auth-param] )
1211275970Scy *  username         = "username" "=" username-value
1212275970Scy *  username-value   = quoted-string
1213275970Scy *  digest-uri       = "uri" "=" digest-uri-value
1214275970Scy *  digest-uri-value = request-uri   ; As specified by HTTP/1.1
1215275970Scy *  message-qop      = "qop" "=" qop-value
1216275970Scy *  cnonce           = "cnonce" "=" cnonce-value
1217275970Scy *  cnonce-value     = nonce-value
1218275970Scy *  nonce-count      = "nc" "=" nc-value
1219275970Scy *  nc-value         = 8LHEX
1220275970Scy *  response         = "response" "=" request-digest
1221275970Scy *  request-digest = <"> 32LHEX <">
1222275970Scy */
1223275970Scystatic int
1224275970Scyhttp_digest_auth(conn_t *conn, const char *hdr, http_auth_challenge_t *c,
1225275970Scy		 http_auth_params_t *parms, struct url *url)
1226275970Scy{
1227275970Scy	int r;
1228275970Scy	char noncecount[10];
1229275970Scy	char cnonce[40];
1230275970Scy	char *options = 0;
1231275970Scy
1232275970Scy	if (!c->realm || !c->nonce) {
1233275970Scy		DEBUG(fprintf(stderr, "realm/nonce not set in challenge\n"));
1234275970Scy		return(-1);
1235275970Scy	}
1236275970Scy	if (!c->algo)
1237275970Scy		c->algo = strdup("");
1238275970Scy
1239275970Scy	if (asprintf(&options, "%s%s%s%s",
1240275970Scy		     *c->algo? ",algorithm=" : "", c->algo,
1241275970Scy		     c->opaque? ",opaque=" : "", c->opaque?c->opaque:"")== -1)
1242275970Scy		return (-1);
1243275970Scy
1244275970Scy	if (!c->qop) {
1245275970Scy		c->qop = strdup("");
1246275970Scy		*noncecount = 0;
1247275970Scy		*cnonce = 0;
1248275970Scy	} else {
1249275970Scy		c->nc++;
1250275970Scy		sprintf(noncecount, "%08x", c->nc);
1251275970Scy		/* We don't try very hard with the cnonce ... */
1252275970Scy		sprintf(cnonce, "%x%lx", getpid(), (unsigned long)time(0));
1253275970Scy	}
1254275970Scy
1255275970Scy	HASHHEX HA1;
1256275970Scy	DigestCalcHA1(c->algo, parms->user, c->realm,
1257275970Scy		      parms->password, c->nonce, cnonce, HA1);
1258275970Scy	DEBUG(fprintf(stderr, "HA1: [%s]\n", HA1));
1259275970Scy	HASHHEX digest;
1260275970Scy	DigestCalcResponse(HA1, c->nonce, noncecount, cnonce, c->qop,
1261275970Scy			   "GET", url->doc, "", digest);
1262275970Scy
1263275970Scy	if (c->qop[0]) {
1264275970Scy		r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
1265275970Scy			     "nonce=\"%s\",uri=\"%s\",response=\"%s\","
1266275970Scy			     "qop=\"auth\", cnonce=\"%s\", nc=%s%s",
1267275970Scy			     hdr, parms->user, c->realm,
1268275970Scy			     c->nonce, url->doc, digest,
1269275970Scy			     cnonce, noncecount, options);
1270275970Scy	} else {
1271275970Scy		r = http_cmd(conn, "%s: Digest username=\"%s\",realm=\"%s\","
1272275970Scy			     "nonce=\"%s\",uri=\"%s\",response=\"%s\"%s",
1273275970Scy			     hdr, parms->user, c->realm,
1274275970Scy			     c->nonce, url->doc, digest, options);
1275275970Scy	}
1276275970Scy	if (options)
1277275970Scy		free(options);
1278275970Scy	return (r);
1279275970Scy}
1280275970Scy
1281275970Scy/*
1282275970Scy * Encode username and password
1283275970Scy */
1284275970Scystatic int
1285275970Scyhttp_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
1286275970Scy{
1287275970Scy	char *upw, *auth;
1288275970Scy	int r;
1289275970Scy
1290275970Scy	DEBUG(fprintf(stderr, "basic: usr: [%s]\n", usr));
1291275970Scy	DEBUG(fprintf(stderr, "basic: pwd: [%s]\n", pwd));
1292275970Scy	if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
1293275970Scy		return (-1);
1294275970Scy	auth = http_base64(upw);
1295275970Scy	free(upw);
1296275970Scy	if (auth == NULL)
1297275970Scy		return (-1);
1298275970Scy	r = http_cmd(conn, "%s: Basic %s", hdr, auth);
1299275970Scy	free(auth);
1300275970Scy	return (r);
1301275970Scy}
1302275970Scy
1303275970Scy/*
1304275970Scy * Chose the challenge to answer and call the appropriate routine to
1305275970Scy * produce the header.
1306275970Scy */
1307275970Scystatic int
1308275970Scyhttp_authorize(conn_t *conn, const char *hdr, http_auth_challenges_t *cs,
1309275970Scy	       http_auth_params_t *parms, struct url *url)
1310275970Scy{
1311275970Scy	http_auth_challenge_t *basic = NULL;
1312275970Scy	http_auth_challenge_t *digest = NULL;
1313275970Scy	int i;
1314275970Scy
1315275970Scy	/* If user or pass are null we're not happy */
1316275970Scy	if (!parms->user || !parms->password) {
1317275970Scy		DEBUG(fprintf(stderr, "NULL usr or pass\n"));
1318275970Scy		return (-1);
1319275970Scy	}
1320275970Scy
1321275970Scy	/* Look for a Digest and a Basic challenge */
1322275970Scy	for (i = 0; i < cs->count; i++) {
1323275970Scy		if (cs->challenges[i]->scheme == HTTPAS_BASIC)
1324275970Scy			basic = cs->challenges[i];
1325275970Scy		if (cs->challenges[i]->scheme == HTTPAS_DIGEST)
1326275970Scy			digest = cs->challenges[i];
1327275970Scy	}
1328275970Scy
1329275970Scy	/* Error if "Digest" was specified and there is no Digest challenge */
1330275970Scy	if (!digest && (parms->scheme &&
1331275970Scy			!strcasecmp(parms->scheme, "digest"))) {
1332275970Scy		DEBUG(fprintf(stderr,
1333275970Scy			      "Digest auth in env, not supported by peer\n"));
1334275970Scy		return (-1);
1335275970Scy	}
1336275970Scy	/*
1337275970Scy	 * If "basic" was specified in the environment, or there is no Digest
1338275970Scy	 * challenge, do the basic thing. Don't need a challenge for this,
1339275970Scy	 * so no need to check basic!=NULL
1340275970Scy	 */
1341275970Scy	if (!digest || (parms->scheme && !strcasecmp(parms->scheme,"basic")))
1342275970Scy		return (http_basic_auth(conn,hdr,parms->user,parms->password));
1343275970Scy
1344275970Scy	/* Else, prefer digest. We just checked that it's not NULL */
1345275970Scy	return (http_digest_auth(conn, hdr, digest, parms, url));
1346275970Scy}
1347275970Scy
1348275970Scy/*****************************************************************************
1349275970Scy * Helper functions for connecting to a server or proxy
1350275970Scy */
1351275970Scy
1352275970Scy/*
1353275970Scy * Connect to the correct HTTP server or proxy.
1354275970Scy */
1355275970Scystatic conn_t *
1356275970Scyhttp_connect(struct url *URL, struct url *purl, const char *flags)
1357275970Scy{
1358275970Scy	conn_t *conn;
1359275970Scy	int verbose;
1360275970Scy	int af, val;
1361275970Scy
1362275970Scy#ifdef INET6
1363275970Scy	af = AF_UNSPEC;
1364275970Scy#else
1365275970Scy	af = AF_INET;
1366275970Scy#endif
1367275970Scy
1368275970Scy	verbose = CHECK_FLAG('v');
1369275970Scy	if (CHECK_FLAG('4'))
1370275970Scy		af = AF_INET;
1371275970Scy#ifdef INET6
1372275970Scy	else if (CHECK_FLAG('6'))
1373275970Scy		af = AF_INET6;
1374275970Scy#endif
1375275970Scy
1376275970Scy	if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
1377275970Scy		URL = purl;
1378275970Scy	} else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
1379275970Scy		/* can't talk http to an ftp server */
1380275970Scy		/* XXX should set an error code */
1381275970Scy		return (NULL);
1382275970Scy	}
1383275970Scy
1384275970Scy	if ((conn = fetch_connect(URL->host, URL->port, af, verbose)) == NULL)
1385275970Scy		/* fetch_connect() has already set an error code */
1386275970Scy		return (NULL);
1387275970Scy	if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
1388275970Scy	    fetch_ssl(conn, verbose) == -1) {
1389275970Scy		fetch_close(conn);
1390275970Scy		/* grrr */
1391275970Scy		errno = EAUTH;
1392275970Scy		fetch_syserr();
1393275970Scy		return (NULL);
1394275970Scy	}
1395275970Scy
1396275970Scy	val = 1;
1397275970Scy	setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
1398275970Scy
1399275970Scy	return (conn);
1400275970Scy}
1401275970Scy
1402275970Scystatic struct url *
1403275970Scyhttp_get_proxy(struct url * url, const char *flags)
1404275970Scy{
1405275970Scy	struct url *purl;
1406275970Scy	char *p;
1407275970Scy
1408275970Scy	if (flags != NULL && strchr(flags, 'd') != NULL)
1409275970Scy		return (NULL);
1410275970Scy	if (fetch_no_proxy_match(url->host))
1411275970Scy		return (NULL);
1412275970Scy	if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
1413275970Scy	    *p && (purl = fetchParseURL(p))) {
1414275970Scy		if (!*purl->scheme)
1415275970Scy			strcpy(purl->scheme, SCHEME_HTTP);
1416275970Scy		if (!purl->port)
1417275970Scy			purl->port = fetch_default_proxy_port(purl->scheme);
1418275970Scy		if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
1419275970Scy			return (purl);
1420275970Scy		fetchFreeURL(purl);
1421275970Scy	}
1422275970Scy	return (NULL);
1423275970Scy}
1424275970Scy
1425275970Scystatic void
1426275970Scyhttp_print_html(FILE *out, FILE *in)
1427275970Scy{
1428275970Scy	size_t len;
1429275970Scy	char *line, *p, *q;
1430275970Scy	int comment, tag;
1431275970Scy
1432275970Scy	comment = tag = 0;
1433275970Scy	while ((line = fgetln(in, &len)) != NULL) {
1434275970Scy		while (len && isspace((unsigned char)line[len - 1]))
1435275970Scy			--len;
1436275970Scy		for (p = q = line; q < line + len; ++q) {
1437275970Scy			if (comment && *q == '-') {
1438275970Scy				if (q + 2 < line + len &&
1439275970Scy				    strcmp(q, "-->") == 0) {
1440275970Scy					tag = comment = 0;
1441275970Scy					q += 2;
1442275970Scy				}
1443275970Scy			} else if (tag && !comment && *q == '>') {
1444275970Scy				p = q + 1;
1445275970Scy				tag = 0;
1446275970Scy			} else if (!tag && *q == '<') {
1447275970Scy				if (q > p)
1448275970Scy					fwrite(p, q - p, 1, out);
1449275970Scy				tag = 1;
1450275970Scy				if (q + 3 < line + len &&
1451275970Scy				    strcmp(q, "<!--") == 0) {
1452275970Scy					comment = 1;
1453275970Scy					q += 3;
1454275970Scy				}
1455275970Scy			}
1456275970Scy		}
1457275970Scy		if (!tag && q > p)
1458275970Scy			fwrite(p, q - p, 1, out);
1459275970Scy		fputc('\n', out);
1460275970Scy	}
1461275970Scy}
1462275970Scy
1463275970Scy
1464275970Scy/*****************************************************************************
1465275970Scy * Core
1466275970Scy */
1467275970Scy
1468275970Scy/*
1469275970Scy * Send a request and process the reply
1470275970Scy *
1471275970Scy * XXX This function is way too long, the do..while loop should be split
1472275970Scy * XXX off into a separate function.
1473275970Scy */
1474275970ScyFILE *
1475275970Scyhttp_request(struct url *URL, const char *op, struct url_stat *us,
1476275970Scy	struct url *purl, const char *flags)
1477275970Scy{
1478275970Scy	char timebuf[80];
1479275970Scy	char hbuf[MAXHOSTNAMELEN + 7], *host;
1480275970Scy	conn_t *conn;
1481275970Scy	struct url *url, *new;
1482275970Scy	int chunked, direct, ims, noredirect, verbose;
1483275970Scy	int e, i, n, val;
1484275970Scy	off_t offset, clength, length, size;
1485275970Scy	time_t mtime;
1486275970Scy	const char *p;
1487275970Scy	FILE *f;
1488275970Scy	hdr_t h;
1489275970Scy	struct tm *timestruct;
1490275970Scy	http_headerbuf_t headerbuf;
1491275970Scy	http_auth_challenges_t server_challenges;
1492275970Scy	http_auth_challenges_t proxy_challenges;
1493275970Scy
1494275970Scy	/* The following calls don't allocate anything */
1495275970Scy	init_http_headerbuf(&headerbuf);
1496275970Scy	init_http_auth_challenges(&server_challenges);
1497275970Scy	init_http_auth_challenges(&proxy_challenges);
1498275970Scy
1499275970Scy	direct = CHECK_FLAG('d');
1500275970Scy	noredirect = CHECK_FLAG('A');
1501275970Scy	verbose = CHECK_FLAG('v');
1502275970Scy	ims = CHECK_FLAG('i');
1503275970Scy
1504275970Scy	if (direct && purl) {
1505275970Scy		fetchFreeURL(purl);
1506275970Scy		purl = NULL;
1507275970Scy	}
1508275970Scy
1509275970Scy	/* try the provided URL first */
1510275970Scy	url = URL;
1511275970Scy
1512275970Scy	/* if the A flag is set, we only get one try */
1513275970Scy	n = noredirect ? 1 : MAX_REDIRECT;
1514275970Scy	i = 0;
1515275970Scy
1516275970Scy	e = HTTP_PROTOCOL_ERROR;
1517275970Scy	do {
1518275970Scy		new = NULL;
1519275970Scy		chunked = 0;
1520275970Scy		offset = 0;
1521275970Scy		clength = -1;
1522275970Scy		length = -1;
1523275970Scy		size = -1;
1524275970Scy		mtime = 0;
1525275970Scy
1526275970Scy		/* check port */
1527275970Scy		if (!url->port)
1528275970Scy			url->port = fetch_default_port(url->scheme);
1529275970Scy
1530275970Scy		/* were we redirected to an FTP URL? */
1531275970Scy		if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
1532275970Scy			if (strcmp(op, "GET") == 0)
1533275970Scy				return (ftp_request(url, "RETR", us, purl, flags));
1534275970Scy			else if (strcmp(op, "HEAD") == 0)
1535275970Scy				return (ftp_request(url, "STAT", us, purl, flags));
1536275970Scy		}
1537275970Scy
1538275970Scy		/* connect to server or proxy */
1539275970Scy		if ((conn = http_connect(url, purl, flags)) == NULL)
1540275970Scy			goto ouch;
1541275970Scy
1542275970Scy		host = url->host;
1543275970Scy#ifdef INET6
1544275970Scy		if (strchr(url->host, ':')) {
1545275970Scy			snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
1546275970Scy			host = hbuf;
1547275970Scy		}
1548275970Scy#endif
1549275970Scy		if (url->port != fetch_default_port(url->scheme)) {
1550275970Scy			if (host != hbuf) {
1551275970Scy				strcpy(hbuf, host);
1552275970Scy				host = hbuf;
1553275970Scy			}
1554275970Scy			snprintf(hbuf + strlen(hbuf),
1555275970Scy			    sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
1556275970Scy		}
1557275970Scy
1558275970Scy		/* send request */
1559275970Scy		if (verbose)
1560275970Scy			fetch_info("requesting %s://%s%s",
1561275970Scy			    url->scheme, host, url->doc);
1562275970Scy		if (purl) {
1563275970Scy			http_cmd(conn, "%s %s://%s%s HTTP/1.1",
1564275970Scy			    op, url->scheme, host, url->doc);
1565275970Scy		} else {
1566275970Scy			http_cmd(conn, "%s %s HTTP/1.1",
1567275970Scy			    op, url->doc);
1568275970Scy		}
1569275970Scy
1570275970Scy		if (ims && url->ims_time) {
1571275970Scy			timestruct = gmtime((time_t *)&url->ims_time);
1572275970Scy			(void)strftime(timebuf, 80, "%a, %d %b %Y %T GMT",
1573275970Scy			    timestruct);
1574275970Scy			if (verbose)
1575275970Scy				fetch_info("If-Modified-Since: %s", timebuf);
1576275970Scy			http_cmd(conn, "If-Modified-Since: %s", timebuf);
1577275970Scy		}
1578275970Scy		/* virtual host */
1579275970Scy		http_cmd(conn, "Host: %s", host);
1580275970Scy
1581275970Scy		/*
1582275970Scy		 * Proxy authorization: we only send auth after we received
1583275970Scy		 * a 407 error. We do not first try basic anyway (changed
1584275970Scy		 * when support was added for digest-auth)
1585275970Scy		 */
1586275970Scy		if (purl && proxy_challenges.valid) {
1587275970Scy			http_auth_params_t aparams;
1588275970Scy			init_http_auth_params(&aparams);
1589275970Scy			if (*purl->user || *purl->pwd) {
1590275970Scy				aparams.user = purl->user ?
1591275970Scy					strdup(purl->user) : strdup("");
1592275970Scy				aparams.password = purl->pwd?
1593275970Scy					strdup(purl->pwd) : strdup("");
1594275970Scy			} else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL &&
1595275970Scy				   *p != '\0') {
1596275970Scy				if (http_authfromenv(p, &aparams) < 0) {
1597275970Scy					http_seterr(HTTP_NEED_PROXY_AUTH);
1598275970Scy					goto ouch;
1599275970Scy				}
1600275970Scy			}
1601275970Scy			http_authorize(conn, "Proxy-Authorization",
1602275970Scy				       &proxy_challenges, &aparams, url);
1603275970Scy			clean_http_auth_params(&aparams);
1604275970Scy		}
1605275970Scy
1606275970Scy		/*
1607275970Scy		 * Server authorization: we never send "a priori"
1608275970Scy		 * Basic auth, which used to be done if user/pass were
1609275970Scy		 * set in the url. This would be weird because we'd send the
1610275970Scy		 * password in the clear even if Digest is finally to be
1611275970Scy		 * used (it would have made more sense for the
1612275970Scy		 * pre-digest version to do this when Basic was specified
1613275970Scy		 * in the environment)
1614275970Scy		 */
1615275970Scy		if (server_challenges.valid) {
1616275970Scy			http_auth_params_t aparams;
1617275970Scy			init_http_auth_params(&aparams);
1618275970Scy			if (*url->user || *url->pwd) {
1619275970Scy				aparams.user = url->user ?
1620275970Scy					strdup(url->user) : strdup("");
1621275970Scy				aparams.password = url->pwd ?
1622275970Scy					strdup(url->pwd) : strdup("");
1623275970Scy			} else if ((p = getenv("HTTP_AUTH")) != NULL &&
1624275970Scy				   *p != '\0') {
1625275970Scy				if (http_authfromenv(p, &aparams) < 0) {
1626275970Scy					http_seterr(HTTP_NEED_AUTH);
1627275970Scy					goto ouch;
1628275970Scy				}
1629275970Scy			} else if (fetchAuthMethod &&
1630275970Scy				   fetchAuthMethod(url) == 0) {
1631275970Scy				aparams.user = url->user ?
1632275970Scy					strdup(url->user) : strdup("");
1633275970Scy				aparams.password = url->pwd ?
1634275970Scy					strdup(url->pwd) : strdup("");
1635275970Scy			} else {
1636275970Scy				http_seterr(HTTP_NEED_AUTH);
1637275970Scy				goto ouch;
1638275970Scy			}
1639275970Scy			http_authorize(conn, "Authorization",
1640275970Scy				       &server_challenges, &aparams, url);
1641275970Scy			clean_http_auth_params(&aparams);
1642275970Scy		}
1643275970Scy
1644275970Scy		/* other headers */
1645275970Scy		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
1646275970Scy			if (strcasecmp(p, "auto") == 0)
1647275970Scy				http_cmd(conn, "Referer: %s://%s%s",
1648275970Scy				    url->scheme, host, url->doc);
1649275970Scy			else
1650275970Scy				http_cmd(conn, "Referer: %s", p);
1651275970Scy		}
1652275970Scy		if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
1653275970Scy			http_cmd(conn, "User-Agent: %s", p);
1654275970Scy		else
1655275970Scy			http_cmd(conn, "User-Agent: %s " _LIBFETCH_VER, getprogname());
1656275970Scy		if (url->offset > 0)
1657275970Scy			http_cmd(conn, "Range: bytes=%lld-", (long long)url->offset);
1658275970Scy		http_cmd(conn, "Connection: close");
1659275970Scy		http_cmd(conn, "");
1660275970Scy
1661275970Scy		/*
1662275970Scy		 * Force the queued request to be dispatched.  Normally, one
1663275970Scy		 * would do this with shutdown(2) but squid proxies can be
1664275970Scy		 * configured to disallow such half-closed connections.  To
1665275970Scy		 * be compatible with such configurations, fiddle with socket
1666275970Scy		 * options to force the pending data to be written.
1667275970Scy		 */
1668275970Scy		val = 0;
1669275970Scy		setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
1670275970Scy			   sizeof(val));
1671275970Scy		val = 1;
1672275970Scy		setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
1673275970Scy			   sizeof(val));
1674275970Scy
1675275970Scy		/* get reply */
1676275970Scy		switch (http_get_reply(conn)) {
1677275970Scy		case HTTP_OK:
1678275970Scy		case HTTP_PARTIAL:
1679275970Scy		case HTTP_NOT_MODIFIED:
1680275970Scy			/* fine */
1681275970Scy			break;
1682275970Scy		case HTTP_MOVED_PERM:
1683275970Scy		case HTTP_MOVED_TEMP:
1684275970Scy		case HTTP_SEE_OTHER:
1685275970Scy			/*
1686275970Scy			 * Not so fine, but we still have to read the
1687275970Scy			 * headers to get the new location.
1688275970Scy			 */
1689275970Scy			break;
1690275970Scy		case HTTP_NEED_AUTH:
1691275970Scy			if (server_challenges.valid) {
1692275970Scy				/*
1693275970Scy				 * We already sent out authorization code,
1694275970Scy				 * so there's nothing more we can do.
1695275970Scy				 */
1696275970Scy				http_seterr(conn->err);
1697275970Scy				goto ouch;
1698275970Scy			}
1699275970Scy			/* try again, but send the password this time */
1700275970Scy			if (verbose)
1701275970Scy				fetch_info("server requires authorization");
1702275970Scy			break;
1703275970Scy		case HTTP_NEED_PROXY_AUTH:
1704275970Scy			if (proxy_challenges.valid) {
1705275970Scy				/*
1706275970Scy				 * We already sent our proxy
1707275970Scy				 * authorization code, so there's
1708275970Scy				 * nothing more we can do. */
1709275970Scy				http_seterr(conn->err);
1710275970Scy				goto ouch;
1711275970Scy			}
1712275970Scy			/* try again, but send the password this time */
1713275970Scy			if (verbose)
1714275970Scy				fetch_info("proxy requires authorization");
1715275970Scy			break;
1716275970Scy		case HTTP_BAD_RANGE:
1717275970Scy			/*
1718275970Scy			 * This can happen if we ask for 0 bytes because
1719275970Scy			 * we already have the whole file.  Consider this
1720275970Scy			 * a success for now, and check sizes later.
1721275970Scy			 */
1722275970Scy			break;
1723275970Scy		case HTTP_PROTOCOL_ERROR:
1724275970Scy			/* fall through */
1725275970Scy		case -1:
1726275970Scy			fetch_syserr();
1727275970Scy			goto ouch;
1728275970Scy		default:
1729275970Scy			http_seterr(conn->err);
1730275970Scy			if (!verbose)
1731275970Scy				goto ouch;
1732275970Scy			/* fall through so we can get the full error message */
1733275970Scy		}
1734275970Scy
1735275970Scy		/* get headers. http_next_header expects one line readahead */
1736275970Scy		if (fetch_getln(conn) == -1) {
1737275970Scy		    fetch_syserr();
1738275970Scy		    goto ouch;
1739275970Scy		}
1740275970Scy		do {
1741275970Scy		    switch ((h = http_next_header(conn, &headerbuf, &p))) {
1742275970Scy			case hdr_syserror:
1743275970Scy				fetch_syserr();
1744275970Scy				goto ouch;
1745275970Scy			case hdr_error:
1746275970Scy				http_seterr(HTTP_PROTOCOL_ERROR);
1747275970Scy				goto ouch;
1748275970Scy			case hdr_content_length:
1749275970Scy				http_parse_length(p, &clength);
1750275970Scy				break;
1751275970Scy			case hdr_content_range:
1752275970Scy				http_parse_range(p, &offset, &length, &size);
1753275970Scy				break;
1754275970Scy			case hdr_last_modified:
1755275970Scy				http_parse_mtime(p, &mtime);
1756275970Scy				break;
1757275970Scy			case hdr_location:
1758275970Scy				if (!HTTP_REDIRECT(conn->err))
1759275970Scy					break;
1760275970Scy				if (new)
1761275970Scy					free(new);
1762275970Scy				if (verbose)
1763275970Scy					fetch_info("%d redirect to %s", conn->err, p);
1764275970Scy				if (*p == '/')
1765275970Scy					/* absolute path */
1766275970Scy					new = fetchMakeURL(url->scheme, url->host, url->port, p,
1767275970Scy					    url->user, url->pwd);
1768275970Scy				else
1769275970Scy					new = fetchParseURL(p);
1770275970Scy				if (new == NULL) {
1771275970Scy					/* XXX should set an error code */
1772275970Scy					DEBUG(fprintf(stderr, "failed to parse new URL\n"));
1773275970Scy					goto ouch;
1774275970Scy				}
1775275970Scy				if (!*new->user && !*new->pwd) {
1776275970Scy					strcpy(new->user, url->user);
1777275970Scy					strcpy(new->pwd, url->pwd);
1778275970Scy				}
1779275970Scy				new->offset = url->offset;
1780275970Scy				new->length = url->length;
1781275970Scy				break;
1782275970Scy			case hdr_transfer_encoding:
1783275970Scy				/* XXX weak test*/
1784275970Scy				chunked = (strcasecmp(p, "chunked") == 0);
1785275970Scy				break;
1786275970Scy			case hdr_www_authenticate:
1787275970Scy				if (conn->err != HTTP_NEED_AUTH)
1788275970Scy					break;
1789275970Scy				if (http_parse_authenticate(p, &server_challenges) == 0)
1790275970Scy					++n;
1791275970Scy				break;
1792275970Scy			case hdr_proxy_authenticate:
1793275970Scy				if (conn->err != HTTP_NEED_PROXY_AUTH)
1794275970Scy					break;
1795275970Scy				if (http_parse_authenticate(p, &proxy_challenges) == 0)
1796275970Scy					++n;
1797275970Scy				break;
1798275970Scy			case hdr_end:
1799275970Scy				/* fall through */
1800275970Scy			case hdr_unknown:
1801275970Scy				/* ignore */
1802275970Scy				break;
1803275970Scy			}
1804275970Scy		} while (h > hdr_end);
1805275970Scy
1806		/* we need to provide authentication */
1807		if (conn->err == HTTP_NEED_AUTH ||
1808		    conn->err == HTTP_NEED_PROXY_AUTH) {
1809			e = conn->err;
1810			if ((conn->err == HTTP_NEED_AUTH &&
1811			     !server_challenges.valid) ||
1812			    (conn->err == HTTP_NEED_PROXY_AUTH &&
1813			     !proxy_challenges.valid)) {
1814				/* 401/7 but no www/proxy-authenticate ?? */
1815				DEBUG(fprintf(stderr, "401/7 and no auth header\n"));
1816				goto ouch;
1817			}
1818			fetch_close(conn);
1819			conn = NULL;
1820			continue;
1821		}
1822
1823		/* requested range not satisfiable */
1824		if (conn->err == HTTP_BAD_RANGE) {
1825			if (url->offset == size && url->length == 0) {
1826				/* asked for 0 bytes; fake it */
1827				offset = url->offset;
1828				clength = -1;
1829				conn->err = HTTP_OK;
1830				break;
1831			} else {
1832				http_seterr(conn->err);
1833				goto ouch;
1834			}
1835		}
1836
1837		/* we have a hit or an error */
1838		if (conn->err == HTTP_OK
1839		    || conn->err == HTTP_NOT_MODIFIED
1840		    || conn->err == HTTP_PARTIAL
1841		    || HTTP_ERROR(conn->err))
1842			break;
1843
1844		/* all other cases: we got a redirect */
1845		e = conn->err;
1846		clean_http_auth_challenges(&server_challenges);
1847		fetch_close(conn);
1848		conn = NULL;
1849		if (!new) {
1850			DEBUG(fprintf(stderr, "redirect with no new location\n"));
1851			break;
1852		}
1853		if (url != URL)
1854			fetchFreeURL(url);
1855		url = new;
1856	} while (++i < n);
1857
1858	/* we failed, or ran out of retries */
1859	if (conn == NULL) {
1860		http_seterr(e);
1861		goto ouch;
1862	}
1863
1864	DEBUG(fprintf(stderr, "offset %lld, length %lld,"
1865		  " size %lld, clength %lld\n",
1866		  (long long)offset, (long long)length,
1867		  (long long)size, (long long)clength));
1868
1869	if (conn->err == HTTP_NOT_MODIFIED) {
1870		http_seterr(HTTP_NOT_MODIFIED);
1871		return (NULL);
1872	}
1873
1874	/* check for inconsistencies */
1875	if (clength != -1 && length != -1 && clength != length) {
1876		http_seterr(HTTP_PROTOCOL_ERROR);
1877		goto ouch;
1878	}
1879	if (clength == -1)
1880		clength = length;
1881	if (clength != -1)
1882		length = offset + clength;
1883	if (length != -1 && size != -1 && length != size) {
1884		http_seterr(HTTP_PROTOCOL_ERROR);
1885		goto ouch;
1886	}
1887	if (size == -1)
1888		size = length;
1889
1890	/* fill in stats */
1891	if (us) {
1892		us->size = size;
1893		us->atime = us->mtime = mtime;
1894	}
1895
1896	/* too far? */
1897	if (URL->offset > 0 && offset > URL->offset) {
1898		http_seterr(HTTP_PROTOCOL_ERROR);
1899		goto ouch;
1900	}
1901
1902	/* report back real offset and size */
1903	URL->offset = offset;
1904	URL->length = clength;
1905
1906	/* wrap it up in a FILE */
1907	if ((f = http_funopen(conn, chunked)) == NULL) {
1908		fetch_syserr();
1909		goto ouch;
1910	}
1911
1912	if (url != URL)
1913		fetchFreeURL(url);
1914	if (purl)
1915		fetchFreeURL(purl);
1916
1917	if (HTTP_ERROR(conn->err)) {
1918		http_print_html(stderr, f);
1919		fclose(f);
1920		f = NULL;
1921	}
1922	clean_http_headerbuf(&headerbuf);
1923	clean_http_auth_challenges(&server_challenges);
1924	clean_http_auth_challenges(&proxy_challenges);
1925	return (f);
1926
1927ouch:
1928	if (url != URL)
1929		fetchFreeURL(url);
1930	if (purl)
1931		fetchFreeURL(purl);
1932	if (conn != NULL)
1933		fetch_close(conn);
1934	clean_http_headerbuf(&headerbuf);
1935	clean_http_auth_challenges(&server_challenges);
1936	clean_http_auth_challenges(&proxy_challenges);
1937	return (NULL);
1938}
1939
1940
1941/*****************************************************************************
1942 * Entry points
1943 */
1944
1945/*
1946 * Retrieve and stat a file by HTTP
1947 */
1948FILE *
1949fetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
1950{
1951	return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
1952}
1953
1954/*
1955 * Retrieve a file by HTTP
1956 */
1957FILE *
1958fetchGetHTTP(struct url *URL, const char *flags)
1959{
1960	return (fetchXGetHTTP(URL, NULL, flags));
1961}
1962
1963/*
1964 * Store a file by HTTP
1965 */
1966FILE *
1967fetchPutHTTP(struct url *URL __unused, const char *flags __unused)
1968{
1969	warnx("fetchPutHTTP(): not implemented");
1970	return (NULL);
1971}
1972
1973/*
1974 * Get an HTTP document's metadata
1975 */
1976int
1977fetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
1978{
1979	FILE *f;
1980
1981	f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
1982	if (f == NULL)
1983		return (-1);
1984	fclose(f);
1985	return (0);
1986}
1987
1988/*
1989 * List a directory
1990 */
1991struct url_ent *
1992fetchListHTTP(struct url *url __unused, const char *flags __unused)
1993{
1994	warnx("fetchListHTTP(): not implemented");
1995	return (NULL);
1996}
1997