xref: /openbsd/usr.sbin/httpd/server_http.c (revision b32af659)
1*b32af659Sclaudio /*	$OpenBSD: server_http.c,v 1.154 2024/02/13 14:00:24 claudio Exp $	*/
2b7b6a941Sreyk 
3b7b6a941Sreyk /*
4e96b74b9Sdenis  * Copyright (c) 2020 Matthias Pressfreund <mpfr@fn.de>
593038d14Sreyk  * Copyright (c) 2006 - 2018 Reyk Floeter <reyk@openbsd.org>
6b7b6a941Sreyk  *
7b7b6a941Sreyk  * Permission to use, copy, modify, and distribute this software for any
8b7b6a941Sreyk  * purpose with or without fee is hereby granted, provided that the above
9b7b6a941Sreyk  * copyright notice and this permission notice appear in all copies.
10b7b6a941Sreyk  *
11b7b6a941Sreyk  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
12b7b6a941Sreyk  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
13b7b6a941Sreyk  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
14b7b6a941Sreyk  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
15b7b6a941Sreyk  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
16b7b6a941Sreyk  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
17b7b6a941Sreyk  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
18b7b6a941Sreyk  */
19b7b6a941Sreyk 
20b7b6a941Sreyk #include <sys/types.h>
21b7b6a941Sreyk #include <sys/queue.h>
22b7b6a941Sreyk #include <sys/socket.h>
23b7b6a941Sreyk #include <sys/tree.h>
24e96b74b9Sdenis #include <sys/stat.h>
25b7b6a941Sreyk 
26b7b6a941Sreyk #include <netinet/in.h>
2786f952e4Sreyk #include <arpa/inet.h>
28b7b6a941Sreyk 
29b7b6a941Sreyk #include <errno.h>
30b7b6a941Sreyk #include <stdlib.h>
31b7b6a941Sreyk #include <string.h>
32b7b6a941Sreyk #include <unistd.h>
33b9fc9a72Sderaadt #include <limits.h>
3459355b5aSreyk #include <fnmatch.h>
35b7b6a941Sreyk #include <stdio.h>
3686f952e4Sreyk #include <time.h>
37e286121aSflorian #include <resolv.h>
38b7b6a941Sreyk #include <event.h>
3959355b5aSreyk #include <ctype.h>
40e3c03affSreyk #include <vis.h>
41e96b74b9Sdenis #include <fcntl.h>
42b7b6a941Sreyk 
43b7b6a941Sreyk #include "httpd.h"
44b7b6a941Sreyk #include "http.h"
4559355b5aSreyk #include "patterns.h"
46b7b6a941Sreyk 
47b7b6a941Sreyk static int	 server_httpmethod_cmp(const void *, const void *);
48b7b6a941Sreyk static int	 server_httperror_cmp(const void *, const void *);
49b7b6a941Sreyk void		 server_httpdesc_free(struct http_descriptor *);
50e286121aSflorian int		 server_http_authenticate(struct server_config *,
51e286121aSflorian 		    struct client *);
52db043ce8Sbenno static int	 http_version_num(char *);
53586dade4Sreyk char		*server_expand_http(struct client *, const char *,
54586dade4Sreyk 		    char *, size_t);
55cbced0bdSian char		*replace_var(char *, const char *, const char *);
56cbced0bdSian char		*read_errdoc(const char *, const char *);
57b7b6a941Sreyk 
58b7b6a941Sreyk static struct http_method	 http_methods[] = HTTP_METHODS;
59b7b6a941Sreyk static struct http_error	 http_errors[] = HTTP_ERRORS;
60b7b6a941Sreyk 
61b7b6a941Sreyk void
server_http(void)62781985a7Srzalamena server_http(void)
63b7b6a941Sreyk {
64b7b6a941Sreyk 	DPRINTF("%s: sorting lookup tables, pid %d", __func__, getpid());
65b7b6a941Sreyk 
66b7b6a941Sreyk 	/* Sort the HTTP lookup arrays */
67b7b6a941Sreyk 	qsort(http_methods, sizeof(http_methods) /
68b7b6a941Sreyk 	    sizeof(http_methods[0]) - 1,
69b7b6a941Sreyk 	    sizeof(http_methods[0]), server_httpmethod_cmp);
70b7b6a941Sreyk 	qsort(http_errors, sizeof(http_errors) /
71b7b6a941Sreyk 	    sizeof(http_errors[0]) - 1,
72b7b6a941Sreyk 	    sizeof(http_errors[0]), server_httperror_cmp);
73b7b6a941Sreyk }
74b7b6a941Sreyk 
75b7b6a941Sreyk void
server_http_init(struct server * srv)76b7b6a941Sreyk server_http_init(struct server *srv)
77b7b6a941Sreyk {
78b7b6a941Sreyk 	/* nothing */
79b7b6a941Sreyk }
80b7b6a941Sreyk 
81b7b6a941Sreyk int
server_httpdesc_init(struct client * clt)82b7b6a941Sreyk server_httpdesc_init(struct client *clt)
83b7b6a941Sreyk {
84b7b6a941Sreyk 	struct http_descriptor	*desc;
85b7b6a941Sreyk 
86b7b6a941Sreyk 	if ((desc = calloc(1, sizeof(*desc))) == NULL)
87b7b6a941Sreyk 		return (-1);
88b7b6a941Sreyk 	RB_INIT(&desc->http_headers);
89d08e4976Sreyk 	clt->clt_descreq = desc;
90d08e4976Sreyk 
91d08e4976Sreyk 	if ((desc = calloc(1, sizeof(*desc))) == NULL) {
92d08e4976Sreyk 		/* req will be cleaned up later */
93d08e4976Sreyk 		return (-1);
94d08e4976Sreyk 	}
95d08e4976Sreyk 	RB_INIT(&desc->http_headers);
96d08e4976Sreyk 	clt->clt_descresp = desc;
97b7b6a941Sreyk 
98b7b6a941Sreyk 	return (0);
99b7b6a941Sreyk }
100b7b6a941Sreyk 
101b7b6a941Sreyk void
server_httpdesc_free(struct http_descriptor * desc)102b7b6a941Sreyk server_httpdesc_free(struct http_descriptor *desc)
103b7b6a941Sreyk {
104d08e4976Sreyk 	if (desc == NULL)
105d08e4976Sreyk 		return;
1063fe25232Sreyk 
107b7b6a941Sreyk 	free(desc->http_path);
108b7b6a941Sreyk 	desc->http_path = NULL;
10916a88448Syasuoka 	free(desc->http_path_orig);
11016a88448Syasuoka 	desc->http_path_orig = NULL;
111de6550b1Sreyk 	free(desc->http_path_alias);
112de6550b1Sreyk 	desc->http_path_alias = NULL;
113b7b6a941Sreyk 	free(desc->http_query);
114b7b6a941Sreyk 	desc->http_query = NULL;
115a23b6848Stb 	free(desc->http_query_alias);
116a23b6848Stb 	desc->http_query_alias = NULL;
117b7b6a941Sreyk 	free(desc->http_version);
118b7b6a941Sreyk 	desc->http_version = NULL;
11947dc2a9dSreyk 	free(desc->http_host);
12047dc2a9dSreyk 	desc->http_host = NULL;
1213fe25232Sreyk 
122b7b6a941Sreyk 	kv_purge(&desc->http_headers);
12344ed0680Sreyk 	desc->http_lastheader = NULL;
124d08e4976Sreyk 	desc->http_method = 0;
125d08e4976Sreyk 	desc->http_chunked = 0;
126b7b6a941Sreyk }
127b7b6a941Sreyk 
128e286121aSflorian int
server_http_authenticate(struct server_config * srv_conf,struct client * clt)129e286121aSflorian server_http_authenticate(struct server_config *srv_conf, struct client *clt)
130e286121aSflorian {
1317d6098fdSreyk 	char			 decoded[1024];
132e286121aSflorian 	FILE			*fp = NULL;
133e286121aSflorian 	struct http_descriptor	*desc = clt->clt_descreq;
1341413cd80Sreyk 	const struct auth	*auth = srv_conf->auth;
135e286121aSflorian 	struct kv		*ba, key;
136e286121aSflorian 	size_t			 linesize = 0;
137e286121aSflorian 	ssize_t			 linelen;
138e286121aSflorian 	int			 ret = -1;
1397d6098fdSreyk 	char			*line = NULL, *user = NULL, *pass = NULL;
1407d6098fdSreyk 	char			*clt_user = NULL, *clt_pass = NULL;
141e286121aSflorian 
142e286121aSflorian 	memset(decoded, 0, sizeof(decoded));
143e286121aSflorian 	key.kv_key = "Authorization";
144e286121aSflorian 
145e286121aSflorian 	if ((ba = kv_find(&desc->http_headers, &key)) == NULL ||
146e286121aSflorian 	    ba->kv_value == NULL)
147e286121aSflorian 		goto done;
148e286121aSflorian 
149e286121aSflorian 	if (strncmp(ba->kv_value, "Basic ", strlen("Basic ")) != 0)
150e286121aSflorian 		goto done;
151e286121aSflorian 
1524703e0faSreyk 	if (b64_pton(strchr(ba->kv_value, ' ') + 1, (uint8_t *)decoded,
153e286121aSflorian 	    sizeof(decoded)) <= 0)
154e286121aSflorian 		goto done;
155e286121aSflorian 
156e286121aSflorian 	if ((clt_pass = strchr(decoded, ':')) == NULL)
157e286121aSflorian 		goto done;
158e286121aSflorian 
159e286121aSflorian 	clt_user = decoded;
160e286121aSflorian 	*clt_pass++ = '\0';
161d22ad799Sflorian 	if ((clt->clt_remote_user = strdup(clt_user)) == NULL)
162d22ad799Sflorian 		goto done;
163e286121aSflorian 
164602531d9Sreyk 	if ((fp = fopen(auth->auth_htpasswd, "r")) == NULL)
165e286121aSflorian 		goto done;
166e286121aSflorian 
167e286121aSflorian 	while ((linelen = getline(&line, &linesize, fp)) != -1) {
168e286121aSflorian 		if (line[linelen - 1] == '\n')
169e286121aSflorian 			line[linelen - 1] = '\0';
170e286121aSflorian 		user = line;
171e286121aSflorian 		pass = strchr(line, ':');
172e286121aSflorian 
173e286121aSflorian 		if (pass == NULL) {
174e286121aSflorian 			explicit_bzero(line, linelen);
175e286121aSflorian 			continue;
176e286121aSflorian 		}
177e286121aSflorian 
178e286121aSflorian 		*pass++ = '\0';
179e286121aSflorian 
180e286121aSflorian 		if (strcmp(clt_user, user) != 0) {
181e286121aSflorian 			explicit_bzero(line, linelen);
182e286121aSflorian 			continue;
183e286121aSflorian 		}
184e286121aSflorian 
185e286121aSflorian 		if (crypt_checkpass(clt_pass, pass) == 0) {
186e286121aSflorian 			explicit_bzero(line, linelen);
187e286121aSflorian 			ret = 0;
188e286121aSflorian 			break;
189e286121aSflorian 		}
190e286121aSflorian 	}
191e286121aSflorian done:
1923ca71284Ssunil 	free(line);
193e286121aSflorian 	if (fp != NULL)
194e286121aSflorian 		fclose(fp);
195e286121aSflorian 
196e286121aSflorian 	if (ba != NULL && ba->kv_value != NULL) {
197e286121aSflorian 		explicit_bzero(ba->kv_value, strlen(ba->kv_value));
198e286121aSflorian 		explicit_bzero(decoded, sizeof(decoded));
199e286121aSflorian 	}
200e286121aSflorian 
201e286121aSflorian 	return (ret);
202e286121aSflorian }
203e286121aSflorian 
204db043ce8Sbenno static int
http_version_num(char * version)2058f1ed696Sbenno http_version_num(char *version)
2068f1ed696Sbenno {
207db043ce8Sbenno 	if (strlen(version) != 8 || strncmp(version, "HTTP/", 5) != 0
208db043ce8Sbenno 	    || !isdigit((unsigned char)version[5]) || version[6] != '.'
209db043ce8Sbenno 	    || !isdigit((unsigned char)version[7]))
210db043ce8Sbenno 		return (-1);
211db043ce8Sbenno 	if (version[5] == '0' && version[7] == '9')
2128f1ed696Sbenno 		return (9);
213db043ce8Sbenno 	if (version[5] == '1') {
214db043ce8Sbenno 		if (version[7] == '0')
2158f1ed696Sbenno 			return (10);
216db043ce8Sbenno 		else
2178f1ed696Sbenno 			/* any other version 1.x gets downgraded to 1.1 */
2188f1ed696Sbenno 			return (11);
219db043ce8Sbenno 	}
2208f1ed696Sbenno 	return (0);
2218f1ed696Sbenno }
2228f1ed696Sbenno 
223b7b6a941Sreyk void
server_read_http(struct bufferevent * bev,void * arg)224b7b6a941Sreyk server_read_http(struct bufferevent *bev, void *arg)
225b7b6a941Sreyk {
226b7b6a941Sreyk 	struct client		*clt = arg;
227d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
228b7b6a941Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
229b7b6a941Sreyk 	char			*line = NULL, *key, *value;
230b7b6a941Sreyk 	const char		*errstr;
231a89b976fSclaudio 	char			*http_version, *query;
232b7b6a941Sreyk 	size_t			 size, linelen;
2338f1ed696Sbenno 	int			 version;
234b7b6a941Sreyk 	struct kv		*hdr = NULL;
235b7b6a941Sreyk 
236b7b6a941Sreyk 	getmonotime(&clt->clt_tv_last);
237b7b6a941Sreyk 
238b7b6a941Sreyk 	size = EVBUFFER_LENGTH(src);
239b7b6a941Sreyk 	DPRINTF("%s: session %d: size %lu, to read %lld",
240b7b6a941Sreyk 	    __func__, clt->clt_id, size, clt->clt_toread);
241b7b6a941Sreyk 	if (!size) {
242b7b6a941Sreyk 		clt->clt_toread = TOREAD_HTTP_HEADER;
243b7b6a941Sreyk 		goto done;
244b7b6a941Sreyk 	}
245b7b6a941Sreyk 
246619eca92Sreyk 	while (!clt->clt_headersdone) {
247619eca92Sreyk 		if (!clt->clt_line) {
248619eca92Sreyk 			/* Peek into the buffer to see if it looks like HTTP */
249619eca92Sreyk 			key = EVBUFFER_DATA(src);
25074875158Sguenther 			if (!isalpha((unsigned char)*key)) {
251619eca92Sreyk 				server_abort_http(clt, 400,
252619eca92Sreyk 				    "invalid request line");
253619eca92Sreyk 				goto abort;
254619eca92Sreyk 			}
255619eca92Sreyk 		}
256619eca92Sreyk 
257619eca92Sreyk 		if ((line = evbuffer_readln(src,
258619eca92Sreyk 		    &linelen, EVBUFFER_EOL_CRLF_STRICT)) == NULL) {
259619eca92Sreyk 			/* No newline found after too many bytes */
260619eca92Sreyk 			if (size > SERVER_MAXHEADERLENGTH) {
261619eca92Sreyk 				server_abort_http(clt, 413,
262619eca92Sreyk 				    "request line too long");
263619eca92Sreyk 				goto abort;
264619eca92Sreyk 			}
265619eca92Sreyk 			break;
266619eca92Sreyk 		}
267b7b6a941Sreyk 
268b7b6a941Sreyk 		/*
269b7b6a941Sreyk 		 * An empty line indicates the end of the request.
270b7b6a941Sreyk 		 * libevent already stripped the \r\n for us.
271b7b6a941Sreyk 		 */
272b7b6a941Sreyk 		if (!linelen) {
273bf6a310cSreyk 			clt->clt_headersdone = 1;
274b7b6a941Sreyk 			free(line);
275b7b6a941Sreyk 			break;
276b7b6a941Sreyk 		}
277b7b6a941Sreyk 		key = line;
278b7b6a941Sreyk 
279b7b6a941Sreyk 		/* Limit the total header length minus \r\n */
280b7b6a941Sreyk 		clt->clt_headerlen += linelen;
281b7b6a941Sreyk 		if (clt->clt_headerlen > SERVER_MAXHEADERLENGTH) {
282b7b6a941Sreyk 			server_abort_http(clt, 413, "request too large");
2830859b34fSreyk 			goto abort;
284b7b6a941Sreyk 		}
285b7b6a941Sreyk 
286b7b6a941Sreyk 		/*
287b7b6a941Sreyk 		 * The first line is the GET/POST/PUT/... request,
288b7b6a941Sreyk 		 * subsequent lines are HTTP headers.
289b7b6a941Sreyk 		 */
290b7b6a941Sreyk 		if (++clt->clt_line == 1)
291b7b6a941Sreyk 			value = strchr(key, ' ');
292b7b6a941Sreyk 		else if (*key == ' ' || *key == '\t')
293b7b6a941Sreyk 			/* Multiline headers wrap with a space or tab */
294b7b6a941Sreyk 			value = NULL;
2956014b236Sbenno 		else {
2966014b236Sbenno 			/* Not a multiline header, should have a : */
297b7b6a941Sreyk 			value = strchr(key, ':');
298b7b6a941Sreyk 			if (value == NULL) {
2996014b236Sbenno 				server_abort_http(clt, 400, "malformed");
3006014b236Sbenno 				goto abort;
3016014b236Sbenno 			}
3026014b236Sbenno 		}
3036014b236Sbenno 		if (value == NULL) {
304b7b6a941Sreyk 			if (clt->clt_line == 1) {
305b7b6a941Sreyk 				server_abort_http(clt, 400, "malformed");
30603fd4389Sreyk 				goto abort;
307b7b6a941Sreyk 			}
308b7b6a941Sreyk 
309b7b6a941Sreyk 			/* Append line to the last header, if present */
310b7b6a941Sreyk 			if (kv_extend(&desc->http_headers,
31103fd4389Sreyk 			    desc->http_lastheader, line) == NULL)
312b7b6a941Sreyk 				goto fail;
313b7b6a941Sreyk 
314b7b6a941Sreyk 			free(line);
315b7b6a941Sreyk 			continue;
316b7b6a941Sreyk 		}
317b7b6a941Sreyk 		if (*value == ':') {
318b7b6a941Sreyk 			*value++ = '\0';
319b7b6a941Sreyk 			value += strspn(value, " \t\r\n");
320b7b6a941Sreyk 		} else {
321b7b6a941Sreyk 			*value++ = '\0';
322b7b6a941Sreyk 		}
323b7b6a941Sreyk 
324b7b6a941Sreyk 		DPRINTF("%s: session %d: header '%s: %s'", __func__,
325b7b6a941Sreyk 		    clt->clt_id, key, value);
326b7b6a941Sreyk 
327b7b6a941Sreyk 		/*
328b7b6a941Sreyk 		 * Identify and handle specific HTTP request methods
329b7b6a941Sreyk 		 */
330b7b6a941Sreyk 		if (clt->clt_line == 1) {
331b7b6a941Sreyk 			if ((desc->http_method = server_httpmethod_byname(key))
332fc062027Sreyk 			    == HTTP_METHOD_NONE) {
333fc062027Sreyk 				server_abort_http(clt, 400, "malformed");
334fc062027Sreyk 				goto abort;
335fc062027Sreyk 			}
336b7b6a941Sreyk 
337b7b6a941Sreyk 			/*
338b7b6a941Sreyk 			 * Decode request path and query
339b7b6a941Sreyk 			 */
340b7b6a941Sreyk 			desc->http_path = strdup(value);
34103fd4389Sreyk 			if (desc->http_path == NULL)
342b7b6a941Sreyk 				goto fail;
34303fd4389Sreyk 
3448f1ed696Sbenno 			http_version = strchr(desc->http_path, ' ');
3458f1ed696Sbenno 			if (http_version == NULL) {
34635d60353Skrw 				server_abort_http(clt, 400, "malformed");
34735d60353Skrw 				goto abort;
34835d60353Skrw 			}
34903fd4389Sreyk 
3508f1ed696Sbenno 			*http_version++ = '\0';
351b7b6a941Sreyk 
352b7b6a941Sreyk 			/*
3538f1ed696Sbenno 			 * We have to allocate the strings because they could
3542e2bd5b6Sreyk 			 * be changed independently by the filters later.
3558f1ed696Sbenno 			 * Allow HTTP version 0.9 to 1.1.
3568f1ed696Sbenno 			 * Downgrade http version > 1.1 <= 1.9 to version 1.1.
3578f1ed696Sbenno 			 * Return HTTP Version Not Supported for anything else.
358b7b6a941Sreyk 			 */
3598f1ed696Sbenno 
3608f1ed696Sbenno 			version = http_version_num(http_version);
3618f1ed696Sbenno 
362db043ce8Sbenno 			if (version == -1) {
363db043ce8Sbenno 				server_abort_http(clt, 400, "malformed");
364db043ce8Sbenno 				goto abort;
365db043ce8Sbenno 			} else if (version == 0) {
3668f1ed696Sbenno 				server_abort_http(clt, 505, "bad http version");
3678f1ed696Sbenno 				goto abort;
3688f1ed696Sbenno 			} else if (version == 11) {
3692e2bd5b6Sreyk 				if ((desc->http_version =
3708f1ed696Sbenno 				    strdup("HTTP/1.1")) == NULL)
371b7b6a941Sreyk 					goto fail;
3728f1ed696Sbenno 			} else {
3738f1ed696Sbenno 				if ((desc->http_version =
3748f1ed696Sbenno 				    strdup(http_version)) == NULL)
3758f1ed696Sbenno 					goto fail;
3768f1ed696Sbenno 			}
37703fd4389Sreyk 
378a89b976fSclaudio 			query = strchr(desc->http_path, '?');
379a89b976fSclaudio 			if (query != NULL) {
380a89b976fSclaudio 				*query++ = '\0';
381a89b976fSclaudio 
382a89b976fSclaudio 				if ((desc->http_query = strdup(query)) == NULL)
383b7b6a941Sreyk 					goto fail;
384a89b976fSclaudio 			}
38503fd4389Sreyk 
386b7b6a941Sreyk 		} else if (desc->http_method != HTTP_METHOD_NONE &&
387b7b6a941Sreyk 		    strcasecmp("Content-Length", key) == 0) {
388b7b6a941Sreyk 			if (desc->http_method == HTTP_METHOD_TRACE ||
389b7b6a941Sreyk 			    desc->http_method == HTTP_METHOD_CONNECT) {
390b7b6a941Sreyk 				/*
391b7b6a941Sreyk 				 * These method should not have a body
392b7b6a941Sreyk 				 * and thus no Content-Length header.
393b7b6a941Sreyk 				 */
394b7b6a941Sreyk 				server_abort_http(clt, 400, "malformed");
395b7b6a941Sreyk 				goto abort;
396b7b6a941Sreyk 			}
397b7b6a941Sreyk 
398b7b6a941Sreyk 			/*
399b7b6a941Sreyk 			 * Need to read data from the client after the
400b7b6a941Sreyk 			 * HTTP header.
401b7b6a941Sreyk 			 * XXX What about non-standard clients not using
402b7b6a941Sreyk 			 * the carriage return? And some browsers seem to
403b7b6a941Sreyk 			 * include the line length in the content-length.
404b7b6a941Sreyk 			 */
405b7b6a941Sreyk 			clt->clt_toread = strtonum(value, 0, LLONG_MAX,
406b7b6a941Sreyk 			    &errstr);
407b7b6a941Sreyk 			if (errstr) {
408b7b6a941Sreyk 				server_abort_http(clt, 500, errstr);
409b7b6a941Sreyk 				goto abort;
410b7b6a941Sreyk 			}
411b7b6a941Sreyk 		}
412b7b6a941Sreyk 
413b7b6a941Sreyk 		if (strcasecmp("Transfer-Encoding", key) == 0 &&
414b7b6a941Sreyk 		    strcasecmp("chunked", value) == 0)
415b7b6a941Sreyk 			desc->http_chunked = 1;
416b7b6a941Sreyk 
417b7b6a941Sreyk 		if (clt->clt_line != 1) {
418b7b6a941Sreyk 			if ((hdr = kv_add(&desc->http_headers, key,
41903fd4389Sreyk 			    value)) == NULL)
420b7b6a941Sreyk 				goto fail;
42103fd4389Sreyk 
422b7b6a941Sreyk 			desc->http_lastheader = hdr;
423b7b6a941Sreyk 		}
424b7b6a941Sreyk 
425b7b6a941Sreyk 		free(line);
426b7b6a941Sreyk 	}
427bf6a310cSreyk 	if (clt->clt_headersdone) {
428b7b6a941Sreyk 		if (desc->http_method == HTTP_METHOD_NONE) {
429b7b6a941Sreyk 			server_abort_http(clt, 406, "no method");
430b7b6a941Sreyk 			return;
431b7b6a941Sreyk 		}
432b7b6a941Sreyk 
433b7b6a941Sreyk 		switch (desc->http_method) {
434b7b6a941Sreyk 		case HTTP_METHOD_CONNECT:
435b7b6a941Sreyk 			/* Data stream */
436b7b6a941Sreyk 			clt->clt_toread = TOREAD_UNLIMITED;
437b7b6a941Sreyk 			bev->readcb = server_read;
438b7b6a941Sreyk 			break;
439b7b6a941Sreyk 		case HTTP_METHOD_GET:
440b7b6a941Sreyk 		case HTTP_METHOD_HEAD:
441f0c872b4Sreyk 		/* WebDAV methods */
442f0c872b4Sreyk 		case HTTP_METHOD_COPY:
443ac272b40Sreyk 		case HTTP_METHOD_MOVE:
444b7b6a941Sreyk 			clt->clt_toread = 0;
445b7b6a941Sreyk 			break;
446a3e115cfSreyk 		case HTTP_METHOD_DELETE:
447986dfe8aSreyk 		case HTTP_METHOD_OPTIONS:
448b7b6a941Sreyk 		case HTTP_METHOD_POST:
449b7b6a941Sreyk 		case HTTP_METHOD_PUT:
450b7b6a941Sreyk 		case HTTP_METHOD_RESPONSE:
451f0c872b4Sreyk 		/* WebDAV methods */
452f0c872b4Sreyk 		case HTTP_METHOD_PROPFIND:
453f0c872b4Sreyk 		case HTTP_METHOD_PROPPATCH:
454f0c872b4Sreyk 		case HTTP_METHOD_MKCOL:
455f0c872b4Sreyk 		case HTTP_METHOD_LOCK:
456f0c872b4Sreyk 		case HTTP_METHOD_UNLOCK:
457f0c872b4Sreyk 		case HTTP_METHOD_VERSION_CONTROL:
458f0c872b4Sreyk 		case HTTP_METHOD_REPORT:
459f0c872b4Sreyk 		case HTTP_METHOD_CHECKOUT:
460f0c872b4Sreyk 		case HTTP_METHOD_CHECKIN:
461f0c872b4Sreyk 		case HTTP_METHOD_UNCHECKOUT:
462f0c872b4Sreyk 		case HTTP_METHOD_MKWORKSPACE:
463f0c872b4Sreyk 		case HTTP_METHOD_UPDATE:
464f0c872b4Sreyk 		case HTTP_METHOD_LABEL:
465f0c872b4Sreyk 		case HTTP_METHOD_MERGE:
466f0c872b4Sreyk 		case HTTP_METHOD_BASELINE_CONTROL:
467f0c872b4Sreyk 		case HTTP_METHOD_MKACTIVITY:
468f0c872b4Sreyk 		case HTTP_METHOD_ORDERPATCH:
469f0c872b4Sreyk 		case HTTP_METHOD_ACL:
470f0c872b4Sreyk 		case HTTP_METHOD_MKREDIRECTREF:
471f0c872b4Sreyk 		case HTTP_METHOD_UPDATEREDIRECTREF:
472f0c872b4Sreyk 		case HTTP_METHOD_SEARCH:
473f0c872b4Sreyk 		case HTTP_METHOD_PATCH:
474b7b6a941Sreyk 			/* HTTP request payload */
475b7b6a941Sreyk 			if (clt->clt_toread > 0)
476b7b6a941Sreyk 				bev->readcb = server_read_httpcontent;
4773fc7a1c7Syasuoka 			if (clt->clt_toread < 0 && !desc->http_chunked)
4783fc7a1c7Syasuoka 				/* 7. of RFC 9112 Section 6.3 */
4793fc7a1c7Syasuoka 				clt->clt_toread = 0;
480b7b6a941Sreyk 			break;
481b7b6a941Sreyk 		default:
482f0c872b4Sreyk 			server_abort_http(clt, 405, "method not allowed");
483f0c872b4Sreyk 			return;
484b7b6a941Sreyk 		}
485b7b6a941Sreyk 		if (desc->http_chunked) {
486b7b6a941Sreyk 			/* Chunked transfer encoding */
487b7b6a941Sreyk 			clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
488b7b6a941Sreyk 			bev->readcb = server_read_httpchunks;
489b7b6a941Sreyk 		}
490b7b6a941Sreyk 
491b7b6a941Sreyk  done:
492b6a65335Sflorian 		if (clt->clt_toread != 0)
493b6a65335Sflorian 			bufferevent_disable(bev, EV_READ);
494781985a7Srzalamena 		server_response(httpd_env, clt);
495b7b6a941Sreyk 		return;
496b7b6a941Sreyk 	}
497b7b6a941Sreyk 	if (clt->clt_done) {
498b7b6a941Sreyk 		server_close(clt, "done");
499b7b6a941Sreyk 		return;
500b7b6a941Sreyk 	}
501b7b6a941Sreyk 	if (EVBUFFER_LENGTH(src) && bev->readcb != server_read_http)
502b7b6a941Sreyk 		bev->readcb(bev, arg);
503b7b6a941Sreyk 	bufferevent_enable(bev, EV_READ);
504b7b6a941Sreyk 	return;
505b7b6a941Sreyk  fail:
506b7b6a941Sreyk 	server_abort_http(clt, 500, strerror(errno));
507b7b6a941Sreyk  abort:
508b7b6a941Sreyk 	free(line);
509b7b6a941Sreyk }
510b7b6a941Sreyk 
511b7b6a941Sreyk void
server_read_httpcontent(struct bufferevent * bev,void * arg)512b7b6a941Sreyk server_read_httpcontent(struct bufferevent *bev, void *arg)
513b7b6a941Sreyk {
514b7b6a941Sreyk 	struct client		*clt = arg;
515b7b6a941Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
516b7b6a941Sreyk 	size_t			 size;
517b7b6a941Sreyk 
518b7b6a941Sreyk 	getmonotime(&clt->clt_tv_last);
519b7b6a941Sreyk 
520b7b6a941Sreyk 	size = EVBUFFER_LENGTH(src);
521b7b6a941Sreyk 	DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
522b7b6a941Sreyk 	    clt->clt_id, size, clt->clt_toread);
523b7b6a941Sreyk 	if (!size)
524b7b6a941Sreyk 		return;
525b7b6a941Sreyk 
526b7b6a941Sreyk 	if (clt->clt_toread > 0) {
527b7b6a941Sreyk 		/* Read content data */
528b7b6a941Sreyk 		if ((off_t)size > clt->clt_toread) {
529b7b6a941Sreyk 			size = clt->clt_toread;
530b6a65335Sflorian 			if (fcgi_add_stdin(clt, src) == -1)
531b7b6a941Sreyk 				goto fail;
532b7b6a941Sreyk 			clt->clt_toread = 0;
533b7b6a941Sreyk 		} else {
534b6a65335Sflorian 			if (fcgi_add_stdin(clt, src) == -1)
535b7b6a941Sreyk 				goto fail;
536b7b6a941Sreyk 			clt->clt_toread -= size;
537b7b6a941Sreyk 		}
538b7b6a941Sreyk 		DPRINTF("%s: done, size %lu, to read %lld", __func__,
539b7b6a941Sreyk 		    size, clt->clt_toread);
540b7b6a941Sreyk 	}
541b7b6a941Sreyk 	if (clt->clt_toread == 0) {
542b6a65335Sflorian 		fcgi_add_stdin(clt, NULL);
543b7b6a941Sreyk 		clt->clt_toread = TOREAD_HTTP_HEADER;
544b6a65335Sflorian 		bufferevent_disable(bev, EV_READ);
545b7b6a941Sreyk 		bev->readcb = server_read_http;
546b6a65335Sflorian 		return;
547b7b6a941Sreyk 	}
548b7b6a941Sreyk 	if (clt->clt_done)
549b7b6a941Sreyk 		goto done;
550b7b6a941Sreyk 	if (bev->readcb != server_read_httpcontent)
551b7b6a941Sreyk 		bev->readcb(bev, arg);
552b6a65335Sflorian 
553b7b6a941Sreyk 	return;
554b7b6a941Sreyk  done:
555b7b6a941Sreyk 	return;
556b7b6a941Sreyk  fail:
557b7b6a941Sreyk 	server_close(clt, strerror(errno));
558b7b6a941Sreyk }
559b7b6a941Sreyk 
560b7b6a941Sreyk void
server_read_httpchunks(struct bufferevent * bev,void * arg)561b7b6a941Sreyk server_read_httpchunks(struct bufferevent *bev, void *arg)
562b7b6a941Sreyk {
563b7b6a941Sreyk 	struct client		*clt = arg;
564b7b6a941Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
565b7b6a941Sreyk 	char			*line;
566b7b6a941Sreyk 	long long		 llval;
567b7b6a941Sreyk 	size_t			 size;
568b7b6a941Sreyk 
569b7b6a941Sreyk 	getmonotime(&clt->clt_tv_last);
570b7b6a941Sreyk 
571b7b6a941Sreyk 	size = EVBUFFER_LENGTH(src);
572b7b6a941Sreyk 	DPRINTF("%s: session %d: size %lu, to read %lld", __func__,
573b7b6a941Sreyk 	    clt->clt_id, size, clt->clt_toread);
574b7b6a941Sreyk 	if (!size)
575b7b6a941Sreyk 		return;
576b7b6a941Sreyk 
577b7b6a941Sreyk 	if (clt->clt_toread > 0) {
578b7b6a941Sreyk 		/* Read chunk data */
579b7b6a941Sreyk 		if ((off_t)size > clt->clt_toread) {
580b7b6a941Sreyk 			size = clt->clt_toread;
581b7b6a941Sreyk 			if (server_bufferevent_write_chunk(clt, src, size)
582b7b6a941Sreyk 			    == -1)
583b7b6a941Sreyk 				goto fail;
584b7b6a941Sreyk 			clt->clt_toread = 0;
585b7b6a941Sreyk 		} else {
586b7b6a941Sreyk 			if (server_bufferevent_write_buffer(clt, src) == -1)
587b7b6a941Sreyk 				goto fail;
588b7b6a941Sreyk 			clt->clt_toread -= size;
589b7b6a941Sreyk 		}
590b7b6a941Sreyk 		DPRINTF("%s: done, size %lu, to read %lld", __func__,
591b7b6a941Sreyk 		    size, clt->clt_toread);
592b7b6a941Sreyk 	}
593b7b6a941Sreyk 	switch (clt->clt_toread) {
594b7b6a941Sreyk 	case TOREAD_HTTP_CHUNK_LENGTH:
5958757e0ccSjsg 		line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
596b7b6a941Sreyk 		if (line == NULL) {
597b7b6a941Sreyk 			/* Ignore empty line, continue */
598b7b6a941Sreyk 			bufferevent_enable(bev, EV_READ);
599b7b6a941Sreyk 			return;
600b7b6a941Sreyk 		}
601b7b6a941Sreyk 		if (strlen(line) == 0) {
602b7b6a941Sreyk 			free(line);
603b7b6a941Sreyk 			goto next;
604b7b6a941Sreyk 		}
605b7b6a941Sreyk 
606b7b6a941Sreyk 		/*
607b7b6a941Sreyk 		 * Read prepended chunk size in hex, ignore the trailer.
608b7b6a941Sreyk 		 * The returned signed value must not be negative.
609b7b6a941Sreyk 		 */
610b7b6a941Sreyk 		if (sscanf(line, "%llx", &llval) != 1 || llval < 0) {
611b7b6a941Sreyk 			free(line);
612b7b6a941Sreyk 			server_close(clt, "invalid chunk size");
613b7b6a941Sreyk 			return;
614b7b6a941Sreyk 		}
615b7b6a941Sreyk 
616b7b6a941Sreyk 		if (server_bufferevent_print(clt, line) == -1 ||
617b7b6a941Sreyk 		    server_bufferevent_print(clt, "\r\n") == -1) {
618b7b6a941Sreyk 			free(line);
619b7b6a941Sreyk 			goto fail;
620b7b6a941Sreyk 		}
621b7b6a941Sreyk 		free(line);
622b7b6a941Sreyk 
623b7b6a941Sreyk 		if ((clt->clt_toread = llval) == 0) {
624b7b6a941Sreyk 			DPRINTF("%s: last chunk", __func__);
625b7b6a941Sreyk 			clt->clt_toread = TOREAD_HTTP_CHUNK_TRAILER;
626b7b6a941Sreyk 		}
627b7b6a941Sreyk 		break;
628b7b6a941Sreyk 	case TOREAD_HTTP_CHUNK_TRAILER:
629b7b6a941Sreyk 		/* Last chunk is 0 bytes followed by trailer and empty line */
6308757e0ccSjsg 		line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
631b7b6a941Sreyk 		if (line == NULL) {
632b7b6a941Sreyk 			/* Ignore empty line, continue */
633b7b6a941Sreyk 			bufferevent_enable(bev, EV_READ);
634b7b6a941Sreyk 			return;
635b7b6a941Sreyk 		}
636b7b6a941Sreyk 		if (server_bufferevent_print(clt, line) == -1 ||
637b7b6a941Sreyk 		    server_bufferevent_print(clt, "\r\n") == -1) {
638b7b6a941Sreyk 			free(line);
639b7b6a941Sreyk 			goto fail;
640b7b6a941Sreyk 		}
641b7b6a941Sreyk 		if (strlen(line) == 0) {
642b7b6a941Sreyk 			/* Switch to HTTP header mode */
643b7b6a941Sreyk 			clt->clt_toread = TOREAD_HTTP_HEADER;
644b7b6a941Sreyk 			bev->readcb = server_read_http;
645b7b6a941Sreyk 		}
646b7b6a941Sreyk 		free(line);
647b7b6a941Sreyk 		break;
648b7b6a941Sreyk 	case 0:
649b7b6a941Sreyk 		/* Chunk is terminated by an empty newline */
6508757e0ccSjsg 		line = evbuffer_readln(src, NULL, EVBUFFER_EOL_CRLF_STRICT);
651b7b6a941Sreyk 		free(line);
652b7b6a941Sreyk 		if (server_bufferevent_print(clt, "\r\n") == -1)
653b7b6a941Sreyk 			goto fail;
654b7b6a941Sreyk 		clt->clt_toread = TOREAD_HTTP_CHUNK_LENGTH;
655b7b6a941Sreyk 		break;
656b7b6a941Sreyk 	}
657b7b6a941Sreyk 
658b7b6a941Sreyk  next:
659b7b6a941Sreyk 	if (clt->clt_done)
660b7b6a941Sreyk 		goto done;
661b7b6a941Sreyk 	if (EVBUFFER_LENGTH(src))
662b7b6a941Sreyk 		bev->readcb(bev, arg);
663b7b6a941Sreyk 	bufferevent_enable(bev, EV_READ);
664b7b6a941Sreyk 	return;
665b7b6a941Sreyk 
666b7b6a941Sreyk  done:
667b7b6a941Sreyk 	server_close(clt, "last http chunk read (done)");
668b7b6a941Sreyk 	return;
669b7b6a941Sreyk  fail:
670b7b6a941Sreyk 	server_close(clt, strerror(errno));
671b7b6a941Sreyk }
672b7b6a941Sreyk 
673b7b6a941Sreyk void
server_read_httprange(struct bufferevent * bev,void * arg)674142cfc82Sreyk server_read_httprange(struct bufferevent *bev, void *arg)
675142cfc82Sreyk {
676142cfc82Sreyk 	struct client		*clt = arg;
677142cfc82Sreyk 	struct evbuffer		*src = EVBUFFER_INPUT(bev);
678142cfc82Sreyk 	size_t			 size;
679142cfc82Sreyk 	struct media_type	*media;
680142cfc82Sreyk 	struct range_data	*r = &clt->clt_ranges;
681142cfc82Sreyk 	struct range		*range;
682142cfc82Sreyk 
683142cfc82Sreyk 	getmonotime(&clt->clt_tv_last);
684142cfc82Sreyk 
685142cfc82Sreyk 	if (r->range_toread > 0) {
686142cfc82Sreyk 		size = EVBUFFER_LENGTH(src);
687142cfc82Sreyk 		if (!size)
688142cfc82Sreyk 			return;
689142cfc82Sreyk 
690142cfc82Sreyk 		/* Read chunk data */
691142cfc82Sreyk 		if ((off_t)size > r->range_toread) {
692142cfc82Sreyk 			size = r->range_toread;
693142cfc82Sreyk 			if (server_bufferevent_write_chunk(clt, src, size)
694142cfc82Sreyk 			    == -1)
695142cfc82Sreyk 				goto fail;
696142cfc82Sreyk 			r->range_toread = 0;
697142cfc82Sreyk 		} else {
698142cfc82Sreyk 			if (server_bufferevent_write_buffer(clt, src) == -1)
699142cfc82Sreyk 				goto fail;
700142cfc82Sreyk 			r->range_toread -= size;
701142cfc82Sreyk 		}
702142cfc82Sreyk 		if (r->range_toread < 1)
703142cfc82Sreyk 			r->range_toread = TOREAD_HTTP_RANGE;
704142cfc82Sreyk 		DPRINTF("%s: done, size %lu, to read %lld", __func__,
705142cfc82Sreyk 		    size, r->range_toread);
706142cfc82Sreyk 	}
707142cfc82Sreyk 
708142cfc82Sreyk 	switch (r->range_toread) {
709142cfc82Sreyk 	case TOREAD_HTTP_RANGE:
710142cfc82Sreyk 		if (r->range_index >= r->range_count) {
711142cfc82Sreyk 			if (r->range_count > 1) {
712142cfc82Sreyk 				/* Add end marker */
713142cfc82Sreyk 				if (server_bufferevent_printf(clt,
714142cfc82Sreyk 				    "\r\n--%llu--\r\n",
715142cfc82Sreyk 				    clt->clt_boundary) == -1)
716142cfc82Sreyk 					goto fail;
717142cfc82Sreyk 			}
718142cfc82Sreyk 			r->range_toread = TOREAD_HTTP_NONE;
719142cfc82Sreyk 			break;
720142cfc82Sreyk 		}
721142cfc82Sreyk 
722142cfc82Sreyk 		range = &r->range[r->range_index];
723142cfc82Sreyk 
724142cfc82Sreyk 		if (r->range_count > 1) {
725142cfc82Sreyk 			media = r->range_media;
726142cfc82Sreyk 			if (server_bufferevent_printf(clt,
727142cfc82Sreyk 			    "\r\n--%llu\r\n"
728142cfc82Sreyk 			    "Content-Type: %s/%s\r\n"
729142cfc82Sreyk 			    "Content-Range: bytes %lld-%lld/%zu\r\n\r\n",
730142cfc82Sreyk 			    clt->clt_boundary,
731142cfc82Sreyk 			    media->media_type, media->media_subtype,
732142cfc82Sreyk 			    range->start, range->end, r->range_total) == -1)
733142cfc82Sreyk 				goto fail;
734142cfc82Sreyk 		}
735142cfc82Sreyk 		r->range_toread = range->end - range->start + 1;
736142cfc82Sreyk 
737142cfc82Sreyk 		if (lseek(clt->clt_fd, range->start, SEEK_SET) == -1)
738142cfc82Sreyk 			goto fail;
739142cfc82Sreyk 
740142cfc82Sreyk 		/* Throw away bytes that are already in the input buffer */
741142cfc82Sreyk 		evbuffer_drain(src, EVBUFFER_LENGTH(src));
742142cfc82Sreyk 
743142cfc82Sreyk 		/* Increment for the next part */
744142cfc82Sreyk 		r->range_index++;
745142cfc82Sreyk 		break;
746142cfc82Sreyk 	case TOREAD_HTTP_NONE:
74710e99d49Sflorian 		goto done;
748142cfc82Sreyk 	case 0:
749142cfc82Sreyk 		break;
750142cfc82Sreyk 	}
751142cfc82Sreyk 
752142cfc82Sreyk 	if (clt->clt_done)
753142cfc82Sreyk 		goto done;
754142cfc82Sreyk 
755142cfc82Sreyk 	if (EVBUFFER_LENGTH(EVBUFFER_OUTPUT(clt->clt_bev)) > (size_t)
756142cfc82Sreyk 	    SERVER_MAX_PREFETCH * clt->clt_sndbufsiz) {
757142cfc82Sreyk 		bufferevent_disable(clt->clt_srvbev, EV_READ);
758142cfc82Sreyk 		clt->clt_srvbev_throttled = 1;
759142cfc82Sreyk 	}
760142cfc82Sreyk 
761142cfc82Sreyk 	return;
762142cfc82Sreyk  done:
763142cfc82Sreyk 	(*bev->errorcb)(bev, EVBUFFER_READ, bev->cbarg);
764142cfc82Sreyk 	return;
765142cfc82Sreyk  fail:
766142cfc82Sreyk 	server_close(clt, strerror(errno));
767142cfc82Sreyk }
768142cfc82Sreyk 
769142cfc82Sreyk void
server_reset_http(struct client * clt)770b7b6a941Sreyk server_reset_http(struct client *clt)
771b7b6a941Sreyk {
772fa361cd1Sreyk 	struct server		*srv = clt->clt_srv;
773b7b6a941Sreyk 
774c145f9a8Sreyk 	server_log(clt, NULL);
775c145f9a8Sreyk 
776d08e4976Sreyk 	server_httpdesc_free(clt->clt_descreq);
777d08e4976Sreyk 	server_httpdesc_free(clt->clt_descresp);
778b7b6a941Sreyk 	clt->clt_headerlen = 0;
779bf6a310cSreyk 	clt->clt_headersdone = 0;
780b7b6a941Sreyk 	clt->clt_done = 0;
781bf6a310cSreyk 	clt->clt_line = 0;
782ac2cdcb6Sreyk 	clt->clt_chunk = 0;
783daa1b608Sflorian 	free(clt->clt_remote_user);
784daa1b608Sflorian 	clt->clt_remote_user = NULL;
7855fa30660Sreyk 	clt->clt_bev->readcb = server_read_http;
786fa361cd1Sreyk 	clt->clt_srv_conf = &srv->srv_conf;
78759355b5aSreyk 	str_match_free(&clt->clt_srv_match);
788b7b6a941Sreyk }
789b7b6a941Sreyk 
790be5ab2e6Schrisz ssize_t
server_http_time(time_t t,char * tmbuf,size_t len)791be5ab2e6Schrisz server_http_time(time_t t, char *tmbuf, size_t len)
7929f126950Sreyk {
7939f126950Sreyk 	struct tm		 tm;
7949f126950Sreyk 
7959f126950Sreyk 	/* New HTTP/1.1 RFC 7231 prefers IMF-fixdate from RFC 5322 */
796be5ab2e6Schrisz 	if (t == -1 || gmtime_r(&t, &tm) == NULL)
797be5ab2e6Schrisz 		return (-1);
798be5ab2e6Schrisz 	else
799be5ab2e6Schrisz 		return (strftime(tmbuf, len, "%a, %d %h %Y %T %Z", &tm));
8009f126950Sreyk }
8019f126950Sreyk 
8026af43371Sreyk const char *
server_http_host(struct sockaddr_storage * ss,char * buf,size_t len)8036af43371Sreyk server_http_host(struct sockaddr_storage *ss, char *buf, size_t len)
8046af43371Sreyk {
805b9fc9a72Sderaadt 	char		hbuf[HOST_NAME_MAX+1];
8066af43371Sreyk 	in_port_t	port;
8076af43371Sreyk 
8086af43371Sreyk 	if (print_host(ss, buf, len) == NULL)
8096af43371Sreyk 		return (NULL);
8106af43371Sreyk 
8116af43371Sreyk 	port = ntohs(server_socket_getport(ss));
8126af43371Sreyk 	if (port == HTTP_PORT)
8136af43371Sreyk 		return (buf);
8146af43371Sreyk 
8156af43371Sreyk 	switch (ss->ss_family) {
8166af43371Sreyk 	case AF_INET:
8176af43371Sreyk 		if ((size_t)snprintf(hbuf, sizeof(hbuf),
8186af43371Sreyk 		    "%s:%u", buf, port) >= sizeof(hbuf))
8196af43371Sreyk 			return (NULL);
8206af43371Sreyk 		break;
8216af43371Sreyk 	case AF_INET6:
8226af43371Sreyk 		if ((size_t)snprintf(hbuf, sizeof(hbuf),
8236af43371Sreyk 		    "[%s]:%u", buf, port) >= sizeof(hbuf))
8246af43371Sreyk 			return (NULL);
8256af43371Sreyk 		break;
8266af43371Sreyk 	}
8276af43371Sreyk 
8286af43371Sreyk 	if (strlcpy(buf, hbuf, len) >= len)
8296af43371Sreyk 		return (NULL);
8306af43371Sreyk 
8316af43371Sreyk 	return (buf);
8326af43371Sreyk }
8336af43371Sreyk 
83477fd0032Sreyk char *
server_http_parsehost(char * host,char * buf,size_t len,int * portval)83577fd0032Sreyk server_http_parsehost(char *host, char *buf, size_t len, int *portval)
83677fd0032Sreyk {
83777fd0032Sreyk 	char		*start, *end, *port;
83877fd0032Sreyk 	const char	*errstr = NULL;
83977fd0032Sreyk 
84077fd0032Sreyk 	if (strlcpy(buf, host, len) >= len) {
84177fd0032Sreyk 		log_debug("%s: host name too long", __func__);
84277fd0032Sreyk 		return (NULL);
84377fd0032Sreyk 	}
84477fd0032Sreyk 
84577fd0032Sreyk 	start = buf;
84677fd0032Sreyk 	end = port = NULL;
84777fd0032Sreyk 
84877fd0032Sreyk 	if (*start == '[' && (end = strchr(start, ']')) != NULL) {
84977fd0032Sreyk 		/* Address enclosed in [] with port, eg. [2001:db8::1]:80 */
85077fd0032Sreyk 		start++;
85177fd0032Sreyk 		*end++ = '\0';
85277fd0032Sreyk 		if ((port = strchr(end, ':')) == NULL || *port == '\0')
85377fd0032Sreyk 			port = NULL;
85477fd0032Sreyk 		else
85577fd0032Sreyk 			port++;
85677fd0032Sreyk 		memmove(buf, start, strlen(start) + 1);
85777fd0032Sreyk 	} else if ((end = strchr(start, ':')) != NULL) {
85877fd0032Sreyk 		/* Name or address with port, eg. www.example.com:80 */
85977fd0032Sreyk 		*end++ = '\0';
86077fd0032Sreyk 		port = end;
86177fd0032Sreyk 	} else {
86277fd0032Sreyk 		/* Name or address with default port, eg. www.example.com */
86377fd0032Sreyk 		port = NULL;
86477fd0032Sreyk 	}
86577fd0032Sreyk 
86677fd0032Sreyk 	if (port != NULL) {
86777fd0032Sreyk 		/* Save the requested port */
86877fd0032Sreyk 		*portval = strtonum(port, 0, 0xffff, &errstr);
86977fd0032Sreyk 		if (errstr != NULL) {
87077fd0032Sreyk 			log_debug("%s: invalid port: %s", __func__,
87177fd0032Sreyk 			    strerror(errno));
87277fd0032Sreyk 			return (NULL);
87377fd0032Sreyk 		}
87477fd0032Sreyk 		*portval = htons(*portval);
87577fd0032Sreyk 	} else {
87677fd0032Sreyk 		/* Port not given, indicate the default port */
87777fd0032Sreyk 		*portval = -1;
87877fd0032Sreyk 	}
87977fd0032Sreyk 
88077fd0032Sreyk 	return (start);
88177fd0032Sreyk }
88277fd0032Sreyk 
883b7b6a941Sreyk void
server_abort_http(struct client * clt,unsigned int code,const char * msg)8844703e0faSreyk server_abort_http(struct client *clt, unsigned int code, const char *msg)
885b7b6a941Sreyk {
886f5d55328Sflorian 	struct server_config	*srv_conf = clt->clt_srv_conf;
887b7b6a941Sreyk 	struct bufferevent	*bev = clt->clt_bev;
8888b4c340eSflorian 	struct http_descriptor	*desc = clt->clt_descreq;
8898b4c340eSflorian 	const char		*httperr = NULL, *style;
8908b4c340eSflorian 	char			*httpmsg, *body = NULL, *extraheader = NULL;
891f5d55328Sflorian 	char			 tmbuf[32], hbuf[128], *hstsheader = NULL;
892b82bc353Sflorian 	char			*clenheader = NULL;
893bf34a23eSreyk 	char			 buf[IBUF_READ_SIZE];
8940674f400Ssemarie 	char			*escapedmsg = NULL;
895cbced0bdSian 	char			 cstr[5];
896cbced0bdSian 	ssize_t			 bodylen;
897b7b6a941Sreyk 
898f8932becSreyk 	if (code == 0) {
899f8932becSreyk 		server_close(clt, "dropped");
900f8932becSreyk 		return;
901f8932becSreyk 	}
902f8932becSreyk 
903b7b6a941Sreyk 	if ((httperr = server_httperror_byid(code)) == NULL)
904b7b6a941Sreyk 		httperr = "Unknown Error";
905b7b6a941Sreyk 
906b7b6a941Sreyk 	if (bev == NULL)
907b7b6a941Sreyk 		goto done;
908b7b6a941Sreyk 
909e9b02f4aSreyk 	if (server_log_http(clt, code, 0) == -1)
910e9b02f4aSreyk 		goto done;
911e9b02f4aSreyk 
912b7b6a941Sreyk 	/* Some system information */
9139fb8351aSreyk 	if (print_host(&srv_conf->ss, hbuf, sizeof(hbuf)) == NULL)
914b7b6a941Sreyk 		goto done;
915b7b6a941Sreyk 
916be5ab2e6Schrisz 	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0)
917be5ab2e6Schrisz 		goto done;
918b7b6a941Sreyk 
919b7b6a941Sreyk 	/* Do not send details of the Internal Server Error */
9205d9e55e4Sreyk 	switch (code) {
9215d9e55e4Sreyk 	case 301:
9225d9e55e4Sreyk 	case 302:
923586dade4Sreyk 	case 303:
9240cc11a24Sbenno 	case 307:
9250cc11a24Sbenno 	case 308:
926586dade4Sreyk 		if (msg == NULL)
927586dade4Sreyk 			break;
928586dade4Sreyk 		memset(buf, 0, sizeof(buf));
929bf34a23eSreyk 		if (server_expand_http(clt, msg, buf, sizeof(buf)) == NULL)
930586dade4Sreyk 			goto done;
931bf34a23eSreyk 		if (asprintf(&extraheader, "Location: %s\r\n", buf) == -1) {
9325d9e55e4Sreyk 			code = 500;
9335d9e55e4Sreyk 			extraheader = NULL;
9345d9e55e4Sreyk 		}
935bf34a23eSreyk 		msg = buf;
9365d9e55e4Sreyk 		break;
937e286121aSflorian 	case 401:
93860acb58bSjsg 		if (msg == NULL)
93960acb58bSjsg 			break;
9400674f400Ssemarie 		if (stravis(&escapedmsg, msg, VIS_DQ) == -1) {
9410674f400Ssemarie 			code = 500;
9420674f400Ssemarie 			extraheader = NULL;
9430674f400Ssemarie 		} else if (asprintf(&extraheader,
9440674f400Ssemarie 		    "WWW-Authenticate: Basic realm=\"%s\"\r\n", escapedmsg)
9450674f400Ssemarie 		    == -1) {
946e286121aSflorian 			code = 500;
947e286121aSflorian 			extraheader = NULL;
948e286121aSflorian 		}
949e286121aSflorian 		break;
950b0faf28cSflorian 	case 416:
95160acb58bSjsg 		if (msg == NULL)
95260acb58bSjsg 			break;
953b0faf28cSflorian 		if (asprintf(&extraheader,
954b0faf28cSflorian 		    "Content-Range: %s\r\n", msg) == -1) {
955b0faf28cSflorian 			code = 500;
956b0faf28cSflorian 			extraheader = NULL;
957b0faf28cSflorian 		}
958b0faf28cSflorian 		break;
9595d9e55e4Sreyk 	default:
960cbebb5b9Sreyk 		/*
961cbebb5b9Sreyk 		 * Do not send details of the error.  Traditionally,
962cbebb5b9Sreyk 		 * web servers responsed with the request path on 40x
963cbebb5b9Sreyk 		 * errors which could be abused to inject JavaScript etc.
964cbebb5b9Sreyk 		 * Instead of sanitizing the path here, we just don't
965cbebb5b9Sreyk 		 * reprint it.
966cbebb5b9Sreyk 		 */
9675d9e55e4Sreyk 		break;
9685d9e55e4Sreyk 	}
969b7b6a941Sreyk 
9700674f400Ssemarie 	free(escapedmsg);
9710674f400Ssemarie 
972cbced0bdSian 	if ((srv_conf->flags & SRVFLAG_ERRDOCS) == 0)
973cbced0bdSian 		goto builtin; /* errdocs not enabled */
974cbced0bdSian 	if ((size_t)snprintf(cstr, sizeof(cstr), "%03u", code) >= sizeof(cstr))
975cbced0bdSian 		goto builtin;
976cbced0bdSian 
977cbced0bdSian 	if ((body = read_errdoc(srv_conf->errdocroot, cstr)) == NULL &&
978cbced0bdSian 	    (body = read_errdoc(srv_conf->errdocroot, HTTPD_ERRDOCTEMPLATE))
979cbced0bdSian 	    == NULL)
980cbced0bdSian 		goto builtin;
981cbced0bdSian 
982cbced0bdSian 	body = replace_var(body, "$HTTP_ERROR", httperr);
983cbced0bdSian 	body = replace_var(body, "$RESPONSE_CODE", cstr);
984cbced0bdSian 	body = replace_var(body, "$SERVER_SOFTWARE", HTTPD_SERVERNAME);
985cbced0bdSian 	bodylen = strlen(body);
986cbced0bdSian 	goto send;
987cbced0bdSian 
988cbced0bdSian  builtin:
989b7b6a941Sreyk 	/* A CSS stylesheet allows minimal customization by the user */
990f687442bSreyk 	style = "body { background-color: white; color: black; font-family: "
991cbebb5b9Sreyk 	    "'Comic Sans MS', 'Chalkboard SE', 'Comic Neue', sans-serif; }\n"
992aa3aed54Scwen 	    "hr { border: 0; border-bottom: 1px dashed; }\n"
993aa3aed54Scwen 	    "@media (prefers-color-scheme: dark) {\n"
994aa3aed54Scwen 	    "body { background-color: #1E1F21; color: #EEEFF1; }\n"
995aa3aed54Scwen 	    "a { color: #BAD7FF; }\n}";
9968b4c340eSflorian 
9978b4c340eSflorian 	/* Generate simple HTML error document */
9988b4c340eSflorian 	if ((bodylen = asprintf(&body,
999d89214cdSreyk 	    "<!DOCTYPE html>\n"
1000b7b6a941Sreyk 	    "<html>\n"
1001b7b6a941Sreyk 	    "<head>\n"
10028643c0edSbentley 	    "<meta charset=\"utf-8\">\n"
1003b7b6a941Sreyk 	    "<title>%03d %s</title>\n"
1004b7b6a941Sreyk 	    "<style type=\"text/css\"><!--\n%s\n--></style>\n"
1005b7b6a941Sreyk 	    "</head>\n"
1006b7b6a941Sreyk 	    "<body>\n"
1007cbebb5b9Sreyk 	    "<h1>%03d %s</h1>\n"
1008cbebb5b9Sreyk 	    "<hr>\n<address>%s</address>\n"
1009b7b6a941Sreyk 	    "</body>\n"
1010b7b6a941Sreyk 	    "</html>\n",
101195d80e7aSjung 	    code, httperr, style, code, httperr, HTTPD_SERVERNAME)) == -1) {
101295d80e7aSjung 		body = NULL;
10138b4c340eSflorian 		goto done;
101495d80e7aSjung 	}
10158b4c340eSflorian 
1016cbced0bdSian  send:
1017ae30f9e2Sbentley 	if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
1018ae30f9e2Sbentley 	    srv_conf->flags & SRVFLAG_TLS) {
1019f5d55328Sflorian 		if (asprintf(&hstsheader, "Strict-Transport-Security: "
102052f7cd50Sreyk 		    "max-age=%d%s%s\r\n", srv_conf->hsts_max_age,
102152f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
102252f7cd50Sreyk 		    "; includeSubDomains" : "",
102352f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
102495d80e7aSjung 		    "; preload" : "") == -1) {
102595d80e7aSjung 			hstsheader = NULL;
1026f5d55328Sflorian 			goto done;
1027f5d55328Sflorian 		}
102895d80e7aSjung 	}
1029f5d55328Sflorian 
1030b82bc353Sflorian 	if ((code >= 100 && code < 200) || code == 204)
1031b82bc353Sflorian 		clenheader = NULL;
1032b82bc353Sflorian 	else {
1033b82bc353Sflorian 		if (asprintf(&clenheader,
1034cbced0bdSian 		    "Content-Length: %zd\r\n", bodylen) == -1) {
1035b82bc353Sflorian 			clenheader = NULL;
1036b82bc353Sflorian 			goto done;
1037b82bc353Sflorian 		}
1038b82bc353Sflorian 	}
1039b82bc353Sflorian 
10408b4c340eSflorian 	/* Add basic HTTP headers */
10418b4c340eSflorian 	if (asprintf(&httpmsg,
10428b4c340eSflorian 	    "HTTP/1.0 %03d %s\r\n"
10438b4c340eSflorian 	    "Date: %s\r\n"
10448b4c340eSflorian 	    "Server: %s\r\n"
10458b4c340eSflorian 	    "Connection: close\r\n"
10468b4c340eSflorian 	    "Content-Type: text/html\r\n"
1047b82bc353Sflorian 	    "%s"
10488b4c340eSflorian 	    "%s"
1049f5d55328Sflorian 	    "%s"
10508b4c340eSflorian 	    "\r\n"
10518b4c340eSflorian 	    "%s",
1052b82bc353Sflorian 	    code, httperr, tmbuf, HTTPD_SERVERNAME,
1053b82bc353Sflorian 	    clenheader == NULL ? "" : clenheader,
10545d9e55e4Sreyk 	    extraheader == NULL ? "" : extraheader,
1055f5d55328Sflorian 	    hstsheader == NULL ? "" : hstsheader,
1056b82bc353Sflorian 	    desc->http_method == HTTP_METHOD_HEAD || clenheader == NULL ?
1057b82bc353Sflorian 	    "" : body) == -1)
1058b7b6a941Sreyk 		goto done;
1059b7b6a941Sreyk 
1060b7b6a941Sreyk 	/* Dump the message without checking for success */
1061b7b6a941Sreyk 	server_dump(clt, httpmsg, strlen(httpmsg));
1062b7b6a941Sreyk 	free(httpmsg);
1063b7b6a941Sreyk 
1064b7b6a941Sreyk  done:
10658b4c340eSflorian 	free(body);
10665d9e55e4Sreyk 	free(extraheader);
1067f5d55328Sflorian 	free(hstsheader);
1068b82bc353Sflorian 	free(clenheader);
1069586dade4Sreyk 	if (msg == NULL)
1070586dade4Sreyk 		msg = "\"\"";
1071df9b638bSreyk 	if (asprintf(&httpmsg, "%s (%03d %s)", msg, code, httperr) == -1) {
1072b7b6a941Sreyk 		server_close(clt, msg);
1073df9b638bSreyk 	} else {
1074b7b6a941Sreyk 		server_close(clt, httpmsg);
1075b7b6a941Sreyk 		free(httpmsg);
1076b7b6a941Sreyk 	}
1077b7b6a941Sreyk }
1078b7b6a941Sreyk 
1079b7b6a941Sreyk void
server_close_http(struct client * clt)1080b7b6a941Sreyk server_close_http(struct client *clt)
1081b7b6a941Sreyk {
1082d08e4976Sreyk 	struct http_descriptor *desc;
1083b7b6a941Sreyk 
1084d08e4976Sreyk 	desc = clt->clt_descreq;
1085b7b6a941Sreyk 	server_httpdesc_free(desc);
1086b7b6a941Sreyk 	free(desc);
1087d08e4976Sreyk 	clt->clt_descreq = NULL;
1088d08e4976Sreyk 
1089d08e4976Sreyk 	desc = clt->clt_descresp;
1090d08e4976Sreyk 	server_httpdesc_free(desc);
1091d08e4976Sreyk 	free(desc);
1092d08e4976Sreyk 	clt->clt_descresp = NULL;
1093daa1b608Sflorian 	free(clt->clt_remote_user);
1094daa1b608Sflorian 	clt->clt_remote_user = NULL;
109559355b5aSreyk 
109659355b5aSreyk 	str_match_free(&clt->clt_srv_match);
1097b7b6a941Sreyk }
1098b7b6a941Sreyk 
1099586dade4Sreyk char *
server_expand_http(struct client * clt,const char * val,char * buf,size_t len)1100586dade4Sreyk server_expand_http(struct client *clt, const char *val, char *buf,
1101586dade4Sreyk     size_t len)
1102586dade4Sreyk {
1103586dade4Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
1104586dade4Sreyk 	struct server_config	*srv_conf = clt->clt_srv_conf;
1105fabb812fStb 	char			 ibuf[128], *str, *path, *query;
110659355b5aSreyk 	const char		*errstr = NULL, *p;
110759355b5aSreyk 	size_t			 size;
110859355b5aSreyk 	int			 n, ret;
1109586dade4Sreyk 
1110586dade4Sreyk 	if (strlcpy(buf, val, len) >= len)
1111586dade4Sreyk 		return (NULL);
1112586dade4Sreyk 
111359355b5aSreyk 	/* Find previously matched substrings by index */
111459355b5aSreyk 	for (p = val; clt->clt_srv_match.sm_nmatch &&
111559355b5aSreyk 	    (p = strstr(p, "%")) != NULL; p++) {
111600f3986fSreyk 		if (!isdigit((unsigned char)*(p + 1)))
111759355b5aSreyk 			continue;
111859355b5aSreyk 
111959355b5aSreyk 		/* Copy number, leading '%' char and add trailing \0 */
112059355b5aSreyk 		size = strspn(p + 1, "0123456789") + 2;
112159355b5aSreyk 		if (size  >= sizeof(ibuf))
112259355b5aSreyk 			return (NULL);
112359355b5aSreyk 		(void)strlcpy(ibuf, p, size);
112459355b5aSreyk 		n = strtonum(ibuf + 1, 0,
112559355b5aSreyk 		    clt->clt_srv_match.sm_nmatch - 1, &errstr);
112659355b5aSreyk 		if (errstr != NULL)
112759355b5aSreyk 			return (NULL);
112859355b5aSreyk 
112959355b5aSreyk 		/* Expand variable with matched value */
11300788fdf8Ssemarie 		if ((str = url_encode(clt->clt_srv_match.sm_match[n])) == NULL)
11310788fdf8Ssemarie 			return (NULL);
11320788fdf8Ssemarie 		ret = expand_string(buf, len, ibuf, str);
11330788fdf8Ssemarie 		free(str);
11340788fdf8Ssemarie 		if (ret != 0)
113559355b5aSreyk 			return (NULL);
113659355b5aSreyk 	}
1137586dade4Sreyk 	if (strstr(val, "$DOCUMENT_URI") != NULL) {
1138bf34a23eSreyk 		if ((path = url_encode(desc->http_path)) == NULL)
1139bf34a23eSreyk 			return (NULL);
1140bf34a23eSreyk 		ret = expand_string(buf, len, "$DOCUMENT_URI", path);
1141bf34a23eSreyk 		free(path);
1142bf34a23eSreyk 		if (ret != 0)
1143586dade4Sreyk 			return (NULL);
1144586dade4Sreyk 	}
1145fabb812fStb 	if (strstr(val, "$QUERY_STRING_ENC") != NULL) {
1146fabb812fStb 		if (desc->http_query == NULL) {
1147fabb812fStb 			ret = expand_string(buf, len, "$QUERY_STRING_ENC", "");
1148fabb812fStb 		} else {
1149fabb812fStb 			if ((query = url_encode(desc->http_query)) == NULL)
1150fabb812fStb 				return (NULL);
1151fabb812fStb 			ret = expand_string(buf, len, "$QUERY_STRING_ENC", query);
1152fabb812fStb 			free(query);
1153fabb812fStb 		}
1154fabb812fStb 		if (ret != 0)
1155fabb812fStb 			return (NULL);
1156fabb812fStb 	}
1157586dade4Sreyk 	if (strstr(val, "$QUERY_STRING") != NULL) {
1158bf34a23eSreyk 		if (desc->http_query == NULL) {
1159bf34a23eSreyk 			ret = expand_string(buf, len, "$QUERY_STRING", "");
1160bf34a23eSreyk 		} else {
116158963e4fSreyk 			ret = expand_string(buf, len, "$QUERY_STRING",
116258963e4fSreyk 			    desc->http_query);
1163bf34a23eSreyk 		}
1164bf34a23eSreyk 		if (ret != 0)
1165586dade4Sreyk 			return (NULL);
1166586dade4Sreyk 	}
116711d703aaSflorian 	if (strstr(val, "$HTTP_HOST") != NULL) {
116811d703aaSflorian 		if (desc->http_host == NULL)
116911d703aaSflorian 			return (NULL);
117011d703aaSflorian 		if ((str = url_encode(desc->http_host)) == NULL)
117111d703aaSflorian 			return (NULL);
117211d703aaSflorian 		expand_string(buf, len, "$HTTP_HOST", str);
117311d703aaSflorian 		free(str);
117411d703aaSflorian 	}
1175586dade4Sreyk 	if (strstr(val, "$REMOTE_") != NULL) {
1176586dade4Sreyk 		if (strstr(val, "$REMOTE_ADDR") != NULL) {
1177586dade4Sreyk 			if (print_host(&clt->clt_ss,
1178586dade4Sreyk 			    ibuf, sizeof(ibuf)) == NULL)
1179586dade4Sreyk 				return (NULL);
1180586dade4Sreyk 			if (expand_string(buf, len,
1181586dade4Sreyk 			    "$REMOTE_ADDR", ibuf) != 0)
1182586dade4Sreyk 				return (NULL);
1183586dade4Sreyk 		}
1184586dade4Sreyk 		if (strstr(val, "$REMOTE_PORT") != NULL) {
1185586dade4Sreyk 			snprintf(ibuf, sizeof(ibuf),
1186586dade4Sreyk 			    "%u", ntohs(clt->clt_port));
1187586dade4Sreyk 			if (expand_string(buf, len,
1188586dade4Sreyk 			    "$REMOTE_PORT", ibuf) != 0)
1189586dade4Sreyk 				return (NULL);
1190586dade4Sreyk 		}
1191586dade4Sreyk 		if (strstr(val, "$REMOTE_USER") != NULL) {
1192586dade4Sreyk 			if ((srv_conf->flags & SRVFLAG_AUTH) &&
1193885c4aa1Sreyk 			    clt->clt_remote_user != NULL) {
1194885c4aa1Sreyk 				if ((str = url_encode(clt->clt_remote_user))
1195885c4aa1Sreyk 				    == NULL)
1196885c4aa1Sreyk 					return (NULL);
1197885c4aa1Sreyk 			} else
1198885c4aa1Sreyk 				str = strdup("");
1199885c4aa1Sreyk 			ret = expand_string(buf, len, "$REMOTE_USER", str);
1200885c4aa1Sreyk 			free(str);
1201885c4aa1Sreyk 			if (ret != 0)
1202586dade4Sreyk 				return (NULL);
1203586dade4Sreyk 		}
1204586dade4Sreyk 	}
1205586dade4Sreyk 	if (strstr(val, "$REQUEST_URI") != NULL) {
1206bf34a23eSreyk 		if ((path = url_encode(desc->http_path)) == NULL)
1207bf34a23eSreyk 			return (NULL);
1208586dade4Sreyk 		if (desc->http_query == NULL) {
1209bf34a23eSreyk 			str = path;
1210bf34a23eSreyk 		} else {
121158963e4fSreyk 			ret = asprintf(&str, "%s?%s", path, desc->http_query);
1212bf34a23eSreyk 			free(path);
1213bf34a23eSreyk 			if (ret == -1)
1214bf34a23eSreyk 				return (NULL);
1215bf34a23eSreyk 		}
1216bf34a23eSreyk 
1217bf34a23eSreyk 		ret = expand_string(buf, len, "$REQUEST_URI", str);
1218586dade4Sreyk 		free(str);
1219bf34a23eSreyk 		if (ret != 0)
1220bf34a23eSreyk 			return (NULL);
1221586dade4Sreyk 	}
1222603a52d7Ssthen 	if (strstr(val, "$REQUEST_SCHEME") != NULL) {
1223603a52d7Ssthen 		if (srv_conf->flags & SRVFLAG_TLS) {
1224603a52d7Ssthen 			ret = expand_string(buf, len, "$REQUEST_SCHEME", "https");
1225603a52d7Ssthen 		} else {
1226603a52d7Ssthen 			ret = expand_string(buf, len, "$REQUEST_SCHEME", "http");
1227603a52d7Ssthen 		}
1228603a52d7Ssthen 		if (ret != 0)
1229603a52d7Ssthen 			return (NULL);
1230603a52d7Ssthen 	}
1231586dade4Sreyk 	if (strstr(val, "$SERVER_") != NULL) {
1232586dade4Sreyk 		if (strstr(val, "$SERVER_ADDR") != NULL) {
1233586dade4Sreyk 			if (print_host(&srv_conf->ss,
1234586dade4Sreyk 			    ibuf, sizeof(ibuf)) == NULL)
1235586dade4Sreyk 				return (NULL);
1236586dade4Sreyk 			if (expand_string(buf, len,
1237586dade4Sreyk 			    "$SERVER_ADDR", ibuf) != 0)
1238586dade4Sreyk 				return (NULL);
1239586dade4Sreyk 		}
1240586dade4Sreyk 		if (strstr(val, "$SERVER_PORT") != NULL) {
1241586dade4Sreyk 			snprintf(ibuf, sizeof(ibuf), "%u",
1242586dade4Sreyk 			    ntohs(srv_conf->port));
1243586dade4Sreyk 			if (expand_string(buf, len,
1244586dade4Sreyk 			    "$SERVER_PORT", ibuf) != 0)
1245586dade4Sreyk 				return (NULL);
1246586dade4Sreyk 		}
1247586dade4Sreyk 		if (strstr(val, "$SERVER_NAME") != NULL) {
1248885c4aa1Sreyk 			if ((str = url_encode(srv_conf->name))
1249885c4aa1Sreyk 			     == NULL)
1250885c4aa1Sreyk 				return (NULL);
1251885c4aa1Sreyk 			ret = expand_string(buf, len, "$SERVER_NAME", str);
1252885c4aa1Sreyk 			free(str);
1253885c4aa1Sreyk 			if (ret != 0)
1254586dade4Sreyk 				return (NULL);
1255586dade4Sreyk 		}
1256586dade4Sreyk 	}
1257586dade4Sreyk 
1258586dade4Sreyk 	return (buf);
1259586dade4Sreyk }
1260586dade4Sreyk 
1261b7b6a941Sreyk int
server_response(struct httpd * httpd,struct client * clt)12625fa30660Sreyk server_response(struct httpd *httpd, struct client *clt)
12635fa30660Sreyk {
1264b9fc9a72Sderaadt 	char			 path[PATH_MAX];
1265b9fc9a72Sderaadt 	char			 hostname[HOST_NAME_MAX+1];
1266d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
1267d08e4976Sreyk 	struct http_descriptor	*resp = clt->clt_descresp;
1268d9bba0abSreyk 	struct server		*srv = clt->clt_srv;
1269de6550b1Sreyk 	struct server_config	*srv_conf = &srv->srv_conf;
12706af43371Sreyk 	struct kv		*kv, key, *host;
127159355b5aSreyk 	struct str_find		 sm;
127259355b5aSreyk 	int			 portval = -1, ret;
127393038d14Sreyk 	char			*hostval, *query;
127459355b5aSreyk 	const char		*errstr = NULL;
12755fa30660Sreyk 
127616a88448Syasuoka 	/* Preserve original path */
1277c9351fd6Sreyk 	if (desc->http_path == NULL ||
127816a88448Syasuoka 	    (desc->http_path_orig = strdup(desc->http_path)) == NULL)
127916a88448Syasuoka 		goto fail;
128016a88448Syasuoka 
128116a88448Syasuoka 	/* Decode the URL */
128216a88448Syasuoka 	if (url_decode(desc->http_path) == NULL)
128393038d14Sreyk 		goto fail;
128493038d14Sreyk 
128593038d14Sreyk 	/* Canonicalize the request path */
128693038d14Sreyk 	if (canonicalize_path(desc->http_path, path, sizeof(path)) == NULL)
1287c9351fd6Sreyk 		goto fail;
1288c9351fd6Sreyk 	free(desc->http_path);
1289c9351fd6Sreyk 	if ((desc->http_path = strdup(path)) == NULL)
12905fa30660Sreyk 		goto fail;
12915fa30660Sreyk 
12926af43371Sreyk 	key.kv_key = "Host";
12936af43371Sreyk 	if ((host = kv_find(&desc->http_headers, &key)) != NULL &&
12946af43371Sreyk 	    host->kv_value == NULL)
12956af43371Sreyk 		host = NULL;
12966af43371Sreyk 
12975fa30660Sreyk 	if (strcmp(desc->http_version, "HTTP/1.1") == 0) {
12985fa30660Sreyk 		/* Host header is mandatory */
12996af43371Sreyk 		if (host == NULL)
13005fa30660Sreyk 			goto fail;
13015fa30660Sreyk 
13025fa30660Sreyk 		/* Is the connection persistent? */
1303091144dbSreyk 		key.kv_key = "Connection";
13045fa30660Sreyk 		if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
13055fa30660Sreyk 		    strcasecmp("close", kv->kv_value) == 0)
13065fa30660Sreyk 			clt->clt_persist = 0;
13075fa30660Sreyk 		else
1308091144dbSreyk 			clt->clt_persist++;
13095fa30660Sreyk 	} else {
1310091144dbSreyk 		/* Is the connection persistent? */
1311091144dbSreyk 		key.kv_key = "Connection";
1312091144dbSreyk 		if ((kv = kv_find(&desc->http_headers, &key)) != NULL &&
1313091144dbSreyk 		    strcasecmp("keep-alive", kv->kv_value) == 0)
1314091144dbSreyk 			clt->clt_persist++;
1315091144dbSreyk 		else
13165fa30660Sreyk 			clt->clt_persist = 0;
13175fa30660Sreyk 	}
13185fa30660Sreyk 
1319d9bba0abSreyk 	/*
1320d9bba0abSreyk 	 * Do we have a Host header and matching configuration?
1321d9bba0abSreyk 	 * XXX the Host can also appear in the URL path.
1322d9bba0abSreyk 	 */
13236af43371Sreyk 	if (host != NULL) {
132477fd0032Sreyk 		if ((hostval = server_http_parsehost(host->kv_value,
132577fd0032Sreyk 		    hostname, sizeof(hostname), &portval)) == NULL)
132677fd0032Sreyk 			goto fail;
132777fd0032Sreyk 
1328d9bba0abSreyk 		TAILQ_FOREACH(srv_conf, &srv->srv_hosts, entry) {
1329bd1bab2fSreyk #ifdef DEBUG
1330bd1bab2fSreyk 			if ((srv_conf->flags & SRVFLAG_LOCATION) == 0) {
133177fd0032Sreyk 				DPRINTF("%s: virtual host \"%s:%u\""
133277fd0032Sreyk 				    " host \"%s\" (\"%s\")",
133377fd0032Sreyk 				    __func__, srv_conf->name,
133477fd0032Sreyk 				    ntohs(srv_conf->port), host->kv_value,
133577fd0032Sreyk 				    hostname);
1336bd1bab2fSreyk 			}
1337bd1bab2fSreyk #endif
133859355b5aSreyk 			if (srv_conf->flags & SRVFLAG_LOCATION)
133959355b5aSreyk 				continue;
134059355b5aSreyk 			else if (srv_conf->flags & SRVFLAG_SERVER_MATCH) {
134159355b5aSreyk 				str_find(hostname, srv_conf->name,
134259355b5aSreyk 				    &sm, 1, &errstr);
134359355b5aSreyk 				ret = errstr == NULL ? 0 : -1;
134459355b5aSreyk 			} else {
134559355b5aSreyk 				ret = fnmatch(srv_conf->name,
134659355b5aSreyk 				    hostname, FNM_CASEFOLD);
134759355b5aSreyk 			}
134859355b5aSreyk 			if (ret == 0 &&
1349b19a8c1eSbenno 			    (portval == -1 || portval == srv_conf->port)) {
1350d9bba0abSreyk 				/* Replace host configuration */
1351d9bba0abSreyk 				clt->clt_srv_conf = srv_conf;
13526af43371Sreyk 				srv_conf = NULL;
1353d9bba0abSreyk 				break;
1354d9bba0abSreyk 			}
1355d9bba0abSreyk 		}
1356d9bba0abSreyk 	}
1357d9bba0abSreyk 
13586af43371Sreyk 	if (srv_conf != NULL) {
13596af43371Sreyk 		/* Use the actual server IP address */
136047dc2a9dSreyk 		if (server_http_host(&clt->clt_srv_ss, hostname,
136147dc2a9dSreyk 		    sizeof(hostname)) == NULL)
13626af43371Sreyk 			goto fail;
13636af43371Sreyk 	} else {
13646af43371Sreyk 		/* Host header was valid and found */
136547dc2a9dSreyk 		if (strlcpy(hostname, host->kv_value, sizeof(hostname)) >=
136647dc2a9dSreyk 		    sizeof(hostname))
13676af43371Sreyk 			goto fail;
13684f194ec8Sreyk 		srv_conf = clt->clt_srv_conf;
13694f194ec8Sreyk 	}
13704f194ec8Sreyk 
1371ca8e2b3eSbenno 	if (clt->clt_persist >= srv_conf->maxrequests)
1372ca8e2b3eSbenno 		clt->clt_persist = 0;
1373ca8e2b3eSbenno 
1374ca8e2b3eSbenno 	/* pipelining should end after the first "idempotent" method */
1375ca8e2b3eSbenno 	if (clt->clt_pipelining && clt->clt_toread > 0)
1376ca8e2b3eSbenno 		clt->clt_persist = 0;
1377ca8e2b3eSbenno 
137847dc2a9dSreyk 	if ((desc->http_host = strdup(hostname)) == NULL)
137947dc2a9dSreyk 		goto fail;
138047dc2a9dSreyk 
1381d08e4976Sreyk 	/* Now fill in the mandatory parts of the response descriptor */
1382d08e4976Sreyk 	resp->http_method = desc->http_method;
1383d08e4976Sreyk 	if ((resp->http_version = strdup(desc->http_version)) == NULL)
1384d08e4976Sreyk 		goto fail;
1385d08e4976Sreyk 
13864f194ec8Sreyk 	/* Now search for the location */
13878b6f8711Stb 	if ((srv_conf = server_getlocation(clt, desc->http_path)) == NULL) {
1388e96b74b9Sdenis 		server_abort_http(clt, 500, desc->http_path);
1389e96b74b9Sdenis 		return (-1);
1390e96b74b9Sdenis 	}
1391de6550b1Sreyk 
139293038d14Sreyk 	/* Optional rewrite */
139393038d14Sreyk 	if (srv_conf->flags & SRVFLAG_PATH_REWRITE) {
139493038d14Sreyk 		/* Expand macros */
139593038d14Sreyk 		if (server_expand_http(clt, srv_conf->path,
139693038d14Sreyk 		    path, sizeof(path)) == NULL)
139793038d14Sreyk 			goto fail;
139893038d14Sreyk 
139993038d14Sreyk 		/*
140093038d14Sreyk 		 * Reset and update the query.  The updated query must already
140193038d14Sreyk 		 * be URL encoded - either specified by the user or by using the
140293038d14Sreyk 		 * original $QUERY_STRING.
140393038d14Sreyk 		 */
1404a23b6848Stb 		free(desc->http_query_alias);
1405a23b6848Stb 		desc->http_query_alias = NULL;
140693038d14Sreyk 		if ((query = strchr(path, '?')) != NULL) {
140793038d14Sreyk 			*query++ = '\0';
1408a23b6848Stb 			if ((desc->http_query_alias = strdup(query)) == NULL)
140993038d14Sreyk 				goto fail;
141093038d14Sreyk 		}
141193038d14Sreyk 
141293038d14Sreyk 		/* Canonicalize the updated request path */
141393038d14Sreyk 		if (canonicalize_path(path,
141493038d14Sreyk 		    path, sizeof(path)) == NULL)
141593038d14Sreyk 			goto fail;
141693038d14Sreyk 
1417a23b6848Stb 		log_debug("%s: rewrote %s?%s -> %s?%s", __func__,
14182c98785eSflorian 		    desc->http_path, desc->http_query ? desc->http_query : "",
14192c98785eSflorian 		    path, query ? query : "");
142093038d14Sreyk 
1421a23b6848Stb 		free(desc->http_path_alias);
1422a23b6848Stb 		if ((desc->http_path_alias = strdup(path)) == NULL)
142393038d14Sreyk 			goto fail;
142493038d14Sreyk 
142593038d14Sreyk 		/* Now search for the updated location */
1426e96b74b9Sdenis 		if ((srv_conf = server_getlocation(clt,
1427e96b74b9Sdenis 		    desc->http_path_alias)) == NULL) {
1428e96b74b9Sdenis 			server_abort_http(clt, 500, desc->http_path_alias);
1429e96b74b9Sdenis 			return (-1);
1430e96b74b9Sdenis 		}
143193038d14Sreyk 	}
143293038d14Sreyk 
1433f0a27d98Sflorian 	if (clt->clt_toread > 0 && (size_t)clt->clt_toread >
1434f0a27d98Sflorian 	    srv_conf->maxrequestbody) {
1435fa3025f4Sbenno 		server_abort_http(clt, 413, "request body too large");
1436f0a27d98Sflorian 		return (-1);
1437f0a27d98Sflorian 	}
1438f0a27d98Sflorian 
1439f8932becSreyk 	if (srv_conf->flags & SRVFLAG_BLOCK) {
1440f8932becSreyk 		server_abort_http(clt, srv_conf->return_code,
1441f8932becSreyk 		    srv_conf->return_uri);
1442f8932becSreyk 		return (-1);
1443f8932becSreyk 	} else if (srv_conf->flags & SRVFLAG_AUTH &&
1444e286121aSflorian 	    server_http_authenticate(srv_conf, clt) == -1) {
1445e286121aSflorian 		server_abort_http(clt, 401, srv_conf->auth_realm);
1446e286121aSflorian 		return (-1);
1447e286121aSflorian 	} else
1448de6550b1Sreyk 		return (server_file(httpd, clt));
1449de6550b1Sreyk  fail:
1450de6550b1Sreyk 	server_abort_http(clt, 400, "bad request");
1451de6550b1Sreyk 	return (-1);
1452de6550b1Sreyk }
1453de6550b1Sreyk 
14544624b10aSchrisz const char *
server_root_strip(const char * path,int n)14554624b10aSchrisz server_root_strip(const char *path, int n)
14564624b10aSchrisz {
14574624b10aSchrisz 	const char *p;
14584624b10aSchrisz 
14594624b10aSchrisz 	/* Strip strip leading directories. Leading '/' is ignored. */
14604624b10aSchrisz 	for (; n > 0 && *path != '\0'; n--)
14614624b10aSchrisz 		if ((p = strchr(++path, '/')) == NULL)
14624624b10aSchrisz 			path = strchr(path, '\0');
14634624b10aSchrisz 		else
14644624b10aSchrisz 			path = p;
14654624b10aSchrisz 
14664624b10aSchrisz 	return (path);
14674624b10aSchrisz }
14684624b10aSchrisz 
1469de6550b1Sreyk struct server_config *
server_getlocation(struct client * clt,const char * path)1470de6550b1Sreyk server_getlocation(struct client *clt, const char *path)
1471de6550b1Sreyk {
1472de6550b1Sreyk 	struct server		*srv = clt->clt_srv;
1473de6550b1Sreyk 	struct server_config	*srv_conf = clt->clt_srv_conf, *location;
147459355b5aSreyk 	const char		*errstr = NULL;
147559355b5aSreyk 	int			 ret;
1476de6550b1Sreyk 
1477de6550b1Sreyk 	/* Now search for the location */
14784f194ec8Sreyk 	TAILQ_FOREACH(location, &srv->srv_hosts, entry) {
1479bd1bab2fSreyk #ifdef DEBUG
1480bd1bab2fSreyk 		if (location->flags & SRVFLAG_LOCATION) {
1481bd1bab2fSreyk 			DPRINTF("%s: location \"%s\" path \"%s\"",
1482bd1bab2fSreyk 			    __func__, location->location, path);
1483bd1bab2fSreyk 		}
1484bd1bab2fSreyk #endif
14854f194ec8Sreyk 		if ((location->flags & SRVFLAG_LOCATION) &&
148659355b5aSreyk 		    location->parent_id == srv_conf->parent_id) {
148759355b5aSreyk 			errstr = NULL;
148859355b5aSreyk 			if (location->flags & SRVFLAG_LOCATION_MATCH) {
148959355b5aSreyk 				ret = str_match(path, location->location,
149059355b5aSreyk 				    &clt->clt_srv_match, &errstr);
149159355b5aSreyk 			} else {
149259355b5aSreyk 				ret = fnmatch(location->location,
149359355b5aSreyk 				    path, FNM_CASEFOLD);
149459355b5aSreyk 			}
149559355b5aSreyk 			if (ret == 0 && errstr == NULL) {
1496e96b74b9Sdenis 				if ((ret = server_locationaccesstest(location,
1497e96b74b9Sdenis 				    path)) == -1)
1498e96b74b9Sdenis 					return (NULL);
1499e96b74b9Sdenis 
1500e96b74b9Sdenis 				if (ret)
1501e96b74b9Sdenis 					continue;
15024f194ec8Sreyk 				/* Replace host configuration */
1503435dc7cdSreyk 				clt->clt_srv_conf = srv_conf = location;
15044f194ec8Sreyk 				break;
15054f194ec8Sreyk 			}
15066af43371Sreyk 		}
150759355b5aSreyk 	}
15086af43371Sreyk 
1509de6550b1Sreyk 	return (srv_conf);
15105fa30660Sreyk }
15115fa30660Sreyk 
15125fa30660Sreyk int
server_locationaccesstest(struct server_config * srv_conf,const char * path)1513e96b74b9Sdenis server_locationaccesstest(struct server_config *srv_conf, const char *path)
1514e96b74b9Sdenis {
1515e96b74b9Sdenis 	int		 rootfd, ret;
1516e96b74b9Sdenis 	struct stat	 sb;
1517e96b74b9Sdenis 
1518e96b74b9Sdenis 	if (((SRVFLAG_LOCATION_FOUND | SRVFLAG_LOCATION_NOT_FOUND) &
1519e96b74b9Sdenis 	    srv_conf->flags) == 0)
1520e96b74b9Sdenis 		return (0);
1521e96b74b9Sdenis 
1522e96b74b9Sdenis 	if ((rootfd = open(srv_conf->root, O_RDONLY)) == -1)
1523e96b74b9Sdenis 		return (-1);
1524e96b74b9Sdenis 
1525e96b74b9Sdenis 	path = server_root_strip(path, srv_conf->strip) + 1;
1526e96b74b9Sdenis 	if ((ret = faccessat(rootfd, path, R_OK, 0)) != -1)
1527e96b74b9Sdenis 		ret = fstatat(rootfd, path, &sb, 0);
1528e96b74b9Sdenis 	close(rootfd);
1529e96b74b9Sdenis 	return ((ret == -1 && SRVFLAG_LOCATION_FOUND & srv_conf->flags) ||
1530e96b74b9Sdenis 	    (ret == 0 && SRVFLAG_LOCATION_NOT_FOUND & srv_conf->flags));
1531e96b74b9Sdenis }
1532e96b74b9Sdenis 
1533e96b74b9Sdenis int
server_response_http(struct client * clt,unsigned int code,struct media_type * media,off_t size,time_t mtime)15344703e0faSreyk server_response_http(struct client *clt, unsigned int code,
1535d1cfc522Skettenis     struct media_type *media, off_t size, time_t mtime)
15365fa30660Sreyk {
1537f5d55328Sflorian 	struct server_config	*srv_conf = clt->clt_srv_conf;
1538d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descreq;
1539d08e4976Sreyk 	struct http_descriptor	*resp = clt->clt_descresp;
15405fa30660Sreyk 	const char		*error;
15415fa30660Sreyk 	struct kv		*ct, *cl;
15429f126950Sreyk 	char			 tmbuf[32];
15435fa30660Sreyk 
1544d24f6b1eSreyk 	if (desc == NULL || media == NULL ||
1545d24f6b1eSreyk 	    (error = server_httperror_byid(code)) == NULL)
15465fa30660Sreyk 		return (-1);
15475fa30660Sreyk 
1548cc526105Sreyk 	if (server_log_http(clt, code, size >= 0 ? size : 0) == -1)
1549ea62a379Sdoug 		return (-1);
1550ea62a379Sdoug 
15515fa30660Sreyk 	/* Add error codes */
15523323ac76Sbenno 	if (kv_setkey(&resp->http_pathquery, "%u", code) == -1 ||
1553d08e4976Sreyk 	    kv_set(&resp->http_pathquery, "%s", error) == -1)
15545fa30660Sreyk 		return (-1);
15555fa30660Sreyk 
15565fa30660Sreyk 	/* Add headers */
1557d08e4976Sreyk 	if (kv_add(&resp->http_headers, "Server", HTTPD_SERVERNAME) == NULL)
15585fa30660Sreyk 		return (-1);
15595fa30660Sreyk 
15605fa30660Sreyk 	/* Is it a persistent connection? */
15615fa30660Sreyk 	if (clt->clt_persist) {
1562d08e4976Sreyk 		if (kv_add(&resp->http_headers,
15635fa30660Sreyk 		    "Connection", "keep-alive") == NULL)
15645fa30660Sreyk 			return (-1);
1565d08e4976Sreyk 	} else if (kv_add(&resp->http_headers, "Connection", "close") == NULL)
15665fa30660Sreyk 		return (-1);
15675fa30660Sreyk 
15685fa30660Sreyk 	/* Set media type */
1569d08e4976Sreyk 	if ((ct = kv_add(&resp->http_headers, "Content-Type", NULL)) == NULL ||
1570d24f6b1eSreyk 	    kv_set(ct, "%s/%s", media->media_type, media->media_subtype) == -1)
15715fa30660Sreyk 		return (-1);
15725fa30660Sreyk 
15735fa30660Sreyk 	/* Set content length, if specified */
1574cc526105Sreyk 	if (size >= 0 && ((cl =
1575d08e4976Sreyk 	    kv_add(&resp->http_headers, "Content-Length", NULL)) == NULL ||
1576cc526105Sreyk 	    kv_set(cl, "%lld", (long long)size) == -1))
15775fa30660Sreyk 		return (-1);
15785fa30660Sreyk 
1579be5ab2e6Schrisz 	/* Set last modification time */
1580be5ab2e6Schrisz 	if (server_http_time(mtime, tmbuf, sizeof(tmbuf)) <= 0 ||
1581d08e4976Sreyk 	    kv_add(&resp->http_headers, "Last-Modified", tmbuf) == NULL)
1582be5ab2e6Schrisz 		return (-1);
1583be5ab2e6Schrisz 
1584f5d55328Sflorian 	/* HSTS header */
1585ae30f9e2Sbentley 	if (srv_conf->flags & SRVFLAG_SERVER_HSTS &&
1586ae30f9e2Sbentley 	    srv_conf->flags & SRVFLAG_TLS) {
1587f5d55328Sflorian 		if ((cl =
1588f5d55328Sflorian 		    kv_add(&resp->http_headers, "Strict-Transport-Security",
1589f5d55328Sflorian 		    NULL)) == NULL ||
15903323ac76Sbenno 		    kv_set(cl, "max-age=%d%s%s", srv_conf->hsts_max_age,
159152f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_SUBDOMAINS ?
159252f7cd50Sreyk 		    "; includeSubDomains" : "",
159352f7cd50Sreyk 		    srv_conf->hsts_flags & HSTSFLAG_PRELOAD ?
159452f7cd50Sreyk 		    "; preload" : "") == -1)
1595f5d55328Sflorian 			return (-1);
1596f5d55328Sflorian 	}
1597f5d55328Sflorian 
1598be5ab2e6Schrisz 	/* Date header is mandatory and should be added as late as possible */
1599be5ab2e6Schrisz 	if (server_http_time(time(NULL), tmbuf, sizeof(tmbuf)) <= 0 ||
1600d08e4976Sreyk 	    kv_add(&resp->http_headers, "Date", tmbuf) == NULL)
16019f126950Sreyk 		return (-1);
16029f126950Sreyk 
16035fa30660Sreyk 	/* Write completed header */
16045fa30660Sreyk 	if (server_writeresponse_http(clt) == -1 ||
16055fa30660Sreyk 	    server_bufferevent_print(clt, "\r\n") == -1 ||
1606d08e4976Sreyk 	    server_headers(clt, resp, server_writeheader_http, NULL) == -1 ||
16075fa30660Sreyk 	    server_bufferevent_print(clt, "\r\n") == -1)
16085fa30660Sreyk 		return (-1);
16095fa30660Sreyk 
1610cc526105Sreyk 	if (size <= 0 || resp->http_method == HTTP_METHOD_HEAD) {
16115fa30660Sreyk 		bufferevent_enable(clt->clt_bev, EV_READ|EV_WRITE);
16125fa30660Sreyk 		if (clt->clt_persist)
16135fa30660Sreyk 			clt->clt_toread = TOREAD_HTTP_HEADER;
16145fa30660Sreyk 		else
16155fa30660Sreyk 			clt->clt_toread = TOREAD_HTTP_NONE;
16165fa30660Sreyk 		clt->clt_done = 0;
16175fa30660Sreyk 		return (0);
16185fa30660Sreyk 	}
16195fa30660Sreyk 
16205fa30660Sreyk 	return (1);
16215fa30660Sreyk }
16225fa30660Sreyk 
16235fa30660Sreyk int
server_writeresponse_http(struct client * clt)1624b7b6a941Sreyk server_writeresponse_http(struct client *clt)
1625b7b6a941Sreyk {
1626d08e4976Sreyk 	struct http_descriptor	*desc = clt->clt_descresp;
1627b7b6a941Sreyk 
1628b7b6a941Sreyk 	DPRINTF("version: %s rescode: %s resmsg: %s", desc->http_version,
1629b7b6a941Sreyk 	    desc->http_rescode, desc->http_resmesg);
1630b7b6a941Sreyk 
1631b7b6a941Sreyk 	if (server_bufferevent_print(clt, desc->http_version) == -1 ||
1632b7b6a941Sreyk 	    server_bufferevent_print(clt, " ") == -1 ||
1633b7b6a941Sreyk 	    server_bufferevent_print(clt, desc->http_rescode) == -1 ||
1634b7b6a941Sreyk 	    server_bufferevent_print(clt, " ") == -1 ||
1635b7b6a941Sreyk 	    server_bufferevent_print(clt, desc->http_resmesg) == -1)
1636b7b6a941Sreyk 		return (-1);
1637b7b6a941Sreyk 
1638b7b6a941Sreyk 	return (0);
1639b7b6a941Sreyk }
1640b7b6a941Sreyk 
1641b7b6a941Sreyk int
server_writeheader_http(struct client * clt,struct kv * hdr,void * arg)16424aa750c1Sreyk server_writeheader_http(struct client *clt, struct kv *hdr, void *arg)
1643b7b6a941Sreyk {
1644b7b6a941Sreyk 	char			*ptr;
1645b7b6a941Sreyk 	const char		*key;
1646b7b6a941Sreyk 
1647b7b6a941Sreyk 	/* The key might have been updated in the parent */
1648b7b6a941Sreyk 	if (hdr->kv_parent != NULL && hdr->kv_parent->kv_key != NULL)
1649b7b6a941Sreyk 		key = hdr->kv_parent->kv_key;
1650b7b6a941Sreyk 	else
1651b7b6a941Sreyk 		key = hdr->kv_key;
1652b7b6a941Sreyk 
1653b7b6a941Sreyk 	ptr = hdr->kv_value;
1654b7b6a941Sreyk 	if (server_bufferevent_print(clt, key) == -1 ||
1655b7b6a941Sreyk 	    (ptr != NULL &&
1656b7b6a941Sreyk 	    (server_bufferevent_print(clt, ": ") == -1 ||
1657b7b6a941Sreyk 	    server_bufferevent_print(clt, ptr) == -1 ||
1658b7b6a941Sreyk 	    server_bufferevent_print(clt, "\r\n") == -1)))
1659b7b6a941Sreyk 		return (-1);
1660b7b6a941Sreyk 	DPRINTF("%s: %s: %s", __func__, key,
1661b7b6a941Sreyk 	    hdr->kv_value == NULL ? "" : hdr->kv_value);
1662b7b6a941Sreyk 
1663b7b6a941Sreyk 	return (0);
1664b7b6a941Sreyk }
1665b7b6a941Sreyk 
1666b7b6a941Sreyk int
server_headers(struct client * clt,void * descp,int (* hdr_cb)(struct client *,struct kv *,void *),void * arg)1667d08e4976Sreyk server_headers(struct client *clt, void *descp,
16684aa750c1Sreyk     int (*hdr_cb)(struct client *, struct kv *, void *), void *arg)
1669b7b6a941Sreyk {
1670b7b6a941Sreyk 	struct kv		*hdr, *kv;
1671d08e4976Sreyk 	struct http_descriptor	*desc = descp;
1672b7b6a941Sreyk 
1673b7b6a941Sreyk 	RB_FOREACH(hdr, kvtree, &desc->http_headers) {
16744aa750c1Sreyk 		if ((hdr_cb)(clt, hdr, arg) == -1)
1675b7b6a941Sreyk 			return (-1);
1676b7b6a941Sreyk 		TAILQ_FOREACH(kv, &hdr->kv_children, kv_entry) {
16774aa750c1Sreyk 			if ((hdr_cb)(clt, kv, arg) == -1)
1678b7b6a941Sreyk 				return (-1);
1679b7b6a941Sreyk 		}
1680b7b6a941Sreyk 	}
1681b7b6a941Sreyk 
1682b7b6a941Sreyk 	return (0);
1683b7b6a941Sreyk }
1684b7b6a941Sreyk 
1685b7b6a941Sreyk enum httpmethod
server_httpmethod_byname(const char * name)1686b7b6a941Sreyk server_httpmethod_byname(const char *name)
1687b7b6a941Sreyk {
1688b7b6a941Sreyk 	enum httpmethod		 id = HTTP_METHOD_NONE;
1689b7b6a941Sreyk 	struct http_method	 method, *res = NULL;
1690b7b6a941Sreyk 
1691b7b6a941Sreyk 	/* Set up key */
1692b7b6a941Sreyk 	method.method_name = name;
1693b7b6a941Sreyk 
1694b7b6a941Sreyk 	if ((res = bsearch(&method, http_methods,
1695b7b6a941Sreyk 	    sizeof(http_methods) / sizeof(http_methods[0]) - 1,
1696b7b6a941Sreyk 	    sizeof(http_methods[0]), server_httpmethod_cmp)) != NULL)
1697b7b6a941Sreyk 		id = res->method_id;
1698b7b6a941Sreyk 
1699b7b6a941Sreyk 	return (id);
1700b7b6a941Sreyk }
1701b7b6a941Sreyk 
1702b7b6a941Sreyk const char *
server_httpmethod_byid(unsigned int id)17034703e0faSreyk server_httpmethod_byid(unsigned int id)
1704b7b6a941Sreyk {
1705f0c872b4Sreyk 	const char	*name = "<UNKNOWN>";
1706b7b6a941Sreyk 	int		 i;
1707b7b6a941Sreyk 
1708b7b6a941Sreyk 	for (i = 0; http_methods[i].method_name != NULL; i++) {
1709b7b6a941Sreyk 		if (http_methods[i].method_id == id) {
1710b7b6a941Sreyk 			name = http_methods[i].method_name;
1711b7b6a941Sreyk 			break;
1712b7b6a941Sreyk 		}
1713b7b6a941Sreyk 	}
1714b7b6a941Sreyk 
1715b7b6a941Sreyk 	return (name);
1716b7b6a941Sreyk }
1717b7b6a941Sreyk 
1718b7b6a941Sreyk static int
server_httpmethod_cmp(const void * a,const void * b)1719b7b6a941Sreyk server_httpmethod_cmp(const void *a, const void *b)
1720b7b6a941Sreyk {
1721b7b6a941Sreyk 	const struct http_method *ma = a;
1722b7b6a941Sreyk 	const struct http_method *mb = b;
17231d89351eSstsp 
17241d89351eSstsp 	/*
17251d89351eSstsp 	 * RFC 2616 section 5.1.1 says that the method is case
17261d89351eSstsp 	 * sensitive so we don't do a strcasecmp here.
17271d89351eSstsp 	 */
1728b7b6a941Sreyk 	return (strcmp(ma->method_name, mb->method_name));
1729b7b6a941Sreyk }
1730b7b6a941Sreyk 
1731b7b6a941Sreyk const char *
server_httperror_byid(unsigned int id)17324703e0faSreyk server_httperror_byid(unsigned int id)
1733b7b6a941Sreyk {
17341a67c375Sreyk 	struct http_error	 error, *res;
1735b7b6a941Sreyk 
1736b7b6a941Sreyk 	/* Set up key */
1737b7b6a941Sreyk 	error.error_code = (int)id;
1738b7b6a941Sreyk 
17391a67c375Sreyk 	if ((res = bsearch(&error, http_errors,
1740b7b6a941Sreyk 	    sizeof(http_errors) / sizeof(http_errors[0]) - 1,
17411a67c375Sreyk 	    sizeof(http_errors[0]), server_httperror_cmp)) != NULL)
1742b7b6a941Sreyk 		return (res->error_name);
17431a67c375Sreyk 
17441a67c375Sreyk 	return (NULL);
1745b7b6a941Sreyk }
1746b7b6a941Sreyk 
1747b7b6a941Sreyk static int
server_httperror_cmp(const void * a,const void * b)1748b7b6a941Sreyk server_httperror_cmp(const void *a, const void *b)
1749b7b6a941Sreyk {
1750b7b6a941Sreyk 	const struct http_error *ea = a;
1751b7b6a941Sreyk 	const struct http_error *eb = b;
1752b7b6a941Sreyk 	return (ea->error_code - eb->error_code);
1753b7b6a941Sreyk }
1754ea62a379Sdoug 
1755cbced0bdSian /*
1756cbced0bdSian  * return -1 on failure, strlen() of read file otherwise.
1757cbced0bdSian  * body is NULL on failure, contents of file with trailing \0 otherwise.
1758cbced0bdSian  */
1759cbced0bdSian char *
read_errdoc(const char * root,const char * file)1760cbced0bdSian read_errdoc(const char *root, const char *file)
1761cbced0bdSian {
1762cbced0bdSian 	struct stat	 sb;
1763cbced0bdSian 	char		*path;
1764cbced0bdSian 	int		 fd;
1765*b32af659Sclaudio 	char		*ret;
1766cbced0bdSian 
1767cbced0bdSian 	if (asprintf(&path, "%s/%s.html", root, file) == -1)
1768cbced0bdSian 		fatal("asprintf");
1769cbced0bdSian 	if ((fd = open(path, O_RDONLY)) == -1) {
1770cbced0bdSian 		free(path);
1771*b32af659Sclaudio 		if (errno != ENOENT)
1772cbced0bdSian 			log_warn("%s: open", __func__);
1773cbced0bdSian 		return (NULL);
1774cbced0bdSian 	}
1775cbced0bdSian 	free(path);
1776cbced0bdSian 	if (fstat(fd, &sb) < 0) {
1777cbced0bdSian 		log_warn("%s: stat", __func__);
1778dabb2923Sop 		close(fd);
1779cbced0bdSian 		return (NULL);
1780cbced0bdSian 	}
1781cbced0bdSian 
1782cbced0bdSian 	if ((ret = calloc(1, sb.st_size + 1)) == NULL)
1783cbced0bdSian 		fatal("calloc");
1784dabb2923Sop 	if (sb.st_size == 0) {
1785dabb2923Sop 		close(fd);
1786cbced0bdSian 		return (ret);
1787dabb2923Sop 	}
1788cbced0bdSian 	if (read(fd, ret, sb.st_size) != sb.st_size) {
1789cbced0bdSian 		log_warn("%s: read", __func__);
1790cbced0bdSian 		close(fd);
1791cbced0bdSian 		free(ret);
1792*b32af659Sclaudio 		return (NULL);
1793cbced0bdSian 	}
1794cbced0bdSian 	close(fd);
1795cbced0bdSian 
1796cbced0bdSian 	return (ret);
1797cbced0bdSian }
1798cbced0bdSian 
1799cbced0bdSian char *
replace_var(char * str,const char * var,const char * repl)1800cbced0bdSian replace_var(char *str, const char *var, const char *repl)
1801cbced0bdSian {
1802cbced0bdSian 	char	*iv, *r;
1803cbced0bdSian 	size_t	 vlen;
1804cbced0bdSian 
1805cbced0bdSian 	vlen = strlen(var);
1806cbced0bdSian 	while ((iv = strstr(str, var)) != NULL) {
1807cbced0bdSian 		*iv = '\0';
1808cbced0bdSian 		if (asprintf(&r, "%s%s%s", str, repl, &iv[vlen]) == -1)
1809cbced0bdSian 			fatal("asprintf");
1810cbced0bdSian 		free(str);
1811cbced0bdSian 		str = r;
1812cbced0bdSian 	}
1813cbced0bdSian 	return (str);
1814cbced0bdSian }
1815cbced0bdSian 
1816ea62a379Sdoug int
server_log_http(struct client * clt,unsigned int code,size_t len)18174703e0faSreyk server_log_http(struct client *clt, unsigned int code, size_t len)
1818ea62a379Sdoug {
1819ea62a379Sdoug 	static char		 tstamp[64];
1820ea62a379Sdoug 	static char		 ip[INET6_ADDRSTRLEN];
1821ea62a379Sdoug 	time_t			 t;
182234ff7cffStb 	struct kv		 key, *agent, *referrer, *xff, *xfp;
1823ea62a379Sdoug 	struct tm		*tm;
1824ea62a379Sdoug 	struct server_config	*srv_conf;
1825ea62a379Sdoug 	struct http_descriptor	*desc;
1826e22b0c74Sreyk 	int			 ret = -1;
18276ebc1f19Ssemarie 	char			*user = NULL;
18286ebc1f19Ssemarie 	char			*path = NULL;
18296ebc1f19Ssemarie 	char			*version = NULL;
18306ebc1f19Ssemarie 	char			*referrer_v = NULL;
18316ebc1f19Ssemarie 	char			*agent_v = NULL;
183234ff7cffStb 	char			*xff_v = NULL;
183334ff7cffStb 	char			*xfp_v = NULL;
1834ea62a379Sdoug 
1835ea62a379Sdoug 	if ((srv_conf = clt->clt_srv_conf) == NULL)
1836ea62a379Sdoug 		return (-1);
1837af3cfad1Sdoug 	if ((srv_conf->flags & SRVFLAG_LOG) == 0)
1838af3cfad1Sdoug 		return (0);
1839d08e4976Sreyk 	if ((desc = clt->clt_descreq) == NULL)
1840ea62a379Sdoug 		return (-1);
1841ea62a379Sdoug 
1842ea62a379Sdoug 	if ((t = time(NULL)) == -1)
1843ea62a379Sdoug 		return (-1);
1844ea62a379Sdoug 	if ((tm = localtime(&t)) == NULL)
1845ea62a379Sdoug 		return (-1);
1846ea62a379Sdoug 	if (strftime(tstamp, sizeof(tstamp), "%d/%b/%Y:%H:%M:%S %z", tm) == 0)
1847ea62a379Sdoug 		return (-1);
1848ea62a379Sdoug 
1849944a3fefSreyk 	if (print_host(&clt->clt_ss, ip, sizeof(ip)) == NULL)
1850944a3fefSreyk 		return (-1);
1851ea62a379Sdoug 
1852ea62a379Sdoug 	/*
1853ea62a379Sdoug 	 * For details on common log format, see:
1854ea62a379Sdoug 	 * https://httpd.apache.org/docs/current/mod/mod_log_config.html
1855af3cfad1Sdoug 	 *
1856af3cfad1Sdoug 	 * httpd's format is similar to these Apache LogFormats:
1857af3cfad1Sdoug 	 * "%v %h %l %u %t \"%r\" %>s %B"
1858af3cfad1Sdoug 	 * "%v %h %l %u %t \"%r\" %>s %B \"%{Referer}i\" \"%{User-agent}i\""
1859ea62a379Sdoug 	 */
1860ea62a379Sdoug 	switch (srv_conf->logformat) {
1861ea62a379Sdoug 	case LOG_FORMAT_COMMON:
1862e3c03affSreyk 		/* Use vis to encode input values from the header */
18636ebc1f19Ssemarie 		if (clt->clt_remote_user &&
1864ba87bb65Sreyk 		    stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
1865e3c03affSreyk 			goto done;
1866e3c03affSreyk 		if (desc->http_version &&
1867ba87bb65Sreyk 		    stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
18686ebc1f19Ssemarie 			goto done;
18696ebc1f19Ssemarie 
1870e3c03affSreyk 		/* The following should be URL-encoded */
18716ebc1f19Ssemarie 		if (desc->http_path &&
1872e22b0c74Sreyk 		    (path = url_encode(desc->http_path)) == NULL)
18736ebc1f19Ssemarie 			goto done;
18746ebc1f19Ssemarie 
18756ebc1f19Ssemarie 		ret = evbuffer_add_printf(clt->clt_log,
187641dcbd59Stim 		    "%s %s - %s [%s] \"%s %s%s%s%s%s\" %03d %zu\n",
187741dcbd59Stim 		    srv_conf->name, ip, clt->clt_remote_user == NULL ? "-" :
187841dcbd59Stim 		    user, tstamp,
1879ea62a379Sdoug 		    server_httpmethod_byid(desc->http_method),
18806ebc1f19Ssemarie 		    desc->http_path == NULL ? "" : path,
1881ea62a379Sdoug 		    desc->http_query == NULL ? "" : "?",
188258963e4fSreyk 		    desc->http_query == NULL ? "" : desc->http_query,
1883ea62a379Sdoug 		    desc->http_version == NULL ? "" : " ",
18846ebc1f19Ssemarie 		    desc->http_version == NULL ? "" : version,
18856ebc1f19Ssemarie 		    code, len);
18866ebc1f19Ssemarie 
1887ea62a379Sdoug 		break;
1888ea62a379Sdoug 
1889ea62a379Sdoug 	case LOG_FORMAT_COMBINED:
189034ff7cffStb 	case LOG_FORMAT_FORWARDED:
1891ea62a379Sdoug 		key.kv_key = "Referer"; /* sic */
1892ea62a379Sdoug 		if ((referrer = kv_find(&desc->http_headers, &key)) != NULL &&
1893ea62a379Sdoug 		    referrer->kv_value == NULL)
1894ea62a379Sdoug 			referrer = NULL;
1895ea62a379Sdoug 
1896ea62a379Sdoug 		key.kv_key = "User-Agent";
1897ea62a379Sdoug 		if ((agent = kv_find(&desc->http_headers, &key)) != NULL &&
1898ea62a379Sdoug 		    agent->kv_value == NULL)
1899ea62a379Sdoug 			agent = NULL;
1900ea62a379Sdoug 
1901e3c03affSreyk 		/* Use vis to encode input values from the header */
19026ebc1f19Ssemarie 		if (clt->clt_remote_user &&
1903ba87bb65Sreyk 		    stravis(&user, clt->clt_remote_user, HTTPD_LOGVIS) == -1)
1904e3c03affSreyk 			goto done;
1905095ccd49Sbenno 		if (clt->clt_remote_user == NULL &&
1906095ccd49Sbenno 		    clt->clt_tls_ctx != NULL &&
1907095ccd49Sbenno 		    (srv_conf->tls_flags & TLSFLAG_CA) &&
1908095ccd49Sbenno 		    tls_peer_cert_subject(clt->clt_tls_ctx) != NULL &&
1909095ccd49Sbenno 		    stravis(&user, tls_peer_cert_subject(clt->clt_tls_ctx),
1910095ccd49Sbenno 		    HTTPD_LOGVIS) == -1)
1911095ccd49Sbenno 			goto done;
1912e3c03affSreyk 		if (desc->http_version &&
1913ba87bb65Sreyk 		    stravis(&version, desc->http_version, HTTPD_LOGVIS) == -1)
1914e3c03affSreyk 			goto done;
1915e3c03affSreyk 		if (agent &&
1916ba87bb65Sreyk 		    stravis(&agent_v, agent->kv_value, HTTPD_LOGVIS) == -1)
19176ebc1f19Ssemarie 			goto done;
19186ebc1f19Ssemarie 
1919e3c03affSreyk 		/* The following should be URL-encoded */
19206ebc1f19Ssemarie 		if (desc->http_path &&
1921e22b0c74Sreyk 		    (path = url_encode(desc->http_path)) == NULL)
19226ebc1f19Ssemarie 			goto done;
1923e22b0c74Sreyk 		if (referrer &&
1924e22b0c74Sreyk 		    (referrer_v = url_encode(referrer->kv_value)) == NULL)
19256ebc1f19Ssemarie 			goto done;
19266ebc1f19Ssemarie 
192734ff7cffStb 		if ((ret = evbuffer_add_printf(clt->clt_log,
192841dcbd59Stim 		    "%s %s - %s [%s] \"%s %s%s%s%s%s\""
192934ff7cffStb 		    " %03d %zu \"%s\" \"%s\"",
1930095ccd49Sbenno 		    srv_conf->name, ip, user == NULL ? "-" :
193141dcbd59Stim 		    user, tstamp,
1932ea62a379Sdoug 		    server_httpmethod_byid(desc->http_method),
19336ebc1f19Ssemarie 		    desc->http_path == NULL ? "" : path,
1934ea62a379Sdoug 		    desc->http_query == NULL ? "" : "?",
193558963e4fSreyk 		    desc->http_query == NULL ? "" : desc->http_query,
1936ea62a379Sdoug 		    desc->http_version == NULL ? "" : " ",
19376ebc1f19Ssemarie 		    desc->http_version == NULL ? "" : version,
1938ea62a379Sdoug 		    code, len,
19396ebc1f19Ssemarie 		    referrer == NULL ? "" : referrer_v,
194034ff7cffStb 		    agent == NULL ? "" : agent_v)) == -1)
194134ff7cffStb 			break;
194234ff7cffStb 
194334ff7cffStb 		if (srv_conf->logformat == LOG_FORMAT_COMBINED)
194434ff7cffStb 			goto finish;
194534ff7cffStb 
194634ff7cffStb 		xff = xfp = NULL;
194734ff7cffStb 
194834ff7cffStb 		key.kv_key = "X-Forwarded-For";
194934ff7cffStb 		if ((xff = kv_find(&desc->http_headers, &key)) != NULL
195034ff7cffStb 		    && xff->kv_value == NULL)
195134ff7cffStb 			xff = NULL;
195234ff7cffStb 
195334ff7cffStb 		if (xff &&
195434ff7cffStb 		    stravis(&xff_v, xff->kv_value, HTTPD_LOGVIS) == -1)
195534ff7cffStb 			goto finish;
195634ff7cffStb 
195734ff7cffStb 		key.kv_key = "X-Forwarded-Port";
1958e95f05c9Sreyk 		if ((xfp = kv_find(&desc->http_headers, &key)) != NULL &&
1959e95f05c9Sreyk 		    (xfp->kv_value == NULL))
196034ff7cffStb 			xfp = NULL;
196134ff7cffStb 
196234ff7cffStb 		if (xfp &&
196334ff7cffStb 		    stravis(&xfp_v, xfp->kv_value, HTTPD_LOGVIS) == -1)
196434ff7cffStb 			goto finish;
196534ff7cffStb 
196634ff7cffStb 		if ((ret = evbuffer_add_printf(clt->clt_log, " %s %s",
196734ff7cffStb 		    xff == NULL ? "-" : xff_v,
196834ff7cffStb 		    xfp == NULL ? "-" : xfp_v)) == -1)
196934ff7cffStb 			break;
197034ff7cffStb finish:
197134ff7cffStb 		ret = evbuffer_add_printf(clt->clt_log, "\n");
19726ebc1f19Ssemarie 
1973ea62a379Sdoug 		break;
1974944a3fefSreyk 
1975944a3fefSreyk 	case LOG_FORMAT_CONNECTION:
1976e3c03affSreyk 		/* URL-encode the path */
19776ebc1f19Ssemarie 		if (desc->http_path &&
1978e22b0c74Sreyk 		    (path = url_encode(desc->http_path)) == NULL)
19796ebc1f19Ssemarie 			goto done;
19806ebc1f19Ssemarie 
19816ebc1f19Ssemarie 		ret = evbuffer_add_printf(clt->clt_log, " [%s]",
19826ebc1f19Ssemarie 		    desc->http_path == NULL ? "" : path);
19836ebc1f19Ssemarie 
1984944a3fefSreyk 		break;
1985ea62a379Sdoug 	}
1986ea62a379Sdoug 
19876ebc1f19Ssemarie done:
19886ebc1f19Ssemarie 	free(user);
19896ebc1f19Ssemarie 	free(path);
19906ebc1f19Ssemarie 	free(version);
19916ebc1f19Ssemarie 	free(referrer_v);
19926ebc1f19Ssemarie 	free(agent_v);
199334ff7cffStb 	free(xff_v);
199434ff7cffStb 	free(xfp_v);
19956ebc1f19Ssemarie 
19966ebc1f19Ssemarie 	return (ret);
1997ea62a379Sdoug }
1998