Home | History | Annotate | Line # | Download | only in isc
httpd.c revision 1.9
      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