xref: /openbsd-src/usr.sbin/rpki-client/http.c (revision b5fa5d51bd4733ed62a748e18f6a838408441343)
1*b5fa5d51Sclaudio /*	$OpenBSD: http.c,v 1.92 2024/11/21 13:32:27 claudio Exp $ */
21ef5b48aSclaudio /*
31ef5b48aSclaudio  * Copyright (c) 2020 Nils Fisher <nils_fisher@hotmail.com>
4c0c0266aSclaudio  * Copyright (c) 2020 Claudio Jeker <claudio@openbsd.org>
51ef5b48aSclaudio  *
61ef5b48aSclaudio  * Permission to use, copy, modify, and distribute this software for any
71ef5b48aSclaudio  * purpose with or without fee is hereby granted, provided that the above
81ef5b48aSclaudio  * copyright notice and this permission notice appear in all copies.
91ef5b48aSclaudio  *
101ef5b48aSclaudio  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
111ef5b48aSclaudio  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
121ef5b48aSclaudio  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
131ef5b48aSclaudio  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
141ef5b48aSclaudio  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
151ef5b48aSclaudio  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
161ef5b48aSclaudio  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
171ef5b48aSclaudio  */
181ef5b48aSclaudio 
191ef5b48aSclaudio /*-
201ef5b48aSclaudio  * Copyright (c) 1997 The NetBSD Foundation, Inc.
211ef5b48aSclaudio  * All rights reserved.
221ef5b48aSclaudio  *
231ef5b48aSclaudio  * This code is derived from software contributed to The NetBSD Foundation
241ef5b48aSclaudio  * by Jason Thorpe and Luke Mewburn.
251ef5b48aSclaudio  *
261ef5b48aSclaudio  * Redistribution and use in source and binary forms, with or without
271ef5b48aSclaudio  * modification, are permitted provided that the following conditions
281ef5b48aSclaudio  * are met:
291ef5b48aSclaudio  * 1. Redistributions of source code must retain the above copyright
301ef5b48aSclaudio  *    notice, this list of conditions and the following disclaimer.
311ef5b48aSclaudio  * 2. Redistributions in binary form must reproduce the above copyright
321ef5b48aSclaudio  *    notice, this list of conditions and the following disclaimer in the
331ef5b48aSclaudio  *    documentation and/or other materials provided with the distribution.
341ef5b48aSclaudio  *
351ef5b48aSclaudio  * THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
361ef5b48aSclaudio  * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
371ef5b48aSclaudio  * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
381ef5b48aSclaudio  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
391ef5b48aSclaudio  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
401ef5b48aSclaudio  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
411ef5b48aSclaudio  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
421ef5b48aSclaudio  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
431ef5b48aSclaudio  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
441ef5b48aSclaudio  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
451ef5b48aSclaudio  * POSSIBILITY OF SUCH DAMAGE.
461ef5b48aSclaudio  */
471ef5b48aSclaudio #include <sys/types.h>
481ef5b48aSclaudio #include <sys/queue.h>
491ef5b48aSclaudio #include <sys/socket.h>
501ef5b48aSclaudio 
5175c55a07Sclaudio #include <assert.h>
521ef5b48aSclaudio #include <ctype.h>
531ef5b48aSclaudio #include <err.h>
541ef5b48aSclaudio #include <errno.h>
5578bab303Sclaudio #include <imsg.h>
561ef5b48aSclaudio #include <limits.h>
571ef5b48aSclaudio #include <netdb.h>
581ef5b48aSclaudio #include <poll.h>
590ce80415Sclaudio #include <signal.h>
601ef5b48aSclaudio #include <stdio.h>
611ef5b48aSclaudio #include <stdlib.h>
621ef5b48aSclaudio #include <string.h>
631ef5b48aSclaudio #include <unistd.h>
641ef5b48aSclaudio #include <vis.h>
6578bab303Sclaudio #include <zlib.h>
661ef5b48aSclaudio 
671ef5b48aSclaudio #include <tls.h>
681ef5b48aSclaudio 
691ef5b48aSclaudio #include "extern.h"
701ef5b48aSclaudio 
711ef5b48aSclaudio #define HTTP_USER_AGENT		"OpenBSD rpki-client"
721ef5b48aSclaudio #define HTTP_BUF_SIZE		(32 * 1024)
7375c55a07Sclaudio #define HTTP_IDLE_TIMEOUT	10
74af6f8e1cSclaudio #define MAX_CONTENTLEN		(2 * 1024 * 1024 * 1024UL)
756e7e5289Sclaudio #define NPFDS			(MAX_HTTP_REQUESTS + 1)
761ef5b48aSclaudio 
7775c55a07Sclaudio enum res {
7875c55a07Sclaudio 	DONE,
7975c55a07Sclaudio 	WANT_POLLIN,
8075c55a07Sclaudio 	WANT_POLLOUT,
8175c55a07Sclaudio };
821ef5b48aSclaudio 
831ef5b48aSclaudio enum http_state {
841ef5b48aSclaudio 	STATE_FREE,
851ef5b48aSclaudio 	STATE_CONNECT,
861ef5b48aSclaudio 	STATE_TLSCONNECT,
876f704872Sclaudio 	STATE_PROXY_REQUEST,
886f704872Sclaudio 	STATE_PROXY_STATUS,
896f704872Sclaudio 	STATE_PROXY_RESPONSE,
901ef5b48aSclaudio 	STATE_REQUEST,
911ef5b48aSclaudio 	STATE_RESPONSE_STATUS,
921ef5b48aSclaudio 	STATE_RESPONSE_HEADER,
931ef5b48aSclaudio 	STATE_RESPONSE_DATA,
9475c55a07Sclaudio 	STATE_RESPONSE_CHUNKED_HEADER,
95fd522c34Sclaudio 	STATE_RESPONSE_CHUNKED_CRLF,
9675c55a07Sclaudio 	STATE_RESPONSE_CHUNKED_TRAILER,
971ef5b48aSclaudio 	STATE_WRITE_DATA,
9875c55a07Sclaudio 	STATE_IDLE,
9975c55a07Sclaudio 	STATE_CLOSE,
1001ef5b48aSclaudio };
1011ef5b48aSclaudio 
1021ef5b48aSclaudio struct http_proxy {
1031ef5b48aSclaudio 	char	*proxyhost;
1046f704872Sclaudio 	char	*proxyport;
1056f704872Sclaudio 	char	*proxyauth;
1066f704872Sclaudio } proxy;
1071ef5b48aSclaudio 
10878bab303Sclaudio struct http_zlib {
10978bab303Sclaudio 	z_stream		 zs;
11078bab303Sclaudio 	char			*zbuf;
11178bab303Sclaudio 	size_t			 zbufsz;
11278bab303Sclaudio 	size_t			 zbufpos;
11378bab303Sclaudio 	size_t			 zinsz;
11478bab303Sclaudio 	int			 zdone;
11578bab303Sclaudio };
11678bab303Sclaudio 
1171ef5b48aSclaudio struct http_connection {
11875c55a07Sclaudio 	LIST_ENTRY(http_connection)	entry;
1191ef5b48aSclaudio 	char			*host;
1201ef5b48aSclaudio 	char			*port;
1211ef5b48aSclaudio 	char			*last_modified;
12275c55a07Sclaudio 	char			*redir_uri;
12375c55a07Sclaudio 	struct http_request	*req;
12475c55a07Sclaudio 	struct pollfd		*pfd;
1251ef5b48aSclaudio 	struct addrinfo		*res0;
1261ef5b48aSclaudio 	struct addrinfo		*res;
1271ef5b48aSclaudio 	struct tls		*tls;
1281ef5b48aSclaudio 	char			*buf;
12978bab303Sclaudio 	struct http_zlib	*zlibctx;
1301ef5b48aSclaudio 	size_t			bufsz;
1311ef5b48aSclaudio 	size_t			bufpos;
132af6f8e1cSclaudio 	size_t			iosz;
133af6f8e1cSclaudio 	size_t			totalsz;
13475c55a07Sclaudio 	time_t			idle_time;
1356f704872Sclaudio 	time_t			io_time;
1361ef5b48aSclaudio 	int			status;
1371ef5b48aSclaudio 	int			fd;
13875c55a07Sclaudio 	int			chunked;
13978bab303Sclaudio 	int			gzipped;
14075c55a07Sclaudio 	int			keep_alive;
1411ef5b48aSclaudio 	short			events;
1421ef5b48aSclaudio 	enum http_state		state;
1431ef5b48aSclaudio };
1441ef5b48aSclaudio 
14575c55a07Sclaudio LIST_HEAD(http_conn_list, http_connection);
1461ef5b48aSclaudio 
14775c55a07Sclaudio struct http_request {
14875c55a07Sclaudio 	TAILQ_ENTRY(http_request)	entry;
14975c55a07Sclaudio 	char			*uri;
15075c55a07Sclaudio 	char			*modified_since;
15175c55a07Sclaudio 	char			*host;
15275c55a07Sclaudio 	char			*port;
15375c55a07Sclaudio 	const char		*path;	/* points into uri */
154b6884e9fSclaudio 	unsigned int		 id;
15575c55a07Sclaudio 	int			 outfd;
15675c55a07Sclaudio 	int			 redirect_loop;
15775c55a07Sclaudio };
15875c55a07Sclaudio 
15975c55a07Sclaudio TAILQ_HEAD(http_req_queue, http_request);
16075c55a07Sclaudio 
16175c55a07Sclaudio static struct http_conn_list	active = LIST_HEAD_INITIALIZER(active);
16275c55a07Sclaudio static struct http_conn_list	idle = LIST_HEAD_INITIALIZER(idle);
16375c55a07Sclaudio static struct http_req_queue	queue = TAILQ_HEAD_INITIALIZER(queue);
164b6884e9fSclaudio static unsigned int		http_conn_count;
16575c55a07Sclaudio 
16625d36c5cSclaudio static struct msgbuf *msgq;
16775c55a07Sclaudio static struct sockaddr_storage http_bindaddr;
16875c55a07Sclaudio static struct tls_config *tls_config;
16975c55a07Sclaudio static uint8_t *tls_ca_mem;
17075c55a07Sclaudio static size_t tls_ca_size;
17175c55a07Sclaudio 
17275c55a07Sclaudio /* HTTP request API */
173b6884e9fSclaudio static void	http_req_new(unsigned int, char *, char *, int, int);
17475c55a07Sclaudio static void	http_req_free(struct http_request *);
175b6884e9fSclaudio static void	http_req_done(unsigned int, enum http_result, const char *);
176b6884e9fSclaudio static void	http_req_fail(unsigned int);
17775c55a07Sclaudio static int	http_req_schedule(struct http_request *);
17875c55a07Sclaudio 
17978bab303Sclaudio /* HTTP decompression helper */
18078bab303Sclaudio static int	http_inflate_new(struct http_connection *);
18178bab303Sclaudio static void	http_inflate_free(struct http_connection *);
18278bab303Sclaudio static void	http_inflate_done(struct http_connection *);
18378bab303Sclaudio static int	http_inflate_data(struct http_connection *);
18478bab303Sclaudio static enum res	http_inflate_advance(struct http_connection *);
18578bab303Sclaudio 
18675c55a07Sclaudio /* HTTP connection API */
18775c55a07Sclaudio static void	http_new(struct http_request *);
188c8a1112eSclaudio static void	http_free(struct http_connection *);
189c8a1112eSclaudio 
19075c55a07Sclaudio static enum res http_done(struct http_connection *, enum http_result);
19175c55a07Sclaudio static enum res http_failed(struct http_connection *);
19275c55a07Sclaudio 
19375c55a07Sclaudio /* HTTP connection FSM functions */
19475c55a07Sclaudio static void	http_do(struct http_connection *,
19575c55a07Sclaudio 		    enum res (*)(struct http_connection *));
19675c55a07Sclaudio 
19775c55a07Sclaudio /* These functions can be used with http_do() */
19875c55a07Sclaudio static enum res	http_connect(struct http_connection *);
19975c55a07Sclaudio static enum res	http_request(struct http_connection *);
20075c55a07Sclaudio static enum res	http_close(struct http_connection *);
20175c55a07Sclaudio static enum res	http_handle(struct http_connection *);
20275c55a07Sclaudio 
20375c55a07Sclaudio /* Internal state functions used by the above functions */
20475c55a07Sclaudio static enum res	http_finish_connect(struct http_connection *);
2056f704872Sclaudio static enum res	proxy_connect(struct http_connection *);
20675c55a07Sclaudio static enum res	http_tls_connect(struct http_connection *);
20775c55a07Sclaudio static enum res	http_tls_handshake(struct http_connection *);
20875c55a07Sclaudio static enum res	http_read(struct http_connection *);
20975c55a07Sclaudio static enum res	http_write(struct http_connection *);
2106f704872Sclaudio static enum res	proxy_read(struct http_connection *);
2116f704872Sclaudio static enum res	proxy_write(struct http_connection *);
21275c55a07Sclaudio static enum res	data_write(struct http_connection *);
21378bab303Sclaudio static enum res	data_inflate_write(struct http_connection *);
21475c55a07Sclaudio 
2151ef5b48aSclaudio /*
2161ef5b48aSclaudio  * Return a string that can be used in error message to identify the
2171ef5b48aSclaudio  * connection.
2181ef5b48aSclaudio  */
2191ef5b48aSclaudio static const char *
22075c55a07Sclaudio http_info(const char *uri)
2211ef5b48aSclaudio {
222760e3f75Sclaudio 	static char buf[80];
2231ef5b48aSclaudio 
22475c55a07Sclaudio 	if (strnvis(buf, uri, sizeof buf, VIS_SAFE) >= (int)sizeof buf) {
2251ef5b48aSclaudio 		/* overflow, add indicator */
2261ef5b48aSclaudio 		memcpy(buf + sizeof buf - 4, "...", 4);
2271ef5b48aSclaudio 	}
2281ef5b48aSclaudio 
2291ef5b48aSclaudio 	return buf;
2301ef5b48aSclaudio }
2311ef5b48aSclaudio 
2321ef5b48aSclaudio /*
233310fe3abSjob  * Return IP address in presentation format.
234310fe3abSjob  */
235310fe3abSjob static const char *
236310fe3abSjob ip_info(const struct http_connection *conn)
237310fe3abSjob {
238eee9c28cSclaudio 	static char	ipbuf[NI_MAXHOST];
239310fe3abSjob 
240310fe3abSjob 	if (conn->res == NULL)
241eee9c28cSclaudio 		return "unknown";
242310fe3abSjob 
243310fe3abSjob 	if (getnameinfo(conn->res->ai_addr, conn->res->ai_addrlen, ipbuf,
244310fe3abSjob 	    sizeof(ipbuf), NULL, 0, NI_NUMERICHOST) != 0)
245eee9c28cSclaudio 		return "unknown";
246310fe3abSjob 
247eee9c28cSclaudio 	return ipbuf;
248eee9c28cSclaudio }
249310fe3abSjob 
250eee9c28cSclaudio static const char *
251eee9c28cSclaudio conn_info(const struct http_connection *conn)
252eee9c28cSclaudio {
253eee9c28cSclaudio 	static char	 buf[100 + NI_MAXHOST];
254eee9c28cSclaudio 	const char	*uri;
255eee9c28cSclaudio 
256eee9c28cSclaudio 	if (conn->req == NULL)
257eee9c28cSclaudio 		uri = conn->host;
258eee9c28cSclaudio 	else
259eee9c28cSclaudio 		uri = conn->req->uri;
260eee9c28cSclaudio 
261eee9c28cSclaudio 	snprintf(buf, sizeof(buf), "%s (%s)", http_info(uri), ip_info(conn));
262310fe3abSjob 	return buf;
263310fe3abSjob }
264310fe3abSjob 
265310fe3abSjob /*
26664247d16Sclaudio  * Determine whether the character needs encoding, per RFC2396.
2671ef5b48aSclaudio  */
2681ef5b48aSclaudio static int
26964247d16Sclaudio to_encode(const char *c0)
2701ef5b48aSclaudio {
27164247d16Sclaudio 	/* 2.4.3. Excluded US-ASCII Characters */
27264247d16Sclaudio 	const char *excluded_chars =
27364247d16Sclaudio 	    " "         /* space */
27464247d16Sclaudio 	    "<>#\""     /* delims (modulo "%", see below) */
27564247d16Sclaudio 	    "{}|\\^[]`" /* unwise */
27664247d16Sclaudio 	    ;
2771ef5b48aSclaudio 	const unsigned char *c = (const unsigned char *)c0;
2781ef5b48aSclaudio 
2791ef5b48aSclaudio 	/*
2801ef5b48aSclaudio 	 * No corresponding graphic US-ASCII.
2811ef5b48aSclaudio 	 * Control characters and octets not used in US-ASCII.
2821ef5b48aSclaudio 	 */
2831ef5b48aSclaudio 	return (iscntrl(*c) || !isascii(*c) ||
2841ef5b48aSclaudio 
2851ef5b48aSclaudio 	    /*
28664247d16Sclaudio 	     * '%' is also reserved, if is not followed by two
2871ef5b48aSclaudio 	     * hexadecimal digits.
2881ef5b48aSclaudio 	     */
28964247d16Sclaudio 	    strchr(excluded_chars, *c) != NULL ||
2907810f2e0Sderaadt 	    (*c == '%' && (!isxdigit(c[1]) || !isxdigit(c[2]))));
2911ef5b48aSclaudio }
2921ef5b48aSclaudio 
2931ef5b48aSclaudio /*
29464247d16Sclaudio  * Encode given URL, per RFC2396.
2951ef5b48aSclaudio  * Allocate and return string to the caller.
2961ef5b48aSclaudio  */
2971ef5b48aSclaudio static char *
2981ef5b48aSclaudio url_encode(const char *path)
2991ef5b48aSclaudio {
3001ef5b48aSclaudio 	size_t i, length, new_length;
3011ef5b48aSclaudio 	char *epath, *epathp;
3021ef5b48aSclaudio 
3031ef5b48aSclaudio 	length = new_length = strlen(path);
3041ef5b48aSclaudio 
3051ef5b48aSclaudio 	/*
3061ef5b48aSclaudio 	 * First pass:
3071ef5b48aSclaudio 	 * Count unsafe characters, and determine length of the
3081ef5b48aSclaudio 	 * final URL.
3091ef5b48aSclaudio 	 */
3101ef5b48aSclaudio 	for (i = 0; i < length; i++)
31164247d16Sclaudio 		if (to_encode(path + i))
3121ef5b48aSclaudio 			new_length += 2;
3131ef5b48aSclaudio 
3141ef5b48aSclaudio 	epath = epathp = malloc(new_length + 1);	/* One more for '\0'. */
3151ef5b48aSclaudio 	if (epath == NULL)
3161ef5b48aSclaudio 		err(1, NULL);
3171ef5b48aSclaudio 
3181ef5b48aSclaudio 	/*
3191ef5b48aSclaudio 	 * Second pass:
3201ef5b48aSclaudio 	 * Encode, and copy final URL.
3211ef5b48aSclaudio 	 */
3221ef5b48aSclaudio 	for (i = 0; i < length; i++)
32364247d16Sclaudio 		if (to_encode(path + i)) {
3241ef5b48aSclaudio 			snprintf(epathp, 4, "%%" "%02x",
3251ef5b48aSclaudio 			    (unsigned char)path[i]);
3261ef5b48aSclaudio 			epathp += 3;
3271ef5b48aSclaudio 		} else
3281ef5b48aSclaudio 			*(epathp++) = path[i];
3291ef5b48aSclaudio 
3301ef5b48aSclaudio 	*epathp = '\0';
3311ef5b48aSclaudio 	return (epath);
3321ef5b48aSclaudio }
3331ef5b48aSclaudio 
3346f704872Sclaudio static char
3356f704872Sclaudio hextochar(const char *str)
3366f704872Sclaudio {
3376f704872Sclaudio 	unsigned char c, ret;
3386f704872Sclaudio 
3396f704872Sclaudio 	c = str[0];
3406f704872Sclaudio 	ret = c;
3416f704872Sclaudio 	if (isalpha(c))
3426f704872Sclaudio 		ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
3436f704872Sclaudio 	else
3446f704872Sclaudio 		ret -= '0';
3456f704872Sclaudio 	ret *= 16;
3466f704872Sclaudio 
3476f704872Sclaudio 	c = str[1];
3486f704872Sclaudio 	ret += c;
3496f704872Sclaudio 	if (isalpha(c))
3506f704872Sclaudio 		ret -= isupper(c) ? 'A' - 10 : 'a' - 10;
3516f704872Sclaudio 	else
3526f704872Sclaudio 		ret -= '0';
3536f704872Sclaudio 	return ret;
3546f704872Sclaudio }
3556f704872Sclaudio 
3566f704872Sclaudio static char *
3576f704872Sclaudio url_decode(const char *str)
3586f704872Sclaudio {
3596f704872Sclaudio 	char *ret, c;
3606f704872Sclaudio 	int i, reallen;
3616f704872Sclaudio 
3626f704872Sclaudio 	if (str == NULL)
3636f704872Sclaudio 		return NULL;
3646f704872Sclaudio 	if ((ret = malloc(strlen(str) + 1)) == NULL)
3656f704872Sclaudio 		err(1, "Can't allocate memory for URL decoding");
3666f704872Sclaudio 	for (i = 0, reallen = 0; str[i] != '\0'; i++, reallen++, ret++) {
3676f704872Sclaudio 		c = str[i];
3686f704872Sclaudio 		if (c == '+') {
3696f704872Sclaudio 			*ret = ' ';
3706f704872Sclaudio 			continue;
3716f704872Sclaudio 		}
3726f704872Sclaudio 		/*
3736f704872Sclaudio 		 * Cannot use strtol here because next char
3746f704872Sclaudio 		 * after %xx may be a digit.
3756f704872Sclaudio 		 */
3766f704872Sclaudio 		if (c == '%' && isxdigit((unsigned char)str[i + 1]) &&
3776f704872Sclaudio 		    isxdigit((unsigned char)str[i + 2])) {
3786f704872Sclaudio 			*ret = hextochar(&str[i + 1]);
3796f704872Sclaudio 			i += 2;
3806f704872Sclaudio 			continue;
3816f704872Sclaudio 		}
3826f704872Sclaudio 		*ret = c;
3836f704872Sclaudio 	}
3846f704872Sclaudio 	*ret = '\0';
3856f704872Sclaudio 	return ret - reallen;
3866f704872Sclaudio }
3876f704872Sclaudio 
3886f704872Sclaudio static char *
3896f704872Sclaudio recode_credentials(const char *userinfo)
3906f704872Sclaudio {
3916f704872Sclaudio 	char *ui, *creds;
3926f704872Sclaudio 	size_t ulen;
3936f704872Sclaudio 
3946f704872Sclaudio 	/* url-decode the user and pass */
3956f704872Sclaudio 	ui = url_decode(userinfo);
3966f704872Sclaudio 
3976f704872Sclaudio 	ulen = strlen(ui);
3986f704872Sclaudio 	if (base64_encode(ui, ulen, &creds) == -1)
3996f704872Sclaudio 		errx(1, "error in base64 encoding");
4006f704872Sclaudio 	free(ui);
4016f704872Sclaudio 	return (creds);
4026f704872Sclaudio }
4036f704872Sclaudio 
4046f704872Sclaudio /*
4056f704872Sclaudio  * Parse a proxy URI and split it up into host, port and userinfo.
4066f704872Sclaudio  */
4076f704872Sclaudio static void
4086f704872Sclaudio proxy_parse_uri(char *uri)
4096f704872Sclaudio {
410fe8e8d49Stb 	char *fullhost, *host, *port = NULL, *cred, *cookie = NULL;
4116f704872Sclaudio 
4126f704872Sclaudio 	if (uri == NULL)
4136f704872Sclaudio 		return;
4146f704872Sclaudio 
4150610060dSjob 	if (strncasecmp(uri, HTTP_PROTO, HTTP_PROTO_LEN) != 0)
4166f704872Sclaudio 		errx(1, "%s: http_proxy not using http schema", http_info(uri));
4176f704872Sclaudio 
418b4964d69Stb 	host = uri + HTTP_PROTO_LEN;
419fe8e8d49Stb 	if ((fullhost = strndup(host, strcspn(host, "/"))) == NULL)
4206f704872Sclaudio 		err(1, NULL);
4216f704872Sclaudio 
422fe8e8d49Stb 	cred = fullhost;
4236f704872Sclaudio 	host = strchr(cred, '@');
4246f704872Sclaudio 	if (host != NULL)
4256f704872Sclaudio 		*host++ = '\0';
4266f704872Sclaudio 	else {
4276f704872Sclaudio 		host = cred;
4286f704872Sclaudio 		cred = NULL;
4296f704872Sclaudio 	}
4306f704872Sclaudio 
4316f704872Sclaudio 	if (*host == '[') {
4326f704872Sclaudio 		char *hosttail;
4336f704872Sclaudio 
4346f704872Sclaudio 		if ((hosttail = strrchr(host, ']')) == NULL)
4356f704872Sclaudio 			errx(1, "%s: unmatched opening bracket",
4366f704872Sclaudio 			    http_info(uri));
4376f704872Sclaudio 		if (hosttail[1] == '\0' || hosttail[1] == ':')
4386f704872Sclaudio 			host++;
4396f704872Sclaudio 		if (hosttail[1] == ':')
4406f704872Sclaudio 			port = hosttail + 2;
4416f704872Sclaudio 		*hosttail = '\0';
4426f704872Sclaudio 	} else {
4436f704872Sclaudio 		if ((port = strrchr(host, ':')) != NULL)
4446f704872Sclaudio 			*port++ = '\0';
4456f704872Sclaudio 	}
4466f704872Sclaudio 
4476f704872Sclaudio 	if (port == NULL)
4486f704872Sclaudio 		port = "443";
4496f704872Sclaudio 
4506f704872Sclaudio 	if (cred != NULL) {
4516f704872Sclaudio 		if (strchr(cred, ':') == NULL)
4526f704872Sclaudio 			errx(1, "%s: malformed proxy url", http_info(uri));
4536f704872Sclaudio 		cred = recode_credentials(cred);
4546f704872Sclaudio 		if (asprintf(&cookie, "Proxy-Authorization: Basic %s\r\n",
4556f704872Sclaudio 		    cred) == -1)
4566f704872Sclaudio 			err(1, NULL);
4576f704872Sclaudio 		free(cred);
4586f704872Sclaudio 	} else
4596f704872Sclaudio 		if ((cookie = strdup("")) == NULL)
4606f704872Sclaudio 			err(1, NULL);
4616f704872Sclaudio 
462fe8e8d49Stb 	if ((proxy.proxyhost = strdup(host)) == NULL)
463fe8e8d49Stb 		err(1, NULL);
464ec013d45Sclaudio 	if ((proxy.proxyport = strdup(port)) == NULL)
465ec013d45Sclaudio 		err(1, NULL);
4666f704872Sclaudio 	proxy.proxyauth = cookie;
467fe8e8d49Stb 
468fe8e8d49Stb 	free(fullhost);
4696f704872Sclaudio }
4706f704872Sclaudio 
47175c55a07Sclaudio /*
47275c55a07Sclaudio  * Parse a URI and split it up into host, port and path.
47375c55a07Sclaudio  * Does some basic URI validation. Both host and port need to be freed
47475c55a07Sclaudio  * by the caller whereas path points into the uri.
47575c55a07Sclaudio  */
4761ef5b48aSclaudio static int
4771ef5b48aSclaudio http_parse_uri(char *uri, char **ohost, char **oport, char **opath)
4781ef5b48aSclaudio {
4791ef5b48aSclaudio 	char *host, *port = NULL, *path;
4801ef5b48aSclaudio 	char *hosttail;
4811ef5b48aSclaudio 
4820610060dSjob 	if (strncasecmp(uri, HTTPS_PROTO, HTTPS_PROTO_LEN) != 0) {
4831ef5b48aSclaudio 		warnx("%s: not using https schema", http_info(uri));
4841ef5b48aSclaudio 		return -1;
4851ef5b48aSclaudio 	}
486b4964d69Stb 	host = uri + HTTPS_PROTO_LEN;
4871ef5b48aSclaudio 	if ((path = strchr(host, '/')) == NULL) {
4881ef5b48aSclaudio 		warnx("%s: missing https path", http_info(uri));
4891ef5b48aSclaudio 		return -1;
4901ef5b48aSclaudio 	}
4911ef5b48aSclaudio 	if (path - uri > INT_MAX - 1) {
4921ef5b48aSclaudio 		warnx("%s: preposterous host length", http_info(uri));
4931ef5b48aSclaudio 		return -1;
4941ef5b48aSclaudio 	}
4956f704872Sclaudio 
4966f704872Sclaudio 	if (memchr(host, '@', path - host) != NULL) {
4976f704872Sclaudio 		warnx("%s: URI with userinfo not supported", http_info(uri));
4986f704872Sclaudio 		return -1;
4996f704872Sclaudio 	}
5006f704872Sclaudio 
5011ef5b48aSclaudio 	if (*host == '[') {
502cf09f547Stb 		if ((hosttail = memrchr(host, ']', path - host)) == NULL) {
503cf09f547Stb 			warnx("%s: unmatched opening bracket", http_info(uri));
504cf09f547Stb 			return -1;
505cf09f547Stb 		}
506cf09f547Stb 		if (hosttail[1] == '/' || hosttail[1] == ':')
5071ef5b48aSclaudio 			host++;
5081ef5b48aSclaudio 		if (hosttail[1] == ':')
509b4b44febStb 			port = hosttail + 2;
5101ef5b48aSclaudio 	} else {
5111ef5b48aSclaudio 		if ((hosttail = memrchr(host, ':', path - host)) != NULL)
5121ef5b48aSclaudio 			port = hosttail + 1;
5131ef5b48aSclaudio 		else
5141ef5b48aSclaudio 			hosttail = path;
5151ef5b48aSclaudio 	}
5161ef5b48aSclaudio 
5171ef5b48aSclaudio 	if ((host = strndup(host, hosttail - host)) == NULL)
5186735b9d8Sclaudio 		err(1, NULL);
5191ef5b48aSclaudio 	if (port != NULL) {
5201ef5b48aSclaudio 		if ((port = strndup(port, path - port)) == NULL)
5216735b9d8Sclaudio 			err(1, NULL);
5221ef5b48aSclaudio 	} else {
5231ef5b48aSclaudio 		if ((port = strdup("443")) == NULL)
5246735b9d8Sclaudio 			err(1, NULL);
5251ef5b48aSclaudio 	}
5261ef5b48aSclaudio 	/* do not include the initial / in path */
5271ef5b48aSclaudio 	path++;
5281ef5b48aSclaudio 
5291ef5b48aSclaudio 	*ohost = host;
5301ef5b48aSclaudio 	*oport = port;
5311ef5b48aSclaudio 	*opath = path;
5321ef5b48aSclaudio 
5331ef5b48aSclaudio 	return 0;
5341ef5b48aSclaudio }
5351ef5b48aSclaudio 
53675c55a07Sclaudio /*
53775c55a07Sclaudio  * Lookup the IP addresses for host:port.
53875c55a07Sclaudio  * Returns 0 on success and -1 on failure.
53975c55a07Sclaudio  */
540c8a1112eSclaudio static int
54175c55a07Sclaudio http_resolv(struct addrinfo **res, const char *host, const char *port)
542c8a1112eSclaudio {
543c8a1112eSclaudio 	struct addrinfo hints;
544c8a1112eSclaudio 	int error;
545c8a1112eSclaudio 
546c8a1112eSclaudio 	memset(&hints, 0, sizeof(hints));
547c8a1112eSclaudio 	hints.ai_family = PF_UNSPEC;
548c8a1112eSclaudio 	hints.ai_socktype = SOCK_STREAM;
54975c55a07Sclaudio 	error = getaddrinfo(host, port, &hints, res);
550c8a1112eSclaudio 	/*
551c8a1112eSclaudio 	 * If the services file is corrupt/missing, fall back
552c8a1112eSclaudio 	 * on our hard-coded defines.
553c8a1112eSclaudio 	 */
554c8a1112eSclaudio 	if (error == EAI_SERVICE)
55575c55a07Sclaudio 		error = getaddrinfo(host, "443", &hints, res);
556c8a1112eSclaudio 	if (error != 0) {
557c8a1112eSclaudio 		warnx("%s: %s", host, gai_strerror(error));
558c8a1112eSclaudio 		return -1;
559c8a1112eSclaudio 	}
560c8a1112eSclaudio 
561c8a1112eSclaudio 	return 0;
562c8a1112eSclaudio }
563c8a1112eSclaudio 
56475c55a07Sclaudio /*
56575c55a07Sclaudio  * Create and queue a new request.
56675c55a07Sclaudio  */
567c8a1112eSclaudio static void
568b6884e9fSclaudio http_req_new(unsigned int id, char *uri, char *modified_since, int count,
569b6884e9fSclaudio     int outfd)
57075c55a07Sclaudio {
57175c55a07Sclaudio 	struct http_request *req;
57275c55a07Sclaudio 	char *host, *port, *path;
57375c55a07Sclaudio 
57475c55a07Sclaudio 	if (http_parse_uri(uri, &host, &port, &path) == -1) {
57575c55a07Sclaudio 		free(uri);
57675c55a07Sclaudio 		free(modified_since);
57775c55a07Sclaudio 		close(outfd);
57875c55a07Sclaudio 		http_req_fail(id);
57975c55a07Sclaudio 		return;
58075c55a07Sclaudio 	}
58175c55a07Sclaudio 
58275c55a07Sclaudio 	if ((req = calloc(1, sizeof(*req))) == NULL)
58375c55a07Sclaudio 		err(1, NULL);
58475c55a07Sclaudio 
58575c55a07Sclaudio 	req->id = id;
58675c55a07Sclaudio 	req->outfd = outfd;
58775c55a07Sclaudio 	req->host = host;
58875c55a07Sclaudio 	req->port = port;
58975c55a07Sclaudio 	req->path = path;
59075c55a07Sclaudio 	req->uri = uri;
59175c55a07Sclaudio 	req->modified_since = modified_since;
59206792369Sclaudio 	req->redirect_loop = count;
59375c55a07Sclaudio 
59475c55a07Sclaudio 	TAILQ_INSERT_TAIL(&queue, req, entry);
59575c55a07Sclaudio }
59675c55a07Sclaudio 
59775c55a07Sclaudio /*
59875c55a07Sclaudio  * Free a request, request is not allowed to be on the req queue.
59975c55a07Sclaudio  */
60075c55a07Sclaudio static void
60175c55a07Sclaudio http_req_free(struct http_request *req)
60275c55a07Sclaudio {
60375c55a07Sclaudio 	if (req == NULL)
60475c55a07Sclaudio 		return;
60575c55a07Sclaudio 
60675c55a07Sclaudio 	free(req->host);
60775c55a07Sclaudio 	free(req->port);
60875c55a07Sclaudio 	/* no need to free req->path it points into req->uri */
60975c55a07Sclaudio 	free(req->uri);
61075c55a07Sclaudio 	free(req->modified_since);
61175c55a07Sclaudio 
61275c55a07Sclaudio 	if (req->outfd != -1)
61375c55a07Sclaudio 		close(req->outfd);
61475c55a07Sclaudio }
61575c55a07Sclaudio 
61675c55a07Sclaudio /*
61775c55a07Sclaudio  * Enqueue request response
61875c55a07Sclaudio  */
61975c55a07Sclaudio static void
620b6884e9fSclaudio http_req_done(unsigned int id, enum http_result res, const char *last_modified)
621c8a1112eSclaudio {
622c8a1112eSclaudio 	struct ibuf *b;
623c8a1112eSclaudio 
62425f7afeeSclaudio 	b = io_new_buffer();
625c8a1112eSclaudio 	io_simple_buffer(b, &id, sizeof(id));
626c8a1112eSclaudio 	io_simple_buffer(b, &res, sizeof(res));
627c8a1112eSclaudio 	io_str_buffer(b, last_modified);
62825d36c5cSclaudio 	io_close_buffer(msgq, b);
629c8a1112eSclaudio }
630c8a1112eSclaudio 
63175c55a07Sclaudio /*
63275c55a07Sclaudio  * Enqueue request failure response
63375c55a07Sclaudio  */
634c8a1112eSclaudio static void
635b6884e9fSclaudio http_req_fail(unsigned int id)
636c8a1112eSclaudio {
637c8a1112eSclaudio 	struct ibuf *b;
638c8a1112eSclaudio 	enum http_result res = HTTP_FAILED;
639c8a1112eSclaudio 
64025f7afeeSclaudio 	b = io_new_buffer();
641c8a1112eSclaudio 	io_simple_buffer(b, &id, sizeof(id));
642c8a1112eSclaudio 	io_simple_buffer(b, &res, sizeof(res));
643c8a1112eSclaudio 	io_str_buffer(b, NULL);
64425d36c5cSclaudio 	io_close_buffer(msgq, b);
645c8a1112eSclaudio }
6461ef5b48aSclaudio 
64775c55a07Sclaudio /*
64875c55a07Sclaudio  * Schedule new requests until maximum number of connections is reached.
64975c55a07Sclaudio  * Try to reuse an idle connection if one exists that matches host and port.
65075c55a07Sclaudio  */
65175c55a07Sclaudio static int
65275c55a07Sclaudio http_req_schedule(struct http_request *req)
6531ef5b48aSclaudio {
6541ef5b48aSclaudio 	struct http_connection *conn;
6551ef5b48aSclaudio 
65675c55a07Sclaudio 	TAILQ_REMOVE(&queue, req, entry);
65775c55a07Sclaudio 
65875c55a07Sclaudio 	/* check list of idle connections first */
65975c55a07Sclaudio 	LIST_FOREACH(conn, &idle, entry) {
66075c55a07Sclaudio 		if (strcmp(conn->host, req->host) != 0)
66175c55a07Sclaudio 			continue;
66275c55a07Sclaudio 		if (strcmp(conn->port, req->port) != 0)
66375c55a07Sclaudio 			continue;
66475c55a07Sclaudio 
66575c55a07Sclaudio 		LIST_REMOVE(conn, entry);
66675c55a07Sclaudio 		LIST_INSERT_HEAD(&active, conn, entry);
66775c55a07Sclaudio 
66875c55a07Sclaudio 		/* use established connection */
66975c55a07Sclaudio 		conn->req = req;
67075c55a07Sclaudio 		conn->idle_time = 0;
67175c55a07Sclaudio 
67275c55a07Sclaudio 		/* start request */
67375c55a07Sclaudio 		http_do(conn, http_request);
67475c55a07Sclaudio 		if (conn->state == STATE_FREE)
67575c55a07Sclaudio 			http_free(conn);
67675c55a07Sclaudio 		return 1;
6771ef5b48aSclaudio 	}
6781ef5b48aSclaudio 
6796e7e5289Sclaudio 	if (http_conn_count < MAX_HTTP_REQUESTS) {
68075c55a07Sclaudio 		http_new(req);
68175c55a07Sclaudio 		return 1;
68275c55a07Sclaudio 	}
68375c55a07Sclaudio 
68475c55a07Sclaudio 	/* no more slots free, requeue */
68575c55a07Sclaudio 	TAILQ_INSERT_HEAD(&queue, req, entry);
68675c55a07Sclaudio 	return 0;
68775c55a07Sclaudio }
68875c55a07Sclaudio 
68975c55a07Sclaudio /*
69078bab303Sclaudio  * Allocate everything to allow inline decompression during write out.
69178bab303Sclaudio  * Returns 0 on success, -1 on failure.
69278bab303Sclaudio  */
69378bab303Sclaudio static int
69478bab303Sclaudio http_inflate_new(struct http_connection *conn)
69578bab303Sclaudio {
69678bab303Sclaudio 	struct http_zlib *zctx;
69778bab303Sclaudio 
69878bab303Sclaudio 	if (conn->zlibctx != NULL)
69978bab303Sclaudio 		return 0;
70078bab303Sclaudio 
70178bab303Sclaudio 	if ((zctx = calloc(1, sizeof(*zctx))) == NULL)
70278bab303Sclaudio 		goto fail;
70378bab303Sclaudio 	zctx->zbufsz = HTTP_BUF_SIZE;
70478bab303Sclaudio 	if ((zctx->zbuf = malloc(zctx->zbufsz)) == NULL)
70578bab303Sclaudio 		goto fail;
70678bab303Sclaudio 	if (inflateInit2(&zctx->zs, MAX_WBITS + 32) != Z_OK)
70778bab303Sclaudio 		goto fail;
70878bab303Sclaudio 	conn->zlibctx = zctx;
70978bab303Sclaudio 	return 0;
71078bab303Sclaudio 
71178bab303Sclaudio  fail:
712f452fe1cStb 	warnx("%s: decompression initialisation failed", conn_info(conn));
71378bab303Sclaudio 	if (zctx != NULL)
71478bab303Sclaudio 		free(zctx->zbuf);
71578bab303Sclaudio 	free(zctx);
71678bab303Sclaudio 	return -1;
71778bab303Sclaudio }
71878bab303Sclaudio 
71978bab303Sclaudio /* Free all memory used by the decompression API */
72078bab303Sclaudio static void
72178bab303Sclaudio http_inflate_free(struct http_connection *conn)
72278bab303Sclaudio {
72378bab303Sclaudio 	if (conn->zlibctx == NULL)
72478bab303Sclaudio 		return;
72578bab303Sclaudio 	inflateEnd(&conn->zlibctx->zs);
72678bab303Sclaudio 	free(conn->zlibctx->zbuf);
72778bab303Sclaudio 	free(conn->zlibctx);
72878bab303Sclaudio 	conn->zlibctx = NULL;
72978bab303Sclaudio }
73078bab303Sclaudio 
73178bab303Sclaudio /* Reset the decompression state to allow a new request to use it */
73278bab303Sclaudio static void
73378bab303Sclaudio http_inflate_done(struct http_connection *conn)
73478bab303Sclaudio {
73578bab303Sclaudio 	if (inflateReset(&conn->zlibctx->zs) != Z_OK)
73678bab303Sclaudio 		http_inflate_free(conn);
73778bab303Sclaudio }
73878bab303Sclaudio 
73978bab303Sclaudio /*
74078bab303Sclaudio  * Inflate the data from conn->buf into zctx->zbuf. The number of bytes
74178bab303Sclaudio  * available in zctx->zbuf is stored in zctx->zbufpos.
74278bab303Sclaudio  * Returns -1 on failure.
74378bab303Sclaudio  */
74478bab303Sclaudio static int
74578bab303Sclaudio http_inflate_data(struct http_connection *conn)
74678bab303Sclaudio {
74778bab303Sclaudio 	struct http_zlib *zctx = conn->zlibctx;
74878bab303Sclaudio 	size_t bsz = conn->bufpos;
74978bab303Sclaudio 	int rv;
75078bab303Sclaudio 
751af6f8e1cSclaudio 	if (conn->iosz < bsz)
75278bab303Sclaudio 		bsz = conn->iosz;
75378bab303Sclaudio 
75478bab303Sclaudio 	zctx->zdone = 0;
75578bab303Sclaudio 	zctx->zbufpos = 0;
75678bab303Sclaudio 	zctx->zinsz = bsz;
75778bab303Sclaudio 	zctx->zs.next_in = conn->buf;
75878bab303Sclaudio 	zctx->zs.avail_in = bsz;
75978bab303Sclaudio 	zctx->zs.next_out = zctx->zbuf;
76078bab303Sclaudio 	zctx->zs.avail_out = zctx->zbufsz;
76178bab303Sclaudio 
76278bab303Sclaudio 	switch ((rv = inflate(&zctx->zs, Z_NO_FLUSH))) {
76378bab303Sclaudio 	case Z_OK:
76478bab303Sclaudio 		break;
76578bab303Sclaudio 	case Z_STREAM_END:
76678bab303Sclaudio 		zctx->zdone = 1;
76778bab303Sclaudio 		break;
76878bab303Sclaudio 	default:
76978bab303Sclaudio 		if (zctx->zs.msg != NULL)
77078bab303Sclaudio 			warnx("%s: inflate failed: %s", conn_info(conn),
77178bab303Sclaudio 			    zctx->zs.msg);
77278bab303Sclaudio 		else
77378bab303Sclaudio 			warnx("%s: inflate failed error %d", conn_info(conn),
77478bab303Sclaudio 			    rv);
77578bab303Sclaudio 		return -1;
77678bab303Sclaudio 	}
77778bab303Sclaudio 
77878bab303Sclaudio 	/* calculate how much can be written out */
77978bab303Sclaudio 	zctx->zbufpos = zctx->zbufsz - zctx->zs.avail_out;
78078bab303Sclaudio 	return 0;
78178bab303Sclaudio }
78278bab303Sclaudio 
78378bab303Sclaudio /*
78478bab303Sclaudio  * Advance the input buffer after the output buffer has been fully written.
78578bab303Sclaudio  * If compression is done finish the transaction else read more data.
78678bab303Sclaudio  */
78778bab303Sclaudio static enum res
78878bab303Sclaudio http_inflate_advance(struct http_connection *conn)
78978bab303Sclaudio {
79078bab303Sclaudio 	struct http_zlib *zctx = conn->zlibctx;
79178bab303Sclaudio 	size_t bsz = zctx->zinsz - zctx->zs.avail_in;
79278bab303Sclaudio 
79378bab303Sclaudio 	/* adjust compressed input buffer */
79478bab303Sclaudio 	conn->bufpos -= bsz;
79578bab303Sclaudio 	conn->iosz -= bsz;
79678bab303Sclaudio 	memmove(conn->buf, conn->buf + bsz, conn->bufpos);
79778bab303Sclaudio 
79878bab303Sclaudio 	if (zctx->zdone) {
79978bab303Sclaudio 		/* all compressed data processed */
80078bab303Sclaudio 		conn->gzipped = 0;
80178bab303Sclaudio 		http_inflate_done(conn);
80278bab303Sclaudio 
80378bab303Sclaudio 		if (conn->iosz == 0) {
80478bab303Sclaudio 			if (!conn->chunked) {
80578bab303Sclaudio 				return http_done(conn, HTTP_OK);
80678bab303Sclaudio 			} else {
80778bab303Sclaudio 				conn->state = STATE_RESPONSE_CHUNKED_CRLF;
80878bab303Sclaudio 				return http_read(conn);
80978bab303Sclaudio 			}
81078bab303Sclaudio 		} else {
81178bab303Sclaudio 			warnx("%s: inflate extra data after end",
81278bab303Sclaudio 			    conn_info(conn));
81378bab303Sclaudio 			return http_failed(conn);
81478bab303Sclaudio 		}
81578bab303Sclaudio 	}
81678bab303Sclaudio 
81778bab303Sclaudio 	if (conn->chunked && conn->iosz == 0)
81878bab303Sclaudio 		conn->state = STATE_RESPONSE_CHUNKED_CRLF;
81978bab303Sclaudio 	else
82078bab303Sclaudio 		conn->state = STATE_RESPONSE_DATA;
82178bab303Sclaudio 	return http_read(conn);
82278bab303Sclaudio }
82378bab303Sclaudio 
82478bab303Sclaudio /*
82575c55a07Sclaudio  * Create a new HTTP connection which will be used for the HTTP request req.
826f452fe1cStb  * On errors a req failure is issued and both connection and request are freed.
82775c55a07Sclaudio  */
82875c55a07Sclaudio static void
82975c55a07Sclaudio http_new(struct http_request *req)
83075c55a07Sclaudio {
83175c55a07Sclaudio 	struct http_connection *conn;
83275c55a07Sclaudio 
8331ef5b48aSclaudio 	if ((conn = calloc(1, sizeof(*conn))) == NULL)
8341ef5b48aSclaudio 		err(1, NULL);
8351ef5b48aSclaudio 
8361ef5b48aSclaudio 	conn->fd = -1;
83775c55a07Sclaudio 	conn->req = req;
83875c55a07Sclaudio 	if ((conn->host = strdup(req->host)) == NULL)
83975c55a07Sclaudio 		err(1, NULL);
84075c55a07Sclaudio 	if ((conn->port = strdup(req->port)) == NULL)
84175c55a07Sclaudio 		err(1, NULL);
84275c55a07Sclaudio 
84375c55a07Sclaudio 	LIST_INSERT_HEAD(&active, conn, entry);
84475c55a07Sclaudio 	http_conn_count++;
8451ef5b48aSclaudio 
8462d3967cbSclaudio 	if (proxy.proxyhost != NULL) {
8476f704872Sclaudio 		if (http_resolv(&conn->res0, proxy.proxyhost,
8486f704872Sclaudio 		    proxy.proxyport) == -1) {
8496f704872Sclaudio 			http_req_fail(req->id);
8506f704872Sclaudio 			http_free(conn);
8516f704872Sclaudio 			return;
8526f704872Sclaudio 		}
8536f704872Sclaudio 	} else {
85475c55a07Sclaudio 		if (http_resolv(&conn->res0, conn->host, conn->port) == -1) {
85575c55a07Sclaudio 			http_req_fail(req->id);
8561ef5b48aSclaudio 			http_free(conn);
85775c55a07Sclaudio 			return;
8581ef5b48aSclaudio 		}
8596f704872Sclaudio 	}
8601ef5b48aSclaudio 
86175c55a07Sclaudio 	/* connect and start request */
86275c55a07Sclaudio 	http_do(conn, http_connect);
86375c55a07Sclaudio 	if (conn->state == STATE_FREE)
86475c55a07Sclaudio 		http_free(conn);
8651ef5b48aSclaudio }
8661ef5b48aSclaudio 
86775c55a07Sclaudio /*
86875c55a07Sclaudio  * Free a no longer active connection, releasing all memory and closing
86975c55a07Sclaudio  * any open file descriptor.
87075c55a07Sclaudio  */
871c8a1112eSclaudio static void
872c8a1112eSclaudio http_free(struct http_connection *conn)
873c8a1112eSclaudio {
87475c55a07Sclaudio 	assert(conn->state == STATE_FREE);
87575c55a07Sclaudio 
87675c55a07Sclaudio 	LIST_REMOVE(conn, entry);
87775c55a07Sclaudio 	http_conn_count--;
87875c55a07Sclaudio 
87975c55a07Sclaudio 	http_req_free(conn->req);
88078bab303Sclaudio 	http_inflate_free(conn);
881c8a1112eSclaudio 	free(conn->host);
882c8a1112eSclaudio 	free(conn->port);
883c8a1112eSclaudio 	free(conn->last_modified);
88475c55a07Sclaudio 	free(conn->redir_uri);
885c8a1112eSclaudio 	free(conn->buf);
886c8a1112eSclaudio 
887c8a1112eSclaudio 	if (conn->res0 != NULL)
888c8a1112eSclaudio 		freeaddrinfo(conn->res0);
889c8a1112eSclaudio 
890c8a1112eSclaudio 	tls_free(conn->tls);
891c8a1112eSclaudio 
892c8a1112eSclaudio 	if (conn->fd != -1)
893c8a1112eSclaudio 		close(conn->fd);
894c8a1112eSclaudio 	free(conn);
895c8a1112eSclaudio }
896c8a1112eSclaudio 
89775c55a07Sclaudio /*
89875c55a07Sclaudio  * Called when a request on this connection is finished.
89975c55a07Sclaudio  * Move connection into idle state and onto idle queue.
90075c55a07Sclaudio  * If there is a request connected to it send back a response
90175c55a07Sclaudio  * with http_result res, else ignore the res.
90275c55a07Sclaudio  */
90375c55a07Sclaudio static enum res
90475c55a07Sclaudio http_done(struct http_connection *conn, enum http_result res)
90575c55a07Sclaudio {
90675c55a07Sclaudio 	assert(conn->bufpos == 0);
90775c55a07Sclaudio 	assert(conn->iosz == 0);
90875c55a07Sclaudio 	assert(conn->chunked == 0);
90975c55a07Sclaudio 	assert(conn->redir_uri == NULL);
910c8a1112eSclaudio 
91178bab303Sclaudio 	if (conn->gzipped) {
91278bab303Sclaudio 		conn->gzipped = 0;
91378bab303Sclaudio 		http_inflate_done(conn);
91478bab303Sclaudio 	}
91578bab303Sclaudio 
91675c55a07Sclaudio 	conn->state = STATE_IDLE;
91775c55a07Sclaudio 	conn->idle_time = getmonotime() + HTTP_IDLE_TIMEOUT;
91875c55a07Sclaudio 
91975c55a07Sclaudio 	if (conn->req) {
92075c55a07Sclaudio 		http_req_done(conn->req->id, res, conn->last_modified);
92175c55a07Sclaudio 		http_req_free(conn->req);
92275c55a07Sclaudio 		conn->req = NULL;
92375c55a07Sclaudio 	}
92475c55a07Sclaudio 
92575c55a07Sclaudio 	if (!conn->keep_alive)
92675c55a07Sclaudio 		return http_close(conn);
92775c55a07Sclaudio 
92875c55a07Sclaudio 	LIST_REMOVE(conn, entry);
92975c55a07Sclaudio 	LIST_INSERT_HEAD(&idle, conn, entry);
93075c55a07Sclaudio 
93175c55a07Sclaudio 	/* reset status and keep-alive for good measures */
93275c55a07Sclaudio 	conn->status = 0;
93375c55a07Sclaudio 	conn->keep_alive = 0;
93475c55a07Sclaudio 
93575c55a07Sclaudio 	return WANT_POLLIN;
93675c55a07Sclaudio }
93775c55a07Sclaudio 
93875c55a07Sclaudio /*
93975c55a07Sclaudio  * Called in case of error, moves connection into free state.
94075c55a07Sclaudio  * This will skip proper shutdown of the TLS session.
94175c55a07Sclaudio  * If a request is pending fail and free the request.
94275c55a07Sclaudio  */
94375c55a07Sclaudio static enum res
94475c55a07Sclaudio http_failed(struct http_connection *conn)
94575c55a07Sclaudio {
94675c55a07Sclaudio 	conn->state = STATE_FREE;
94775c55a07Sclaudio 
94875c55a07Sclaudio 	if (conn->req) {
94975c55a07Sclaudio 		http_req_fail(conn->req->id);
95075c55a07Sclaudio 		http_req_free(conn->req);
95175c55a07Sclaudio 		conn->req = NULL;
95275c55a07Sclaudio 	}
95375c55a07Sclaudio 
95475c55a07Sclaudio 	return DONE;
95575c55a07Sclaudio }
95675c55a07Sclaudio 
95775c55a07Sclaudio /*
9589170c2daSclaudio  * Called in case of connect timeout, try an alternate connection.
9599170c2daSclaudio  */
9609170c2daSclaudio static enum res
9619170c2daSclaudio http_connect_failed(struct http_connection *conn)
9629170c2daSclaudio {
9639170c2daSclaudio 	assert(conn->state == STATE_CONNECT);
9649170c2daSclaudio 	close(conn->fd);
9659170c2daSclaudio 	conn->fd = -1;
9669170c2daSclaudio 
9679170c2daSclaudio 	return http_connect(conn);
9689170c2daSclaudio }
9699170c2daSclaudio 
9709170c2daSclaudio /*
97175c55a07Sclaudio  * Call the function f and update the connection events based
97275c55a07Sclaudio  * on the return value.
97375c55a07Sclaudio  */
97475c55a07Sclaudio static void
97575c55a07Sclaudio http_do(struct http_connection *conn, enum res (*f)(struct http_connection *))
97675c55a07Sclaudio {
97775c55a07Sclaudio 	switch (f(conn)) {
97875c55a07Sclaudio 	case DONE:
97975c55a07Sclaudio 		conn->events = 0;
98075c55a07Sclaudio 		break;
98175c55a07Sclaudio 	case WANT_POLLIN:
98275c55a07Sclaudio 		conn->events = POLLIN;
98375c55a07Sclaudio 		break;
98475c55a07Sclaudio 	case WANT_POLLOUT:
98575c55a07Sclaudio 		conn->events = POLLOUT;
98675c55a07Sclaudio 		break;
98775c55a07Sclaudio 	default:
988eee9c28cSclaudio 		errx(1, "%s: unexpected function return", conn_info(conn));
98975c55a07Sclaudio 	}
99075c55a07Sclaudio }
99175c55a07Sclaudio 
99275c55a07Sclaudio /*
9936f704872Sclaudio  * Connection successfully establish, initiate TLS handshake or proxy request.
99475c55a07Sclaudio  */
99575c55a07Sclaudio static enum res
996bc49575fSclaudio http_connect_done(struct http_connection *conn)
997bc49575fSclaudio {
9982d3967cbSclaudio 	if (proxy.proxyhost != NULL)
9996f704872Sclaudio 		return proxy_connect(conn);
100075c55a07Sclaudio 	return http_tls_connect(conn);
1001bc49575fSclaudio }
1002bc49575fSclaudio 
100375c55a07Sclaudio /*
100475c55a07Sclaudio  * Start an asynchronous connect.
100575c55a07Sclaudio  */
100675c55a07Sclaudio static enum res
10071ef5b48aSclaudio http_connect(struct http_connection *conn)
10081ef5b48aSclaudio {
10090abf3cc6Sclaudio 	const char *cause = NULL;
1010eee9c28cSclaudio 	struct addrinfo *res;
10111ef5b48aSclaudio 
101275c55a07Sclaudio 	assert(conn->fd == -1);
1013f12b699fSclaudio 	conn->state = STATE_CONNECT;
1014f12b699fSclaudio 
10151ef5b48aSclaudio 	/* start the loop below with first or next address */
10161ef5b48aSclaudio 	if (conn->res == NULL)
10171ef5b48aSclaudio 		conn->res = conn->res0;
10181ef5b48aSclaudio 	else
10191ef5b48aSclaudio 		conn->res = conn->res->ai_next;
10201ef5b48aSclaudio 	for (; conn->res != NULL; conn->res = conn->res->ai_next) {
10210abf3cc6Sclaudio 		int fd, save_errno;
10221ef5b48aSclaudio 
1023eee9c28cSclaudio 		res = conn->res;
10241ef5b48aSclaudio 		fd = socket(res->ai_family,
10251ef5b48aSclaudio 		    res->ai_socktype | SOCK_NONBLOCK, res->ai_protocol);
10261ef5b48aSclaudio 		if (fd == -1) {
10271ef5b48aSclaudio 			cause = "socket";
10281ef5b48aSclaudio 			continue;
10291ef5b48aSclaudio 		}
10301ef5b48aSclaudio 		conn->fd = fd;
10311ef5b48aSclaudio 
10321ef5b48aSclaudio 		if (http_bindaddr.ss_family == res->ai_family) {
10331ef5b48aSclaudio 			if (bind(conn->fd, (struct sockaddr *)&http_bindaddr,
1034dc8fd1cbSclaudio 			    res->ai_addrlen) == -1) {
1035dc8fd1cbSclaudio 				save_errno = errno;
1036dc8fd1cbSclaudio 				close(conn->fd);
1037dc8fd1cbSclaudio 				conn->fd = -1;
1038dc8fd1cbSclaudio 				errno = save_errno;
1039dc8fd1cbSclaudio 				cause = "bind";
1040dc8fd1cbSclaudio 				continue;
1041dc8fd1cbSclaudio 			}
10421ef5b48aSclaudio 		}
10431ef5b48aSclaudio 
10440abf3cc6Sclaudio 		if (connect(conn->fd, res->ai_addr, res->ai_addrlen) == -1) {
10451ef5b48aSclaudio 			if (errno == EINPROGRESS) {
10460abf3cc6Sclaudio 				/* wait for async connect to finish. */
10471ef5b48aSclaudio 				return WANT_POLLOUT;
10481ef5b48aSclaudio 			} else {
10491ef5b48aSclaudio 				save_errno = errno;
10501ef5b48aSclaudio 				close(conn->fd);
10511ef5b48aSclaudio 				conn->fd = -1;
10521ef5b48aSclaudio 				errno = save_errno;
10531ef5b48aSclaudio 				cause = "connect";
10541ef5b48aSclaudio 				continue;
10551ef5b48aSclaudio 			}
10561ef5b48aSclaudio 		}
10571ef5b48aSclaudio 
10580abf3cc6Sclaudio 		break;	/* okay we got one */
10591ef5b48aSclaudio 	}
10600abf3cc6Sclaudio 
10610abf3cc6Sclaudio 	if (conn->fd == -1) {
1062eee9c28cSclaudio 		if (cause != NULL) {
1063eee9c28cSclaudio 			conn->res = res;
1064eee9c28cSclaudio 			warn("%s: %s", conn_info(conn), cause);
1065eee9c28cSclaudio 		}
106675c55a07Sclaudio 		return http_failed(conn);
10671ef5b48aSclaudio 	}
1068beb64842Sclaudio 
1069bc49575fSclaudio 	return http_connect_done(conn);
10701ef5b48aSclaudio }
10711ef5b48aSclaudio 
107275c55a07Sclaudio /*
1073f452fe1cStb  * Called once an asynchronous connect request finished.
107475c55a07Sclaudio  */
107575c55a07Sclaudio static enum res
1076c8a1112eSclaudio http_finish_connect(struct http_connection *conn)
10771ef5b48aSclaudio {
1078c8a1112eSclaudio 	int error = 0;
1079c8a1112eSclaudio 	socklen_t len;
1080c8a1112eSclaudio 
1081c8a1112eSclaudio 	len = sizeof(error);
1082c8a1112eSclaudio 	if (getsockopt(conn->fd, SOL_SOCKET, SO_ERROR, &error, &len) == -1) {
1083eee9c28cSclaudio 		warn("%s: getsockopt SO_ERROR", conn_info(conn));
1084d8c93623Sclaudio 		return http_connect_failed(conn);
10851ef5b48aSclaudio 	}
1086c8a1112eSclaudio 	if (error != 0) {
1087c8a1112eSclaudio 		errno = error;
1088eee9c28cSclaudio 		warn("%s: connect", conn_info(conn));
1089d8c93623Sclaudio 		return http_connect_failed(conn);
1090c8a1112eSclaudio 	}
1091c8a1112eSclaudio 
1092c8a1112eSclaudio 	return http_connect_done(conn);
1093c8a1112eSclaudio }
10941ef5b48aSclaudio 
109575c55a07Sclaudio /*
109675c55a07Sclaudio  * Initiate TLS session on a new connection.
109775c55a07Sclaudio  */
109875c55a07Sclaudio static enum res
10991ef5b48aSclaudio http_tls_connect(struct http_connection *conn)
11001ef5b48aSclaudio {
110175c55a07Sclaudio 	assert(conn->state == STATE_CONNECT);
110275c55a07Sclaudio 	conn->state = STATE_TLSCONNECT;
110375c55a07Sclaudio 
11041ef5b48aSclaudio 	if ((conn->tls = tls_client()) == NULL) {
11051ef5b48aSclaudio 		warn("tls_client");
110675c55a07Sclaudio 		return http_failed(conn);
11071ef5b48aSclaudio 	}
11081ef5b48aSclaudio 	if (tls_configure(conn->tls, tls_config) == -1) {
110978bab303Sclaudio 		warnx("%s: TLS configuration: %s", conn_info(conn),
11101ef5b48aSclaudio 		    tls_error(conn->tls));
111175c55a07Sclaudio 		return http_failed(conn);
11121ef5b48aSclaudio 	}
11131ef5b48aSclaudio 	if (tls_connect_socket(conn->tls, conn->fd, conn->host) == -1) {
111478bab303Sclaudio 		warnx("%s: TLS connect: %s", conn_info(conn),
11151ef5b48aSclaudio 		    tls_error(conn->tls));
111675c55a07Sclaudio 		return http_failed(conn);
11171ef5b48aSclaudio 	}
111875c55a07Sclaudio 
11191ef5b48aSclaudio 	return http_tls_handshake(conn);
11201ef5b48aSclaudio }
11211ef5b48aSclaudio 
112275c55a07Sclaudio /*
112375c55a07Sclaudio  * Do the tls_handshake and then send out the HTTP request.
112475c55a07Sclaudio  */
112575c55a07Sclaudio static enum res
1126c8a1112eSclaudio http_tls_handshake(struct http_connection *conn)
1127c8a1112eSclaudio {
1128c8a1112eSclaudio 	switch (tls_handshake(conn->tls)) {
112975c55a07Sclaudio 	case -1:
1130eee9c28cSclaudio 		warnx("%s: TLS handshake: %s", conn_info(conn),
113175c55a07Sclaudio 		    tls_error(conn->tls));
113275c55a07Sclaudio 		return http_failed(conn);
1133c8a1112eSclaudio 	case TLS_WANT_POLLIN:
1134c8a1112eSclaudio 		return WANT_POLLIN;
1135c8a1112eSclaudio 	case TLS_WANT_POLLOUT:
1136c8a1112eSclaudio 		return WANT_POLLOUT;
1137c8a1112eSclaudio 	}
113875c55a07Sclaudio 
113975c55a07Sclaudio 	return http_request(conn);
1140c8a1112eSclaudio }
1141c8a1112eSclaudio 
11426f704872Sclaudio static enum res
11436f704872Sclaudio proxy_connect(struct http_connection *conn)
11446f704872Sclaudio {
11456f704872Sclaudio 	char *host;
11466f704872Sclaudio 	int r;
11476f704872Sclaudio 
11486f704872Sclaudio 	assert(conn->state == STATE_CONNECT);
11496f704872Sclaudio 	conn->state = STATE_PROXY_REQUEST;
11506f704872Sclaudio 
11516f704872Sclaudio 	/* Construct the Host header from host and port info */
11526f704872Sclaudio 	if (strchr(conn->host, ':')) {
11536f704872Sclaudio 		if (asprintf(&host, "[%s]:%s", conn->host, conn->port) == -1)
11546f704872Sclaudio 			err(1, NULL);
11556f704872Sclaudio 
11566f704872Sclaudio 	} else {
11576f704872Sclaudio 		if (asprintf(&host, "%s:%s", conn->host, conn->port) == -1)
11586f704872Sclaudio 			err(1, NULL);
11596f704872Sclaudio 	}
11606f704872Sclaudio 
11616f704872Sclaudio 	free(conn->buf);
11626f704872Sclaudio 	conn->bufpos = 0;
11636f704872Sclaudio 	/* XXX handle auth */
11646f704872Sclaudio 	if ((r = asprintf(&conn->buf, "CONNECT %s HTTP/1.1\r\n"
1165f3de69f6Stb 	    "Host: %s\r\n"
1166c222e9aeStb 	    "User-Agent: " HTTP_USER_AGENT "\r\n%s\r\n", host, host,
11676f704872Sclaudio 	    proxy.proxyauth)) == -1)
11686f704872Sclaudio 		err(1, NULL);
11696f704872Sclaudio 	conn->bufsz = r;
11706f704872Sclaudio 
11716f704872Sclaudio 	free(host);
11726f704872Sclaudio 
11736f704872Sclaudio 	return proxy_write(conn);
11746f704872Sclaudio }
11756f704872Sclaudio 
117675c55a07Sclaudio /*
117775c55a07Sclaudio  * Build the HTTP request and send it out.
117875c55a07Sclaudio  */
117975c55a07Sclaudio static enum res
11801ef5b48aSclaudio http_request(struct http_connection *conn)
11811ef5b48aSclaudio {
11821ef5b48aSclaudio 	char *host, *epath, *modified_since;
11835d2a5cd6Sclaudio 	int r, with_port = 0;
11841ef5b48aSclaudio 
118575c55a07Sclaudio 	assert(conn->state == STATE_IDLE || conn->state == STATE_TLSCONNECT);
118675c55a07Sclaudio 	conn->state = STATE_REQUEST;
118775c55a07Sclaudio 
11881ef5b48aSclaudio 	/*
11891ef5b48aSclaudio 	 * Send port number only if it's specified and does not equal
11901ef5b48aSclaudio 	 * the default. Some broken HTTP servers get confused if you explicitly
11911ef5b48aSclaudio 	 * send them the port number.
11921ef5b48aSclaudio 	 */
119375c55a07Sclaudio 	if (strcmp(conn->port, "443") != 0)
11941ef5b48aSclaudio 		with_port = 1;
11951ef5b48aSclaudio 
11961ef5b48aSclaudio 	/* Construct the Host header from host and port info */
11971ef5b48aSclaudio 	if (strchr(conn->host, ':')) {
11981ef5b48aSclaudio 		if (asprintf(&host, "[%s]%s%s", conn->host,
11991ef5b48aSclaudio 		    with_port ? ":" : "", with_port ? conn->port : "") == -1)
12001ef5b48aSclaudio 			err(1, NULL);
12011ef5b48aSclaudio 
12021ef5b48aSclaudio 	} else {
12031ef5b48aSclaudio 		if (asprintf(&host, "%s%s%s", conn->host,
12041ef5b48aSclaudio 		    with_port ? ":" : "", with_port ? conn->port : "") == -1)
12051ef5b48aSclaudio 			err(1, NULL);
12061ef5b48aSclaudio 	}
12071ef5b48aSclaudio 
12081ef5b48aSclaudio 	/*
12091ef5b48aSclaudio 	 * Construct and send the request. Proxy requests don't want leading /.
12101ef5b48aSclaudio 	 */
121175c55a07Sclaudio 	epath = url_encode(conn->req->path);
12121ef5b48aSclaudio 
12131ef5b48aSclaudio 	modified_since = NULL;
121475c55a07Sclaudio 	if (conn->req->modified_since != NULL) {
12151ef5b48aSclaudio 		if (asprintf(&modified_since, "If-Modified-Since: %s\r\n",
121675c55a07Sclaudio 		    conn->req->modified_since) == -1)
12171ef5b48aSclaudio 			err(1, NULL);
12181ef5b48aSclaudio 	}
12191ef5b48aSclaudio 
12201ef5b48aSclaudio 	free(conn->buf);
12211ef5b48aSclaudio 	conn->bufpos = 0;
12225d2a5cd6Sclaudio 	if ((r = asprintf(&conn->buf,
12231ef5b48aSclaudio 	    "GET /%s HTTP/1.1\r\n"
1224b1ebb9f1Sjob 	    "Host: %s\r\n"
1225fd3f1878Sclaudio 	    "Accept: */*\r\n"
122678bab303Sclaudio 	    "Accept-Encoding: gzip, deflate\r\n"
12271ef5b48aSclaudio 	    "User-Agent: " HTTP_USER_AGENT "\r\n"
1228b1ebb9f1Sjob 	    "%s\r\n",
12291ef5b48aSclaudio 	    epath, host,
12301ef5b48aSclaudio 	    modified_since ? modified_since : "")) == -1)
12311ef5b48aSclaudio 		err(1, NULL);
12325d2a5cd6Sclaudio 	conn->bufsz = r;
12331ef5b48aSclaudio 
12341ef5b48aSclaudio 	free(epath);
12351ef5b48aSclaudio 	free(host);
12361ef5b48aSclaudio 	free(modified_since);
12371ef5b48aSclaudio 
123875c55a07Sclaudio 	return http_write(conn);
12391ef5b48aSclaudio }
12401ef5b48aSclaudio 
124175c55a07Sclaudio /*
124275c55a07Sclaudio  * Parse the HTTP status line.
1243eb6e4c97Sclaudio  * Return 0 for status codes 100, 103, 200, 203, 301-304, 307-308.
1244eb6e4c97Sclaudio  * The other 1xx and 2xx status codes are explicitly not handled and are
1245eb6e4c97Sclaudio  * considered an error.
124675c55a07Sclaudio  * Failure codes and other errors return -1.
124775c55a07Sclaudio  * The redirect loop limit is enforced here.
124875c55a07Sclaudio  */
12491ef5b48aSclaudio static int
12501ef5b48aSclaudio http_parse_status(struct http_connection *conn, char *buf)
12511ef5b48aSclaudio {
12529ffee665Sclaudio #define HTTP_11	"HTTP/1.1 "
12531ef5b48aSclaudio 	const char *errstr;
12541ef5b48aSclaudio 	char *cp, ststr[4];
12551ef5b48aSclaudio 	char gerror[200];
12561ef5b48aSclaudio 	int status;
12571ef5b48aSclaudio 
12589ffee665Sclaudio 	/* Check if the protocol is 1.1 and enable keep-alive in that case */
12599ffee665Sclaudio 	if (strncmp(buf, HTTP_11, strlen(HTTP_11)) == 0)
12609ffee665Sclaudio 		conn->keep_alive = 1;
12619ffee665Sclaudio 
12621ef5b48aSclaudio 	cp = strchr(buf, ' ');
12631ef5b48aSclaudio 	if (cp == NULL) {
1264eee9c28cSclaudio 		warnx("Improper response from %s", conn_info(conn));
12651ef5b48aSclaudio 		return -1;
12661ef5b48aSclaudio 	} else
12671ef5b48aSclaudio 		cp++;
12681ef5b48aSclaudio 
12691ef5b48aSclaudio 	strlcpy(ststr, cp, sizeof(ststr));
1270eb6e4c97Sclaudio 	status = strtonum(ststr, 100, 599, &errstr);
12711ef5b48aSclaudio 	if (errstr != NULL) {
12721ef5b48aSclaudio 		strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
1273eee9c28cSclaudio 		warnx("Error retrieving %s: %s", conn_info(conn),
127475c55a07Sclaudio 		    gerror);
12751ef5b48aSclaudio 		return -1;
12761ef5b48aSclaudio 	}
12771ef5b48aSclaudio 
12781ef5b48aSclaudio 	switch (status) {
1279eb6e4c97Sclaudio 	case 301:	/* Redirect: moved permanently */
1280eb6e4c97Sclaudio 	case 302:	/* Redirect: found / moved temporarily */
1281eb6e4c97Sclaudio 	case 303:	/* Redirect: see other */
1282eb6e4c97Sclaudio 	case 307:	/* Redirect: temporary redirect */
1283eb6e4c97Sclaudio 	case 308:	/* Redirect: permanent redirect */
128475c55a07Sclaudio 		if (conn->req->redirect_loop++ > 10) {
12851ef5b48aSclaudio 			warnx("%s: Too many redirections requested",
1286eee9c28cSclaudio 			    conn_info(conn));
12871ef5b48aSclaudio 			return -1;
12881ef5b48aSclaudio 		}
12891ef5b48aSclaudio 		/* FALLTHROUGH */
1290eb6e4c97Sclaudio 	case 100:	/* Informational: continue (ignored) */
1291eb6e4c97Sclaudio 	case 103:	/* Informational: early hints (ignored) */
1292eb6e4c97Sclaudio 		/* FALLTHROUGH */
1293eb6e4c97Sclaudio 	case 200:	/* Success: OK */
1294eb6e4c97Sclaudio 	case 203:	/* Success: non-authoritative information (proxy) */
1295eb6e4c97Sclaudio 	case 304:	/* Redirect: not modified */
12961ef5b48aSclaudio 		conn->status = status;
12971ef5b48aSclaudio 		break;
12981ef5b48aSclaudio 	default:
12991ef5b48aSclaudio 		strnvis(gerror, cp, sizeof gerror, VIS_SAFE);
1300eee9c28cSclaudio 		warnx("Error retrieving %s: %s", conn_info(conn),
130175c55a07Sclaudio 		    gerror);
130275c55a07Sclaudio 		return -1;
13031ef5b48aSclaudio 	}
13041ef5b48aSclaudio 
13051ef5b48aSclaudio 	return 0;
13061ef5b48aSclaudio }
13071ef5b48aSclaudio 
130875c55a07Sclaudio /*
130975c55a07Sclaudio  * Returns true if the connection status is any of the redirect codes.
131075c55a07Sclaudio  */
13111ef5b48aSclaudio static inline int
13121ef5b48aSclaudio http_isredirect(struct http_connection *conn)
13131ef5b48aSclaudio {
13141ef5b48aSclaudio 	if ((conn->status >= 301 && conn->status <= 303) ||
131509b708f5Sclaudio 	    conn->status == 307 || conn->status == 308)
13161ef5b48aSclaudio 		return 1;
13171ef5b48aSclaudio 	return 0;
13181ef5b48aSclaudio }
13191ef5b48aSclaudio 
1320eb6e4c97Sclaudio static inline int
1321eb6e4c97Sclaudio http_isok(struct http_connection *conn)
1322eb6e4c97Sclaudio {
1323eb6e4c97Sclaudio 	if (conn->status >= 200 && conn->status < 300)
1324eb6e4c97Sclaudio 		return 1;
1325eb6e4c97Sclaudio 	return 0;
1326eb6e4c97Sclaudio }
1327eb6e4c97Sclaudio 
132875c55a07Sclaudio static void
132975c55a07Sclaudio http_redirect(struct http_connection *conn)
133047d3b88aSclaudio {
133175c55a07Sclaudio 	char *uri, *mod_since = NULL;
133275c55a07Sclaudio 	int outfd;
133375c55a07Sclaudio 
133475c55a07Sclaudio 	/* move uri and fd out for new request */
133575c55a07Sclaudio 	outfd = conn->req->outfd;
133675c55a07Sclaudio 	conn->req->outfd = -1;
133775c55a07Sclaudio 
133875c55a07Sclaudio 	uri = conn->redir_uri;
133975c55a07Sclaudio 	conn->redir_uri = NULL;
134075c55a07Sclaudio 
134175c55a07Sclaudio 	if (conn->req->modified_since)
134275c55a07Sclaudio 		if ((mod_since = strdup(conn->req->modified_since)) == NULL)
134375c55a07Sclaudio 			err(1, NULL);
134447d3b88aSclaudio 
134547d3b88aSclaudio 	logx("redirect to %s", http_info(uri));
134606792369Sclaudio 	http_req_new(conn->req->id, uri, mod_since, conn->req->redirect_loop,
134706792369Sclaudio 	    outfd);
134847d3b88aSclaudio 
134975c55a07Sclaudio 	/* clear request before moving connection to idle */
135075c55a07Sclaudio 	http_req_free(conn->req);
135175c55a07Sclaudio 	conn->req = NULL;
135247d3b88aSclaudio }
135347d3b88aSclaudio 
135447d3b88aSclaudio static int
13551ef5b48aSclaudio http_parse_header(struct http_connection *conn, char *buf)
13561ef5b48aSclaudio {
13571ef5b48aSclaudio #define CONTENTLEN "Content-Length:"
13581ef5b48aSclaudio #define LOCATION "Location:"
135975c55a07Sclaudio #define CONNECTION "Connection:"
13601ef5b48aSclaudio #define TRANSFER_ENCODING "Transfer-Encoding:"
136178bab303Sclaudio #define CONTENT_ENCODING "Content-Encoding:"
13621ef5b48aSclaudio #define LAST_MODIFIED "Last-Modified:"
13631ef5b48aSclaudio 	const char *errstr;
13641ef5b48aSclaudio 	char *cp, *redirurl;
13651ef5b48aSclaudio 	char *locbase, *loctail;
13661ef5b48aSclaudio 
13671ef5b48aSclaudio 	cp = buf;
13681ef5b48aSclaudio 	/* empty line, end of header */
13691ef5b48aSclaudio 	if (*cp == '\0')
13701ef5b48aSclaudio 		return 0;
13711ef5b48aSclaudio 	else if (strncasecmp(cp, CONTENTLEN, sizeof(CONTENTLEN) - 1) == 0) {
13721ef5b48aSclaudio 		cp += sizeof(CONTENTLEN) - 1;
1373380deba5Sclaudio 		cp += strspn(cp, " \t");
137474d62246Sclaudio 		conn->iosz = strtonum(cp, 0, MAX_CONTENTLEN, &errstr);
13751ef5b48aSclaudio 		if (errstr != NULL) {
1376d7bb5489Sclaudio 			warnx("Content-Length of %s is %s",
1377eee9c28cSclaudio 			    conn_info(conn), errstr);
13781ef5b48aSclaudio 			return -1;
13791ef5b48aSclaudio 		}
13801ef5b48aSclaudio 	} else if (http_isredirect(conn) &&
13811ef5b48aSclaudio 	    strncasecmp(cp, LOCATION, sizeof(LOCATION) - 1) == 0) {
13821ef5b48aSclaudio 		cp += sizeof(LOCATION) - 1;
1383380deba5Sclaudio 		cp += strspn(cp, " \t");
13841ef5b48aSclaudio 		/*
13851ef5b48aSclaudio 		 * If there is a colon before the first slash, this URI
13861ef5b48aSclaudio 		 * is not relative. RFC 3986 4.2
13871ef5b48aSclaudio 		 */
13881ef5b48aSclaudio 		if (cp[strcspn(cp, ":/")] != ':') {
13891ef5b48aSclaudio 			/* XXX doesn't handle protocol-relative URIs */
13901ef5b48aSclaudio 			if (*cp == '/') {
13911ef5b48aSclaudio 				locbase = NULL;
13921ef5b48aSclaudio 				cp++;
13931ef5b48aSclaudio 			} else {
139475c55a07Sclaudio 				locbase = strdup(conn->req->path);
13951ef5b48aSclaudio 				if (locbase == NULL)
13966735b9d8Sclaudio 					err(1, NULL);
13971ef5b48aSclaudio 				loctail = strchr(locbase, '#');
13981ef5b48aSclaudio 				if (loctail != NULL)
13991ef5b48aSclaudio 					*loctail = '\0';
14001ef5b48aSclaudio 				loctail = strchr(locbase, '?');
14011ef5b48aSclaudio 				if (loctail != NULL)
14021ef5b48aSclaudio 					*loctail = '\0';
14031ef5b48aSclaudio 				loctail = strrchr(locbase, '/');
14041ef5b48aSclaudio 				if (loctail == NULL) {
14051ef5b48aSclaudio 					free(locbase);
14061ef5b48aSclaudio 					locbase = NULL;
14071ef5b48aSclaudio 				} else
14081ef5b48aSclaudio 					loctail[1] = '\0';
14091ef5b48aSclaudio 			}
14101ef5b48aSclaudio 			/* Construct URL from relative redirect */
14111ef5b48aSclaudio 			if (asprintf(&redirurl, "%.*s/%s%s",
141275c55a07Sclaudio 			    (int)(conn->req->path - conn->req->uri),
141375c55a07Sclaudio 			    conn->req->uri, locbase ? locbase : "", cp) == -1)
14141ef5b48aSclaudio 				err(1, "Cannot build redirect URL");
14151ef5b48aSclaudio 			free(locbase);
14161ef5b48aSclaudio 		} else if ((redirurl = strdup(cp)) == NULL)
14171ef5b48aSclaudio 			err(1, "Cannot build redirect URL");
14181ef5b48aSclaudio 		loctail = strchr(redirurl, '#');
14191ef5b48aSclaudio 		if (loctail != NULL)
14201ef5b48aSclaudio 			*loctail = '\0';
142175c55a07Sclaudio 		conn->redir_uri = redirurl;
1422608bb3c7Sjob 		if (!valid_origin(redirurl, conn->req->uri)) {
1423608bb3c7Sjob 			warnx("%s: cross origin redirect to %s", conn->req->uri,
1424608bb3c7Sjob 			    http_info(redirurl));
1425608bb3c7Sjob 			return -1;
1426608bb3c7Sjob 		}
14271ef5b48aSclaudio 	} else if (strncasecmp(cp, TRANSFER_ENCODING,
14281ef5b48aSclaudio 	    sizeof(TRANSFER_ENCODING) - 1) == 0) {
14291ef5b48aSclaudio 		cp += sizeof(TRANSFER_ENCODING) - 1;
1430380deba5Sclaudio 		cp += strspn(cp, " \t");
14311ef5b48aSclaudio 		if (strcasecmp(cp, "chunked") == 0)
14321ef5b48aSclaudio 			conn->chunked = 1;
143378bab303Sclaudio 	} else if (strncasecmp(cp, CONTENT_ENCODING,
143478bab303Sclaudio 	    sizeof(CONTENT_ENCODING) - 1) == 0) {
143578bab303Sclaudio 		cp += sizeof(CONTENT_ENCODING) - 1;
143678bab303Sclaudio 		cp += strspn(cp, " \t");
143778bab303Sclaudio 		if (strcasecmp(cp, "gzip") == 0 ||
143878bab303Sclaudio 		    strcasecmp(cp, "deflate") == 0) {
143978bab303Sclaudio 			if (http_inflate_new(conn) == -1)
144078bab303Sclaudio 				return -1;
144178bab303Sclaudio 			conn->gzipped = 1;
144278bab303Sclaudio 		}
144375c55a07Sclaudio 	} else if (strncasecmp(cp, CONNECTION, sizeof(CONNECTION) - 1) == 0) {
144475c55a07Sclaudio 		cp += sizeof(CONNECTION) - 1;
1445380deba5Sclaudio 		cp += strspn(cp, " \t");
14469ffee665Sclaudio 		if (strcasecmp(cp, "close") == 0)
14479ffee665Sclaudio 			conn->keep_alive = 0;
14489ffee665Sclaudio 		else if (strcasecmp(cp, "keep-alive") == 0)
144975c55a07Sclaudio 			conn->keep_alive = 1;
14501ef5b48aSclaudio 	} else if (strncasecmp(cp, LAST_MODIFIED,
14511ef5b48aSclaudio 	    sizeof(LAST_MODIFIED) - 1) == 0) {
14521ef5b48aSclaudio 		cp += sizeof(LAST_MODIFIED) - 1;
1453380deba5Sclaudio 		cp += strspn(cp, " \t");
14544b0eaf5cStb 		free(conn->last_modified);
145562764b10Stb 		if ((conn->last_modified = strdup(cp)) == NULL)
145662764b10Stb 			err(1, NULL);
14571ef5b48aSclaudio 	}
14581ef5b48aSclaudio 
14591ef5b48aSclaudio 	return 1;
14601ef5b48aSclaudio }
14611ef5b48aSclaudio 
146275c55a07Sclaudio /*
146375c55a07Sclaudio  * Return one line from the HTTP response.
146475c55a07Sclaudio  * The line returned has any possible '\r' and '\n' at the end stripped.
146575c55a07Sclaudio  * The buffer is advanced to the start of the next line.
146675c55a07Sclaudio  * If there is currently no full line in the buffer NULL is returned.
146775c55a07Sclaudio  */
14681ef5b48aSclaudio static char *
14691ef5b48aSclaudio http_get_line(struct http_connection *conn)
14701ef5b48aSclaudio {
14711ef5b48aSclaudio 	char *end, *line;
14721ef5b48aSclaudio 	size_t len;
14731ef5b48aSclaudio 
14741ef5b48aSclaudio 	end = memchr(conn->buf, '\n', conn->bufpos);
14751ef5b48aSclaudio 	if (end == NULL)
14761ef5b48aSclaudio 		return NULL;
14771ef5b48aSclaudio 
14781ef5b48aSclaudio 	len = end - conn->buf;
1479d8670119Sclaudio 	while (len > 0 && (conn->buf[len - 1] == '\r' ||
1480d8670119Sclaudio 	    conn->buf[len - 1] == ' ' || conn->buf[len - 1] == '\t'))
14811ef5b48aSclaudio 		--len;
1482d8670119Sclaudio 
14831ef5b48aSclaudio 	if ((line = strndup(conn->buf, len)) == NULL)
14846735b9d8Sclaudio 		err(1, NULL);
14851ef5b48aSclaudio 
14861ef5b48aSclaudio 	/* consume line including \n */
14871ef5b48aSclaudio 	end++;
14881ef5b48aSclaudio 	conn->bufpos -= end - conn->buf;
14891ef5b48aSclaudio 	memmove(conn->buf, end, conn->bufpos);
14901ef5b48aSclaudio 
14911ef5b48aSclaudio 	return line;
14921ef5b48aSclaudio }
14931ef5b48aSclaudio 
149475c55a07Sclaudio /*
149575c55a07Sclaudio  * Parse the header between data chunks during chunked transfers.
149675c55a07Sclaudio  * Returns 0 if a new chunk size could be correctly read.
149775c55a07Sclaudio  * If the chuck size could not be converted properly -1 is returned.
149875c55a07Sclaudio  */
14991ef5b48aSclaudio static int
15001ef5b48aSclaudio http_parse_chunked(struct http_connection *conn, char *buf)
15011ef5b48aSclaudio {
15021ef5b48aSclaudio 	char *header = buf;
15031ef5b48aSclaudio 	char *end;
1504d7bb5489Sclaudio 	unsigned long chunksize;
15051ef5b48aSclaudio 
1506d8670119Sclaudio 	/* strip any optional chunk extension */
1507d8670119Sclaudio 	header[strcspn(header, "; \t")] = '\0';
15081ef5b48aSclaudio 	errno = 0;
15091ef5b48aSclaudio 	chunksize = strtoul(header, &end, 16);
1510d7bb5489Sclaudio 	if (header[0] == '\0' || *end != '\0' || (errno == ERANGE &&
1511af6f8e1cSclaudio 	    chunksize == ULONG_MAX) || chunksize > MAX_CONTENTLEN)
15121ef5b48aSclaudio 		return -1;
15131ef5b48aSclaudio 
151475c55a07Sclaudio 	conn->iosz = chunksize;
15151ef5b48aSclaudio 	return 0;
15161ef5b48aSclaudio }
15171ef5b48aSclaudio 
151875c55a07Sclaudio static enum res
15191ef5b48aSclaudio http_read(struct http_connection *conn)
15201ef5b48aSclaudio {
15211ef5b48aSclaudio 	ssize_t s;
15221ef5b48aSclaudio 	char *buf;
1523cec4d91cSclaudio 	int done;
15241ef5b48aSclaudio 
15250fec2b2fSanton 	if (conn->bufpos > 0)
15260fec2b2fSanton 		goto again;
15270fec2b2fSanton 
1528cec4d91cSclaudio read_more:
15291ef5b48aSclaudio 	s = tls_read(conn->tls, conn->buf + conn->bufpos,
15301ef5b48aSclaudio 	    conn->bufsz - conn->bufpos);
15311ef5b48aSclaudio 	if (s == -1) {
1532eee9c28cSclaudio 		warnx("%s: TLS read: %s", conn_info(conn),
15331ef5b48aSclaudio 		    tls_error(conn->tls));
153475c55a07Sclaudio 		return http_failed(conn);
15351ef5b48aSclaudio 	} else if (s == TLS_WANT_POLLIN) {
15361ef5b48aSclaudio 		return WANT_POLLIN;
15371ef5b48aSclaudio 	} else if (s == TLS_WANT_POLLOUT) {
15381ef5b48aSclaudio 		return WANT_POLLOUT;
15391ef5b48aSclaudio 	}
15401ef5b48aSclaudio 
1541cb5cc1c3Sclaudio 	if (s == 0) {
154275c55a07Sclaudio 		if (conn->req)
15431ef5b48aSclaudio 			warnx("%s: short read, connection closed",
1544eee9c28cSclaudio 			    conn_info(conn));
154575c55a07Sclaudio 		return http_failed(conn);
15461ef5b48aSclaudio 	}
15471ef5b48aSclaudio 
15481ef5b48aSclaudio 	conn->bufpos += s;
15491ef5b48aSclaudio 
1550cec4d91cSclaudio again:
15511ef5b48aSclaudio 	switch (conn->state) {
15526f704872Sclaudio 	case STATE_PROXY_STATUS:
15536f704872Sclaudio 		buf = http_get_line(conn);
15546f704872Sclaudio 		if (buf == NULL)
15556f704872Sclaudio 			goto read_more;
15566f704872Sclaudio 		if (http_parse_status(conn, buf) == -1) {
15576f704872Sclaudio 			free(buf);
15586f704872Sclaudio 			return http_failed(conn);
15596f704872Sclaudio 		}
15606f704872Sclaudio 		free(buf);
15616f704872Sclaudio 		conn->state = STATE_PROXY_RESPONSE;
15626f704872Sclaudio 		goto again;
15636f704872Sclaudio 	case STATE_PROXY_RESPONSE:
15646f704872Sclaudio 		while (1) {
15656f704872Sclaudio 			buf = http_get_line(conn);
15666f704872Sclaudio 			if (buf == NULL)
15676f704872Sclaudio 				goto read_more;
15686f704872Sclaudio 			/* empty line, end of header */
156959b3ab32Stb 			if (*buf == '\0') {
157059b3ab32Stb 				free(buf);
15716f704872Sclaudio 				break;
15726f704872Sclaudio 			}
157359b3ab32Stb 			free(buf);
157459b3ab32Stb 		}
15756f704872Sclaudio 		/* proxy is ready to take connection */
15766f704872Sclaudio 		if (conn->status == 200) {
15776f704872Sclaudio 			conn->state = STATE_CONNECT;
15786f704872Sclaudio 			return http_tls_connect(conn);
15796f704872Sclaudio 		}
15806f704872Sclaudio 		return http_failed(conn);
15811ef5b48aSclaudio 	case STATE_RESPONSE_STATUS:
15821ef5b48aSclaudio 		buf = http_get_line(conn);
15831ef5b48aSclaudio 		if (buf == NULL)
1584cec4d91cSclaudio 			goto read_more;
15856f704872Sclaudio 
15861ef5b48aSclaudio 		if (http_parse_status(conn, buf) == -1) {
15871ef5b48aSclaudio 			free(buf);
158875c55a07Sclaudio 			return http_failed(conn);
15891ef5b48aSclaudio 		}
15901ef5b48aSclaudio 		free(buf);
15911ef5b48aSclaudio 		conn->state = STATE_RESPONSE_HEADER;
1592cec4d91cSclaudio 		goto again;
15931ef5b48aSclaudio 	case STATE_RESPONSE_HEADER:
1594cec4d91cSclaudio 		done = 0;
1595cec4d91cSclaudio 		while (!done) {
15961ef5b48aSclaudio 			int rv;
15971ef5b48aSclaudio 
1598cec4d91cSclaudio 			buf = http_get_line(conn);
1599cec4d91cSclaudio 			if (buf == NULL)
1600cec4d91cSclaudio 				goto read_more;
1601cec4d91cSclaudio 
16021ef5b48aSclaudio 			rv = http_parse_header(conn, buf);
16031ef5b48aSclaudio 			free(buf);
160475c55a07Sclaudio 
1605cec4d91cSclaudio 			if (rv == -1)
160675c55a07Sclaudio 				return http_failed(conn);
1607cec4d91cSclaudio 			if (rv == 0)
1608cec4d91cSclaudio 				done = 1;
16091ef5b48aSclaudio 		}
1610cec4d91cSclaudio 
1611cec4d91cSclaudio 		/* Check status header and decide what to do next */
1612eb6e4c97Sclaudio 		if (http_isok(conn) || http_isredirect(conn)) {
161375c55a07Sclaudio 			if (http_isredirect(conn))
161475c55a07Sclaudio 				http_redirect(conn);
161575c55a07Sclaudio 
161674d62246Sclaudio 			conn->totalsz = 0;
1617cec4d91cSclaudio 			if (conn->chunked)
161875c55a07Sclaudio 				conn->state = STATE_RESPONSE_CHUNKED_HEADER;
1619cec4d91cSclaudio 			else
1620cec4d91cSclaudio 				conn->state = STATE_RESPONSE_DATA;
1621cec4d91cSclaudio 			goto again;
1622eb6e4c97Sclaudio 		} else if (conn->status == 100 || conn->status == 103) {
1623eb6e4c97Sclaudio 			conn->state = STATE_RESPONSE_STATUS;
1624cec4d91cSclaudio 		} else if (conn->status == 304) {
162575c55a07Sclaudio 			return http_done(conn, HTTP_NOT_MOD);
1626cec4d91cSclaudio 		}
1627cec4d91cSclaudio 
162875c55a07Sclaudio 		return http_failed(conn);
16291ef5b48aSclaudio 	case STATE_RESPONSE_DATA:
163075c55a07Sclaudio 		if (conn->bufpos != conn->bufsz &&
1631af6f8e1cSclaudio 		    conn->iosz > conn->bufpos)
1632cec4d91cSclaudio 			goto read_more;
163375c55a07Sclaudio 
16342e80dbe3Sclaudio 		/* got a buffer full of data */
163575c55a07Sclaudio 		if (conn->req == NULL) {
163675c55a07Sclaudio 			/*
163775c55a07Sclaudio 			 * After redirects all data needs to be discarded.
163875c55a07Sclaudio 			 */
1639af6f8e1cSclaudio 			if (conn->iosz < conn->bufpos) {
164075c55a07Sclaudio 				conn->bufpos -= conn->iosz;
164175c55a07Sclaudio 				conn->iosz = 0;
164275c55a07Sclaudio 			} else {
164375c55a07Sclaudio 				conn->iosz -= conn->bufpos;
164475c55a07Sclaudio 				conn->bufpos = 0;
164575c55a07Sclaudio 			}
164675c55a07Sclaudio 			if (conn->chunked)
1647fd522c34Sclaudio 				conn->state = STATE_RESPONSE_CHUNKED_CRLF;
164875c55a07Sclaudio 			else
164975c55a07Sclaudio 				conn->state = STATE_RESPONSE_DATA;
165075c55a07Sclaudio 			goto read_more;
165175c55a07Sclaudio 		}
165275c55a07Sclaudio 
165375c55a07Sclaudio 		conn->state = STATE_WRITE_DATA;
165475c55a07Sclaudio 		return WANT_POLLOUT;
165575c55a07Sclaudio 	case STATE_RESPONSE_CHUNKED_HEADER:
165675c55a07Sclaudio 		assert(conn->iosz == 0);
165775c55a07Sclaudio 
16581ef5b48aSclaudio 		buf = http_get_line(conn);
16591ef5b48aSclaudio 		if (buf == NULL)
1660cec4d91cSclaudio 			goto read_more;
166175c55a07Sclaudio 		if (http_parse_chunked(conn, buf) != 0) {
1662eee9c28cSclaudio 			warnx("%s: bad chunk encoding", conn_info(conn));
16631ef5b48aSclaudio 			free(buf);
166475c55a07Sclaudio 			return http_failed(conn);
16651ef5b48aSclaudio 		}
166659b3ab32Stb 		free(buf);
16671ef5b48aSclaudio 
166875c55a07Sclaudio 		/*
166975c55a07Sclaudio 		 * check if transfer is done, in which case the last trailer
167075c55a07Sclaudio 		 * still needs to be processed.
167175c55a07Sclaudio 		 */
1672fd522c34Sclaudio 		if (conn->iosz == 0)
167375c55a07Sclaudio 			conn->state = STATE_RESPONSE_CHUNKED_TRAILER;
1674fd522c34Sclaudio 		else
167575c55a07Sclaudio 			conn->state = STATE_RESPONSE_DATA;
167675c55a07Sclaudio 		goto again;
1677fd522c34Sclaudio 	case STATE_RESPONSE_CHUNKED_CRLF:
167875c55a07Sclaudio 		buf = http_get_line(conn);
167975c55a07Sclaudio 		if (buf == NULL)
1680cec4d91cSclaudio 			goto read_more;
1681fd522c34Sclaudio 		/* expect empty line to finish a chunk of data */
1682fd522c34Sclaudio 		if (*buf != '\0') {
1683eee9c28cSclaudio 			warnx("%s: bad chunk encoding", conn_info(conn));
168475c55a07Sclaudio 			free(buf);
168575c55a07Sclaudio 			return http_failed(conn);
168675c55a07Sclaudio 		}
168775c55a07Sclaudio 		free(buf);
168875c55a07Sclaudio 		conn->state = STATE_RESPONSE_CHUNKED_HEADER;
168975c55a07Sclaudio 		goto again;
1690fd522c34Sclaudio 	case STATE_RESPONSE_CHUNKED_TRAILER:
1691fd522c34Sclaudio 		buf = http_get_line(conn);
1692fd522c34Sclaudio 		if (buf == NULL)
1693fd522c34Sclaudio 			goto read_more;
1694fd522c34Sclaudio 		/* the trailer may include extra headers, just ignore them */
1695fd522c34Sclaudio 		if (*buf != '\0') {
1696fd522c34Sclaudio 			free(buf);
1697fd522c34Sclaudio 			goto again;
1698fd522c34Sclaudio 		}
1699fd522c34Sclaudio 		free(buf);
1700fd522c34Sclaudio 		conn->chunked = 0;
1701fd522c34Sclaudio 		return http_done(conn, HTTP_OK);
17021ef5b48aSclaudio 	default:
17031ef5b48aSclaudio 		errx(1, "unexpected http state");
17041ef5b48aSclaudio 	}
17051ef5b48aSclaudio }
17061ef5b48aSclaudio 
170775c55a07Sclaudio /*
170875c55a07Sclaudio  * Send out the HTTP request. When done, replace buffer with the read buffer.
170975c55a07Sclaudio  */
171075c55a07Sclaudio static enum res
171147d3b88aSclaudio http_write(struct http_connection *conn)
171247d3b88aSclaudio {
171347d3b88aSclaudio 	ssize_t s;
171447d3b88aSclaudio 
171575c55a07Sclaudio 	assert(conn->state == STATE_REQUEST);
171675c55a07Sclaudio 
171775c55a07Sclaudio 	while (conn->bufpos < conn->bufsz) {
171847d3b88aSclaudio 		s = tls_write(conn->tls, conn->buf + conn->bufpos,
171947d3b88aSclaudio 		    conn->bufsz - conn->bufpos);
172047d3b88aSclaudio 		if (s == -1) {
1721eee9c28cSclaudio 			warnx("%s: TLS write: %s", conn_info(conn),
172247d3b88aSclaudio 			    tls_error(conn->tls));
172375c55a07Sclaudio 			return http_failed(conn);
172447d3b88aSclaudio 		} else if (s == TLS_WANT_POLLIN) {
172547d3b88aSclaudio 			return WANT_POLLIN;
172647d3b88aSclaudio 		} else if (s == TLS_WANT_POLLOUT) {
172747d3b88aSclaudio 			return WANT_POLLOUT;
172847d3b88aSclaudio 		}
172947d3b88aSclaudio 
173047d3b88aSclaudio 		conn->bufpos += s;
173147d3b88aSclaudio 	}
173247d3b88aSclaudio 
173375c55a07Sclaudio 	/* done writing, first thing we need the status */
173475c55a07Sclaudio 	conn->state = STATE_RESPONSE_STATUS;
173575c55a07Sclaudio 
173675c55a07Sclaudio 	/* free write buffer and allocate the read buffer */
173775c55a07Sclaudio 	free(conn->buf);
173875c55a07Sclaudio 	conn->bufpos = 0;
173975c55a07Sclaudio 	conn->bufsz = HTTP_BUF_SIZE;
174075c55a07Sclaudio 	if ((conn->buf = malloc(conn->bufsz)) == NULL)
174175c55a07Sclaudio 		err(1, NULL);
174275c55a07Sclaudio 
174375c55a07Sclaudio 	return http_read(conn);
174475c55a07Sclaudio }
174575c55a07Sclaudio 
17466f704872Sclaudio static enum res
17476f704872Sclaudio proxy_read(struct http_connection *conn)
17486f704872Sclaudio {
17496f704872Sclaudio 	ssize_t s;
17506f704872Sclaudio 	char *buf;
17516f704872Sclaudio 	int done;
17526f704872Sclaudio 
17536f704872Sclaudio 	s = read(conn->fd, conn->buf + conn->bufpos,
17546f704872Sclaudio 	    conn->bufsz - conn->bufpos);
17556f704872Sclaudio 	if (s == -1) {
1756eee9c28cSclaudio 		warn("%s: read", conn_info(conn));
17576f704872Sclaudio 		return http_failed(conn);
17586f704872Sclaudio 	}
17596f704872Sclaudio 
17606f704872Sclaudio 	if (s == 0) {
17616f704872Sclaudio 		if (conn->req)
17626f704872Sclaudio 			warnx("%s: short read, connection closed",
1763eee9c28cSclaudio 			    conn_info(conn));
17646f704872Sclaudio 		return http_failed(conn);
17656f704872Sclaudio 	}
17666f704872Sclaudio 
17676f704872Sclaudio 	conn->bufpos += s;
17686f704872Sclaudio 
17696f704872Sclaudio again:
17706f704872Sclaudio 	switch (conn->state) {
17716f704872Sclaudio 	case STATE_PROXY_STATUS:
17726f704872Sclaudio 		buf = http_get_line(conn);
17736f704872Sclaudio 		if (buf == NULL)
17746f704872Sclaudio 			return WANT_POLLIN;
17756f704872Sclaudio 		if (http_parse_status(conn, buf) == -1) {
17766f704872Sclaudio 			free(buf);
17776f704872Sclaudio 			return http_failed(conn);
17786f704872Sclaudio 		}
17796f704872Sclaudio 		free(buf);
17806f704872Sclaudio 		conn->state = STATE_PROXY_RESPONSE;
17816f704872Sclaudio 		goto again;
17826f704872Sclaudio 	case STATE_PROXY_RESPONSE:
17836f704872Sclaudio 		done = 0;
17846f704872Sclaudio 		while (!done) {
17856f704872Sclaudio 			buf = http_get_line(conn);
17866f704872Sclaudio 			if (buf == NULL)
17876f704872Sclaudio 				return WANT_POLLIN;
17886f704872Sclaudio 			/* empty line, end of header */
17896f704872Sclaudio 			if (*buf == '\0')
17906f704872Sclaudio 				done = 1;
17916f704872Sclaudio 			free(buf);
17926f704872Sclaudio 		}
17936f704872Sclaudio 		/* proxy is ready, connect to remote */
17946f704872Sclaudio 		if (conn->status == 200) {
17956f704872Sclaudio 			conn->state = STATE_CONNECT;
17966f704872Sclaudio 			return http_tls_connect(conn);
17976f704872Sclaudio 		}
17986f704872Sclaudio 		return http_failed(conn);
17996f704872Sclaudio 	default:
18006f704872Sclaudio 		errx(1, "unexpected http state");
18016f704872Sclaudio 	}
18026f704872Sclaudio }
18036f704872Sclaudio 
18046f704872Sclaudio /*
18056f704872Sclaudio  * Send out the proxy request. When done, replace buffer with the read buffer.
18066f704872Sclaudio  */
18076f704872Sclaudio static enum res
18086f704872Sclaudio proxy_write(struct http_connection *conn)
18096f704872Sclaudio {
18106f704872Sclaudio 	ssize_t s;
18116f704872Sclaudio 
18126f704872Sclaudio 	assert(conn->state == STATE_PROXY_REQUEST);
18136f704872Sclaudio 
18146f704872Sclaudio 	s = write(conn->fd, conn->buf + conn->bufpos,
18156f704872Sclaudio 	    conn->bufsz - conn->bufpos);
18166f704872Sclaudio 	if (s == -1) {
1817eee9c28cSclaudio 		warn("%s: write", conn_info(conn));
18186f704872Sclaudio 		return http_failed(conn);
18196f704872Sclaudio 	}
18206f704872Sclaudio 	conn->bufpos += s;
18216f704872Sclaudio 	if (conn->bufpos < conn->bufsz)
18226f704872Sclaudio 		return WANT_POLLOUT;
18236f704872Sclaudio 
18246f704872Sclaudio 	/* done writing, first thing we need the status */
18256f704872Sclaudio 	conn->state = STATE_PROXY_STATUS;
18266f704872Sclaudio 
18276f704872Sclaudio 	/* free write buffer and allocate the read buffer */
18286f704872Sclaudio 	free(conn->buf);
18296f704872Sclaudio 	conn->bufpos = 0;
18306f704872Sclaudio 	conn->bufsz = HTTP_BUF_SIZE;
18316f704872Sclaudio 	if ((conn->buf = malloc(conn->bufsz)) == NULL)
18326f704872Sclaudio 		err(1, NULL);
18336f704872Sclaudio 
18346f704872Sclaudio 	return WANT_POLLIN;
18356f704872Sclaudio }
18366f704872Sclaudio 
183775c55a07Sclaudio /*
183875c55a07Sclaudio  * Properly shutdown the TLS session else move connection into free state.
183975c55a07Sclaudio  */
184075c55a07Sclaudio static enum res
184147d3b88aSclaudio http_close(struct http_connection *conn)
184247d3b88aSclaudio {
184375c55a07Sclaudio 	assert(conn->state == STATE_IDLE || conn->state == STATE_CLOSE);
184475c55a07Sclaudio 
184575c55a07Sclaudio 	conn->state = STATE_CLOSE;
184629ff95dfSclaudio 	LIST_REMOVE(conn, entry);
184729ff95dfSclaudio 	LIST_INSERT_HEAD(&active, conn, entry);
184875c55a07Sclaudio 
184947d3b88aSclaudio 	if (conn->tls != NULL) {
185047d3b88aSclaudio 		switch (tls_close(conn->tls)) {
185147d3b88aSclaudio 		case TLS_WANT_POLLIN:
185247d3b88aSclaudio 			return WANT_POLLIN;
185347d3b88aSclaudio 		case TLS_WANT_POLLOUT:
185447d3b88aSclaudio 			return WANT_POLLOUT;
185547d3b88aSclaudio 		case 0:
185647d3b88aSclaudio 		case -1:
185747d3b88aSclaudio 			break;
185847d3b88aSclaudio 		}
185947d3b88aSclaudio 	}
186047d3b88aSclaudio 
186175c55a07Sclaudio 	conn->state = STATE_FREE;
186275c55a07Sclaudio 	return DONE;
186347d3b88aSclaudio }
186447d3b88aSclaudio 
186575c55a07Sclaudio /*
186675c55a07Sclaudio  * Write data into provided file descriptor. If all data got written
186775c55a07Sclaudio  * the connection may change into idle state.
186875c55a07Sclaudio  */
186975c55a07Sclaudio static enum res
18701ef5b48aSclaudio data_write(struct http_connection *conn)
18711ef5b48aSclaudio {
18721ef5b48aSclaudio 	ssize_t s;
18731ef5b48aSclaudio 	size_t bsz = conn->bufpos;
18741ef5b48aSclaudio 
187575c55a07Sclaudio 	assert(conn->state == STATE_WRITE_DATA);
187675c55a07Sclaudio 
1877af6f8e1cSclaudio 	if (conn->iosz < bsz)
1878d7bb5489Sclaudio 		bsz = conn->iosz;
18791ef5b48aSclaudio 
188075c55a07Sclaudio 	s = write(conn->req->outfd, conn->buf, bsz);
18811ef5b48aSclaudio 	if (s == -1) {
1882eee9c28cSclaudio 		warn("%s: data write", conn_info(conn));
188375c55a07Sclaudio 		return http_failed(conn);
18841ef5b48aSclaudio 	}
18851ef5b48aSclaudio 
188674d62246Sclaudio 	conn->totalsz += s;
188774d62246Sclaudio 	if (conn->totalsz > MAX_CONTENTLEN) {
1888eee9c28cSclaudio 		warn("%s: too much data offered", conn_info(conn));
188974d62246Sclaudio 		return http_failed(conn);
189074d62246Sclaudio 	}
189174d62246Sclaudio 
18921ef5b48aSclaudio 	conn->bufpos -= s;
1893d7bb5489Sclaudio 	conn->iosz -= s;
18941ef5b48aSclaudio 	memmove(conn->buf, conn->buf + s, conn->bufpos);
18951ef5b48aSclaudio 
1896d7bb5489Sclaudio 	/* check if regular file transfer is finished */
189775c55a07Sclaudio 	if (!conn->chunked && conn->iosz == 0)
189875c55a07Sclaudio 		return http_done(conn, HTTP_OK);
18991ef5b48aSclaudio 
1900d7bb5489Sclaudio 	/* all data written, switch back to read */
1901cec4d91cSclaudio 	if (conn->bufpos == 0 || conn->iosz == 0) {
190203816d72Sclaudio 		if (conn->chunked && conn->iosz == 0)
1903fd522c34Sclaudio 			conn->state = STATE_RESPONSE_CHUNKED_CRLF;
1904cec4d91cSclaudio 		else
1905cec4d91cSclaudio 			conn->state = STATE_RESPONSE_DATA;
1906cec4d91cSclaudio 		return http_read(conn);
1907cec4d91cSclaudio 	}
19081ef5b48aSclaudio 
1909d7bb5489Sclaudio 	/* still more data to write in buffer */
19101ef5b48aSclaudio 	return WANT_POLLOUT;
19111ef5b48aSclaudio }
19121ef5b48aSclaudio 
1913f12b699fSclaudio /*
191478bab303Sclaudio  * Inflate and write data into provided file descriptor.
191578bab303Sclaudio  * This is a simplified version of data_write() that just writes out the
191678bab303Sclaudio  * decompressed file stream. All the buffer handling is done by
191778bab303Sclaudio  * http_inflate_data() and http_inflate_advance().
191878bab303Sclaudio  */
191978bab303Sclaudio static enum res
192078bab303Sclaudio data_inflate_write(struct http_connection *conn)
192178bab303Sclaudio {
192278bab303Sclaudio 	struct http_zlib *zctx = conn->zlibctx;
192378bab303Sclaudio 	ssize_t s;
192478bab303Sclaudio 
192578bab303Sclaudio 	assert(conn->state == STATE_WRITE_DATA);
192678bab303Sclaudio 
192778bab303Sclaudio 	/* no decompressed data, get more */
192878bab303Sclaudio 	if (zctx->zbufpos == 0)
192978bab303Sclaudio 		if (http_inflate_data(conn) == -1)
193078bab303Sclaudio 			return http_failed(conn);
193178bab303Sclaudio 
193278bab303Sclaudio 	s = write(conn->req->outfd, zctx->zbuf, zctx->zbufpos);
193378bab303Sclaudio 	if (s == -1) {
193478bab303Sclaudio 		warn("%s: data write", conn_info(conn));
193578bab303Sclaudio 		return http_failed(conn);
193678bab303Sclaudio 	}
193778bab303Sclaudio 
193878bab303Sclaudio 	conn->totalsz += s;
193978bab303Sclaudio 	if (conn->totalsz > MAX_CONTENTLEN) {
194078bab303Sclaudio 		warn("%s: too much decompressed data offered", conn_info(conn));
194178bab303Sclaudio 		return http_failed(conn);
194278bab303Sclaudio 	}
194378bab303Sclaudio 
194478bab303Sclaudio 	/* adjust output buffer */
194578bab303Sclaudio 	zctx->zbufpos -= s;
194678bab303Sclaudio 	memmove(zctx->zbuf, zctx->zbuf + s, zctx->zbufpos);
194778bab303Sclaudio 
194878bab303Sclaudio 	/* all decompressed data written, progress input */
194978bab303Sclaudio 	if (zctx->zbufpos == 0)
195078bab303Sclaudio 		return http_inflate_advance(conn);
195178bab303Sclaudio 
195278bab303Sclaudio 	/* still more data to write in buffer */
195378bab303Sclaudio 	return WANT_POLLOUT;
195478bab303Sclaudio }
195578bab303Sclaudio 
195678bab303Sclaudio /*
1957f12b699fSclaudio  * Do one IO call depending on the connection state.
1958f12b699fSclaudio  * Return WANT_POLLIN or WANT_POLLOUT to poll for more data.
1959f12b699fSclaudio  * If 0 is returned this stage is finished and the protocol should move
1960f12b699fSclaudio  * to the next stage by calling http_nextstep(). On error return -1.
1961f12b699fSclaudio  */
196275c55a07Sclaudio static enum res
196375c55a07Sclaudio http_handle(struct http_connection *conn)
19641ef5b48aSclaudio {
196575c55a07Sclaudio 	assert(conn->pfd != NULL && conn->pfd->revents != 0);
196675c55a07Sclaudio 
19676f704872Sclaudio 	conn->io_time = 0;
19686f704872Sclaudio 
19691ef5b48aSclaudio 	switch (conn->state) {
19701ef5b48aSclaudio 	case STATE_CONNECT:
197175c55a07Sclaudio 		return http_finish_connect(conn);
19721ef5b48aSclaudio 	case STATE_TLSCONNECT:
19731ef5b48aSclaudio 		return http_tls_handshake(conn);
19741ef5b48aSclaudio 	case STATE_REQUEST:
19751ef5b48aSclaudio 		return http_write(conn);
19766f704872Sclaudio 	case STATE_PROXY_REQUEST:
19776f704872Sclaudio 		return proxy_write(conn);
19786f704872Sclaudio 	case STATE_PROXY_STATUS:
19796f704872Sclaudio 	case STATE_PROXY_RESPONSE:
19806f704872Sclaudio 		return proxy_read(conn);
19811ef5b48aSclaudio 	case STATE_RESPONSE_STATUS:
19821ef5b48aSclaudio 	case STATE_RESPONSE_HEADER:
19831ef5b48aSclaudio 	case STATE_RESPONSE_DATA:
198475c55a07Sclaudio 	case STATE_RESPONSE_CHUNKED_HEADER:
1985fd522c34Sclaudio 	case STATE_RESPONSE_CHUNKED_CRLF:
198675c55a07Sclaudio 	case STATE_RESPONSE_CHUNKED_TRAILER:
19871ef5b48aSclaudio 		return http_read(conn);
19881ef5b48aSclaudio 	case STATE_WRITE_DATA:
198978bab303Sclaudio 		if (conn->gzipped)
199078bab303Sclaudio 			return data_inflate_write(conn);
199178bab303Sclaudio 		else
19921ef5b48aSclaudio 			return data_write(conn);
199375c55a07Sclaudio 	case STATE_CLOSE:
19941ef5b48aSclaudio 		return http_close(conn);
199575c55a07Sclaudio 	case STATE_IDLE:
199675c55a07Sclaudio 		conn->state = STATE_RESPONSE_HEADER;
199729ff95dfSclaudio 		LIST_REMOVE(conn, entry);
199829ff95dfSclaudio 		LIST_INSERT_HEAD(&active, conn, entry);
199975c55a07Sclaudio 		return http_read(conn);
20001ef5b48aSclaudio 	case STATE_FREE:
20011ef5b48aSclaudio 		errx(1, "bad http state");
20021ef5b48aSclaudio 	}
20035d2a5cd6Sclaudio 	errx(1, "unknown http state");
20041ef5b48aSclaudio }
20051ef5b48aSclaudio 
2006f12b699fSclaudio /*
200775c55a07Sclaudio  * Initialisation done before pledge() call to load certificates.
2008f12b699fSclaudio  */
2009c8a1112eSclaudio static void
2010c8a1112eSclaudio http_setup(void)
2011c8a1112eSclaudio {
20126f704872Sclaudio 	char *httpproxy;
20136f704872Sclaudio 
2014c8a1112eSclaudio 	tls_config = tls_config_new();
2015c8a1112eSclaudio 	if (tls_config == NULL)
2016c8a1112eSclaudio 		errx(1, "tls config failed");
201775c55a07Sclaudio 
2018c8a1112eSclaudio #if 0
2019c8a1112eSclaudio 	/* TODO Should we allow extra protos and ciphers? */
2020c8a1112eSclaudio 	if (tls_config_set_protocols(tls_config, TLS_PROTOCOLS_ALL) == -1)
2021c8a1112eSclaudio 		errx(1, "tls set protocols failed: %s",
2022c8a1112eSclaudio 		    tls_config_error(tls_config));
2023c8a1112eSclaudio 	if (tls_config_set_ciphers(tls_config, "legacy") == -1)
2024c8a1112eSclaudio 		errx(1, "tls set ciphers failed: %s",
2025c8a1112eSclaudio 		    tls_config_error(tls_config));
2026c8a1112eSclaudio #endif
2027c8a1112eSclaudio 
2028c8a1112eSclaudio 	/* load cert file from disk now */
2029c8a1112eSclaudio 	tls_ca_mem = tls_load_file(tls_default_ca_cert_file(),
2030c8a1112eSclaudio 	    &tls_ca_size, NULL);
2031c8a1112eSclaudio 	if (tls_ca_mem == NULL)
2032c8a1112eSclaudio 		err(1, "tls_load_file: %s", tls_default_ca_cert_file());
2033c8a1112eSclaudio 	tls_config_set_ca_mem(tls_config, tls_ca_mem, tls_ca_size);
2034c8a1112eSclaudio 
20356f704872Sclaudio 	if ((httpproxy = getenv("http_proxy")) != NULL && *httpproxy == '\0')
20366f704872Sclaudio 		httpproxy = NULL;
20376f704872Sclaudio 
20386f704872Sclaudio 	proxy_parse_uri(httpproxy);
2039c8a1112eSclaudio }
2040c8a1112eSclaudio 
20411ef5b48aSclaudio void
20421ef5b48aSclaudio proc_http(char *bind_addr, int fd)
20431ef5b48aSclaudio {
204475c55a07Sclaudio 	struct pollfd pfds[NPFDS];
204575c55a07Sclaudio 	struct http_connection *conn, *nc;
204675c55a07Sclaudio 	struct http_request *req, *nr;
2047*b5fa5d51Sclaudio 	struct ibuf *b;
20481ef5b48aSclaudio 
20491db5fd2bSclaudio 	if (pledge("stdio rpath inet dns recvfd", NULL) == -1)
20501db5fd2bSclaudio 		err(1, "pledge");
20511db5fd2bSclaudio 
20521ef5b48aSclaudio 	if (bind_addr != NULL) {
20531ef5b48aSclaudio 		struct addrinfo hints, *res;
20541ef5b48aSclaudio 
20551ef5b48aSclaudio 		bzero(&hints, sizeof(hints));
20561ef5b48aSclaudio 		hints.ai_family = AF_UNSPEC;
20571ef5b48aSclaudio 		hints.ai_socktype = SOCK_DGRAM; /*dummy*/
20581ef5b48aSclaudio 		hints.ai_flags = AI_NUMERICHOST;
20591ef5b48aSclaudio 		if (getaddrinfo(bind_addr, NULL, &hints, &res) == 0) {
20601ef5b48aSclaudio 			memcpy(&http_bindaddr, res->ai_addr, res->ai_addrlen);
20611ef5b48aSclaudio 			freeaddrinfo(res);
20621ef5b48aSclaudio 		}
20631ef5b48aSclaudio 	}
20641ef5b48aSclaudio 	http_setup();
20651ef5b48aSclaudio 
20661ef5b48aSclaudio 	if (pledge("stdio inet dns recvfd", NULL) == -1)
20671ef5b48aSclaudio 		err(1, "pledge");
20681ef5b48aSclaudio 
2069*b5fa5d51Sclaudio 	if ((msgq = msgbuf_new_reader(sizeof(size_t), io_parse_hdr, NULL)) ==
2070*b5fa5d51Sclaudio 	    NULL)
207125d36c5cSclaudio 		err(1, NULL);
20721ef5b48aSclaudio 
20731ef5b48aSclaudio 	for (;;) {
207475c55a07Sclaudio 		time_t now;
207575c55a07Sclaudio 		int timeout;
207695b65f7cSderaadt 		size_t i;
207795b65f7cSderaadt 
207840604d84Sclaudio 		memset(&pfds, 0, sizeof(pfds));
207975c55a07Sclaudio 		pfds[0].fd = fd;
208075c55a07Sclaudio 		pfds[0].events = POLLIN;
208125d36c5cSclaudio 		if (msgbuf_queuelen(msgq) > 0)
208275c55a07Sclaudio 			pfds[0].events |= POLLOUT;
20831549bb4dSderaadt 
208475c55a07Sclaudio 		i = 1;
208575c55a07Sclaudio 		timeout = INFTIM;
208675c55a07Sclaudio 		now = getmonotime();
208775c55a07Sclaudio 		LIST_FOREACH(conn, &active, entry) {
2088bd2dbcbdSclaudio 			if (i >= NPFDS)
2089bd2dbcbdSclaudio 				errx(1, "too many connections");
2090bd2dbcbdSclaudio 
20919170c2daSclaudio 			if (conn->io_time == 0) {
20929170c2daSclaudio 				if (conn->state == STATE_CONNECT)
20939170c2daSclaudio 					conn->io_time = now + MAX_CONN_TIMEOUT;
20949170c2daSclaudio 				else
20952778dc25Sjob 					conn->io_time = now + MAX_IO_TIMEOUT;
20969170c2daSclaudio 			}
20976f704872Sclaudio 
20986f704872Sclaudio 			if (conn->io_time <= now)
20996f704872Sclaudio 				timeout = 0;
21006f704872Sclaudio 			else {
21016f704872Sclaudio 				int diff = conn->io_time - now;
21026f704872Sclaudio 				diff *= 1000;
21036f704872Sclaudio 				if (timeout == INFTIM || diff < timeout)
21046f704872Sclaudio 					timeout = diff;
21056f704872Sclaudio 			}
21061ef5b48aSclaudio 			if (conn->state == STATE_WRITE_DATA)
210775c55a07Sclaudio 				pfds[i].fd = conn->req->outfd;
21081ef5b48aSclaudio 			else
21091ef5b48aSclaudio 				pfds[i].fd = conn->fd;
21101ef5b48aSclaudio 
21111ef5b48aSclaudio 			pfds[i].events = conn->events;
211275c55a07Sclaudio 			conn->pfd = &pfds[i];
211375c55a07Sclaudio 			i++;
21141ef5b48aSclaudio 		}
211575c55a07Sclaudio 		LIST_FOREACH(conn, &idle, entry) {
2116bd2dbcbdSclaudio 			if (i >= NPFDS)
2117bd2dbcbdSclaudio 				errx(1, "too many connections");
2118bd2dbcbdSclaudio 
211975c55a07Sclaudio 			if (conn->idle_time <= now)
212075c55a07Sclaudio 				timeout = 0;
212175c55a07Sclaudio 			else {
212275c55a07Sclaudio 				int diff = conn->idle_time - now;
212375c55a07Sclaudio 				diff *= 1000;
212475c55a07Sclaudio 				if (timeout == INFTIM || diff < timeout)
212575c55a07Sclaudio 					timeout = diff;
212675c55a07Sclaudio 			}
212775c55a07Sclaudio 			pfds[i].fd = conn->fd;
212875c55a07Sclaudio 			pfds[i].events = POLLIN;
212975c55a07Sclaudio 			conn->pfd = &pfds[i];
213075c55a07Sclaudio 			i++;
213175c55a07Sclaudio 		}
21321ef5b48aSclaudio 
21337ba5db23Sclaudio 		if (poll(pfds, i, timeout) == -1) {
21347ba5db23Sclaudio 			if (errno == EINTR)
21357ba5db23Sclaudio 				continue;
21361ef5b48aSclaudio 			err(1, "poll");
21377ba5db23Sclaudio 		}
21381ef5b48aSclaudio 
213975c55a07Sclaudio 		if (pfds[0].revents & POLLHUP)
21401ef5b48aSclaudio 			break;
214175c55a07Sclaudio 		if (pfds[0].revents & POLLOUT) {
214225d36c5cSclaudio 			if (msgbuf_write(fd, msgq) == -1) {
21439aadc625Sclaudio 				if (errno == EPIPE)
21441cedc9bdSclaudio 					errx(1, "write: connection closed");
21459aadc625Sclaudio 				else
21461cedc9bdSclaudio 					err(1, "write");
21471cedc9bdSclaudio 			}
21481cedc9bdSclaudio 		}
214975c55a07Sclaudio 		if (pfds[0].revents & POLLIN) {
2150*b5fa5d51Sclaudio 			switch (msgbuf_read(fd, msgq)) {
2151*b5fa5d51Sclaudio 			case -1:
2152*b5fa5d51Sclaudio 				err(1, "msgbuf_read");
2153*b5fa5d51Sclaudio 			case 0:
2154*b5fa5d51Sclaudio 				errx(1, "msgbuf_read: connection closed");
2155*b5fa5d51Sclaudio 			}
2156*b5fa5d51Sclaudio 			while ((b = io_buf_get(msgq)) != NULL) {
2157b6884e9fSclaudio 				unsigned int id;
21581ef5b48aSclaudio 				char *uri;
21591ef5b48aSclaudio 				char *mod;
21601ef5b48aSclaudio 
21617eb79a4aSclaudio 				io_read_buf(b, &id, sizeof(id));
21627eb79a4aSclaudio 				io_read_str(b, &uri);
21637eb79a4aSclaudio 				io_read_str(b, &mod);
21641ef5b48aSclaudio 
216575c55a07Sclaudio 				/* queue up new requests */
2166cf954fffSclaudio 				http_req_new(id, uri, mod, 0, ibuf_fd_get(b));
21677eb79a4aSclaudio 				ibuf_free(b);
21687eb79a4aSclaudio 			}
216975c55a07Sclaudio 		}
217075c55a07Sclaudio 
217175c55a07Sclaudio 		now = getmonotime();
217275c55a07Sclaudio 		/* process idle connections */
217375c55a07Sclaudio 		LIST_FOREACH_SAFE(conn, &idle, entry, nc) {
217475c55a07Sclaudio 			if (conn->pfd != NULL && conn->pfd->revents != 0)
217575c55a07Sclaudio 				http_do(conn, http_handle);
2176e8a0bc21Sclaudio 			else if (conn->idle_time <= now) {
2177e8a0bc21Sclaudio 				conn->io_time = 0;
217875c55a07Sclaudio 				http_do(conn, http_close);
2179e8a0bc21Sclaudio 			}
218075c55a07Sclaudio 
218175c55a07Sclaudio 			if (conn->state == STATE_FREE)
218275c55a07Sclaudio 				http_free(conn);
218375c55a07Sclaudio 		}
218475c55a07Sclaudio 
218575c55a07Sclaudio 		/* then active http requests */
218675c55a07Sclaudio 		LIST_FOREACH_SAFE(conn, &active, entry, nc) {
218775c55a07Sclaudio 			/* check if event is ready */
218875c55a07Sclaudio 			if (conn->pfd != NULL && conn->pfd->revents != 0)
218975c55a07Sclaudio 				http_do(conn, http_handle);
2190e8a0bc21Sclaudio 			else if (conn->io_time != 0 && conn->io_time <= now) {
21913029ed57Sclaudio 				conn->io_time = 0;
21929170c2daSclaudio 				if (conn->state == STATE_CONNECT) {
2193eee9c28cSclaudio 					warnx("%s: connect timeout",
2194eee9c28cSclaudio 					    conn_info(conn));
21959170c2daSclaudio 					http_do(conn, http_connect_failed);
21969170c2daSclaudio 				} else {
21976f704872Sclaudio 					warnx("%s: timeout, connection closed",
2198eee9c28cSclaudio 					    conn_info(conn));
21996f704872Sclaudio 					http_do(conn, http_failed);
22006f704872Sclaudio 				}
22019170c2daSclaudio 			}
220275c55a07Sclaudio 
220375c55a07Sclaudio 			if (conn->state == STATE_FREE)
220475c55a07Sclaudio 				http_free(conn);
220575c55a07Sclaudio 		}
220675c55a07Sclaudio 
220775c55a07Sclaudio 		TAILQ_FOREACH_SAFE(req, &queue, entry, nr)
220875c55a07Sclaudio 			if (!http_req_schedule(req))
22091ef5b48aSclaudio 				break;
22101ef5b48aSclaudio 	}
22111ef5b48aSclaudio 
22121ef5b48aSclaudio 	exit(0);
22131ef5b48aSclaudio }
2214