httpd.c revision 1.1.1.10 1 /* $NetBSD: httpd.c,v 1.1.1.10 2026/01/29 18:19:50 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 long val = strtol(header->value, &endptr, 10);
431
432 errno = 0;
433
434 /* ensure we consumed all digits */
435 if ((header->value + header->value_len) != endptr) {
436 return ISC_R_BADNUMBER;
437 }
438 /* ensure there was no minus sign */
439 if (val < 0) {
440 return ISC_R_BADNUMBER;
441 }
442 /* ensure it did not overflow */
443 if (errno != 0) {
444 return ISC_R_RANGE;
445 }
446 content_len = val;
447 } else if (name_match(header, "Connection")) {
448 /* multiple fields in a connection header are allowed */
449 if (value_match(header, "close")) {
450 httpd->flags |= CONNECTION_CLOSE;
451 }
452 if (value_match(header, "keep-alive")) {
453 keep_alive = true;
454 }
455 } else if (name_match(header, "Host")) {
456 host_header = true;
457 } else if (name_match(header, "Accept-Encoding")) {
458 if (value_match(header, "deflate")) {
459 httpd->flags |= ACCEPT_DEFLATE;
460 }
461 } else if (name_match(header, "If-Modified-Since") &&
462 header->value_len < ISC_FORMATHTTPTIMESTAMP_SIZE)
463 {
464 char timestamp[ISC_FORMATHTTPTIMESTAMP_SIZE + 1];
465 memmove(timestamp, header->value, header->value_len);
466 timestamp[header->value_len] = 0;
467
468 /* Ignore the value if it can't be parsed */
469 (void)isc_time_parsehttptimestamp(
470 timestamp, &httpd->if_modified_since);
471 }
472 }
473
474 /*
475 * The Content-Length is optional in an HTTP request.
476 * For a GET the length must be zero.
477 */
478 if (httpd->method == METHOD_GET && content_len != 0) {
479 return ISC_R_BADNUMBER;
480 }
481
482 if (content_len >= HTTP_MAX_REQUEST_LEN) {
483 return ISC_R_RANGE;
484 }
485
486 size_t consume = httpd->consume + content_len;
487 if (consume > httpd->recvlen) {
488 /* The request data isn't complete yet. */
489 return ISC_R_NOMORE;
490 }
491
492 /* Consume the request's data, which we do not use. */
493 httpd->consume = consume;
494
495 switch (httpd->minor_version) {
496 case 0:
497 /*
498 * RFC 9112 section 9.3 says close takes priority if
499 * keep-alive is also present
500 */
501 if ((httpd->flags & CONNECTION_CLOSE) == 0 && keep_alive) {
502 httpd->flags |= CONNECTION_KEEP_ALIVE;
503 } else {
504 httpd->flags |= CONNECTION_CLOSE;
505 }
506 break;
507 case 1:
508 if (!host_header) {
509 return ISC_R_RANGE;
510 }
511 break;
512 default:
513 return ISC_R_UNEXPECTED;
514 }
515
516 /*
517 * Looks like a a valid request, so now we know we won't have
518 * to process this buffer again. We can NULL-terminate the
519 * URL for the caller's benefit, and set recvlen to 0 so
520 * the next read will overwrite this one instead of appending
521 * to the buffer.
522 */
523
524 return ISC_R_SUCCESS;
525 }
526
527 static void
528 httpd_free(isc_httpd_t *httpd) {
529 isc_httpdmgr_t *httpdmgr = NULL;
530
531 REQUIRE(VALID_HTTPD(httpd));
532
533 httpdmgr = httpd->mgr;
534
535 REQUIRE(VALID_HTTPDMGR(httpdmgr));
536
537 LOCK(&httpdmgr->lock);
538 ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
539 UNLOCK(&httpdmgr->lock);
540
541 httpd->recvbuf[0] = 0;
542 httpd->recvlen = 0;
543 httpd->consume = 0;
544 httpd->method = METHOD_UNKNOWN;
545 httpd->flags = 0;
546
547 httpd->minor_version = -1;
548 httpd->path = NULL;
549 httpd->up = (isc_url_parser_t){ 0 };
550 isc_time_set(&httpd->if_modified_since, 0, 0);
551
552 httpd->magic = 0;
553 httpd->mgr = NULL;
554
555 isc_mem_put(httpdmgr->mctx, httpd, sizeof(*httpd));
556
557 isc_httpdmgr_detach(&httpdmgr);
558
559 #if ENABLE_AFL
560 if (finishhook != NULL) {
561 finishhook();
562 }
563 #endif /* ENABLE_AFL */
564 }
565
566 #if ISC_HTTPD_TRACE
567 ISC_REFCOUNT_TRACE_IMPL(isc_httpd, httpd_free)
568 #else
569 ISC_REFCOUNT_IMPL(isc_httpd, httpd_free);
570 #endif
571
572 static void
573 isc__httpd_sendreq_free(isc_httpd_sendreq_t *req) {
574 /* Clean up buffers */
575
576 isc_buffer_free(&req->sendbuffer);
577
578 isc_mem_putanddetach(&req->mctx, req, sizeof(*req));
579 }
580
581 static isc_httpd_sendreq_t *
582 isc__httpd_sendreq_new(isc_httpd_t *httpd) {
583 isc_httpdmgr_t *httpdmgr = httpd->mgr;
584 isc_httpd_sendreq_t *req;
585
586 REQUIRE(VALID_HTTPDMGR(httpdmgr));
587
588 req = isc_mem_get(httpdmgr->mctx, sizeof(*req));
589 *req = (isc_httpd_sendreq_t){ 0 };
590
591 isc_mem_attach(httpdmgr->mctx, &req->mctx);
592
593 /*
594 * Initialize the buffer for our headers.
595 */
596 isc_buffer_allocate(req->mctx, &req->sendbuffer, HTTP_SENDLEN);
597 isc_buffer_clear(req->sendbuffer);
598
599 isc_buffer_initnull(&req->bodybuffer);
600
601 isc_httpd_attach(httpd, &req->httpd);
602
603 return req;
604 }
605
606 static void
607 new_httpd(isc_httpdmgr_t *httpdmgr, isc_nmhandle_t *handle) {
608 isc_httpd_t *httpd = NULL;
609
610 REQUIRE(VALID_HTTPDMGR(httpdmgr));
611
612 httpd = isc_mem_get(httpdmgr->mctx, sizeof(*httpd));
613 *httpd = (isc_httpd_t){
614 .magic = HTTPD_MAGIC,
615 .link = ISC_LINK_INITIALIZER,
616 .references = ISC_REFCOUNT_INITIALIZER(1),
617 };
618
619 isc_nmhandle_attach(handle, &httpd->handle);
620
621 isc_httpdmgr_attach(httpdmgr, &httpd->mgr);
622
623 LOCK(&httpdmgr->lock);
624 ISC_LIST_APPEND(httpdmgr->running, httpd, link);
625 UNLOCK(&httpdmgr->lock);
626
627 isc_nm_read(handle, httpd_request, httpd);
628 }
629
630 static isc_result_t
631 httpd_newconn(isc_nmhandle_t *handle, isc_result_t result, void *arg) {
632 isc_httpdmgr_t *httpdmgr = (isc_httpdmgr_t *)arg;
633 isc_sockaddr_t peeraddr;
634
635 REQUIRE(VALID_HTTPDMGR(httpdmgr));
636
637 if ((httpdmgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0) {
638 return ISC_R_CANCELED;
639 } else if (result == ISC_R_CANCELED) {
640 isc_httpdmgr_shutdown(&httpdmgr);
641 return result;
642 } else if (result != ISC_R_SUCCESS) {
643 return result;
644 }
645
646 peeraddr = isc_nmhandle_peeraddr(handle);
647 if (httpdmgr->client_ok != NULL &&
648 !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg))
649 {
650 return ISC_R_FAILURE;
651 }
652
653 new_httpd(httpdmgr, handle);
654
655 return ISC_R_SUCCESS;
656 }
657
658 static isc_result_t
659 render_404(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
660 unsigned int *retcode, const char **retmsg, const char **mimetype,
661 isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
662 static char msg[] = "No such URL.\r\n";
663
664 UNUSED(httpd);
665 UNUSED(urlinfo);
666 UNUSED(arg);
667
668 *retcode = 404;
669 *retmsg = "No such URL";
670 *mimetype = "text/plain";
671 isc_buffer_reinit(b, msg, strlen(msg));
672 isc_buffer_add(b, strlen(msg));
673 *freecb = NULL;
674 *freecb_args = NULL;
675
676 return ISC_R_SUCCESS;
677 }
678
679 static isc_result_t
680 render_500(const isc_httpd_t *httpd, const isc_httpdurl_t *urlinfo, void *arg,
681 unsigned int *retcode, const char **retmsg, const char **mimetype,
682 isc_buffer_t *b, isc_httpdfree_t **freecb, void **freecb_args) {
683 static char msg[] = "Internal server failure.\r\n";
684
685 UNUSED(httpd);
686 UNUSED(urlinfo);
687 UNUSED(arg);
688
689 *retcode = 500;
690 *retmsg = "Internal server failure";
691 *mimetype = "text/plain";
692 isc_buffer_reinit(b, msg, strlen(msg));
693 isc_buffer_add(b, strlen(msg));
694 *freecb = NULL;
695 *freecb_args = NULL;
696
697 return ISC_R_SUCCESS;
698 }
699
700 #ifdef HAVE_ZLIB
701 /*%<
702 * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it
703 * if necessary.
704 *
705 * Requires:
706 *\li httpd a valid isc_httpd_t object
707 *
708 * Returns:
709 *\li #ISC_R_SUCCESS -- all is well.
710 *\li #ISC_R_NOMEMORY -- not enough memory to compress data
711 *\li #ISC_R_FAILURE -- error during compression or compressed
712 * data would be larger than input data
713 */
714 static isc_result_t
715 httpd_compress(isc_httpd_sendreq_t *req) {
716 z_stream zstr;
717 int ret, inputlen;
718
719 /*
720 * We're setting output buffer size to input size so it fails if the
721 * compressed data size would be bigger than the input size.
722 */
723 inputlen = isc_buffer_usedlength(&req->bodybuffer);
724 if (inputlen == 0) {
725 return ISC_R_FAILURE;
726 }
727
728 isc_buffer_allocate(req->mctx, &req->compbuffer, inputlen);
729 isc_buffer_clear(req->compbuffer);
730
731 zstr = (z_stream){
732 .total_in = inputlen,
733 .avail_out = inputlen,
734 .avail_in = inputlen,
735 .next_in = isc_buffer_base(&req->bodybuffer),
736 .next_out = isc_buffer_base(req->compbuffer),
737 };
738
739 ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION);
740 if (ret == Z_OK) {
741 ret = deflate(&zstr, Z_FINISH);
742 }
743 deflateEnd(&zstr);
744 if (ret == Z_STREAM_END) {
745 isc_buffer_add(req->compbuffer, zstr.total_out);
746 return ISC_R_SUCCESS;
747 } else {
748 isc_buffer_free(&req->compbuffer);
749 return ISC_R_FAILURE;
750 }
751 }
752 #endif /* ifdef HAVE_ZLIB */
753
754 static void
755 prepare_response(void *arg) {
756 isc_httpd_sendreq_t *req = arg;
757 isc_httpd_t *httpd = req->httpd;
758 isc_httpdmgr_t *mgr = httpd->mgr;
759 isc_time_t now = isc_time_now();
760 char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
761 const char *path = "/";
762 size_t path_len = 1;
763 bool is_compressed = false;
764 isc_httpdurl_t *url = NULL;
765 isc_result_t result;
766
767 REQUIRE(VALID_HTTPD(httpd));
768 REQUIRE(req != NULL);
769
770 isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
771
772 if (httpd->up.field_set & (1 << ISC_UF_PATH)) {
773 path = &httpd->path[httpd->up.field_data[ISC_UF_PATH].off];
774 path_len = httpd->up.field_data[ISC_UF_PATH].len;
775 }
776
777 LOCK(&mgr->lock);
778 ISC_LIST_FOREACH(mgr->urls, url, link) {
779 if (strncmp(path, url->url, path_len) == 0) {
780 break;
781 }
782 }
783 UNLOCK(&mgr->lock);
784
785 if (url == NULL) {
786 result = mgr->render_404(httpd, NULL, NULL, &req->retcode,
787 &req->retmsg, &req->mimetype,
788 &req->bodybuffer, &req->freecb,
789 &req->freecb_arg);
790 } else {
791 result = url->action(httpd, url, url->action_arg, &req->retcode,
792 &req->retmsg, &req->mimetype,
793 &req->bodybuffer, &req->freecb,
794 &req->freecb_arg);
795 }
796 if (result != ISC_R_SUCCESS) {
797 result = mgr->render_500(httpd, url, NULL, &req->retcode,
798 &req->retmsg, &req->mimetype,
799 &req->bodybuffer, &req->freecb,
800 &req->freecb_arg);
801 RUNTIME_CHECK(result == ISC_R_SUCCESS);
802 }
803
804 #ifdef HAVE_ZLIB
805 if ((httpd->flags & ACCEPT_DEFLATE) != 0) {
806 result = httpd_compress(req);
807 if (result == ISC_R_SUCCESS) {
808 is_compressed = true;
809 }
810 }
811 #endif /* ifdef HAVE_ZLIB */
812
813 httpd_response(httpd, req);
814 /* RFC 9112 9.6: SHOULD send Connection: close in last response */
815 if ((httpd->flags & CONNECTION_CLOSE) != 0) {
816 httpd_addheader(req, "Connection", "close");
817 } else if ((httpd->flags & CONNECTION_KEEP_ALIVE) != 0) {
818 httpd_addheader(req, "Connection", "Keep-Alive");
819 }
820 httpd_addheader(req, "Content-Type", req->mimetype);
821 httpd_addheader(req, "Date", datebuf);
822 httpd_addheader(req, "Expires", datebuf);
823
824 if (url != NULL && url->isstatic) {
825 char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
826 isc_time_formathttptimestamp(&url->loadtime, loadbuf,
827 sizeof(loadbuf));
828 httpd_addheader(req, "Last-Modified", loadbuf);
829 httpd_addheader(req, "Cache-Control: public", NULL);
830 } else {
831 httpd_addheader(req, "Last-Modified", datebuf);
832 httpd_addheader(req, "Pragma: no-cache", NULL);
833 httpd_addheader(req, "Cache-Control: no-cache", NULL);
834 }
835
836 httpd_addheader(req, "Server: libisc", NULL);
837
838 if (is_compressed) {
839 httpd_addheader(req, "Content-Encoding", "deflate");
840 httpd_addheaderuint(req, "Content-Length",
841 isc_buffer_usedlength(req->compbuffer));
842 } else {
843 httpd_addheaderuint(req, "Content-Length",
844 isc_buffer_usedlength(&req->bodybuffer));
845 }
846
847 httpd_endheaders(req); /* done */
848
849 /*
850 * Append either the compressed or the non-compressed response body to
851 * the response headers and store the result in httpd->sendbuffer.
852 */
853 if (is_compressed) {
854 isc_buffer_putmem(req->sendbuffer,
855 isc_buffer_base(req->compbuffer),
856 isc_buffer_usedlength(req->compbuffer));
857 isc_buffer_free(&req->compbuffer);
858 } else {
859 isc_buffer_putmem(req->sendbuffer,
860 isc_buffer_base(&req->bodybuffer),
861 isc_buffer_usedlength(&req->bodybuffer));
862 }
863
864 /* Free the bodybuffer */
865 if (req->freecb != NULL && isc_buffer_length(&req->bodybuffer) > 0) {
866 req->freecb(&req->bodybuffer, req->freecb_arg);
867 }
868
869 /* Consume the request from the recv buffer. */
870 INSIST(httpd->consume != 0);
871 INSIST(httpd->consume <= httpd->recvlen);
872 if (httpd->consume < httpd->recvlen) {
873 memmove(httpd->recvbuf, httpd->recvbuf + httpd->consume,
874 httpd->recvlen - httpd->consume);
875 }
876 httpd->recvlen -= httpd->consume;
877 httpd->consume = 0;
878 }
879
880 static void
881 prepare_response_done(void *arg) {
882 isc_region_t r;
883 isc_httpd_sendreq_t *req = arg;
884 isc_httpd_t *httpd = req->httpd;
885
886 /*
887 * Determine total response size.
888 */
889 isc_buffer_usedregion(req->sendbuffer, &r);
890
891 isc_nm_send(httpd->handle, &r, httpd_senddone, req);
892 }
893
894 static void
895 httpd_request(isc_nmhandle_t *handle, isc_result_t eresult,
896 isc_region_t *region, void *arg) {
897 isc_httpd_t *httpd = arg;
898 isc_httpdmgr_t *mgr = httpd->mgr;
899 size_t last_len = 0;
900 isc_result_t result;
901
902 REQUIRE(VALID_HTTPD(httpd));
903
904 REQUIRE(httpd->handle == handle);
905
906 if (eresult != ISC_R_SUCCESS) {
907 goto close_readhandle;
908 }
909
910 REQUIRE((mgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) == 0);
911
912 isc_nm_read_stop(handle);
913
914 /*
915 * If we are being called from httpd_senddone(), the last HTTP request
916 * was processed successfully, reset the last_len to 0, even if there's
917 * data in the httpd->recvbuf.
918 */
919 last_len = (region == NULL) ? 0 : httpd->recvlen;
920
921 /* Store the received data into the recvbuf */
922 if (region != NULL) {
923 if (httpd->recvlen + region->length > sizeof(httpd->recvbuf)) {
924 goto close_readhandle;
925 }
926
927 memmove(httpd->recvbuf + httpd->recvlen, region->base,
928 region->length);
929 httpd->recvlen += region->length;
930 }
931
932 result = process_request(httpd, last_len);
933
934 if (result == ISC_R_NOMORE) {
935 if (httpd->recvlen > HTTP_MAX_REQUEST_LEN) {
936 goto close_readhandle;
937 }
938
939 /* Wait for more data, the handle is still attached */
940 isc_nm_read(handle, httpd_request, arg);
941 return;
942 }
943
944 /* XXXFANF it would be more polite to reply 400 bad request */
945 if (result != ISC_R_SUCCESS) {
946 goto close_readhandle;
947 }
948
949 isc_httpd_sendreq_t *req = isc__httpd_sendreq_new(httpd);
950 isc_nmhandle_ref(handle);
951 isc_work_enqueue(isc_loop(), prepare_response, prepare_response_done,
952 req);
953 return;
954
955 close_readhandle:
956 isc_nmhandle_close(httpd->handle);
957 isc_nmhandle_detach(&httpd->handle);
958
959 isc_httpd_detach(&httpd);
960 }
961
962 void
963 isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
964 isc_httpdmgr_t *httpdmgr = NULL;
965
966 REQUIRE(httpdmgrp != NULL);
967 REQUIRE(VALID_HTTPDMGR(*httpdmgrp));
968
969 httpdmgr = *httpdmgrp;
970 *httpdmgrp = NULL;
971
972 isc_nm_stoplistening(httpdmgr->sock);
973
974 LOCK(&httpdmgr->lock);
975
976 isc_httpd_t *httpd = NULL, *next = NULL;
977 ISC_LIST_FOREACH_SAFE(httpdmgr->running, httpd, link, next) {
978 if (httpd->handle != NULL) {
979 httpd_request(httpd->handle, ISC_R_SUCCESS, NULL,
980 httpd);
981 }
982 }
983
984 httpdmgr->flags |= ISC_HTTPDMGR_SHUTTINGDOWN;
985
986 UNLOCK(&httpdmgr->lock);
987
988 isc_nmsocket_close(&httpdmgr->sock);
989
990 isc_httpdmgr_detach(&httpdmgr);
991 }
992
993 static void
994 httpd_response(isc_httpd_t *httpd, isc_httpd_sendreq_t *req) {
995 isc_result_t result;
996
997 result = isc_buffer_printf(req->sendbuffer, "HTTP/1.%u %03u %s\r\n",
998 httpd->minor_version, req->retcode,
999 req->retmsg);
1000
1001 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1002 }
1003
1004 static void
1005 httpd_addheader(isc_httpd_sendreq_t *req, const char *name, const char *val) {
1006 isc_result_t result;
1007
1008 if (val != NULL) {
1009 result = isc_buffer_printf(req->sendbuffer, "%s: %s\r\n", name,
1010 val);
1011 } else {
1012 result = isc_buffer_printf(req->sendbuffer, "%s\r\n", name);
1013 }
1014
1015 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1016 }
1017
1018 static void
1019 httpd_endheaders(isc_httpd_sendreq_t *req) {
1020 isc_result_t result;
1021
1022 result = isc_buffer_printf(req->sendbuffer, "\r\n");
1023
1024 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1025 }
1026
1027 static void
1028 httpd_addheaderuint(isc_httpd_sendreq_t *req, const char *name, int val) {
1029 isc_result_t result;
1030
1031 result = isc_buffer_printf(req->sendbuffer, "%s: %d\r\n", name, val);
1032
1033 RUNTIME_CHECK(result == ISC_R_SUCCESS);
1034 }
1035
1036 static void
1037 httpd_senddone(isc_nmhandle_t *handle, isc_result_t eresult, void *arg) {
1038 isc_httpd_sendreq_t *req = (isc_httpd_sendreq_t *)arg;
1039 isc_httpd_t *httpd = req->httpd;
1040
1041 REQUIRE(VALID_HTTPD(httpd));
1042
1043 if ((httpd->mgr->flags & ISC_HTTPDMGR_SHUTTINGDOWN) != 0) {
1044 goto detach;
1045 }
1046
1047 if (eresult == ISC_R_SUCCESS && (httpd->flags & CONNECTION_CLOSE) != 0)
1048 {
1049 eresult = ISC_R_EOF;
1050 }
1051
1052 /*
1053 * Calling httpd_request() with region NULL restarts reading.
1054 */
1055 httpd_request(handle, eresult, NULL, httpd);
1056
1057 detach:
1058 isc_nmhandle_detach(&handle);
1059 isc__httpd_sendreq_free(req);
1060 isc_httpd_detach(&httpd);
1061 }
1062
1063 isc_result_t
1064 isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url, bool isstatic,
1065 isc_httpdaction_t *func, void *arg) {
1066 isc_httpdurl_t *item;
1067
1068 REQUIRE(VALID_HTTPDMGR(httpdmgr));
1069
1070 if (url == NULL) {
1071 httpdmgr->render_404 = func;
1072 return ISC_R_SUCCESS;
1073 }
1074
1075 item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t));
1076
1077 item->url = isc_mem_strdup(httpdmgr->mctx, url);
1078
1079 item->action = func;
1080 item->action_arg = arg;
1081 item->isstatic = isstatic;
1082 item->loadtime = isc_time_now();
1083
1084 ISC_LINK_INIT(item, link);
1085
1086 LOCK(&httpdmgr->lock);
1087 ISC_LIST_APPEND(httpdmgr->urls, item, link);
1088 UNLOCK(&httpdmgr->lock);
1089
1090 return ISC_R_SUCCESS;
1091 }
1092
1093 void
1094 isc_httpd_setfinishhook(void (*fn)(void)) {
1095 #if ENABLE_AFL
1096 finishhook = fn;
1097 #else /* ENABLE_AFL */
1098 UNUSED(fn);
1099 #endif /* ENABLE_AFL */
1100 }
1101
1102 bool
1103 isc_httpdurl_isstatic(const isc_httpdurl_t *url) {
1104 return url->isstatic;
1105 }
1106
1107 const isc_time_t *
1108 isc_httpdurl_loadtime(const isc_httpdurl_t *url) {
1109 return &url->loadtime;
1110 }
1111
1112 const isc_time_t *
1113 isc_httpd_if_modified_since(const isc_httpd_t *httpd) {
1114 return (const isc_time_t *)&httpd->if_modified_since;
1115 }
1116