xref: /netbsd-src/external/mpl/bind/dist/lib/isc/httpd.c (revision b2c35e17b976cf7ccd7250c86c6f5e95090ed636)
1 /*	$NetBSD: httpd.c,v 1.9 2024/02/21 22:52:28 christos Exp $	*/
2 
3 /*
4  * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5  *
6  * SPDX-License-Identifier: MPL-2.0
7  *
8  * This Source Code Form is subject to the terms of the Mozilla Public
9  * License, v. 2.0. If a copy of the MPL was not distributed with this
10  * file, you can obtain one at https://mozilla.org/MPL/2.0/.
11  *
12  * See the COPYRIGHT file distributed with this work for additional
13  * information regarding copyright ownership.
14  */
15 
16 /*! \file */
17 
18 #include <ctype.h>
19 #include <inttypes.h>
20 #include <stdbool.h>
21 #include <string.h>
22 
23 #include <isc/buffer.h>
24 #include <isc/httpd.h>
25 #include <isc/mem.h>
26 #include <isc/netmgr.h>
27 #include <isc/print.h>
28 #include <isc/refcount.h>
29 #include <isc/sockaddr.h>
30 #include <isc/string.h>
31 #include <isc/time.h>
32 #include <isc/url.h>
33 #include <isc/util.h>
34 
35 #include "netmgr/netmgr-int.h"
36 #include "picohttpparser.h"
37 
38 #ifdef HAVE_ZLIB
39 #include <zlib.h>
40 #endif /* ifdef HAVE_ZLIB */
41 
42 #define CHECK(m)                               \
43 	do {                                   \
44 		result = (m);                  \
45 		if (result != ISC_R_SUCCESS) { \
46 			goto cleanup;          \
47 		}                              \
48 	} while (0)
49 
50 /*
51  * Size the recv buffer to hold at maximum two full buffers from isc_nm_read(),
52  * so we don't have to handle the truncation.
53  */
54 #define HTTP_RECVLEN	     ISC_NETMGR_TCP_RECVBUF_SIZE * 2
55 #define HTTP_SENDLEN	     ISC_NETMGR_TCP_RECVBUF_SIZE
56 #define HTTP_HEADERS_NUM     100
57 #define HTTP_MAX_REQUEST_LEN 4096
58 
59 typedef enum httpd_flags {
60 	CONNECTION_CLOSE = 1 << 0,	/* connection must close */
61 	CONNECTION_KEEP_ALIVE = 1 << 1, /* response needs a keep-alive header */
62 	ACCEPT_DEFLATE = 1 << 2,	/* response can be compressed */
63 } httpd_flags_t;
64 
65 #define HTTPD_MAGIC    ISC_MAGIC('H', 't', 'p', 'd')
66 #define VALID_HTTPD(m) ISC_MAGIC_VALID(m, HTTPD_MAGIC)
67 
68 #define HTTPDMGR_MAGIC	  ISC_MAGIC('H', 'p', 'd', 'm')
69 #define VALID_HTTPDMGR(m) ISC_MAGIC_VALID(m, HTTPDMGR_MAGIC)
70 
71 /*%
72  * HTTP methods.
73  */
74 typedef enum { METHOD_UNKNOWN = 0, METHOD_GET = 1, METHOD_POST = 2 } method_t;
75 
76 /*%
77  * HTTP urls.  These are the URLs we manage, and the function to call to
78  * provide the data for it.  We pass in the base url (so the same function
79  * can handle multiple requests), and a structure to fill in to return a
80  * result to the client.  We also pass in a pointer to be filled in for
81  * the data cleanup function.
82  */
83 struct isc_httpdurl {
84 	char *url;
85 	isc_httpdaction_t *action;
86 	void *action_arg;
87 	bool isstatic;
88 	isc_time_t loadtime;
89 	ISC_LINK(isc_httpdurl_t) link;
90 };
91 
92 /*% http client */
93 struct isc_httpd {
94 	unsigned int magic; /* HTTPD_MAGIC */
95 
96 	isc_httpdmgr_t *mgr; /*%< our parent */
97 	ISC_LINK(isc_httpd_t) link;
98 
99 	isc_nmhandle_t *handle;	    /* Permanent pointer to handle */
100 	isc_nmhandle_t *readhandle; /* Waiting for a read callback */
101 
102 	/*%
103 	 * Received data state.
104 	 */
105 	char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */
106 	size_t recvlen;		    /*%< length recv'd */
107 	size_t consume;		    /*%< length of last command */
108 
109 	method_t method;
110 	int minor_version;
111 	httpd_flags_t flags;
112 	const char *path;
113 	isc_url_parser_t up;
114 	isc_time_t if_modified_since;
115 };
116 
117 struct isc_httpdmgr {
118 	unsigned int magic; /* HTTPDMGR_MAGIC */
119 	isc_refcount_t references;
120 	isc_mem_t *mctx;
121 	isc_nmsocket_t *sock;
122 
123 	isc_httpdclientok_t *client_ok;	 /*%< client validator */
124 	isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */
125 	void *cb_arg;			 /*%< argument for the above */
126 
127 	unsigned int flags;
128 	ISC_LIST(isc_httpd_t) running; /*%< running clients */
129 
130 	isc_mutex_t lock;
131 
132 	ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */
133 	isc_httpdaction_t *render_404;
134 	isc_httpdaction_t *render_500;
135 };
136 
137 typedef struct isc_httpd_sendreq {
138 	isc_mem_t *mctx;
139 	isc_httpd_t *httpd;
140 	isc_nmhandle_t *handle;
141 
142 	/*%
143 	 * Transmit data state.
144 	 *
145 	 * This is the data buffer we will transmit.
146 	 *
147 	 * This free function pointer is filled in by the rendering function
148 	 * we call.  The free function is called after the data is transmitted
149 	 * to the client.
150 	 *
151 	 * We currently use three buffers total:
152 	 *
153 	 * sendbuffer - gets filled as we gather the data
154 	 *
155 	 * bodybuffer - for the client to fill in (which it manages, it provides
156 	 * the space for it, etc) -- we will pass that buffer structure back to
157 	 * the caller, who is responsible for managing the space it may have
158 	 * allocated as backing store for it.  we only allocate the buffer
159 	 * itself, not the backing store.
160 	 *
161 	 * compbuffer - managed by us, that contains the compressed HTTP data,
162 	 * if compression is used.
163 	 */
164 	isc_buffer_t *sendbuffer;
165 	isc_buffer_t *compbuffer;
166 
167 	isc_buffer_t bodybuffer;
168 
169 	const char *mimetype;
170 	unsigned int retcode;
171 	const char *retmsg;
172 	isc_httpdfree_t *freecb;
173 	void *freecb_arg;
174 
175 } isc_httpd_sendreq_t;
176 
177 static isc_result_t
178 httpd_newconn(isc_nmhandle_t *, isc_result_t, void *);
179 static void
180 httpd_request(isc_nmhandle_t *, isc_result_t, isc_region_t *, void *);
181 static void
182 httpd_senddone(isc_nmhandle_t *, isc_result_t, void *);
183 static void
184 httpd_reset(void *);
185 static void
186 httpd_put(void *);
187 
188 static void
189 httpd_addheader(isc_httpd_sendreq_t *, const char *, const char *);
190 static void
191 httpd_addheaderuint(isc_httpd_sendreq_t *, const char *, int);
192 static void
193 httpd_endheaders(isc_httpd_sendreq_t *);
194 static void
195 httpd_response(isc_httpd_t *, isc_httpd_sendreq_t *);
196 
197 static isc_result_t
198 process_request(isc_httpd_t *, size_t);
199 
200 static isc_httpdaction_t render_404;
201 static isc_httpdaction_t render_500;
202 
203 #if ENABLE_AFL
204 static void (*finishhook)(void) = NULL;
205 #endif /* ENABLE_AFL */
206 
207 static void
208 destroy_httpdmgr(isc_httpdmgr_t *);
209 
210 static void
211 httpdmgr_attach(isc_httpdmgr_t *, isc_httpdmgr_t **);
212 static void
213 httpdmgr_detach(isc_httpdmgr_t **);
214 
215 isc_result_t
216 isc_httpdmgr_create(isc_nm_t *nm, isc_mem_t *mctx, isc_sockaddr_t *addr,
217 		    isc_httpdclientok_t *client_ok,
218 		    isc_httpdondestroy_t *ondestroy, void *cb_arg,
219 		    isc_httpdmgr_t **httpdmgrp) {
220 	isc_result_t result;
221 	isc_httpdmgr_t *httpdmgr = NULL;
222 
223 	REQUIRE(nm != NULL);
224 	REQUIRE(mctx != NULL);
225 	REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL);
226 
227 	httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t));
228 	*httpdmgr = (isc_httpdmgr_t){ .client_ok = client_ok,
229 				      .ondestroy = ondestroy,
230 				      .cb_arg = cb_arg,
231 				      .render_404 = render_404,
232 				      .render_500 = render_500 };
233 
234 	isc_mutex_init(&httpdmgr->lock);
235 	isc_mem_attach(mctx, &httpdmgr->mctx);
236 
237 	ISC_LIST_INIT(httpdmgr->running);
238 	ISC_LIST_INIT(httpdmgr->urls);
239 
240 	isc_refcount_init(&httpdmgr->references, 1);
241 
242 	CHECK(isc_nm_listentcp(nm, addr, httpd_newconn, httpdmgr,
243 			       sizeof(isc_httpd_t), 5, NULL, &httpdmgr->sock));
244 
245 	httpdmgr->magic = HTTPDMGR_MAGIC;
246 	*httpdmgrp = httpdmgr;
247 
248 	return (ISC_R_SUCCESS);
249 
250 cleanup:
251 	httpdmgr->magic = 0;
252 	isc_refcount_decrementz(&httpdmgr->references);
253 	isc_refcount_destroy(&httpdmgr->references);
254 	isc_mem_detach(&httpdmgr->mctx);
255 	isc_mutex_destroy(&httpdmgr->lock);
256 	isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
257 
258 	return (result);
259 }
260 
261 static void
262 httpdmgr_attach(isc_httpdmgr_t *source, isc_httpdmgr_t **targetp) {
263 	REQUIRE(VALID_HTTPDMGR(source));
264 	REQUIRE(targetp != NULL && *targetp == NULL);
265 
266 	isc_refcount_increment(&source->references);
267 
268 	*targetp = source;
269 }
270 
271 static void
272 httpdmgr_detach(isc_httpdmgr_t **httpdmgrp) {
273 	isc_httpdmgr_t *httpdmgr = NULL;
274 
275 	REQUIRE(httpdmgrp != NULL);
276 	REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
277 
278 	httpdmgr = *httpdmgrp;
279 	*httpdmgrp = NULL;
280 
281 	if (isc_refcount_decrement(&httpdmgr->references) == 1) {
282 		destroy_httpdmgr(httpdmgr);
283 	}
284 }
285 
286 static void
287 destroy_httpdmgr(isc_httpdmgr_t *httpdmgr) {
288 	isc_httpdurl_t *url;
289 
290 	isc_refcount_destroy(&httpdmgr->references);
291 
292 	LOCK(&httpdmgr->lock);
293 
294 	REQUIRE((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0);
295 	REQUIRE(ISC_LIST_EMPTY(httpdmgr->running));
296 
297 	httpdmgr->magic = 0;
298 
299 	if (httpdmgr->sock != NULL) {
300 		isc_nmsocket_close(&httpdmgr->sock);
301 	}
302 
303 	/*
304 	 * Clear out the list of all actions we know about.  Just free the
305 	 * memory.
306 	 */
307 	url = ISC_LIST_HEAD(httpdmgr->urls);
308 	while (url != NULL) {
309 		isc_mem_free(httpdmgr->mctx, url->url);
310 		ISC_LIST_UNLINK(httpdmgr->urls, url, link);
311 		isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t));
312 		url = ISC_LIST_HEAD(httpdmgr->urls);
313 	}
314 
315 	UNLOCK(&httpdmgr->lock);
316 	isc_mutex_destroy(&httpdmgr->lock);
317 
318 	if (httpdmgr->ondestroy != NULL) {
319 		(httpdmgr->ondestroy)(httpdmgr->cb_arg);
320 	}
321 	isc_mem_putanddetach(&httpdmgr->mctx, httpdmgr, sizeof(isc_httpdmgr_t));
322 }
323 
324 static bool
325 name_match(const struct phr_header *header, const char *match) {
326 	size_t match_len = strlen(match);
327 	if (match_len != header->name_len) {
328 		return (false);
329 	}
330 	return (strncasecmp(header->name, match, match_len) == 0);
331 }
332 
333 static bool
334 value_match(const struct phr_header *header, const char *match) {
335 	size_t match_len = strlen(match);
336 	size_t limit;
337 
338 	if (match_len > header->value_len) {
339 		return (false);
340 	}
341 
342 	limit = header->value_len - match_len + 1;
343 
344 	for (size_t i = 0; i < limit; i++) {
345 		if (isspace((unsigned char)header->value[i])) {
346 			while (i < limit &&
347 			       isspace((unsigned char)header->value[i]))
348 			{
349 				i++;
350 			}
351 			continue;
352 		}
353 
354 		if (strncasecmp(&header->value[i], match, match_len) == 0) {
355 			i += match_len;
356 			/*
357 			 * Sanity check; f.e. for 'deflate' match only
358 			 * 'deflate[,;]', but not 'deflateyou'
359 			 */
360 			if (i == header->value_len || header->value[i] == ',' ||
361 			    header->value[i] == ';')
362 			{
363 				return (true);
364 			}
365 		}
366 
367 		while (i < limit && header->value[i] != ',') {
368 			i++;
369 		}
370 	}
371 	return (false);
372 }
373 
374 static isc_result_t
375 process_request(isc_httpd_t *httpd, size_t last_len) {
376 	int pret;
377 	const char *method = NULL;
378 	size_t method_len = 0;
379 	const char *path;
380 	size_t path_len = 0;
381 	struct phr_header headers[HTTP_HEADERS_NUM];
382 	size_t num_headers;
383 	isc_result_t result;
384 
385 	num_headers = ARRAY_SIZE(headers);
386 
387 	pret = phr_parse_request(httpd->recvbuf, httpd->recvlen, &method,
388 				 &method_len, &path, &path_len,
389 				 &httpd->minor_version, headers, &num_headers,
390 				 last_len);
391 
392 	if (pret == -1) {
393 		/* Parse Error */
394 		return (ISC_R_UNEXPECTED);
395 	} else if (pret == -2) {
396 		/* Need more data */
397 		return (ISC_R_NOMORE);
398 	}
399 
400 	INSIST(pret > 0);
401 
402 	if (pret > HTTP_MAX_REQUEST_LEN) {
403 		return (ISC_R_RANGE);
404 	}
405 
406 	httpd->consume = pret;
407 
408 	/*
409 	 * Determine if this is a POST or GET method.  Any other values will
410 	 * cause an error to be returned.
411 	 */
412 	if (strncmp(method, "GET ", method_len) == 0) {
413 		httpd->method = METHOD_GET;
414 	} else if (strncmp(method, "POST ", method_len) == 0) {
415 		httpd->method = METHOD_POST;
416 	} else {
417 		return (ISC_R_RANGE);
418 	}
419 
420 	/*
421 	 * Parse the URL
422 	 */
423 	result = isc_url_parse(path, path_len, 0, &httpd->up);
424 	if (result != ISC_R_SUCCESS) {
425 		return (result);
426 	}
427 	httpd->path = path;
428 
429 	/*
430 	 * Examine headers that can affect this request's response
431 	 */
432 	httpd->flags = 0;
433 
434 	size_t content_len = 0;
435 	bool keep_alive = false;
436 	bool host_header = false;
437 
438 	isc_time_set(&httpd->if_modified_since, 0, 0);
439 
440 	for (size_t i = 0; i < num_headers; i++) {
441 		struct phr_header *header = &headers[i];
442 
443 		if (name_match(header, "Content-Length")) {
444 			char *endptr;
445 			long val = strtol(header->value, &endptr, 10);
446 
447 			errno = 0;
448 
449 			/* ensure we consumed all digits */
450 			if ((header->value + header->value_len) != endptr) {
451 				return (ISC_R_BADNUMBER);
452 			}
453 			/* ensure there was no minus sign */
454 			if (val < 0) {
455 				return (ISC_R_BADNUMBER);
456 			}
457 			/* ensure it did not overflow */
458 			if (errno != 0) {
459 				return (ISC_R_RANGE);
460 			}
461 			content_len = val;
462 		} else if (name_match(header, "Connection")) {
463 			/* multiple fields in a connection header are allowed */
464 			if (value_match(header, "close")) {
465 				httpd->flags |= CONNECTION_CLOSE;
466 			}
467 			if (value_match(header, "keep-alive")) {
468 				keep_alive = true;
469 			}
470 		} else if (name_match(header, "Host")) {
471 			host_header = true;
472 		} else if (name_match(header, "Accept-Encoding")) {
473 			if (value_match(header, "deflate")) {
474 				httpd->flags |= ACCEPT_DEFLATE;
475 			}
476 		} else if (name_match(header, "If-Modified-Since") &&
477 			   header->value_len < ISC_FORMATHTTPTIMESTAMP_SIZE)
478 		{
479 			char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1];
480 			memmove(timestamp, header->value, header->value_len);
481 			timestamp[header->value_len] = 0;
482 
483 			/* Ignore the value if it can't be parsed */
484 			(void)isc_time_parsehttptimestamp(
485 				timestamp, &httpd->if_modified_since);
486 		}
487 	}
488 
489 	/*
490 	 * The Content-Length is optional in an HTTP request.
491 	 * For a GET the length must be zero.
492 	 */
493 	if (httpd->method == METHOD_GET && content_len != 0) {
494 		return (ISC_R_BADNUMBER);
495 	}
496 
497 	if (content_len >= HTTP_MAX_REQUEST_LEN) {
498 		return (ISC_R_RANGE);
499 	}
500 
501 	size_t consume = httpd->consume + content_len;
502 	if (consume > httpd->recvlen) {
503 		/* The request data isn't complete yet. */
504 		return (ISC_R_NOMORE);
505 	}
506 
507 	/* Consume the request's data, which we do not use. */
508 	httpd->consume = consume;
509 
510 	switch (httpd->minor_version) {
511 	case 0:
512 		/*
513 		 * RFC 9112 section 9.3 says close takes priority if
514 		 * keep-alive is also present
515 		 */
516 		if ((httpd->flags & CONNECTION_CLOSE) == 0 && keep_alive) {
517 			httpd->flags |= CONNECTION_KEEP_ALIVE;
518 		} else {
519 			httpd->flags |= CONNECTION_CLOSE;
520 		}
521 		break;
522 	case 1:
523 		if (!host_header) {
524 			return (ISC_R_RANGE);
525 		}
526 		break;
527 	default:
528 		return (ISC_R_UNEXPECTED);
529 	}
530 
531 	/*
532 	 * Looks like a a valid request, so now we know we won't have
533 	 * to process this buffer again. We can NULL-terminate the
534 	 * URL for the caller's benefit, and set recvlen to 0 so
535 	 * the next read will overwrite this one instead of appending
536 	 * to the buffer.
537 	 */
538 
539 	return (ISC_R_SUCCESS);
540 }
541 
542 static void
543 httpd_reset(void *arg) {
544 	isc_httpd_t *httpd = (isc_httpd_t *)arg;
545 	isc_httpdmgr_t *httpdmgr = NULL;
546 
547 	REQUIRE(VALID_HTTPD(httpd));
548 
549 	httpdmgr = httpd->mgr;
550 
551 	REQUIRE(VALID_HTTPDMGR(httpdmgr));
552 
553 	LOCK(&httpdmgr->lock);
554 	ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
555 	UNLOCK(&httpdmgr->lock);
556 
557 	httpd->recvbuf[0] = 0;
558 	httpd->recvlen = 0;
559 	httpd->consume = 0;
560 	httpd->method = METHOD_UNKNOWN;
561 	httpd->flags = 0;
562 
563 	httpd->minor_version = -1;
564 	httpd->path = NULL;
565 	httpd->up = (isc_url_parser_t){ 0 };
566 	isc_time_set(&httpd->if_modified_since, 0, 0);
567 }
568 
569 static void
570 isc__httpd_sendreq_free(isc_httpd_sendreq_t *req) {
571 	/* Clean up buffers */
572 
573 	isc_buffer_free(&req->sendbuffer);
574 
575 	isc_mem_putanddetach(&req->mctx, req, sizeof(*req));
576 }
577 
578 static isc_httpd_sendreq_t *
579 isc__httpd_sendreq_new(isc_httpd_t *httpd) {
580 	isc_httpdmgr_t *httpdmgr = httpd->mgr;
581 	isc_httpd_sendreq_t *req;
582 
583 	REQUIRE(VALID_HTTPDMGR(httpdmgr));
584 
585 	req = isc_mem_get(httpdmgr->mctx, sizeof(*req));
586 	*req = (isc_httpd_sendreq_t){ 0 };
587 
588 	isc_mem_attach(httpdmgr->mctx, &req->mctx);
589 
590 	/*
591 	 * Initialize the buffer for our headers.
592 	 */
593 	isc_buffer_allocate(req->mctx, &req->sendbuffer, HTTP_SENDLEN);
594 	isc_buffer_clear(req->sendbuffer);
595 	isc_buffer_setautorealloc(req->sendbuffer, true);
596 
597 	isc_buffer_initnull(&req->bodybuffer);
598 
599 	return (req);
600 }
601 
602 static void
603 httpd_put(void *arg) {
604 	isc_httpd_t *httpd = (isc_httpd_t *)arg;
605 	isc_httpdmgr_t *mgr = NULL;
606 
607 	REQUIRE(VALID_HTTPD(httpd));
608 
609 	mgr = httpd->mgr;
610 	REQUIRE(VALID_HTTPDMGR(mgr));
611 
612 	httpd->magic = 0;
613 	httpd->mgr = NULL;
614 
615 	isc_mem_put(mgr->mctx, httpd, sizeof(*httpd));
616 
617 	httpdmgr_detach(&mgr);
618 
619 #if ENABLE_AFL
620 	if (finishhook != NULL) {
621 		finishhook();
622 	}
623 #endif /* ENABLE_AFL */
624 }
625 
626 static void
627 new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) {
628 	isc_httpd_t *httpd = NULL;
629 
630 	REQUIRE(VALID_HTTPDMGR(httpdmgr));
631 
632 	httpd = isc_nmhandle_getdata(handle);
633 	if (httpd == NULL) {
634 		httpd = isc_mem_get(httpdmgr->mctx, sizeof(*httpd));
635 		*httpd = (isc_httpd_t){ .handle = NULL };
636 		httpdmgr_attach(httpdmgr, &httpd->mgr);
637 	}
638 
639 	if (httpd->handle == NULL) {
640 		isc_nmhandle_setdata(handle, httpd, httpd_reset, httpd_put);
641 		httpd->handle = handle;
642 	} else {
643 		INSIST(httpd->handle == handle);
644 	}
645 
646 	ISC_LINK_INIT(httpd, link);
647 
648 	httpd->magic = HTTPD_MAGIC;
649 
650 	LOCK(&httpdmgr->lock);
651 	ISC_LIST_APPEND(httpdmgr->running, httpd, link);
652 	UNLOCK(&httpdmgr->lock);
653 
654 	isc_nmhandle_attach(httpd->handle, &httpd->readhandle);
655 	isc_nm_read(handle, httpd_request, httpdmgr);
656 }
657 
658 static isc_result_t
659 httpd_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
660 	isc_httpdmgr_t *httpdmgr = (isc_httpdmgr_t *)arg;
661 	isc_sockaddr_t peeraddr;
662 
663 	REQUIRE(VALID_HTTPDMGR(httpdmgr));
664 
665 	if ((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0) {
666 		return (ISC_R_CANCELED);
667 	} else if (result == ISC_R_CANCELED) {
668 		isc_httpdmgr_shutdown(&httpdmgr);
669 		return (result);
670 	} else if (result != ISC_R_SUCCESS) {
671 		return (result);
672 	}
673 
674 	peeraddr = isc_nmhandle_peeraddr(handle);
675 	if (httpdmgr->client_ok != NULL &&
676 	    !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg))
677 	{
678 		return (ISC_R_FAILURE);
679 	}
680 
681 	new_httpd(httpdmgr, handle);
682 
683 	return (ISC_R_SUCCESS);
684 }
685 
686 static isc_result_t
687 render_404(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
688 	   unsigned int *retcode, const char **retmsg, const char **mimetype,
689 	   isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
690 	static char msg[] = "No such URL.\r\n";
691 
692 	UNUSED(httpd);
693 	UNUSED(urlinfo);
694 	UNUSED(arg);
695 
696 	*retcode = 404;
697 	*retmsg = "No such URL";
698 	*mimetype = "text/plain";
699 	isc_buffer_reinit(b, msg, strlen(msg));
700 	isc_buffer_add(b, strlen(msg));
701 	*freecb = NULL;
702 	*freecb_args = NULL;
703 
704 	return (ISC_R_SUCCESS);
705 }
706 
707 static isc_result_t
708 render_500(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
709 	   unsigned int *retcode, const char **retmsg, const char **mimetype,
710 	   isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
711 	static char msg[] = "Internal server failure.\r\n";
712 
713 	UNUSED(httpd);
714 	UNUSED(urlinfo);
715 	UNUSED(arg);
716 
717 	*retcode = 500;
718 	*retmsg = "Internal server failure";
719 	*mimetype = "text/plain";
720 	isc_buffer_reinit(b, msg, strlen(msg));
721 	isc_buffer_add(b, strlen(msg));
722 	*freecb = NULL;
723 	*freecb_args = NULL;
724 
725 	return (ISC_R_SUCCESS);
726 }
727 
728 #ifdef HAVE_ZLIB
729 /*%<
730  * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it
731  * if necessary.
732  *
733  * Requires:
734  *\li	httpd a valid isc_httpd_t object
735  *
736  * Returns:
737  *\li	#ISC_R_SUCCESS	  -- all is well.
738  *\li	#ISC_R_NOMEMORY	  -- not enough memory to compress data
739  *\li	#ISC_R_FAILURE	  -- error during compression or compressed
740  *			     data would be larger than input data
741  */
742 static isc_result_t
743 httpd_compress(isc_httpd_sendreq_t *req) {
744 	z_stream zstr;
745 	int ret, inputlen;
746 
747 	/*
748 	 * We're setting output buffer size to input size so it fails if the
749 	 * compressed data size would be bigger than the input size.
750 	 */
751 	inputlen = isc_buffer_usedlength(&req->bodybuffer);
752 	if (inputlen == 0) {
753 		return (ISC_R_FAILURE);
754 	}
755 
756 	isc_buffer_allocate(req->mctx, &req->compbuffer, inputlen);
757 	isc_buffer_clear(req->compbuffer);
758 
759 	zstr = (z_stream){
760 		.total_in = inputlen,
761 		.avail_out = inputlen,
762 		.avail_in = inputlen,
763 		.next_in = isc_buffer_base(&req->bodybuffer),
764 		.next_out = isc_buffer_base(req->compbuffer),
765 	};
766 
767 	ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION);
768 	if (ret == Z_OK) {
769 		ret = deflate(&zstr, Z_FINISH);
770 	}
771 	deflateEnd(&zstr);
772 	if (ret == Z_STREAM_END) {
773 		isc_buffer_add(req->compbuffer, zstr.total_out);
774 		return (ISC_R_SUCCESS);
775 	} else {
776 		isc_buffer_free(&req->compbuffer);
777 		return (ISC_R_FAILURE);
778 	}
779 }
780 #endif /* ifdef HAVE_ZLIB */
781 
782 static void
783 prepare_response(isc_httpdmgr_t *mgr, isc_httpd_t *httpd,
784 		 isc_httpd_sendreq_t **reqp) {
785 	isc_httpd_sendreq_t *req = NULL;
786 	isc_time_t now;
787 	char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
788 	const char *path = "/";
789 	size_t path_len = 1;
790 	bool is_compressed = false;
791 	isc_httpdurl_t *url = NULL;
792 	isc_result_t result;
793 
794 	REQUIRE(VALID_HTTPD(httpd));
795 	REQUIRE(reqp != NULL && *reqp == NULL);
796 
797 	isc_time_now(&now);
798 	isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
799 
800 	if (httpd->up.field_set & (1 << ISC_UF_PATH)) {
801 		path = &httpd->path[httpd->up.field_data[ISC_UF_PATH].off];
802 		path_len = httpd->up.field_data[ISC_UF_PATH].len;
803 	}
804 
805 	LOCK(&mgr->lock);
806 	url = ISC_LIST_HEAD(mgr->urls);
807 	while (url != NULL) {
808 		if (strncmp(path, url->url, path_len) == 0) {
809 			break;
810 		}
811 		url = ISC_LIST_NEXT(url, link);
812 	}
813 	UNLOCK(&mgr->lock);
814 
815 	req = isc__httpd_sendreq_new(httpd);
816 
817 	if (url == NULL) {
818 		result = mgr->render_404(httpd, NULL, NULL, &req->retcode,
819 					 &req->retmsg, &req->mimetype,
820 					 &req->bodybuffer, &req->freecb,
821 					 &req->freecb_arg);
822 	} else {
823 		result = url->action(httpd, url, url->action_arg, &req->retcode,
824 				     &req->retmsg, &req->mimetype,
825 				     &req->bodybuffer, &req->freecb,
826 				     &req->freecb_arg);
827 	}
828 	if (result != ISC_R_SUCCESS) {
829 		result = mgr->render_500(httpd, url, NULL, &req->retcode,
830 					 &req->retmsg, &req->mimetype,
831 					 &req->bodybuffer, &req->freecb,
832 					 &req->freecb_arg);
833 		RUNTIME_CHECK(result == ISC_R_SUCCESS);
834 	}
835 
836 #ifdef HAVE_ZLIB
837 	if ((httpd->flags & ACCEPT_DEFLATE) != 0) {
838 		result = httpd_compress(req);
839 		if (result == ISC_R_SUCCESS) {
840 			is_compressed = true;
841 		}
842 	}
843 #endif /* ifdef HAVE_ZLIB */
844 
845 	httpd_response(httpd, req);
846 	/* RFC 9112 § 9.6: SHOULD send Connection: close in last response */
847 	if ((httpd->flags & CONNECTION_CLOSE) != 0) {
848 		httpd_addheader(req, "Connection", "close");
849 	} else if ((httpd->flags & CONNECTION_KEEP_ALIVE) != 0) {
850 		httpd_addheader(req, "Connection", "Keep-Alive");
851 	}
852 	httpd_addheader(req, "Content-Type", req->mimetype);
853 	httpd_addheader(req, "Date", datebuf);
854 	httpd_addheader(req, "Expires", datebuf);
855 
856 	if (url != NULL && url->isstatic) {
857 		char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
858 		isc_time_formathttptimestamp(&url->loadtime, loadbuf,
859 					     sizeof(loadbuf));
860 		httpd_addheader(req, "Last-Modified", loadbuf);
861 		httpd_addheader(req, "Cache-Control: public", NULL);
862 	} else {
863 		httpd_addheader(req, "Last-Modified", datebuf);
864 		httpd_addheader(req, "Pragma: no-cache", NULL);
865 		httpd_addheader(req, "Cache-Control: no-cache", NULL);
866 	}
867 
868 	httpd_addheader(req, "Server: libisc", NULL);
869 
870 	if (is_compressed) {
871 		httpd_addheader(req, "Content-Encoding", "deflate");
872 		httpd_addheaderuint(req, "Content-Length",
873 				    isc_buffer_usedlength(req->compbuffer));
874 	} else {
875 		httpd_addheaderuint(req, "Content-Length",
876 				    isc_buffer_usedlength(&req->bodybuffer));
877 	}
878 
879 	httpd_endheaders(req); /* done */
880 
881 	/*
882 	 * Append either the compressed or the non-compressed response body to
883 	 * the response headers and store the result in httpd->sendbuffer.
884 	 */
885 	if (is_compressed) {
886 		isc_buffer_putmem(req->sendbuffer,
887 				  isc_buffer_base(req->compbuffer),
888 				  isc_buffer_usedlength(req->compbuffer));
889 		isc_buffer_free(&req->compbuffer);
890 	} else {
891 		isc_buffer_putmem(req->sendbuffer,
892 				  isc_buffer_base(&req->bodybuffer),
893 				  isc_buffer_usedlength(&req->bodybuffer));
894 	}
895 
896 	/* Free the bodybuffer */
897 	if (req->freecb != NULL && isc_buffer_length(&req->bodybuffer) > 0) {
898 		req->freecb(&req->bodybuffer, req->freecb_arg);
899 	}
900 
901 	/* Consume the request from the recv buffer. */
902 	INSIST(httpd->consume != 0);
903 	INSIST(httpd->consume <= httpd->recvlen);
904 	if (httpd->consume < httpd->recvlen) {
905 		memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume,
906 			httpd->recvlen - httpd->consume);
907 	}
908 	httpd->recvlen -= httpd->consume;
909 	httpd->consume = 0;
910 
911 	/*
912 	 * We don't need to attach to httpd here because it gets only cleaned
913 	 * when the last handle has been detached
914 	 */
915 	req->httpd = httpd;
916 
917 	*reqp = req;
918 }
919 
920 static void
921 httpd_request(isc_nmhandle_t *handle, isc_result_t eresult,
922 	      isc_region_t *region, void *arg) {
923 	isc_result_t result;
924 	isc_httpdmgr_t *mgr = arg;
925 	isc_httpd_t *httpd = NULL;
926 	isc_httpd_sendreq_t *req = NULL;
927 	isc_region_t r;
928 	size_t last_len = 0;
929 
930 	httpd = isc_nmhandle_getdata(handle);
931 
932 	REQUIRE(VALID_HTTPD(httpd));
933 
934 	REQUIRE(httpd->handle == handle);
935 
936 	if (httpd->readhandle == NULL) {
937 		/* The channel has been already closed, just bail out */
938 		return;
939 	}
940 
941 	if (eresult != ISC_R_SUCCESS) {
942 		goto close_readhandle;
943 	}
944 
945 	REQUIRE(httpd->readhandle == handle);
946 
947 	isc_nm_pauseread(httpd->readhandle);
948 
949 	/*
950 	 * If we are being called from httpd_senddone(), the last HTTP request
951 	 * was processed successfully, reset the last_len to 0, even if there's
952 	 * data in the httpd->recvbuf.
953 	 */
954 	last_len = (region == NULL) ? 0 : httpd->recvlen;
955 
956 	/* Store the received data into the recvbuf */
957 	if (region != NULL) {
958 		if (httpd->recvlen + region->length > sizeof(httpd->recvbuf)) {
959 			goto close_readhandle;
960 		}
961 
962 		memmove(httpd->recvbuf + httpd->recvlen, region->base,
963 			region->length);
964 		httpd->recvlen += region->length;
965 	}
966 
967 	result = process_request(httpd, last_len);
968 
969 	if (result == ISC_R_NOMORE) {
970 		if (httpd->recvlen > HTTP_MAX_REQUEST_LEN) {
971 			goto close_readhandle;
972 		}
973 
974 		/* Wait for more data, the readhandle is still attached */
975 		isc_nm_resumeread(httpd->readhandle);
976 		return;
977 	}
978 
979 	/* XXXFANF it would be more polite to reply 400 bad request */
980 	if (result != ISC_R_SUCCESS) {
981 		goto close_readhandle;
982 	}
983 
984 	prepare_response(mgr, httpd, &req);
985 
986 	/*
987 	 * Determine total response size.
988 	 */
989 	isc_buffer_usedregion(req->sendbuffer, &r);
990 
991 	isc_nmhandle_attach(httpd->handle, &req->handle);
992 	isc_nm_send(httpd->handle, &r, httpd_senddone, req);
993 	return;
994 
995 close_readhandle:
996 	isc_nm_pauseread(httpd->readhandle);
997 	isc_nmhandle_detach(&httpd->readhandle);
998 }
999 
1000 void
1001 isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
1002 	isc_httpdmgr_t *httpdmgr;
1003 	isc_httpd_t *httpd;
1004 
1005 	REQUIRE(httpdmgrp != NULL);
1006 	REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
1007 
1008 	httpdmgr = *httpdmgrp;
1009 	*httpdmgrp = NULL;
1010 
1011 	isc_nm_stoplistening(httpdmgr->sock);
1012 
1013 	LOCK(&httpdmgr->lock);
1014 	httpdmgr->flags |= ISC_HTTPDMGR_SHUTTINGDOWN;
1015 
1016 	httpd = ISC_LIST_HEAD(httpdmgr->running);
1017 	while (httpd != NULL) {
1018 		isc_nm_cancelread(httpd->readhandle);
1019 		httpd = ISC_LIST_NEXT(httpd, link);
1020 	}
1021 	UNLOCK(&httpdmgr->lock);
1022 
1023 	isc_nmsocket_close(&httpdmgr->sock);
1024 
1025 	httpdmgr_detach(&httpdmgr);
1026 }
1027 
1028 static void
1029 httpd_response(isc_httpd_t *httpd, isc_httpd_sendreq_t *req) {
1030 	isc_result_t result;
1031 
1032 	result = isc_buffer_printf(req->sendbuffer, "HTTP/1.%u %03u %s\r\n",
1033 				   httpd->minor_version, req->retcode,
1034 				   req->retmsg);
1035 
1036 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
1037 }
1038 
1039 static void
1040 httpd_addheader(isc_httpd_sendreq_t *req, const char *name, const char *val) {
1041 	isc_result_t result;
1042 
1043 	if (val != NULL) {
1044 		result = isc_buffer_printf(req->sendbuffer, "%s: %s\r\n", name,
1045 					   val);
1046 	} else {
1047 		result = isc_buffer_printf(req->sendbuffer, "%s\r\n", name);
1048 	}
1049 
1050 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
1051 }
1052 
1053 static void
1054 httpd_endheaders(isc_httpd_sendreq_t *req) {
1055 	isc_result_t result;
1056 
1057 	result = isc_buffer_printf(req->sendbuffer, "\r\n");
1058 
1059 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
1060 }
1061 
1062 static void
1063 httpd_addheaderuint(isc_httpd_sendreq_t *req, const char *name, int val) {
1064 	isc_result_t result;
1065 
1066 	result = isc_buffer_printf(req->sendbuffer, "%s: %d\r\n", name, val);
1067 
1068 	RUNTIME_CHECK(result == ISC_R_SUCCESS);
1069 }
1070 
1071 static void
1072 httpd_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
1073 	isc_httpd_sendreq_t *req = (isc_httpd_sendreq_t *)arg;
1074 	isc_httpd_t *httpd = req->httpd;
1075 
1076 	REQUIRE(VALID_HTTPD(httpd));
1077 
1078 	if (httpd->readhandle == NULL) {
1079 		goto detach;
1080 	}
1081 
1082 	if (eresult == ISC_R_SUCCESS && (httpd->flags & CONNECTION_CLOSE) == 0)
1083 	{
1084 		/*
1085 		 * Calling httpd_request() with region NULL restarts
1086 		 * reading.
1087 		 */
1088 		httpd_request(handle, ISC_R_SUCCESS, NULL, httpd->mgr);
1089 	} else {
1090 		isc_nm_cancelread(httpd->readhandle);
1091 	}
1092 
1093 detach:
1094 	isc_nmhandle_detach(&handle);
1095 
1096 	isc__httpd_sendreq_free(req);
1097 }
1098 
1099 isc_result_t
1100 isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic,
1101 		    isc_httpdaction_t *func, void *arg) {
1102 	isc_httpdurl_t *item;
1103 
1104 	REQUIRE(VALID_HTTPDMGR(httpdmgr));
1105 
1106 	if (url == NULL) {
1107 		httpdmgr->render_404 = func;
1108 		return (ISC_R_SUCCESS);
1109 	}
1110 
1111 	item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t));
1112 
1113 	item->url = isc_mem_strdup(httpdmgr->mctx, url);
1114 
1115 	item->action = func;
1116 	item->action_arg = arg;
1117 	item->isstatic = isstatic;
1118 	isc_time_now(&item->loadtime);
1119 
1120 	ISC_LINK_INIT(item, link);
1121 
1122 	LOCK(&httpdmgr->lock);
1123 	ISC_LIST_APPEND(httpdmgr->urls, item, link);
1124 	UNLOCK(&httpdmgr->lock);
1125 
1126 	return (ISC_R_SUCCESS);
1127 }
1128 
1129 void
1130 isc_httpd_setfinishhook(void (*fn)(void)) {
1131 #if ENABLE_AFL
1132 	finishhook = fn;
1133 #else  /* ENABLE_AFL */
1134 	UNUSED(fn);
1135 #endif /* ENABLE_AFL */
1136 }
1137 
1138 bool
1139 isc_httpdurl_isstatic(const isc_httpdurl_t *url) {
1140 	return (url->isstatic);
1141 }
1142 
1143 const isc_time_t *
1144 isc_httpdurl_loadtime(const isc_httpdurl_t *url) {
1145 	return (&url->loadtime);
1146 }
1147 
1148 const isc_time_t *
1149 isc_httpd_if_modified_since(const isc_httpd_t *httpd) {
1150 	return ((const isc_time_t *)&httpd->if_modified_since);
1151 }
1152