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