xref: /netbsd/external/bsd/fetch/dist/libfetch/http.c (revision 75975423)
1*75975423Skamil /*	$NetBSD: http.c,v 1.4 2020/06/01 00:55:24 kamil Exp $	*/
2fe618babSjoerg /*-
3fe618babSjoerg  * Copyright (c) 2000-2004 Dag-Erling Co�dan Sm�rgrav
4fe618babSjoerg  * Copyright (c) 2003 Thomas Klausner <wiz@NetBSD.org>
59da2cc5cSjoerg  * Copyright (c) 2008, 2009 Joerg Sonnenberger <joerg@NetBSD.org>
6fe618babSjoerg  * All rights reserved.
7fe618babSjoerg  *
8fe618babSjoerg  * Redistribution and use in source and binary forms, with or without
9fe618babSjoerg  * modification, are permitted provided that the following conditions
10fe618babSjoerg  * are met:
11fe618babSjoerg  * 1. Redistributions of source code must retain the above copyright
12fe618babSjoerg  *    notice, this list of conditions and the following disclaimer
13fe618babSjoerg  *    in this position and unchanged.
14fe618babSjoerg  * 2. Redistributions in binary form must reproduce the above copyright
15fe618babSjoerg  *    notice, this list of conditions and the following disclaimer in the
16fe618babSjoerg  *    documentation and/or other materials provided with the distribution.
17fe618babSjoerg  * 3. The name of the author may not be used to endorse or promote products
18fe618babSjoerg  *    derived from this software without specific prior written permission.
19fe618babSjoerg  *
20fe618babSjoerg  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
21fe618babSjoerg  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
22fe618babSjoerg  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
23fe618babSjoerg  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
24fe618babSjoerg  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
25fe618babSjoerg  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26fe618babSjoerg  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27fe618babSjoerg  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28fe618babSjoerg  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29fe618babSjoerg  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30fe618babSjoerg  *
31fe618babSjoerg  * $FreeBSD: http.c,v 1.83 2008/02/06 11:39:55 des Exp $
32fe618babSjoerg  */
33fe618babSjoerg 
34fe618babSjoerg /*
35fe618babSjoerg  * The following copyright applies to the base64 code:
36fe618babSjoerg  *
37fe618babSjoerg  *-
38fe618babSjoerg  * Copyright 1997 Massachusetts Institute of Technology
39fe618babSjoerg  *
40fe618babSjoerg  * Permission to use, copy, modify, and distribute this software and
41fe618babSjoerg  * its documentation for any purpose and without fee is hereby
42fe618babSjoerg  * granted, provided that both the above copyright notice and this
43fe618babSjoerg  * permission notice appear in all copies, that both the above
44fe618babSjoerg  * copyright notice and this permission notice appear in all
45fe618babSjoerg  * supporting documentation, and that the name of M.I.T. not be used
46fe618babSjoerg  * in advertising or publicity pertaining to distribution of the
47fe618babSjoerg  * software without specific, written prior permission.  M.I.T. makes
48fe618babSjoerg  * no representations about the suitability of this software for any
49fe618babSjoerg  * purpose.  It is provided "as is" without express or implied
50fe618babSjoerg  * warranty.
51fe618babSjoerg  *
52fe618babSjoerg  * THIS SOFTWARE IS PROVIDED BY M.I.T. ``AS IS''.  M.I.T. DISCLAIMS
53fe618babSjoerg  * ALL EXPRESS OR IMPLIED WARRANTIES WITH REGARD TO THIS SOFTWARE,
54fe618babSjoerg  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
55fe618babSjoerg  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. IN NO EVENT
56fe618babSjoerg  * SHALL M.I.T. BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
57fe618babSjoerg  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
58fe618babSjoerg  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
59fe618babSjoerg  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
60fe618babSjoerg  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
61fe618babSjoerg  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
62fe618babSjoerg  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
63fe618babSjoerg  * SUCH DAMAGE.
64fe618babSjoerg  */
65fe618babSjoerg 
667bacb5afSjoerg #if defined(__linux__) || defined(__MINT__)
677bacb5afSjoerg /* Keep this down to Linux or MiNT, it can create surprises elsewhere. */
68fe618babSjoerg #define _GNU_SOURCE
69fe618babSjoerg #endif
70fe618babSjoerg 
71*75975423Skamil #ifndef _REENTRANT
727bacb5afSjoerg /* Needed for gmtime_r on Interix */
737bacb5afSjoerg #define _REENTRANT
74*75975423Skamil #endif
757bacb5afSjoerg 
76fe618babSjoerg #if HAVE_CONFIG_H
77fe618babSjoerg #include "config.h"
78fe618babSjoerg #endif
79fe618babSjoerg #ifndef NETBSD
80fe618babSjoerg #include <nbcompat.h>
81fe618babSjoerg #endif
82fe618babSjoerg 
83fe618babSjoerg #include <sys/types.h>
84fe618babSjoerg #include <sys/socket.h>
85fe618babSjoerg 
86fe618babSjoerg #include <ctype.h>
87fe618babSjoerg #include <errno.h>
88fe618babSjoerg #include <locale.h>
89fe618babSjoerg #include <stdarg.h>
90fe618babSjoerg #ifndef NETBSD
91fe618babSjoerg #include <nbcompat/stdio.h>
92fe618babSjoerg #else
93fe618babSjoerg #include <stdio.h>
94fe618babSjoerg #endif
95fe618babSjoerg #include <stdlib.h>
96fe618babSjoerg #include <string.h>
97fe618babSjoerg #include <time.h>
98fe618babSjoerg #include <unistd.h>
99fe618babSjoerg 
100fe618babSjoerg #include <netinet/in.h>
101fe618babSjoerg #include <netinet/tcp.h>
102fe618babSjoerg 
1037bacb5afSjoerg #ifndef NETBSD
1047bacb5afSjoerg #include <nbcompat/netdb.h>
1057bacb5afSjoerg #else
1067bacb5afSjoerg #include <netdb.h>
1077bacb5afSjoerg #endif
1087bacb5afSjoerg 
1097bacb5afSjoerg #include <arpa/inet.h>
1107bacb5afSjoerg 
111fe618babSjoerg #include "fetch.h"
112fe618babSjoerg #include "common.h"
113fe618babSjoerg #include "httperr.h"
114fe618babSjoerg 
115fe618babSjoerg /* Maximum number of redirects to follow */
116fe618babSjoerg #define MAX_REDIRECT 5
117fe618babSjoerg 
118fe618babSjoerg /* Symbolic names for reply codes we care about */
119fe618babSjoerg #define HTTP_OK			200
120fe618babSjoerg #define HTTP_PARTIAL		206
121fe618babSjoerg #define HTTP_MOVED_PERM		301
122fe618babSjoerg #define HTTP_MOVED_TEMP		302
123fe618babSjoerg #define HTTP_SEE_OTHER		303
1249da2cc5cSjoerg #define HTTP_NOT_MODIFIED	304
125fe618babSjoerg #define HTTP_TEMP_REDIRECT	307
126fe618babSjoerg #define HTTP_NEED_AUTH		401
127fe618babSjoerg #define HTTP_NEED_PROXY_AUTH	407
128fe618babSjoerg #define HTTP_BAD_RANGE		416
129fe618babSjoerg #define HTTP_PROTOCOL_ERROR	999
130fe618babSjoerg 
131fe618babSjoerg #define HTTP_REDIRECT(xyz) ((xyz) == HTTP_MOVED_PERM \
132fe618babSjoerg 			    || (xyz) == HTTP_MOVED_TEMP \
133fe618babSjoerg 			    || (xyz) == HTTP_TEMP_REDIRECT \
134fe618babSjoerg 			    || (xyz) == HTTP_SEE_OTHER)
135fe618babSjoerg 
136fe618babSjoerg #define HTTP_ERROR(xyz) ((xyz) > 400 && (xyz) < 599)
137fe618babSjoerg 
138fe618babSjoerg 
139fe618babSjoerg /*****************************************************************************
140fe618babSjoerg  * I/O functions for decoding chunked streams
141fe618babSjoerg  */
142fe618babSjoerg 
143fe618babSjoerg struct httpio
144fe618babSjoerg {
145fe618babSjoerg 	conn_t		*conn;		/* connection */
146fe618babSjoerg 	int		 chunked;	/* chunked mode */
14770160d70Sjoerg 	int		 keep_alive;	/* keep-alive mode */
148fe618babSjoerg 	char		*buf;		/* chunk buffer */
149fe618babSjoerg 	size_t		 bufsize;	/* size of chunk buffer */
150fe618babSjoerg 	ssize_t		 buflen;	/* amount of data currently in buffer */
151ae6590afSchristos 	size_t		 bufpos;	/* current read offset in buffer */
152fe618babSjoerg 	int		 eof;		/* end-of-file flag */
153fe618babSjoerg 	int		 error;		/* error flag */
154fe618babSjoerg 	size_t		 chunksize;	/* remaining size of current chunk */
15570160d70Sjoerg 	off_t		 contentlength;	/* remaining size of the content */
156fe618babSjoerg };
157fe618babSjoerg 
158fe618babSjoerg /*
159fe618babSjoerg  * Get next chunk header
160fe618babSjoerg  */
161ae6590afSchristos static ssize_t
http_new_chunk(struct httpio * io)162fe618babSjoerg http_new_chunk(struct httpio *io)
163fe618babSjoerg {
164fe618babSjoerg 	char *p;
165fe618babSjoerg 
166fe618babSjoerg 	if (fetch_getln(io->conn) == -1)
167fe618babSjoerg 		return (-1);
168fe618babSjoerg 
169fe618babSjoerg 	if (io->conn->buflen < 2 || !isxdigit((unsigned char)*io->conn->buf))
170fe618babSjoerg 		return (-1);
171fe618babSjoerg 
172fe618babSjoerg 	for (p = io->conn->buf; *p && !isspace((unsigned char)*p); ++p) {
173fe618babSjoerg 		if (*p == ';')
174fe618babSjoerg 			break;
175fe618babSjoerg 		if (!isxdigit((unsigned char)*p))
176fe618babSjoerg 			return (-1);
177fe618babSjoerg 		if (isdigit((unsigned char)*p)) {
178fe618babSjoerg 			io->chunksize = io->chunksize * 16 +
179fe618babSjoerg 			    *p - '0';
180fe618babSjoerg 		} else {
181fe618babSjoerg 			io->chunksize = io->chunksize * 16 +
182fe618babSjoerg 			    10 + tolower((unsigned char)*p) - 'a';
183fe618babSjoerg 		}
184fe618babSjoerg 	}
185fe618babSjoerg 
186fe618babSjoerg 	return (io->chunksize);
187fe618babSjoerg }
188fe618babSjoerg 
189fe618babSjoerg /*
190fe618babSjoerg  * Grow the input buffer to at least len bytes
191fe618babSjoerg  */
192fe618babSjoerg static int
http_growbuf(struct httpio * io,size_t len)193fe618babSjoerg http_growbuf(struct httpio *io, size_t len)
194fe618babSjoerg {
195fe618babSjoerg 	char *tmp;
196fe618babSjoerg 
197fe618babSjoerg 	if (io->bufsize >= len)
198fe618babSjoerg 		return (0);
199fe618babSjoerg 
200fe618babSjoerg 	if ((tmp = realloc(io->buf, len)) == NULL)
201fe618babSjoerg 		return (-1);
202fe618babSjoerg 	io->buf = tmp;
203fe618babSjoerg 	io->bufsize = len;
204fe618babSjoerg 	return (0);
205fe618babSjoerg }
206fe618babSjoerg 
207fe618babSjoerg /*
208fe618babSjoerg  * Fill the input buffer, do chunk decoding on the fly
209fe618babSjoerg  */
210ae6590afSchristos static ssize_t
http_fillbuf(struct httpio * io,size_t len)211fe618babSjoerg http_fillbuf(struct httpio *io, size_t len)
212fe618babSjoerg {
213fe618babSjoerg 	if (io->error)
214fe618babSjoerg 		return (-1);
215fe618babSjoerg 	if (io->eof)
216fe618babSjoerg 		return (0);
217fe618babSjoerg 
21870160d70Sjoerg 	if (io->contentlength >= 0 && (off_t)len > io->contentlength)
21970160d70Sjoerg 		len = io->contentlength;
22070160d70Sjoerg 
221fe618babSjoerg 	if (io->chunked == 0) {
222fe618babSjoerg 		if (http_growbuf(io, len) == -1)
223fe618babSjoerg 			return (-1);
224fe618babSjoerg 		if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
225fe618babSjoerg 			io->error = 1;
226fe618babSjoerg 			return (-1);
227fe618babSjoerg 		}
22870160d70Sjoerg 		if (io->contentlength)
22970160d70Sjoerg 			io->contentlength -= io->buflen;
230fe618babSjoerg 		io->bufpos = 0;
231fe618babSjoerg 		return (io->buflen);
232fe618babSjoerg 	}
233fe618babSjoerg 
234fe618babSjoerg 	if (io->chunksize == 0) {
235fe618babSjoerg 		switch (http_new_chunk(io)) {
236fe618babSjoerg 		case -1:
237fe618babSjoerg 			io->error = 1;
238fe618babSjoerg 			return (-1);
239fe618babSjoerg 		case 0:
240fe618babSjoerg 			io->eof = 1;
24170160d70Sjoerg 			if (fetch_getln(io->conn) == -1)
24270160d70Sjoerg 				return (-1);
243fe618babSjoerg 			return (0);
244fe618babSjoerg 		}
245fe618babSjoerg 	}
246fe618babSjoerg 
247fe618babSjoerg 	if (len > io->chunksize)
248fe618babSjoerg 		len = io->chunksize;
249fe618babSjoerg 	if (http_growbuf(io, len) == -1)
250fe618babSjoerg 		return (-1);
251fe618babSjoerg 	if ((io->buflen = fetch_read(io->conn, io->buf, len)) == -1) {
252fe618babSjoerg 		io->error = 1;
253fe618babSjoerg 		return (-1);
254fe618babSjoerg 	}
255fe618babSjoerg 	io->chunksize -= io->buflen;
25670160d70Sjoerg 	if (io->contentlength >= 0)
25770160d70Sjoerg 		io->contentlength -= io->buflen;
258fe618babSjoerg 
259fe618babSjoerg 	if (io->chunksize == 0) {
260fe618babSjoerg 		char endl[2];
261fe618babSjoerg 		ssize_t len2;
262fe618babSjoerg 
263fe618babSjoerg 		len2 = fetch_read(io->conn, endl, 2);
264fe618babSjoerg 		if (len2 == 1 && fetch_read(io->conn, endl + 1, 1) != 1)
265fe618babSjoerg 			return (-1);
266fe618babSjoerg 		if (len2 == -1 || endl[0] != '\r' || endl[1] != '\n')
267fe618babSjoerg 			return (-1);
268fe618babSjoerg 	}
269fe618babSjoerg 
270fe618babSjoerg 	io->bufpos = 0;
271fe618babSjoerg 
272fe618babSjoerg 	return (io->buflen);
273fe618babSjoerg }
274fe618babSjoerg 
275fe618babSjoerg /*
276fe618babSjoerg  * Read function
277fe618babSjoerg  */
278fe618babSjoerg static ssize_t
http_readfn(void * v,void * buf,size_t len)279fe618babSjoerg http_readfn(void *v, void *buf, size_t len)
280fe618babSjoerg {
281fe618babSjoerg 	struct httpio *io = (struct httpio *)v;
282fe618babSjoerg 	size_t l, pos;
283fe618babSjoerg 
284fe618babSjoerg 	if (io->error)
285fe618babSjoerg 		return (-1);
286fe618babSjoerg 	if (io->eof)
287fe618babSjoerg 		return (0);
288fe618babSjoerg 
289fe618babSjoerg 	for (pos = 0; len > 0; pos += l, len -= l) {
290fe618babSjoerg 		/* empty buffer */
291ae6590afSchristos 		if (!io->buf || (ssize_t)io->bufpos == io->buflen)
292fe618babSjoerg 			if (http_fillbuf(io, len) < 1)
293fe618babSjoerg 				break;
294fe618babSjoerg 		l = io->buflen - io->bufpos;
295fe618babSjoerg 		if (len < l)
296fe618babSjoerg 			l = len;
297fe618babSjoerg 		memcpy((char *)buf + pos, io->buf + io->bufpos, l);
298fe618babSjoerg 		io->bufpos += l;
299fe618babSjoerg 	}
300fe618babSjoerg 
301fe618babSjoerg 	if (!pos && io->error)
302fe618babSjoerg 		return (-1);
303fe618babSjoerg 	return (pos);
304fe618babSjoerg }
305fe618babSjoerg 
306fe618babSjoerg /*
307fe618babSjoerg  * Write function
308fe618babSjoerg  */
309fe618babSjoerg static ssize_t
http_writefn(void * v,const void * buf,size_t len)310fe618babSjoerg http_writefn(void *v, const void *buf, size_t len)
311fe618babSjoerg {
312fe618babSjoerg 	struct httpio *io = (struct httpio *)v;
313fe618babSjoerg 
314fe618babSjoerg 	return (fetch_write(io->conn, buf, len));
315fe618babSjoerg }
316fe618babSjoerg 
317fe618babSjoerg /*
318fe618babSjoerg  * Close function
319fe618babSjoerg  */
320fe618babSjoerg static void
http_closefn(void * v)321fe618babSjoerg http_closefn(void *v)
322fe618babSjoerg {
323fe618babSjoerg 	struct httpio *io = (struct httpio *)v;
324fe618babSjoerg 
32570160d70Sjoerg 	if (io->keep_alive) {
32670160d70Sjoerg 		int val;
32770160d70Sjoerg 
32870160d70Sjoerg 		val = 0;
32970160d70Sjoerg 		setsockopt(io->conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
330ae6590afSchristos 			   (socklen_t)sizeof(val));
33170160d70Sjoerg 			  fetch_cache_put(io->conn, fetch_close);
33270160d70Sjoerg #ifdef TCP_NOPUSH
33370160d70Sjoerg 		val = 1;
33470160d70Sjoerg 		setsockopt(io->conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
33570160d70Sjoerg 		    sizeof(val));
33670160d70Sjoerg #endif
33770160d70Sjoerg 	} else {
338fe618babSjoerg 		fetch_close(io->conn);
33970160d70Sjoerg 	}
34070160d70Sjoerg 
341fe618babSjoerg 	free(io->buf);
342fe618babSjoerg 	free(io);
343fe618babSjoerg }
344fe618babSjoerg 
345fe618babSjoerg /*
346fe618babSjoerg  * Wrap a file descriptor up
347fe618babSjoerg  */
348fe618babSjoerg static fetchIO *
http_funopen(conn_t * conn,int chunked,int keep_alive,off_t clength)34970160d70Sjoerg http_funopen(conn_t *conn, int chunked, int keep_alive, off_t clength)
350fe618babSjoerg {
351fe618babSjoerg 	struct httpio *io;
352fe618babSjoerg 	fetchIO *f;
353fe618babSjoerg 
354fe618babSjoerg 	if ((io = calloc(1, sizeof(*io))) == NULL) {
355fe618babSjoerg 		fetch_syserr();
356fe618babSjoerg 		return (NULL);
357fe618babSjoerg 	}
358fe618babSjoerg 	io->conn = conn;
359fe618babSjoerg 	io->chunked = chunked;
36070160d70Sjoerg 	io->contentlength = clength;
36170160d70Sjoerg 	io->keep_alive = keep_alive;
362fe618babSjoerg 	f = fetchIO_unopen(io, http_readfn, http_writefn, http_closefn);
363fe618babSjoerg 	if (f == NULL) {
364fe618babSjoerg 		fetch_syserr();
365fe618babSjoerg 		free(io);
366fe618babSjoerg 		return (NULL);
367fe618babSjoerg 	}
368fe618babSjoerg 	return (f);
369fe618babSjoerg }
370fe618babSjoerg 
371fe618babSjoerg 
372fe618babSjoerg /*****************************************************************************
373fe618babSjoerg  * Helper functions for talking to the server and parsing its replies
374fe618babSjoerg  */
375fe618babSjoerg 
376fe618babSjoerg /* Header types */
377fe618babSjoerg typedef enum {
378fe618babSjoerg 	hdr_syserror = -2,
379fe618babSjoerg 	hdr_error = -1,
380fe618babSjoerg 	hdr_end = 0,
381fe618babSjoerg 	hdr_unknown = 1,
38270160d70Sjoerg 	hdr_connection,
383fe618babSjoerg 	hdr_content_length,
384fe618babSjoerg 	hdr_content_range,
385fe618babSjoerg 	hdr_last_modified,
386fe618babSjoerg 	hdr_location,
387fe618babSjoerg 	hdr_transfer_encoding,
388fe618babSjoerg 	hdr_www_authenticate
389fe618babSjoerg } hdr_t;
390fe618babSjoerg 
391fe618babSjoerg /* Names of interesting headers */
392fe618babSjoerg static struct {
393fe618babSjoerg 	hdr_t		 num;
394fe618babSjoerg 	const char	*name;
395fe618babSjoerg } hdr_names[] = {
39670160d70Sjoerg 	{ hdr_connection,		"Connection" },
397fe618babSjoerg 	{ hdr_content_length,		"Content-Length" },
398fe618babSjoerg 	{ hdr_content_range,		"Content-Range" },
399fe618babSjoerg 	{ hdr_last_modified,		"Last-Modified" },
400fe618babSjoerg 	{ hdr_location,			"Location" },
401fe618babSjoerg 	{ hdr_transfer_encoding,	"Transfer-Encoding" },
402fe618babSjoerg 	{ hdr_www_authenticate,		"WWW-Authenticate" },
403fe618babSjoerg 	{ hdr_unknown,			NULL },
404fe618babSjoerg };
405fe618babSjoerg 
406fe618babSjoerg /*
407fe618babSjoerg  * Send a formatted line; optionally echo to terminal
408fe618babSjoerg  */
409a3a9b1ecSjoerg __printflike(2, 3)
410fe618babSjoerg static int
http_cmd(conn_t * conn,const char * fmt,...)411fe618babSjoerg http_cmd(conn_t *conn, const char *fmt, ...)
412fe618babSjoerg {
413fe618babSjoerg 	va_list ap;
414fe618babSjoerg 	size_t len;
415fe618babSjoerg 	char *msg;
416ae6590afSchristos 	ssize_t r;
417fe618babSjoerg 
418fe618babSjoerg 	va_start(ap, fmt);
419fe618babSjoerg 	len = vasprintf(&msg, fmt, ap);
420fe618babSjoerg 	va_end(ap);
421fe618babSjoerg 
422fe618babSjoerg 	if (msg == NULL) {
423fe618babSjoerg 		errno = ENOMEM;
424fe618babSjoerg 		fetch_syserr();
425fe618babSjoerg 		return (-1);
426fe618babSjoerg 	}
427fe618babSjoerg 
42870160d70Sjoerg 	r = fetch_write(conn, msg, len);
429fe618babSjoerg 	free(msg);
430fe618babSjoerg 
431fe618babSjoerg 	if (r == -1) {
432fe618babSjoerg 		fetch_syserr();
433fe618babSjoerg 		return (-1);
434fe618babSjoerg 	}
435fe618babSjoerg 
436fe618babSjoerg 	return (0);
437fe618babSjoerg }
438fe618babSjoerg 
439fe618babSjoerg /*
440fe618babSjoerg  * Get and parse status line
441fe618babSjoerg  */
442fe618babSjoerg static int
http_get_reply(conn_t * conn)443fe618babSjoerg http_get_reply(conn_t *conn)
444fe618babSjoerg {
445fe618babSjoerg 	char *p;
446fe618babSjoerg 
447fe618babSjoerg 	if (fetch_getln(conn) == -1)
448fe618babSjoerg 		return (-1);
449fe618babSjoerg 	/*
450fe618babSjoerg 	 * A valid status line looks like "HTTP/m.n xyz reason" where m
451fe618babSjoerg 	 * and n are the major and minor protocol version numbers and xyz
452fe618babSjoerg 	 * is the reply code.
453fe618babSjoerg 	 * Unfortunately, there are servers out there (NCSA 1.5.1, to name
454fe618babSjoerg 	 * just one) that do not send a version number, so we can't rely
455fe618babSjoerg 	 * on finding one, but if we do, insist on it being 1.0 or 1.1.
456fe618babSjoerg 	 * We don't care about the reason phrase.
457fe618babSjoerg 	 */
458fe618babSjoerg 	if (strncmp(conn->buf, "HTTP", 4) != 0)
459fe618babSjoerg 		return (HTTP_PROTOCOL_ERROR);
460fe618babSjoerg 	p = conn->buf + 4;
461fe618babSjoerg 	if (*p == '/') {
462fe618babSjoerg 		if (p[1] != '1' || p[2] != '.' || (p[3] != '0' && p[3] != '1'))
463fe618babSjoerg 			return (HTTP_PROTOCOL_ERROR);
464fe618babSjoerg 		p += 4;
465fe618babSjoerg 	}
466fe618babSjoerg 	if (*p != ' ' ||
467fe618babSjoerg 	    !isdigit((unsigned char)p[1]) ||
468fe618babSjoerg 	    !isdigit((unsigned char)p[2]) ||
469fe618babSjoerg 	    !isdigit((unsigned char)p[3]))
470fe618babSjoerg 		return (HTTP_PROTOCOL_ERROR);
471fe618babSjoerg 
472fe618babSjoerg 	conn->err = (p[1] - '0') * 100 + (p[2] - '0') * 10 + (p[3] - '0');
473fe618babSjoerg 	return (conn->err);
474fe618babSjoerg }
475fe618babSjoerg 
476fe618babSjoerg /*
477fe618babSjoerg  * Check a header; if the type matches the given string, return a pointer
478fe618babSjoerg  * to the beginning of the value.
479fe618babSjoerg  */
480fe618babSjoerg static const char *
http_match(const char * str,const char * hdr)481fe618babSjoerg http_match(const char *str, const char *hdr)
482fe618babSjoerg {
483fe618babSjoerg 	while (*str && *hdr &&
484fe618babSjoerg 	    tolower((unsigned char)*str++) == tolower((unsigned char)*hdr++))
485fe618babSjoerg 		/* nothing */;
486fe618babSjoerg 	if (*str || *hdr != ':')
487fe618babSjoerg 		return (NULL);
488fe618babSjoerg 	while (*hdr && isspace((unsigned char)*++hdr))
489fe618babSjoerg 		/* nothing */;
490fe618babSjoerg 	return (hdr);
491fe618babSjoerg }
492fe618babSjoerg 
493fe618babSjoerg /*
494fe618babSjoerg  * Get the next header and return the appropriate symbolic code.
495fe618babSjoerg  */
496fe618babSjoerg static hdr_t
http_next_header(conn_t * conn,const char ** p)497fe618babSjoerg http_next_header(conn_t *conn, const char **p)
498fe618babSjoerg {
499fe618babSjoerg 	int i;
500fe618babSjoerg 
501fe618babSjoerg 	if (fetch_getln(conn) == -1)
502fe618babSjoerg 		return (hdr_syserror);
503fe618babSjoerg 	while (conn->buflen && isspace((unsigned char)conn->buf[conn->buflen - 1]))
504fe618babSjoerg 		conn->buflen--;
505fe618babSjoerg 	conn->buf[conn->buflen] = '\0';
506fe618babSjoerg 	if (conn->buflen == 0)
507fe618babSjoerg 		return (hdr_end);
508fe618babSjoerg 	/*
509fe618babSjoerg 	 * We could check for malformed headers but we don't really care.
510fe618babSjoerg 	 * A valid header starts with a token immediately followed by a
511fe618babSjoerg 	 * colon; a token is any sequence of non-control, non-whitespace
512fe618babSjoerg 	 * characters except "()<>@,;:\\\"{}".
513fe618babSjoerg 	 */
514fe618babSjoerg 	for (i = 0; hdr_names[i].num != hdr_unknown; i++)
515fe618babSjoerg 		if ((*p = http_match(hdr_names[i].name, conn->buf)) != NULL)
516fe618babSjoerg 			return (hdr_names[i].num);
517fe618babSjoerg 	return (hdr_unknown);
518fe618babSjoerg }
519fe618babSjoerg 
520fe618babSjoerg /*
521fe618babSjoerg  * Parse a last-modified header
522fe618babSjoerg  */
523fe618babSjoerg static int
http_parse_mtime(const char * p,time_t * mtime)524fe618babSjoerg http_parse_mtime(const char *p, time_t *mtime)
525fe618babSjoerg {
526fe618babSjoerg 	char locale[64], *r;
527fe618babSjoerg 	struct tm tm;
528fe618babSjoerg 
529fe618babSjoerg 	strncpy(locale, setlocale(LC_TIME, NULL), sizeof(locale));
530fe618babSjoerg 	setlocale(LC_TIME, "C");
531fe618babSjoerg 	r = strptime(p, "%a, %d %b %Y %H:%M:%S GMT", &tm);
532fe618babSjoerg 	/* XXX should add support for date-2 and date-3 */
533fe618babSjoerg 	setlocale(LC_TIME, locale);
534fe618babSjoerg 	if (r == NULL)
535fe618babSjoerg 		return (-1);
536fe618babSjoerg 	*mtime = timegm(&tm);
537fe618babSjoerg 	return (0);
538fe618babSjoerg }
539fe618babSjoerg 
540fe618babSjoerg /*
541fe618babSjoerg  * Parse a content-length header
542fe618babSjoerg  */
543fe618babSjoerg static int
http_parse_length(const char * p,off_t * length)544fe618babSjoerg http_parse_length(const char *p, off_t *length)
545fe618babSjoerg {
546fe618babSjoerg 	off_t len;
547fe618babSjoerg 
548fe618babSjoerg 	for (len = 0; *p && isdigit((unsigned char)*p); ++p)
549fe618babSjoerg 		len = len * 10 + (*p - '0');
550fe618babSjoerg 	if (*p)
551fe618babSjoerg 		return (-1);
552fe618babSjoerg 	*length = len;
553fe618babSjoerg 	return (0);
554fe618babSjoerg }
555fe618babSjoerg 
556fe618babSjoerg /*
557fe618babSjoerg  * Parse a content-range header
558fe618babSjoerg  */
559fe618babSjoerg static int
http_parse_range(const char * p,off_t * offset,off_t * length,off_t * size)560fe618babSjoerg http_parse_range(const char *p, off_t *offset, off_t *length, off_t *size)
561fe618babSjoerg {
562fe618babSjoerg 	off_t first, last, len;
563fe618babSjoerg 
564fe618babSjoerg 	if (strncasecmp(p, "bytes ", 6) != 0)
565fe618babSjoerg 		return (-1);
566fe618babSjoerg 	p += 6;
567fe618babSjoerg 	if (*p == '*') {
568fe618babSjoerg 		first = last = -1;
569fe618babSjoerg 		++p;
570fe618babSjoerg 	} else {
571fe618babSjoerg 		for (first = 0; *p && isdigit((unsigned char)*p); ++p)
572fe618babSjoerg 			first = first * 10 + *p - '0';
573fe618babSjoerg 		if (*p != '-')
574fe618babSjoerg 			return (-1);
575fe618babSjoerg 		for (last = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
576fe618babSjoerg 			last = last * 10 + *p - '0';
577fe618babSjoerg 	}
578fe618babSjoerg 	if (first > last || *p != '/')
579fe618babSjoerg 		return (-1);
580fe618babSjoerg 	for (len = 0, ++p; *p && isdigit((unsigned char)*p); ++p)
581fe618babSjoerg 		len = len * 10 + *p - '0';
582fe618babSjoerg 	if (*p || len < last - first + 1)
583fe618babSjoerg 		return (-1);
584fe618babSjoerg 	if (first == -1)
585fe618babSjoerg 		*length = 0;
586fe618babSjoerg 	else
587fe618babSjoerg 		*length = last - first + 1;
588fe618babSjoerg 	*offset = first;
589fe618babSjoerg 	*size = len;
590fe618babSjoerg 	return (0);
591fe618babSjoerg }
592fe618babSjoerg 
593fe618babSjoerg 
594fe618babSjoerg /*****************************************************************************
595fe618babSjoerg  * Helper functions for authorization
596fe618babSjoerg  */
597fe618babSjoerg 
598fe618babSjoerg /*
599fe618babSjoerg  * Base64 encoding
600fe618babSjoerg  */
601fe618babSjoerg static char *
http_base64(const char * src)602fe618babSjoerg http_base64(const char *src)
603fe618babSjoerg {
604fe618babSjoerg 	static const char base64[] =
605fe618babSjoerg 	    "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
606fe618babSjoerg 	    "abcdefghijklmnopqrstuvwxyz"
607fe618babSjoerg 	    "0123456789+/";
608fe618babSjoerg 	char *str, *dst;
609fe618babSjoerg 	size_t l;
610ae6590afSchristos 	unsigned int t, r;
611fe618babSjoerg 
612fe618babSjoerg 	l = strlen(src);
613fe618babSjoerg 	if ((str = malloc(((l + 2) / 3) * 4 + 1)) == NULL)
614fe618babSjoerg 		return (NULL);
615fe618babSjoerg 	dst = str;
616fe618babSjoerg 	r = 0;
617fe618babSjoerg 
618fe618babSjoerg 	while (l >= 3) {
619fe618babSjoerg 		t = (src[0] << 16) | (src[1] << 8) | src[2];
620fe618babSjoerg 		dst[0] = base64[(t >> 18) & 0x3f];
621fe618babSjoerg 		dst[1] = base64[(t >> 12) & 0x3f];
622fe618babSjoerg 		dst[2] = base64[(t >> 6) & 0x3f];
623fe618babSjoerg 		dst[3] = base64[(t >> 0) & 0x3f];
624fe618babSjoerg 		src += 3; l -= 3;
625fe618babSjoerg 		dst += 4; r += 4;
626fe618babSjoerg 	}
627fe618babSjoerg 
628fe618babSjoerg 	switch (l) {
629fe618babSjoerg 	case 2:
630fe618babSjoerg 		t = (src[0] << 16) | (src[1] << 8);
631fe618babSjoerg 		dst[0] = base64[(t >> 18) & 0x3f];
632fe618babSjoerg 		dst[1] = base64[(t >> 12) & 0x3f];
633fe618babSjoerg 		dst[2] = base64[(t >> 6) & 0x3f];
634fe618babSjoerg 		dst[3] = '=';
635fe618babSjoerg 		dst += 4;
636fe618babSjoerg 		r += 4;
637fe618babSjoerg 		break;
638fe618babSjoerg 	case 1:
639fe618babSjoerg 		t = src[0] << 16;
640fe618babSjoerg 		dst[0] = base64[(t >> 18) & 0x3f];
641fe618babSjoerg 		dst[1] = base64[(t >> 12) & 0x3f];
642fe618babSjoerg 		dst[2] = dst[3] = '=';
643fe618babSjoerg 		dst += 4;
644fe618babSjoerg 		r += 4;
645fe618babSjoerg 		break;
646fe618babSjoerg 	case 0:
647fe618babSjoerg 		break;
648fe618babSjoerg 	}
649fe618babSjoerg 
650fe618babSjoerg 	*dst = 0;
651fe618babSjoerg 	return (str);
652fe618babSjoerg }
653fe618babSjoerg 
654fe618babSjoerg /*
655fe618babSjoerg  * Encode username and password
656fe618babSjoerg  */
657fe618babSjoerg static int
http_basic_auth(conn_t * conn,const char * hdr,const char * usr,const char * pwd)658fe618babSjoerg http_basic_auth(conn_t *conn, const char *hdr, const char *usr, const char *pwd)
659fe618babSjoerg {
660fe618babSjoerg 	char *upw, *auth;
661fe618babSjoerg 	int r;
662fe618babSjoerg 
663fe618babSjoerg 	if (asprintf(&upw, "%s:%s", usr, pwd) == -1)
664fe618babSjoerg 		return (-1);
665fe618babSjoerg 	auth = http_base64(upw);
666fe618babSjoerg 	free(upw);
667fe618babSjoerg 	if (auth == NULL)
668fe618babSjoerg 		return (-1);
66970160d70Sjoerg 	r = http_cmd(conn, "%s: Basic %s\r\n", hdr, auth);
670fe618babSjoerg 	free(auth);
671fe618babSjoerg 	return (r);
672fe618babSjoerg }
673fe618babSjoerg 
674fe618babSjoerg /*
675fe618babSjoerg  * Send an authorization header
676fe618babSjoerg  */
677fe618babSjoerg static int
http_authorize(conn_t * conn,const char * hdr,const char * p)678fe618babSjoerg http_authorize(conn_t *conn, const char *hdr, const char *p)
679fe618babSjoerg {
680fe618babSjoerg 	/* basic authorization */
681fe618babSjoerg 	if (strncasecmp(p, "basic:", 6) == 0) {
682fe618babSjoerg 		char *user, *pwd, *str;
683fe618babSjoerg 		int r;
684fe618babSjoerg 
685fe618babSjoerg 		/* skip realm */
686fe618babSjoerg 		for (p += 6; *p && *p != ':'; ++p)
687fe618babSjoerg 			/* nothing */ ;
688fe618babSjoerg 		if (!*p || strchr(++p, ':') == NULL)
689fe618babSjoerg 			return (-1);
690fe618babSjoerg 		if ((str = strdup(p)) == NULL)
691fe618babSjoerg 			return (-1); /* XXX */
692fe618babSjoerg 		user = str;
693fe618babSjoerg 		pwd = strchr(str, ':');
694fe618babSjoerg 		*pwd++ = '\0';
695fe618babSjoerg 		r = http_basic_auth(conn, hdr, user, pwd);
696fe618babSjoerg 		free(str);
697fe618babSjoerg 		return (r);
698fe618babSjoerg 	}
699fe618babSjoerg 	return (-1);
700fe618babSjoerg }
701fe618babSjoerg 
702fe618babSjoerg 
703fe618babSjoerg /*****************************************************************************
704fe618babSjoerg  * Helper functions for connecting to a server or proxy
705fe618babSjoerg  */
706fe618babSjoerg 
707fe618babSjoerg /*
708fe618babSjoerg  * Connect to the correct HTTP server or proxy.
709fe618babSjoerg  */
710fe618babSjoerg static conn_t *
http_connect(struct url * URL,struct url * purl,const char * flags,int * cached)71170160d70Sjoerg http_connect(struct url *URL, struct url *purl, const char *flags, int *cached)
712fe618babSjoerg {
713fe618babSjoerg 	conn_t *conn;
714fe618babSjoerg 	int af, verbose;
715fe618babSjoerg #ifdef TCP_NOPUSH
716fe618babSjoerg 	int val;
717fe618babSjoerg #endif
718fe618babSjoerg 
71970160d70Sjoerg 	*cached = 1;
72070160d70Sjoerg 
721fe618babSjoerg #ifdef INET6
722fe618babSjoerg 	af = AF_UNSPEC;
723fe618babSjoerg #else
724fe618babSjoerg 	af = AF_INET;
725fe618babSjoerg #endif
726fe618babSjoerg 
727fe618babSjoerg 	verbose = CHECK_FLAG('v');
728fe618babSjoerg 	if (CHECK_FLAG('4'))
729fe618babSjoerg 		af = AF_INET;
730fe618babSjoerg #ifdef INET6
731fe618babSjoerg 	else if (CHECK_FLAG('6'))
732fe618babSjoerg 		af = AF_INET6;
733fe618babSjoerg #endif
734fe618babSjoerg 
735fe618babSjoerg 	if (purl && strcasecmp(URL->scheme, SCHEME_HTTPS) != 0) {
736fe618babSjoerg 		URL = purl;
737fe618babSjoerg 	} else if (strcasecmp(URL->scheme, SCHEME_FTP) == 0) {
738fe618babSjoerg 		/* can't talk http to an ftp server */
739fe618babSjoerg 		/* XXX should set an error code */
740fe618babSjoerg 		return (NULL);
741fe618babSjoerg 	}
742fe618babSjoerg 
74370160d70Sjoerg 	if ((conn = fetch_cache_get(URL, af)) != NULL) {
74470160d70Sjoerg 		*cached = 1;
74570160d70Sjoerg 		return (conn);
74670160d70Sjoerg 	}
74770160d70Sjoerg 
74870160d70Sjoerg 	if ((conn = fetch_connect(URL, af, verbose)) == NULL)
749fe618babSjoerg 		/* fetch_connect() has already set an error code */
750fe618babSjoerg 		return (NULL);
751fe618babSjoerg 	if (strcasecmp(URL->scheme, SCHEME_HTTPS) == 0 &&
752fe618babSjoerg 	    fetch_ssl(conn, verbose) == -1) {
753fe618babSjoerg 		fetch_close(conn);
754fe618babSjoerg 		/* grrr */
755fe618babSjoerg #ifdef EAUTH
756fe618babSjoerg 		errno = EAUTH;
757fe618babSjoerg #else
758fe618babSjoerg 		errno = EPERM;
759fe618babSjoerg #endif
760fe618babSjoerg 		fetch_syserr();
761fe618babSjoerg 		return (NULL);
762fe618babSjoerg 	}
763fe618babSjoerg 
764fe618babSjoerg #ifdef TCP_NOPUSH
765fe618babSjoerg 	val = 1;
766fe618babSjoerg 	setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val, sizeof(val));
767fe618babSjoerg #endif
768fe618babSjoerg 
769fe618babSjoerg 	return (conn);
770fe618babSjoerg }
771fe618babSjoerg 
772fe618babSjoerg static struct url *
http_get_proxy(struct url * url,const char * flags)773fe618babSjoerg http_get_proxy(struct url * url, const char *flags)
774fe618babSjoerg {
775fe618babSjoerg 	struct url *purl;
776fe618babSjoerg 	char *p;
777fe618babSjoerg 
778fe618babSjoerg 	if (flags != NULL && strchr(flags, 'd') != NULL)
779fe618babSjoerg 		return (NULL);
780fe618babSjoerg 	if (fetch_no_proxy_match(url->host))
781fe618babSjoerg 		return (NULL);
782fe618babSjoerg 	if (((p = getenv("HTTP_PROXY")) || (p = getenv("http_proxy"))) &&
783fe618babSjoerg 	    *p && (purl = fetchParseURL(p))) {
784fe618babSjoerg 		if (!*purl->scheme)
785fe618babSjoerg 			strcpy(purl->scheme, SCHEME_HTTP);
786fe618babSjoerg 		if (!purl->port)
787fe618babSjoerg 			purl->port = fetch_default_proxy_port(purl->scheme);
788fe618babSjoerg 		if (strcasecmp(purl->scheme, SCHEME_HTTP) == 0)
789fe618babSjoerg 			return (purl);
790fe618babSjoerg 		fetchFreeURL(purl);
791fe618babSjoerg 	}
792fe618babSjoerg 	return (NULL);
793fe618babSjoerg }
794fe618babSjoerg 
7959da2cc5cSjoerg static void
set_if_modified_since(conn_t * conn,time_t last_modified)7969da2cc5cSjoerg set_if_modified_since(conn_t *conn, time_t last_modified)
7979da2cc5cSjoerg {
7989da2cc5cSjoerg 	static const char weekdays[] = "SunMonTueWedThuFriSat";
7999da2cc5cSjoerg 	static const char months[] = "JanFebMarAprMayJunJulAugSepOctNovDec";
8009da2cc5cSjoerg 	struct tm tm;
8019da2cc5cSjoerg 	char buf[80];
8029da2cc5cSjoerg 	gmtime_r(&last_modified, &tm);
8039da2cc5cSjoerg 	snprintf(buf, sizeof(buf), "%.3s, %02d %.3s %4d %02d:%02d:%02d GMT",
8049da2cc5cSjoerg 	    weekdays + tm.tm_wday * 3, tm.tm_mday, months + tm.tm_mon * 3,
8059da2cc5cSjoerg 	    tm.tm_year + 1900, tm.tm_hour, tm.tm_min, tm.tm_sec);
80670160d70Sjoerg 	http_cmd(conn, "If-Modified-Since: %s\r\n", buf);
8079da2cc5cSjoerg }
8089da2cc5cSjoerg 
8099da2cc5cSjoerg 
810fe618babSjoerg /*****************************************************************************
811fe618babSjoerg  * Core
812fe618babSjoerg  */
813fe618babSjoerg 
814fe618babSjoerg /*
815fe618babSjoerg  * Send a request and process the reply
816fe618babSjoerg  *
817fe618babSjoerg  * XXX This function is way too long, the do..while loop should be split
818fe618babSjoerg  * XXX off into a separate function.
819fe618babSjoerg  */
820fe618babSjoerg fetchIO *
http_request(struct url * URL,const char * op,struct url_stat * us,struct url * purl,const char * flags)821fe618babSjoerg http_request(struct url *URL, const char *op, struct url_stat *us,
822fe618babSjoerg     struct url *purl, const char *flags)
823fe618babSjoerg {
824fe618babSjoerg 	conn_t *conn;
825fe618babSjoerg 	struct url *url, *new;
82670160d70Sjoerg 	int chunked, direct, if_modified_since, need_auth, noredirect;
82770160d70Sjoerg 	int keep_alive, verbose, cached;
828fe618babSjoerg 	int e, i, n, val;
829fe618babSjoerg 	off_t offset, clength, length, size;
830fe618babSjoerg 	time_t mtime;
831fe618babSjoerg 	const char *p;
832fe618babSjoerg 	fetchIO *f;
833fe618babSjoerg 	hdr_t h;
834fe618babSjoerg 	char hbuf[URL_HOSTLEN + 7], *host;
835fe618babSjoerg 
836fe618babSjoerg 	direct = CHECK_FLAG('d');
837fe618babSjoerg 	noredirect = CHECK_FLAG('A');
838fe618babSjoerg 	verbose = CHECK_FLAG('v');
8399da2cc5cSjoerg 	if_modified_since = CHECK_FLAG('i');
84070160d70Sjoerg 	keep_alive = 0;
841fe618babSjoerg 
842fe618babSjoerg 	if (direct && purl) {
843fe618babSjoerg 		fetchFreeURL(purl);
844fe618babSjoerg 		purl = NULL;
845fe618babSjoerg 	}
846fe618babSjoerg 
847fe618babSjoerg 	/* try the provided URL first */
848fe618babSjoerg 	url = URL;
849fe618babSjoerg 
850fe618babSjoerg 	/* if the A flag is set, we only get one try */
851fe618babSjoerg 	n = noredirect ? 1 : MAX_REDIRECT;
852fe618babSjoerg 	i = 0;
853fe618babSjoerg 
854fe618babSjoerg 	e = HTTP_PROTOCOL_ERROR;
855fe618babSjoerg 	need_auth = 0;
856fe618babSjoerg 	do {
857fe618babSjoerg 		new = NULL;
858fe618babSjoerg 		chunked = 0;
859fe618babSjoerg 		offset = 0;
860fe618babSjoerg 		clength = -1;
861fe618babSjoerg 		length = -1;
862fe618babSjoerg 		size = -1;
863fe618babSjoerg 		mtime = 0;
864fe618babSjoerg 
865fe618babSjoerg 		/* check port */
866fe618babSjoerg 		if (!url->port)
867fe618babSjoerg 			url->port = fetch_default_port(url->scheme);
868fe618babSjoerg 
869fe618babSjoerg 		/* were we redirected to an FTP URL? */
870fe618babSjoerg 		if (purl == NULL && strcmp(url->scheme, SCHEME_FTP) == 0) {
871fe618babSjoerg 			if (strcmp(op, "GET") == 0)
872fe618babSjoerg 				return (ftp_request(url, "RETR", NULL, us, purl, flags));
873fe618babSjoerg 			else if (strcmp(op, "HEAD") == 0)
874fe618babSjoerg 				return (ftp_request(url, "STAT", NULL, us, purl, flags));
875fe618babSjoerg 		}
876fe618babSjoerg 
877fe618babSjoerg 		/* connect to server or proxy */
87870160d70Sjoerg 		if ((conn = http_connect(url, purl, flags, &cached)) == NULL)
879fe618babSjoerg 			goto ouch;
880fe618babSjoerg 
881fe618babSjoerg 		host = url->host;
882fe618babSjoerg #ifdef INET6
883fe618babSjoerg 		if (strchr(url->host, ':')) {
884fe618babSjoerg 			snprintf(hbuf, sizeof(hbuf), "[%s]", url->host);
885fe618babSjoerg 			host = hbuf;
886fe618babSjoerg 		}
887fe618babSjoerg #endif
888fe618babSjoerg 		if (url->port != fetch_default_port(url->scheme)) {
889fe618babSjoerg 			if (host != hbuf) {
890fe618babSjoerg 				strcpy(hbuf, host);
891fe618babSjoerg 				host = hbuf;
892fe618babSjoerg 			}
893fe618babSjoerg 			snprintf(hbuf + strlen(hbuf),
894fe618babSjoerg 			    sizeof(hbuf) - strlen(hbuf), ":%d", url->port);
895fe618babSjoerg 		}
896fe618babSjoerg 
897fe618babSjoerg 		/* send request */
898fe618babSjoerg 		if (verbose)
899fe618babSjoerg 			fetch_info("requesting %s://%s%s",
900fe618babSjoerg 			    url->scheme, host, url->doc);
901fe618babSjoerg 		if (purl) {
90270160d70Sjoerg 			http_cmd(conn, "%s %s://%s%s HTTP/1.1\r\n",
903fe618babSjoerg 			    op, url->scheme, host, url->doc);
904fe618babSjoerg 		} else {
90570160d70Sjoerg 			http_cmd(conn, "%s %s HTTP/1.1\r\n",
906fe618babSjoerg 			    op, url->doc);
907fe618babSjoerg 		}
908fe618babSjoerg 
9099da2cc5cSjoerg 		if (if_modified_since && url->last_modified > 0)
9109da2cc5cSjoerg 			set_if_modified_since(conn, url->last_modified);
9119da2cc5cSjoerg 
912fe618babSjoerg 		/* virtual host */
91370160d70Sjoerg 		http_cmd(conn, "Host: %s\r\n", host);
914fe618babSjoerg 
915fe618babSjoerg 		/* proxy authorization */
916fe618babSjoerg 		if (purl) {
917fe618babSjoerg 			if (*purl->user || *purl->pwd)
918fe618babSjoerg 				http_basic_auth(conn, "Proxy-Authorization",
919fe618babSjoerg 				    purl->user, purl->pwd);
920fe618babSjoerg 			else if ((p = getenv("HTTP_PROXY_AUTH")) != NULL && *p != '\0')
921fe618babSjoerg 				http_authorize(conn, "Proxy-Authorization", p);
922fe618babSjoerg 		}
923fe618babSjoerg 
924fe618babSjoerg 		/* server authorization */
925fe618babSjoerg 		if (need_auth || *url->user || *url->pwd) {
926fe618babSjoerg 			if (*url->user || *url->pwd)
927fe618babSjoerg 				http_basic_auth(conn, "Authorization", url->user, url->pwd);
928fe618babSjoerg 			else if ((p = getenv("HTTP_AUTH")) != NULL && *p != '\0')
929fe618babSjoerg 				http_authorize(conn, "Authorization", p);
930fe618babSjoerg 			else if (fetchAuthMethod && fetchAuthMethod(url) == 0) {
931fe618babSjoerg 				http_basic_auth(conn, "Authorization", url->user, url->pwd);
932fe618babSjoerg 			} else {
933fe618babSjoerg 				http_seterr(HTTP_NEED_AUTH);
934fe618babSjoerg 				goto ouch;
935fe618babSjoerg 			}
936fe618babSjoerg 		}
937fe618babSjoerg 
938fe618babSjoerg 		/* other headers */
939fe618babSjoerg 		if ((p = getenv("HTTP_REFERER")) != NULL && *p != '\0') {
940fe618babSjoerg 			if (strcasecmp(p, "auto") == 0)
94170160d70Sjoerg 				http_cmd(conn, "Referer: %s://%s%s\r\n",
942fe618babSjoerg 				    url->scheme, host, url->doc);
943fe618babSjoerg 			else
94470160d70Sjoerg 				http_cmd(conn, "Referer: %s\r\n", p);
945fe618babSjoerg 		}
946fe618babSjoerg 		if ((p = getenv("HTTP_USER_AGENT")) != NULL && *p != '\0')
94770160d70Sjoerg 			http_cmd(conn, "User-Agent: %s\r\n", p);
948fe618babSjoerg 		else
94970160d70Sjoerg 			http_cmd(conn, "User-Agent: %s\r\n", _LIBFETCH_VER);
950fe618babSjoerg 		if (url->offset > 0)
95170160d70Sjoerg 			http_cmd(conn, "Range: bytes=%lld-\r\n", (long long)url->offset);
95270160d70Sjoerg 		http_cmd(conn, "\r\n");
953fe618babSjoerg 
954fe618babSjoerg 		/*
955fe618babSjoerg 		 * Force the queued request to be dispatched.  Normally, one
956fe618babSjoerg 		 * would do this with shutdown(2) but squid proxies can be
957fe618babSjoerg 		 * configured to disallow such half-closed connections.  To
958fe618babSjoerg 		 * be compatible with such configurations, fiddle with socket
959fe618babSjoerg 		 * options to force the pending data to be written.
960fe618babSjoerg 		 */
961fe618babSjoerg #ifdef TCP_NOPUSH
962fe618babSjoerg 		val = 0;
963fe618babSjoerg 		setsockopt(conn->sd, IPPROTO_TCP, TCP_NOPUSH, &val,
964fe618babSjoerg 			   sizeof(val));
965fe618babSjoerg #endif
966fe618babSjoerg 		val = 1;
967fe618babSjoerg 		setsockopt(conn->sd, IPPROTO_TCP, TCP_NODELAY, &val,
968ae6590afSchristos 		    (socklen_t)sizeof(val));
969fe618babSjoerg 
970fe618babSjoerg 		/* get reply */
971fe618babSjoerg 		switch (http_get_reply(conn)) {
972fe618babSjoerg 		case HTTP_OK:
973fe618babSjoerg 		case HTTP_PARTIAL:
9749da2cc5cSjoerg 		case HTTP_NOT_MODIFIED:
975fe618babSjoerg 			/* fine */
976fe618babSjoerg 			break;
977fe618babSjoerg 		case HTTP_MOVED_PERM:
978fe618babSjoerg 		case HTTP_MOVED_TEMP:
979fe618babSjoerg 		case HTTP_SEE_OTHER:
980fe618babSjoerg 			/*
981fe618babSjoerg 			 * Not so fine, but we still have to read the
982fe618babSjoerg 			 * headers to get the new location.
983fe618babSjoerg 			 */
984fe618babSjoerg 			break;
985fe618babSjoerg 		case HTTP_NEED_AUTH:
986fe618babSjoerg 			if (need_auth) {
987fe618babSjoerg 				/*
988fe618babSjoerg 				 * We already sent out authorization code,
989fe618babSjoerg 				 * so there's nothing more we can do.
990fe618babSjoerg 				 */
991fe618babSjoerg 				http_seterr(conn->err);
992fe618babSjoerg 				goto ouch;
993fe618babSjoerg 			}
994fe618babSjoerg 			/* try again, but send the password this time */
995fe618babSjoerg 			if (verbose)
996fe618babSjoerg 				fetch_info("server requires authorization");
997fe618babSjoerg 			break;
998fe618babSjoerg 		case HTTP_NEED_PROXY_AUTH:
999fe618babSjoerg 			/*
1000fe618babSjoerg 			 * If we're talking to a proxy, we already sent
1001fe618babSjoerg 			 * our proxy authorization code, so there's
1002fe618babSjoerg 			 * nothing more we can do.
1003fe618babSjoerg 			 */
1004fe618babSjoerg 			http_seterr(conn->err);
1005fe618babSjoerg 			goto ouch;
1006fe618babSjoerg 		case HTTP_BAD_RANGE:
1007fe618babSjoerg 			/*
1008fe618babSjoerg 			 * This can happen if we ask for 0 bytes because
1009fe618babSjoerg 			 * we already have the whole file.  Consider this
1010fe618babSjoerg 			 * a success for now, and check sizes later.
1011fe618babSjoerg 			 */
1012fe618babSjoerg 			break;
1013fe618babSjoerg 		case HTTP_PROTOCOL_ERROR:
1014fe618babSjoerg 			/* fall through */
1015fe618babSjoerg 		case -1:
101670160d70Sjoerg 			--i;
101770160d70Sjoerg 			if (cached)
101870160d70Sjoerg 				continue;
1019fe618babSjoerg 			fetch_syserr();
1020fe618babSjoerg 			goto ouch;
1021fe618babSjoerg 		default:
1022fe618babSjoerg 			http_seterr(conn->err);
1023fe618babSjoerg 			if (!verbose)
1024fe618babSjoerg 				goto ouch;
1025fe618babSjoerg 			/* fall through so we can get the full error message */
1026fe618babSjoerg 		}
1027fe618babSjoerg 
1028fe618babSjoerg 		/* get headers */
1029fe618babSjoerg 		do {
1030fe618babSjoerg 			switch ((h = http_next_header(conn, &p))) {
1031fe618babSjoerg 			case hdr_syserror:
1032fe618babSjoerg 				fetch_syserr();
1033fe618babSjoerg 				goto ouch;
1034fe618babSjoerg 			case hdr_error:
1035fe618babSjoerg 				http_seterr(HTTP_PROTOCOL_ERROR);
1036fe618babSjoerg 				goto ouch;
103770160d70Sjoerg 			case hdr_connection:
103870160d70Sjoerg 				/* XXX too weak? */
103970160d70Sjoerg 				keep_alive = (strcasecmp(p, "keep-alive") == 0);
104070160d70Sjoerg 				break;
1041fe618babSjoerg 			case hdr_content_length:
1042fe618babSjoerg 				http_parse_length(p, &clength);
1043fe618babSjoerg 				break;
1044fe618babSjoerg 			case hdr_content_range:
1045fe618babSjoerg 				http_parse_range(p, &offset, &length, &size);
1046fe618babSjoerg 				break;
1047fe618babSjoerg 			case hdr_last_modified:
1048fe618babSjoerg 				http_parse_mtime(p, &mtime);
1049fe618babSjoerg 				break;
1050fe618babSjoerg 			case hdr_location:
1051fe618babSjoerg 				if (!HTTP_REDIRECT(conn->err))
1052fe618babSjoerg 					break;
1053fe618babSjoerg 				if (new)
1054fe618babSjoerg 					free(new);
1055fe618babSjoerg 				if (verbose)
1056fe618babSjoerg 					fetch_info("%d redirect to %s", conn->err, p);
1057fe618babSjoerg 				if (*p == '/')
1058fe618babSjoerg 					/* absolute path */
1059fe618babSjoerg 					new = fetchMakeURL(url->scheme, url->host, url->port, p,
1060fe618babSjoerg 					    url->user, url->pwd);
1061fe618babSjoerg 				else
1062fe618babSjoerg 					new = fetchParseURL(p);
1063fe618babSjoerg 				if (new == NULL) {
1064fe618babSjoerg 					/* XXX should set an error code */
1065fe618babSjoerg 					goto ouch;
1066fe618babSjoerg 				}
1067fe618babSjoerg 				if (!*new->user && !*new->pwd) {
1068fe618babSjoerg 					strcpy(new->user, url->user);
1069fe618babSjoerg 					strcpy(new->pwd, url->pwd);
1070fe618babSjoerg 				}
1071fe618babSjoerg 				new->offset = url->offset;
1072fe618babSjoerg 				new->length = url->length;
1073fe618babSjoerg 				break;
1074fe618babSjoerg 			case hdr_transfer_encoding:
1075fe618babSjoerg 				/* XXX weak test*/
1076fe618babSjoerg 				chunked = (strcasecmp(p, "chunked") == 0);
1077fe618babSjoerg 				break;
1078fe618babSjoerg 			case hdr_www_authenticate:
1079fe618babSjoerg 				if (conn->err != HTTP_NEED_AUTH)
1080fe618babSjoerg 					break;
1081fe618babSjoerg 				/* if we were smarter, we'd check the method and realm */
1082fe618babSjoerg 				break;
1083fe618babSjoerg 			case hdr_end:
1084fe618babSjoerg 				/* fall through */
1085fe618babSjoerg 			case hdr_unknown:
1086fe618babSjoerg 				/* ignore */
1087fe618babSjoerg 				break;
1088fe618babSjoerg 			}
1089fe618babSjoerg 		} while (h > hdr_end);
1090fe618babSjoerg 
1091fe618babSjoerg 		/* we need to provide authentication */
1092fe618babSjoerg 		if (conn->err == HTTP_NEED_AUTH) {
1093fe618babSjoerg 			e = conn->err;
1094fe618babSjoerg 			need_auth = 1;
1095fe618babSjoerg 			fetch_close(conn);
1096fe618babSjoerg 			conn = NULL;
1097fe618babSjoerg 			continue;
1098fe618babSjoerg 		}
1099fe618babSjoerg 
1100fe618babSjoerg 		/* requested range not satisfiable */
1101fe618babSjoerg 		if (conn->err == HTTP_BAD_RANGE) {
1102fe618babSjoerg 			if (url->offset == size && url->length == 0) {
1103fe618babSjoerg 				/* asked for 0 bytes; fake it */
1104fe618babSjoerg 				offset = url->offset;
1105fe618babSjoerg 				conn->err = HTTP_OK;
1106fe618babSjoerg 				break;
1107fe618babSjoerg 			} else {
1108fe618babSjoerg 				http_seterr(conn->err);
1109fe618babSjoerg 				goto ouch;
1110fe618babSjoerg 			}
1111fe618babSjoerg 		}
1112fe618babSjoerg 
1113fe618babSjoerg 		/* we have a hit or an error */
11149da2cc5cSjoerg 		if (conn->err == HTTP_OK ||
11159da2cc5cSjoerg 		    conn->err == HTTP_PARTIAL ||
11169da2cc5cSjoerg 		    conn->err == HTTP_NOT_MODIFIED ||
11179da2cc5cSjoerg 		    HTTP_ERROR(conn->err))
1118fe618babSjoerg 			break;
1119fe618babSjoerg 
1120fe618babSjoerg 		/* all other cases: we got a redirect */
1121fe618babSjoerg 		e = conn->err;
1122fe618babSjoerg 		need_auth = 0;
1123fe618babSjoerg 		fetch_close(conn);
1124fe618babSjoerg 		conn = NULL;
1125fe618babSjoerg 		if (!new)
1126fe618babSjoerg 			break;
1127fe618babSjoerg 		if (url != URL)
1128fe618babSjoerg 			fetchFreeURL(url);
1129fe618babSjoerg 		url = new;
1130fe618babSjoerg 	} while (++i < n);
1131fe618babSjoerg 
1132fe618babSjoerg 	/* we failed, or ran out of retries */
1133fe618babSjoerg 	if (conn == NULL) {
1134fe618babSjoerg 		http_seterr(e);
1135fe618babSjoerg 		goto ouch;
1136fe618babSjoerg 	}
1137fe618babSjoerg 
1138fe618babSjoerg 	/* check for inconsistencies */
1139fe618babSjoerg 	if (clength != -1 && length != -1 && clength != length) {
1140fe618babSjoerg 		http_seterr(HTTP_PROTOCOL_ERROR);
1141fe618babSjoerg 		goto ouch;
1142fe618babSjoerg 	}
1143fe618babSjoerg 	if (clength == -1)
1144fe618babSjoerg 		clength = length;
1145fe618babSjoerg 	if (clength != -1)
1146fe618babSjoerg 		length = offset + clength;
1147fe618babSjoerg 	if (length != -1 && size != -1 && length != size) {
1148fe618babSjoerg 		http_seterr(HTTP_PROTOCOL_ERROR);
1149fe618babSjoerg 		goto ouch;
1150fe618babSjoerg 	}
1151fe618babSjoerg 	if (size == -1)
1152fe618babSjoerg 		size = length;
1153fe618babSjoerg 
1154fe618babSjoerg 	/* fill in stats */
1155fe618babSjoerg 	if (us) {
1156fe618babSjoerg 		us->size = size;
1157fe618babSjoerg 		us->atime = us->mtime = mtime;
1158fe618babSjoerg 	}
1159fe618babSjoerg 
1160fe618babSjoerg 	/* too far? */
1161fe618babSjoerg 	if (URL->offset > 0 && offset > URL->offset) {
1162fe618babSjoerg 		http_seterr(HTTP_PROTOCOL_ERROR);
1163fe618babSjoerg 		goto ouch;
1164fe618babSjoerg 	}
1165fe618babSjoerg 
1166fe618babSjoerg 	/* report back real offset and size */
1167fe618babSjoerg 	URL->offset = offset;
1168fe618babSjoerg 	URL->length = clength;
1169fe618babSjoerg 
117070160d70Sjoerg 	if (clength == -1 && !chunked)
117170160d70Sjoerg 		keep_alive = 0;
117270160d70Sjoerg 
11739da2cc5cSjoerg 	if (conn->err == HTTP_NOT_MODIFIED) {
11749da2cc5cSjoerg 		http_seterr(HTTP_NOT_MODIFIED);
117570160d70Sjoerg 		if (keep_alive) {
117670160d70Sjoerg 			fetch_cache_put(conn, fetch_close);
117770160d70Sjoerg 			conn = NULL;
117870160d70Sjoerg 		}
1179eace2ff2Sjoerg 		goto ouch;
11809da2cc5cSjoerg 	}
11819da2cc5cSjoerg 
1182fe618babSjoerg 	/* wrap it up in a fetchIO */
118370160d70Sjoerg 	if ((f = http_funopen(conn, chunked, keep_alive, clength)) == NULL) {
1184fe618babSjoerg 		fetch_syserr();
1185fe618babSjoerg 		goto ouch;
1186fe618babSjoerg 	}
1187fe618babSjoerg 
1188fe618babSjoerg 	if (url != URL)
1189fe618babSjoerg 		fetchFreeURL(url);
1190fe618babSjoerg 	if (purl)
1191fe618babSjoerg 		fetchFreeURL(purl);
1192fe618babSjoerg 
1193fe618babSjoerg 	if (HTTP_ERROR(conn->err)) {
119470160d70Sjoerg 
119570160d70Sjoerg 		if (keep_alive) {
119670160d70Sjoerg 			char buf[512];
119770160d70Sjoerg 			do {
119870160d70Sjoerg 			} while (fetchIO_read(f, buf, sizeof(buf)) > 0);
119970160d70Sjoerg 		}
120070160d70Sjoerg 
1201fe618babSjoerg 		fetchIO_close(f);
1202fe618babSjoerg 		f = NULL;
1203fe618babSjoerg 	}
1204fe618babSjoerg 
1205fe618babSjoerg 	return (f);
1206fe618babSjoerg 
1207fe618babSjoerg ouch:
1208fe618babSjoerg 	if (url != URL)
1209fe618babSjoerg 		fetchFreeURL(url);
1210fe618babSjoerg 	if (purl)
1211fe618babSjoerg 		fetchFreeURL(purl);
1212fe618babSjoerg 	if (conn != NULL)
1213fe618babSjoerg 		fetch_close(conn);
1214fe618babSjoerg 	return (NULL);
1215fe618babSjoerg }
1216fe618babSjoerg 
1217fe618babSjoerg 
1218fe618babSjoerg /*****************************************************************************
1219fe618babSjoerg  * Entry points
1220fe618babSjoerg  */
1221fe618babSjoerg 
1222fe618babSjoerg /*
1223fe618babSjoerg  * Retrieve and stat a file by HTTP
1224fe618babSjoerg  */
1225fe618babSjoerg fetchIO *
fetchXGetHTTP(struct url * URL,struct url_stat * us,const char * flags)1226fe618babSjoerg fetchXGetHTTP(struct url *URL, struct url_stat *us, const char *flags)
1227fe618babSjoerg {
1228fe618babSjoerg 	return (http_request(URL, "GET", us, http_get_proxy(URL, flags), flags));
1229fe618babSjoerg }
1230fe618babSjoerg 
1231fe618babSjoerg /*
1232fe618babSjoerg  * Retrieve a file by HTTP
1233fe618babSjoerg  */
1234fe618babSjoerg fetchIO *
fetchGetHTTP(struct url * URL,const char * flags)1235fe618babSjoerg fetchGetHTTP(struct url *URL, const char *flags)
1236fe618babSjoerg {
1237fe618babSjoerg 	return (fetchXGetHTTP(URL, NULL, flags));
1238fe618babSjoerg }
1239fe618babSjoerg 
1240fe618babSjoerg /*
1241fe618babSjoerg  * Store a file by HTTP
1242fe618babSjoerg  */
1243fe618babSjoerg fetchIO *
1244ae6590afSchristos /*ARGSUSED*/
fetchPutHTTP(struct url * URL __unused,const char * flags __unused)1245ae6590afSchristos fetchPutHTTP(struct url *URL __unused, const char *flags __unused)
1246fe618babSjoerg {
1247fe618babSjoerg 	fprintf(stderr, "fetchPutHTTP(): not implemented\n");
1248fe618babSjoerg 	return (NULL);
1249fe618babSjoerg }
1250fe618babSjoerg 
1251fe618babSjoerg /*
1252fe618babSjoerg  * Get an HTTP document's metadata
1253fe618babSjoerg  */
1254fe618babSjoerg int
fetchStatHTTP(struct url * URL,struct url_stat * us,const char * flags)1255fe618babSjoerg fetchStatHTTP(struct url *URL, struct url_stat *us, const char *flags)
1256fe618babSjoerg {
1257fe618babSjoerg 	fetchIO *f;
1258fe618babSjoerg 
1259fe618babSjoerg 	f = http_request(URL, "HEAD", us, http_get_proxy(URL, flags), flags);
1260fe618babSjoerg 	if (f == NULL)
1261fe618babSjoerg 		return (-1);
1262fe618babSjoerg 	fetchIO_close(f);
1263fe618babSjoerg 	return (0);
1264fe618babSjoerg }
1265fe618babSjoerg 
1266fe618babSjoerg enum http_states {
1267fe618babSjoerg 	ST_NONE,
1268fe618babSjoerg 	ST_LT,
1269fe618babSjoerg 	ST_LTA,
1270fe618babSjoerg 	ST_TAGA,
1271fe618babSjoerg 	ST_H,
1272fe618babSjoerg 	ST_R,
1273fe618babSjoerg 	ST_E,
1274fe618babSjoerg 	ST_F,
1275fe618babSjoerg 	ST_HREF,
1276fe618babSjoerg 	ST_HREFQ,
1277fe618babSjoerg 	ST_TAG,
1278fe618babSjoerg 	ST_TAGAX,
1279fe618babSjoerg 	ST_TAGAQ
1280fe618babSjoerg };
1281fe618babSjoerg 
1282fe618babSjoerg struct index_parser {
1283fe618babSjoerg 	struct url_list *ue;
1284fe618babSjoerg 	struct url *url;
1285fe618babSjoerg 	enum http_states state;
1286fe618babSjoerg };
1287fe618babSjoerg 
1288eace2ff2Sjoerg static ssize_t
parse_index(struct index_parser * parser,const char * buf,size_t len)1289fe618babSjoerg parse_index(struct index_parser *parser, const char *buf, size_t len)
1290fe618babSjoerg {
1291fe618babSjoerg 	char *end_attr, p = *buf;
1292fe618babSjoerg 
1293fe618babSjoerg 	switch (parser->state) {
1294fe618babSjoerg 	case ST_NONE:
1295fe618babSjoerg 		/* Plain text, not in markup */
1296fe618babSjoerg 		if (p == '<')
1297fe618babSjoerg 			parser->state = ST_LT;
1298fe618babSjoerg 		return 1;
1299fe618babSjoerg 	case ST_LT:
1300fe618babSjoerg 		/* In tag -- "<" already found */
1301fe618babSjoerg 		if (p == '>')
1302fe618babSjoerg 			parser->state = ST_NONE;
1303fe618babSjoerg 		else if (p == 'a' || p == 'A')
1304fe618babSjoerg 			parser->state = ST_LTA;
1305fe618babSjoerg 		else if (!isspace((unsigned char)p))
1306fe618babSjoerg 			parser->state = ST_TAG;
1307fe618babSjoerg 		return 1;
1308fe618babSjoerg 	case ST_LTA:
1309fe618babSjoerg 		/* In tag -- "<a" already found */
1310fe618babSjoerg 		if (p == '>')
1311fe618babSjoerg 			parser->state = ST_NONE;
1312fe618babSjoerg 		else if (p == '"')
1313fe618babSjoerg 			parser->state = ST_TAGAQ;
1314fe618babSjoerg 		else if (isspace((unsigned char)p))
1315fe618babSjoerg 			parser->state = ST_TAGA;
1316fe618babSjoerg 		else
1317fe618babSjoerg 			parser->state = ST_TAG;
1318fe618babSjoerg 		return 1;
1319fe618babSjoerg 	case ST_TAG:
1320fe618babSjoerg 		/* In tag, but not "<a" -- disregard */
1321fe618babSjoerg 		if (p == '>')
1322fe618babSjoerg 			parser->state = ST_NONE;
1323fe618babSjoerg 		return 1;
1324fe618babSjoerg 	case ST_TAGA:
1325fe618babSjoerg 		/* In a-tag -- "<a " already found */
1326fe618babSjoerg 		if (p == '>')
1327fe618babSjoerg 			parser->state = ST_NONE;
1328fe618babSjoerg 		else if (p == '"')
1329fe618babSjoerg 			parser->state = ST_TAGAQ;
1330fe618babSjoerg 		else if (p == 'h' || p == 'H')
1331fe618babSjoerg 			parser->state = ST_H;
1332fe618babSjoerg 		else if (!isspace((unsigned char)p))
1333fe618babSjoerg 			parser->state = ST_TAGAX;
1334fe618babSjoerg 		return 1;
1335fe618babSjoerg 	case ST_TAGAX:
1336fe618babSjoerg 		/* In unknown keyword in a-tag */
1337fe618babSjoerg 		if (p == '>')
1338fe618babSjoerg 			parser->state = ST_NONE;
1339fe618babSjoerg 		else if (p == '"')
1340fe618babSjoerg 			parser->state = ST_TAGAQ;
1341fe618babSjoerg 		else if (isspace((unsigned char)p))
1342fe618babSjoerg 			parser->state = ST_TAGA;
1343fe618babSjoerg 		return 1;
1344fe618babSjoerg 	case ST_TAGAQ:
1345fe618babSjoerg 		/* In a-tag, unknown argument for keys. */
1346fe618babSjoerg 		if (p == '>')
1347fe618babSjoerg 			parser->state = ST_NONE;
1348fe618babSjoerg 		else if (p == '"')
1349fe618babSjoerg 			parser->state = ST_TAGA;
1350fe618babSjoerg 		return 1;
1351fe618babSjoerg 	case ST_H:
1352fe618babSjoerg 		/* In a-tag -- "<a h" already found */
1353fe618babSjoerg 		if (p == '>')
1354fe618babSjoerg 			parser->state = ST_NONE;
1355fe618babSjoerg 		else if (p == '"')
1356fe618babSjoerg 			parser->state = ST_TAGAQ;
1357fe618babSjoerg 		else if (p == 'r' || p == 'R')
1358fe618babSjoerg 			parser->state = ST_R;
1359fe618babSjoerg 		else if (isspace((unsigned char)p))
1360fe618babSjoerg 			parser->state = ST_TAGA;
1361fe618babSjoerg 		else
1362fe618babSjoerg 			parser->state = ST_TAGAX;
1363fe618babSjoerg 		return 1;
1364fe618babSjoerg 	case ST_R:
1365fe618babSjoerg 		/* In a-tag -- "<a hr" already found */
1366fe618babSjoerg 		if (p == '>')
1367fe618babSjoerg 			parser->state = ST_NONE;
1368fe618babSjoerg 		else if (p == '"')
1369fe618babSjoerg 			parser->state = ST_TAGAQ;
1370fe618babSjoerg 		else if (p == 'e' || p == 'E')
1371fe618babSjoerg 			parser->state = ST_E;
1372fe618babSjoerg 		else if (isspace((unsigned char)p))
1373fe618babSjoerg 			parser->state = ST_TAGA;
1374fe618babSjoerg 		else
1375fe618babSjoerg 			parser->state = ST_TAGAX;
1376fe618babSjoerg 		return 1;
1377fe618babSjoerg 	case ST_E:
1378fe618babSjoerg 		/* In a-tag -- "<a hre" already found */
1379fe618babSjoerg 		if (p == '>')
1380fe618babSjoerg 			parser->state = ST_NONE;
1381fe618babSjoerg 		else if (p == '"')
1382fe618babSjoerg 			parser->state = ST_TAGAQ;
1383fe618babSjoerg 		else if (p == 'f' || p == 'F')
1384fe618babSjoerg 			parser->state = ST_F;
1385fe618babSjoerg 		else if (isspace((unsigned char)p))
1386fe618babSjoerg 			parser->state = ST_TAGA;
1387fe618babSjoerg 		else
1388fe618babSjoerg 			parser->state = ST_TAGAX;
1389fe618babSjoerg 		return 1;
1390fe618babSjoerg 	case ST_F:
1391fe618babSjoerg 		/* In a-tag -- "<a href" already found */
1392fe618babSjoerg 		if (p == '>')
1393fe618babSjoerg 			parser->state = ST_NONE;
1394fe618babSjoerg 		else if (p == '"')
1395fe618babSjoerg 			parser->state = ST_TAGAQ;
1396fe618babSjoerg 		else if (p == '=')
1397fe618babSjoerg 			parser->state = ST_HREF;
1398fe618babSjoerg 		else if (!isspace((unsigned char)p))
1399fe618babSjoerg 			parser->state = ST_TAGAX;
1400fe618babSjoerg 		return 1;
1401fe618babSjoerg 	case ST_HREF:
1402fe618babSjoerg 		/* In a-tag -- "<a href=" already found */
1403fe618babSjoerg 		if (p == '>')
1404fe618babSjoerg 			parser->state = ST_NONE;
1405fe618babSjoerg 		else if (p == '"')
1406fe618babSjoerg 			parser->state = ST_HREFQ;
1407fe618babSjoerg 		else if (!isspace((unsigned char)p))
1408fe618babSjoerg 			parser->state = ST_TAGA;
1409fe618babSjoerg 		return 1;
1410fe618babSjoerg 	case ST_HREFQ:
1411fe618babSjoerg 		/* In href of the a-tag */
1412fe618babSjoerg 		end_attr = memchr(buf, '"', len);
1413fe618babSjoerg 		if (end_attr == NULL)
1414fe618babSjoerg 			return 0;
1415fe618babSjoerg 		*end_attr = '\0';
1416fe618babSjoerg 		parser->state = ST_TAGA;
1417eace2ff2Sjoerg 		if (fetch_add_entry(parser->ue, parser->url, buf, 1))
1418eace2ff2Sjoerg 			return -1;
1419fe618babSjoerg 		return end_attr + 1 - buf;
1420fe618babSjoerg 	}
142170160d70Sjoerg 	/* NOTREACHED */
1422fe618babSjoerg 	abort();
1423fe618babSjoerg }
1424fe618babSjoerg 
1425eace2ff2Sjoerg struct http_index_cache {
1426eace2ff2Sjoerg 	struct http_index_cache *next;
1427eace2ff2Sjoerg 	struct url *location;
1428eace2ff2Sjoerg 	struct url_list ue;
1429eace2ff2Sjoerg };
1430eace2ff2Sjoerg 
1431eace2ff2Sjoerg static struct http_index_cache *index_cache;
1432eace2ff2Sjoerg 
1433fe618babSjoerg /*
1434fe618babSjoerg  * List a directory
1435fe618babSjoerg  */
1436fe618babSjoerg int
1437ae6590afSchristos /*ARGSUSED*/
fetchListHTTP(struct url_list * ue,struct url * url,const char * pattern __unused,const char * flags)1438ae6590afSchristos fetchListHTTP(struct url_list *ue, struct url *url, const char *pattern __unused, const char *flags)
1439fe618babSjoerg {
1440fe618babSjoerg 	fetchIO *f;
1441fe618babSjoerg 	char buf[2 * PATH_MAX];
1442eace2ff2Sjoerg 	size_t buf_len, sum_processed;
1443eace2ff2Sjoerg 	ssize_t read_len, processed;
1444fe618babSjoerg 	struct index_parser state;
1445eace2ff2Sjoerg 	struct http_index_cache *cache = NULL;
1446eace2ff2Sjoerg 	int do_cache, ret;
1447eace2ff2Sjoerg 
1448eace2ff2Sjoerg 	do_cache = CHECK_FLAG('c');
1449eace2ff2Sjoerg 
1450eace2ff2Sjoerg 	if (do_cache) {
1451eace2ff2Sjoerg 		for (cache = index_cache; cache != NULL; cache = cache->next) {
1452eace2ff2Sjoerg 			if (strcmp(cache->location->scheme, url->scheme))
1453eace2ff2Sjoerg 				continue;
1454eace2ff2Sjoerg 			if (strcmp(cache->location->user, url->user))
1455eace2ff2Sjoerg 				continue;
1456eace2ff2Sjoerg 			if (strcmp(cache->location->pwd, url->pwd))
1457eace2ff2Sjoerg 				continue;
1458eace2ff2Sjoerg 			if (strcmp(cache->location->host, url->host))
1459eace2ff2Sjoerg 				continue;
1460eace2ff2Sjoerg 			if (cache->location->port != url->port)
1461eace2ff2Sjoerg 				continue;
1462eace2ff2Sjoerg 			if (strcmp(cache->location->doc, url->doc))
1463eace2ff2Sjoerg 				continue;
1464eace2ff2Sjoerg 			return fetchAppendURLList(ue, &cache->ue);
1465eace2ff2Sjoerg 		}
1466eace2ff2Sjoerg 
1467eace2ff2Sjoerg 		cache = malloc(sizeof(*cache));
1468eace2ff2Sjoerg 		fetchInitURLList(&cache->ue);
1469eace2ff2Sjoerg 		cache->location = fetchCopyURL(url);
1470eace2ff2Sjoerg 	}
1471eace2ff2Sjoerg 
1472eace2ff2Sjoerg 	f = fetchGetHTTP(url, flags);
1473eace2ff2Sjoerg 	if (f == NULL) {
1474eace2ff2Sjoerg 		if (do_cache) {
1475eace2ff2Sjoerg 			fetchFreeURLList(&cache->ue);
1476eace2ff2Sjoerg 			fetchFreeURL(cache->location);
1477eace2ff2Sjoerg 			free(cache);
1478eace2ff2Sjoerg 		}
1479eace2ff2Sjoerg 		return -1;
1480eace2ff2Sjoerg 	}
1481fe618babSjoerg 
1482fe618babSjoerg 	state.url = url;
1483fe618babSjoerg 	state.state = ST_NONE;
1484eace2ff2Sjoerg 	if (do_cache) {
1485eace2ff2Sjoerg 		state.ue = &cache->ue;
1486eace2ff2Sjoerg 	} else {
1487fe618babSjoerg 		state.ue = ue;
1488eace2ff2Sjoerg 	}
1489fe618babSjoerg 
1490fe618babSjoerg 	buf_len = 0;
1491fe618babSjoerg 
1492fe618babSjoerg 	while ((read_len = fetchIO_read(f, buf + buf_len, sizeof(buf) - buf_len)) > 0) {
1493fe618babSjoerg 		buf_len += read_len;
1494fe618babSjoerg 		sum_processed = 0;
1495fe618babSjoerg 		do {
1496fe618babSjoerg 			processed = parse_index(&state, buf + sum_processed, buf_len);
1497eace2ff2Sjoerg 			if (processed == -1)
1498eace2ff2Sjoerg 				break;
1499fe618babSjoerg 			buf_len -= processed;
1500fe618babSjoerg 			sum_processed += processed;
1501fe618babSjoerg 		} while (processed != 0 && buf_len > 0);
1502eace2ff2Sjoerg 		if (processed == -1) {
1503eace2ff2Sjoerg 			read_len = -1;
1504eace2ff2Sjoerg 			break;
1505eace2ff2Sjoerg 		}
1506fe618babSjoerg 		memmove(buf, buf + sum_processed, buf_len);
1507fe618babSjoerg 	}
1508fe618babSjoerg 
1509fe618babSjoerg 	fetchIO_close(f);
1510eace2ff2Sjoerg 
1511eace2ff2Sjoerg 	ret = read_len < 0 ? -1 : 0;
1512eace2ff2Sjoerg 
1513eace2ff2Sjoerg 	if (do_cache) {
1514eace2ff2Sjoerg 		if (ret == 0) {
1515eace2ff2Sjoerg 			cache->next = index_cache;
1516eace2ff2Sjoerg 			index_cache = cache;
1517eace2ff2Sjoerg 		}
1518eace2ff2Sjoerg 
1519eace2ff2Sjoerg 		if (fetchAppendURLList(ue, &cache->ue))
1520eace2ff2Sjoerg 			ret = -1;
1521eace2ff2Sjoerg 	}
1522eace2ff2Sjoerg 
1523eace2ff2Sjoerg 	return ret;
1524fe618babSjoerg }
1525