httpd.c revision 1.2.2.2 1 /* $NetBSD: httpd.c,v 1.2.2.2 2018/09/06 06:55:05 pgoyette Exp $ */
2
3 /*
4 * Copyright (C) Internet Systems Consortium, Inc. ("ISC")
5 *
6 * This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 *
10 * See the COPYRIGHT file distributed with this work for additional
11 * information regarding copyright ownership.
12 */
13
14
15 /*! \file */
16
17 #include <config.h>
18
19 #include <isc/buffer.h>
20 #include <isc/httpd.h>
21 #include <isc/mem.h>
22 #include <isc/print.h>
23 #include <isc/socket.h>
24 #include <isc/string.h>
25 #include <isc/task.h>
26 #include <isc/time.h>
27 #include <isc/util.h>
28
29 #include <string.h>
30
31 #ifdef HAVE_ZLIB
32 #include <zlib.h>
33 #endif
34
35 /*%
36 * TODO:
37 *
38 * o Put in better checks to make certain things are passed in correctly.
39 * This includes a magic number for externally-visible structures,
40 * checking for NULL-ness before dereferencing, etc.
41 * o Make the URL processing external functions which will fill-in a buffer
42 * structure we provide, or return an error and we will render a generic
43 * page and close the client.
44 */
45
46 #define MSHUTTINGDOWN(cm) ((cm->flags & ISC_HTTPDMGR_FLAGSHUTTINGDOWN) != 0)
47 #define MSETSHUTTINGDOWN(cm) (cm->flags |= ISC_HTTPDMGR_FLAGSHUTTINGDOWN)
48
49 #ifdef DEBUG_HTTPD
50 #define ENTER(x) do { fprintf(stderr, "ENTER %s\n", (x)); } while (/*CONSTCOND*/0)
51 #define EXIT(x) do { fprintf(stderr, "EXIT %s\n", (x)); } while (/*CONSTCOND*/0)
52 #define NOTICE(x) do { fprintf(stderr, "NOTICE %s\n", (x)); } while (/*CONSTCOND*/0)
53 #else
54 #define ENTER(x) do { } while(/*CONSTCOND*/0)
55 #define EXIT(x) do { } while(/*CONSTCOND*/0)
56 #define NOTICE(x) do { } while(/*CONSTCOND*/0)
57 #endif
58
59 #define HTTP_RECVLEN 1024
60 #define HTTP_SENDGROW 1024
61 #define HTTP_SEND_MAXLEN 10240
62
63 #define HTTPD_CLOSE 0x0001 /* Got a Connection: close header */
64 #define HTTPD_FOUNDHOST 0x0002 /* Got a Host: header */
65 #define HTTPD_KEEPALIVE 0x0004 /* Got a Connection: Keep-Alive */
66 #define HTTPD_ACCEPT_DEFLATE 0x0008
67
68 /*% http client */
69 struct isc_httpd {
70 isc_httpdmgr_t *mgr; /*%< our parent */
71 ISC_LINK(isc_httpd_t) link;
72 unsigned int state;
73 isc_socket_t *sock;
74
75 /*%
76 * Received data state.
77 */
78 char recvbuf[HTTP_RECVLEN]; /*%< receive buffer */
79 isc_uint32_t recvlen; /*%< length recv'd */
80 char *headers; /*%< set in process_request() */
81 unsigned int method;
82 char *url;
83 char *querystring;
84 char *protocol;
85
86 /*
87 * Flags on the httpd client.
88 */
89 int flags;
90
91 /*%
92 * Transmit data state.
93 *
94 * This is the data buffer we will transmit.
95 *
96 * This free function pointer is filled in by the rendering function
97 * we call. The free function is called after the data is transmitted
98 * to the client.
99 *
100 * The bufflist is the list of buffers we are currently transmitting.
101 * The headerbuffer is where we render our headers to. If we run out of
102 * space when rendering a header, we will change the size of our
103 * buffer. We will not free it until we are finished, and will
104 * allocate an additional HTTP_SENDGROW bytes per header space grow.
105 *
106 * We currently use three buffers total, one for the headers (which
107 * we manage), another for the client to fill in (which it manages,
108 * it provides the space for it, etc) -- we will pass that buffer
109 * structure back to the caller, who is responsible for managing the
110 * space it may have allocated as backing store for it. This second
111 * buffer is bodybuffer, and we only allocate the buffer itself, not
112 * the backing store.
113 * The third buffer is compbuffer, managed by us, that contains the
114 * compressed HTTP data, if compression is used.
115 *
116 */
117 isc_bufferlist_t bufflist;
118 isc_buffer_t headerbuffer;
119 isc_buffer_t compbuffer;
120
121 const char *mimetype;
122 unsigned int retcode;
123 const char *retmsg;
124 isc_buffer_t bodybuffer;
125 isc_httpdfree_t *freecb;
126 void *freecb_arg;
127 };
128
129 /*% lightweight socket manager for httpd output */
130 struct isc_httpdmgr {
131 isc_mem_t *mctx;
132 isc_socket_t *sock; /*%< listening socket */
133 isc_task_t *task; /*%< owning task */
134 isc_timermgr_t *timermgr;
135
136 isc_httpdclientok_t *client_ok; /*%< client validator */
137 isc_httpdondestroy_t *ondestroy; /*%< cleanup callback */
138 void *cb_arg; /*%< argument for the above */
139
140 unsigned int flags;
141 ISC_LIST(isc_httpd_t) running; /*%< running clients */
142
143 isc_mutex_t lock;
144
145 ISC_LIST(isc_httpdurl_t) urls; /*%< urls we manage */
146 isc_httpdaction_t *render_404;
147 isc_httpdaction_t *render_500;
148 };
149
150 /*%
151 * HTTP methods.
152 */
153 #define ISC_HTTPD_METHODUNKNOWN 0
154 #define ISC_HTTPD_METHODGET 1
155 #define ISC_HTTPD_METHODPOST 2
156
157 /*%
158 * Client states.
159 *
160 * _IDLE The client is not doing anything at all. This state should
161 * only occur just after creation, and just before being
162 * destroyed.
163 *
164 * _RECV The client is waiting for data after issuing a socket recv().
165 *
166 * _RECVDONE Data has been received, and is being processed.
167 *
168 * _SEND All data for a response has completed, and a reply was
169 * sent via a socket send() call.
170 *
171 * _SENDDONE Send is completed.
172 *
173 * Badly formatted state table:
174 *
175 * IDLE -> RECV when client has a recv() queued.
176 *
177 * RECV -> RECVDONE when recvdone event received.
178 *
179 * RECVDONE -> SEND if the data for a reply is at hand.
180 *
181 * SEND -> RECV when a senddone event was received.
182 *
183 * At any time -> RECV on error. If RECV fails, the client will
184 * self-destroy, closing the socket and freeing memory.
185 */
186 #define ISC_HTTPD_STATEIDLE 0
187 #define ISC_HTTPD_STATERECV 1
188 #define ISC_HTTPD_STATERECVDONE 2
189 #define ISC_HTTPD_STATESEND 3
190 #define ISC_HTTPD_STATESENDDONE 4
191
192 #define ISC_HTTPD_ISRECV(c) ((c)->state == ISC_HTTPD_STATERECV)
193 #define ISC_HTTPD_ISRECVDONE(c) ((c)->state == ISC_HTTPD_STATERECVDONE)
194 #define ISC_HTTPD_ISSEND(c) ((c)->state == ISC_HTTPD_STATESEND)
195 #define ISC_HTTPD_ISSENDDONE(c) ((c)->state == ISC_HTTPD_STATESENDDONE)
196
197 /*%
198 * Overall magic test that means we're not idle.
199 */
200 #define ISC_HTTPD_SETRECV(c) ((c)->state = ISC_HTTPD_STATERECV)
201 #define ISC_HTTPD_SETRECVDONE(c) ((c)->state = ISC_HTTPD_STATERECVDONE)
202 #define ISC_HTTPD_SETSEND(c) ((c)->state = ISC_HTTPD_STATESEND)
203 #define ISC_HTTPD_SETSENDDONE(c) ((c)->state = ISC_HTTPD_STATESENDDONE)
204
205 static void isc_httpd_accept(isc_task_t *, isc_event_t *);
206 static void isc_httpd_recvdone(isc_task_t *, isc_event_t *);
207 static void isc_httpd_senddone(isc_task_t *, isc_event_t *);
208 static void destroy_client(isc_httpd_t **);
209 static isc_result_t process_request(isc_httpd_t *, int);
210 static void httpdmgr_destroy(isc_httpdmgr_t *);
211 static isc_result_t grow_headerspace(isc_httpd_t *);
212 static void reset_client(isc_httpd_t *httpd);
213
214 static isc_httpdaction_t render_404;
215 static isc_httpdaction_t render_500;
216
217 static void (*finishhook)(void) = NULL;
218
219 static void
220 destroy_client(isc_httpd_t **httpdp) {
221 isc_httpd_t *httpd = *httpdp;
222 isc_httpdmgr_t *httpdmgr = httpd->mgr;
223 isc_region_t r;
224
225 *httpdp = NULL;
226
227 LOCK(&httpdmgr->lock);
228
229 isc_socket_detach(&httpd->sock);
230 ISC_LIST_UNLINK(httpdmgr->running, httpd, link);
231
232 isc_buffer_region(&httpd->headerbuffer, &r);
233 if (r.length > 0) {
234 isc_mem_put(httpdmgr->mctx, r.base, r.length);
235 }
236
237 isc_buffer_region(&httpd->compbuffer, &r);
238 if (r.length > 0) {
239 isc_mem_put(httpdmgr->mctx, r.base, r.length);
240 }
241
242 isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t));
243
244 UNLOCK(&httpdmgr->lock);
245
246 if (finishhook != NULL)
247 finishhook();
248
249 httpdmgr_destroy(httpdmgr);
250 }
251
252 isc_result_t
253 isc_httpdmgr_create(isc_mem_t *mctx, isc_socket_t *sock, isc_task_t *task,
254 isc_httpdclientok_t *client_ok,
255 isc_httpdondestroy_t *ondestroy, void *cb_arg,
256 isc_timermgr_t *tmgr, isc_httpdmgr_t **httpdmgrp)
257 {
258 isc_result_t result;
259 isc_httpdmgr_t *httpdmgr;
260
261 REQUIRE(mctx != NULL);
262 REQUIRE(sock != NULL);
263 REQUIRE(task != NULL);
264 REQUIRE(tmgr != NULL);
265 REQUIRE(httpdmgrp != NULL && *httpdmgrp == NULL);
266
267 httpdmgr = isc_mem_get(mctx, sizeof(isc_httpdmgr_t));
268 if (httpdmgr == NULL)
269 return (ISC_R_NOMEMORY);
270
271 result = isc_mutex_init(&httpdmgr->lock);
272 if (result != ISC_R_SUCCESS) {
273 isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
274 return (result);
275 }
276 httpdmgr->mctx = NULL;
277 isc_mem_attach(mctx, &httpdmgr->mctx);
278 httpdmgr->sock = NULL;
279 isc_socket_attach(sock, &httpdmgr->sock);
280 httpdmgr->task = NULL;
281 isc_task_attach(task, &httpdmgr->task);
282 httpdmgr->timermgr = tmgr; /* XXXMLG no attach function? */
283 httpdmgr->client_ok = client_ok;
284 httpdmgr->ondestroy = ondestroy;
285 httpdmgr->cb_arg = cb_arg;
286 httpdmgr->flags = 0;
287
288 ISC_LIST_INIT(httpdmgr->running);
289 ISC_LIST_INIT(httpdmgr->urls);
290
291 /* XXXMLG ignore errors on isc_socket_listen() */
292 result = isc_socket_listen(sock, SOMAXCONN);
293 if (result != ISC_R_SUCCESS) {
294 UNEXPECTED_ERROR(__FILE__, __LINE__,
295 "isc_socket_listen() failed: %s",
296 isc_result_totext(result));
297 goto cleanup;
298 }
299
300 (void)isc_socket_filter(sock, "httpready");
301
302 result = isc_socket_accept(sock, task, isc_httpd_accept, httpdmgr);
303 if (result != ISC_R_SUCCESS)
304 goto cleanup;
305
306 httpdmgr->render_404 = render_404;
307 httpdmgr->render_500 = render_500;
308
309 *httpdmgrp = httpdmgr;
310 return (ISC_R_SUCCESS);
311
312 cleanup:
313 isc_task_detach(&httpdmgr->task);
314 isc_socket_detach(&httpdmgr->sock);
315 isc_mem_detach(&httpdmgr->mctx);
316 (void)isc_mutex_destroy(&httpdmgr->lock);
317 isc_mem_put(mctx, httpdmgr, sizeof(isc_httpdmgr_t));
318 return (result);
319 }
320
321 static void
322 httpdmgr_destroy(isc_httpdmgr_t *httpdmgr) {
323 isc_mem_t *mctx;
324 isc_httpdurl_t *url;
325
326 ENTER("httpdmgr_destroy");
327
328 LOCK(&httpdmgr->lock);
329
330 if (!MSHUTTINGDOWN(httpdmgr)) {
331 NOTICE("httpdmgr_destroy not shutting down yet");
332 UNLOCK(&httpdmgr->lock);
333 return;
334 }
335
336 /*
337 * If all clients are not shut down, don't do anything yet.
338 */
339 if (!ISC_LIST_EMPTY(httpdmgr->running)) {
340 NOTICE("httpdmgr_destroy clients still active");
341 UNLOCK(&httpdmgr->lock);
342 return;
343 }
344
345 NOTICE("httpdmgr_destroy detaching socket, task, and timermgr");
346
347 isc_socket_detach(&httpdmgr->sock);
348 isc_task_detach(&httpdmgr->task);
349 httpdmgr->timermgr = NULL;
350
351 /*
352 * Clear out the list of all actions we know about. Just free the
353 * memory.
354 */
355 url = ISC_LIST_HEAD(httpdmgr->urls);
356 while (url != NULL) {
357 isc_mem_free(httpdmgr->mctx, url->url);
358 ISC_LIST_UNLINK(httpdmgr->urls, url, link);
359 isc_mem_put(httpdmgr->mctx, url, sizeof(isc_httpdurl_t));
360 url = ISC_LIST_HEAD(httpdmgr->urls);
361 }
362
363 UNLOCK(&httpdmgr->lock);
364 (void)isc_mutex_destroy(&httpdmgr->lock);
365
366 if (httpdmgr->ondestroy != NULL)
367 (httpdmgr->ondestroy)(httpdmgr->cb_arg);
368
369 mctx = httpdmgr->mctx;
370 isc_mem_putanddetach(&mctx, httpdmgr, sizeof(isc_httpdmgr_t));
371
372 EXIT("httpdmgr_destroy");
373 }
374
375 #define LENGTHOK(s) (httpd->recvbuf - (s) < (int)httpd->recvlen)
376 #define BUFLENOK(s) (httpd->recvbuf - (s) < HTTP_RECVLEN)
377
378 /*
379 * Look for the given header in headers.
380 * If value is specified look for it terminated with a character in eov.
381 */
382 static isc_boolean_t
383 have_header(isc_httpd_t *httpd, const char *header, const char *value,
384 const char *eov)
385 {
386 char *cr, *nl, *h;
387 size_t hlen, vlen = 0;
388
389 h = httpd->headers;
390 hlen = strlen(header);
391 if (value != NULL) {
392 INSIST(eov != NULL);
393 vlen = strlen(value);
394 }
395
396 for (;;) {
397 if (strncasecmp(h, header, hlen) != 0) {
398 /*
399 * Skip to next line;
400 */
401 cr = strchr(h, '\r');
402 if (cr != NULL && cr[1] == '\n')
403 cr++;
404 nl = strchr(h, '\n');
405
406 /* last header? */
407 h = cr;
408 if (h == NULL || (nl != NULL && nl < h))
409 h = nl;
410 if (h == NULL)
411 return (ISC_FALSE);
412 h++;
413 continue;
414 }
415
416 if (value == NULL)
417 return (ISC_TRUE);
418
419 /*
420 * Skip optional leading white space.
421 */
422 h += hlen;
423 while (*h == ' ' || *h == '\t')
424 h++;
425 /*
426 * Terminate token search on NULL or EOL.
427 */
428 while (*h != 0 && *h != '\r' && *h != '\n') {
429 if (strncasecmp(h, value, vlen) == 0)
430 if (strchr(eov, h[vlen]) != NULL)
431 return (ISC_TRUE);
432 /*
433 * Skip to next token.
434 */
435 h += strcspn(h, eov);
436 if (h[0] == '\r' && h[1] == '\n')
437 h++;
438 if (h[0] != 0)
439 h++;
440 }
441 return (ISC_FALSE);
442 }
443 }
444
445 static isc_result_t
446 process_request(isc_httpd_t *httpd, int length) {
447 char *s;
448 char *p;
449 int delim;
450
451 ENTER("request");
452
453 httpd->recvlen += length;
454
455 httpd->recvbuf[httpd->recvlen] = 0;
456 httpd->headers = NULL;
457
458 /*
459 * If we don't find a blank line in our buffer, return that we need
460 * more data.
461 */
462 s = strstr(httpd->recvbuf, "\r\n\r\n");
463 delim = 2;
464 if (s == NULL) {
465 s = strstr(httpd->recvbuf, "\n\n");
466 delim = 1;
467 }
468 if (s == NULL)
469 return (ISC_R_NOTFOUND);
470
471 /*
472 * NUL terminate request at the blank line.
473 */
474 s[delim] = 0;
475
476 /*
477 * Determine if this is a POST or GET method. Any other values will
478 * cause an error to be returned.
479 */
480 if (strncmp(httpd->recvbuf, "GET ", 4) == 0) {
481 httpd->method = ISC_HTTPD_METHODGET;
482 p = httpd->recvbuf + 4;
483 } else if (strncmp(httpd->recvbuf, "POST ", 5) == 0) {
484 httpd->method = ISC_HTTPD_METHODPOST;
485 p = httpd->recvbuf + 5;
486 } else {
487 return (ISC_R_RANGE);
488 }
489
490 /*
491 * From now on, p is the start of our buffer.
492 */
493
494 /*
495 * Extract the URL.
496 */
497 s = p;
498 while (LENGTHOK(s) && BUFLENOK(s) &&
499 (*s != '\n' && *s != '\r' && *s != '\0' && *s != ' '))
500 s++;
501 if (!LENGTHOK(s))
502 return (ISC_R_NOTFOUND);
503 if (!BUFLENOK(s))
504 return (ISC_R_NOMEMORY);
505 *s = 0;
506
507 /*
508 * Make the URL relative.
509 */
510 if ((strncmp(p, "http:/", 6) == 0)
511 || (strncmp(p, "https:/", 7) == 0)) {
512 /* Skip first / */
513 while (*p != '/' && *p != 0)
514 p++;
515 if (*p == 0)
516 return (ISC_R_RANGE);
517 p++;
518 /* Skip second / */
519 while (*p != '/' && *p != 0)
520 p++;
521 if (*p == 0)
522 return (ISC_R_RANGE);
523 p++;
524 /* Find third / */
525 while (*p != '/' && *p != 0)
526 p++;
527 if (*p == 0) {
528 p--;
529 *p = '/';
530 }
531 }
532
533 httpd->url = p;
534 p = s + 1;
535 s = p;
536
537 /*
538 * Now, see if there is a ? mark in the URL. If so, this is
539 * part of the query string, and we will split it from the URL.
540 */
541 httpd->querystring = strchr(httpd->url, '?');
542 if (httpd->querystring != NULL) {
543 *(httpd->querystring) = 0;
544 httpd->querystring++;
545 }
546
547 /*
548 * Extract the HTTP/1.X protocol. We will bounce on anything but
549 * HTTP/1.0 or HTTP/1.1 for now.
550 */
551 while (LENGTHOK(s) && BUFLENOK(s) &&
552 (*s != '\n' && *s != '\r' && *s != '\0'))
553 s++;
554 if (!LENGTHOK(s))
555 return (ISC_R_NOTFOUND);
556 if (!BUFLENOK(s))
557 return (ISC_R_NOMEMORY);
558 /*
559 * Check that we have the expected eol delimiter.
560 */
561 if (strncmp(s, delim == 1 ? "\n" : "\r\n", delim) != 0)
562 return (ISC_R_RANGE);
563 *s = 0;
564 if ((strncmp(p, "HTTP/1.0", 8) != 0)
565 && (strncmp(p, "HTTP/1.1", 8) != 0))
566 return (ISC_R_RANGE);
567 httpd->protocol = p;
568 p = s + delim; /* skip past eol */
569 s = p;
570
571 httpd->headers = s;
572
573 if (have_header(httpd, "Connection:", "close", ", \t\r\n"))
574 httpd->flags |= HTTPD_CLOSE;
575
576 if (have_header(httpd, "Host:", NULL, NULL))
577 httpd->flags |= HTTPD_FOUNDHOST;
578
579 if (strncmp(httpd->protocol, "HTTP/1.0", 8) == 0) {
580 if (have_header(httpd, "Connection:", "Keep-Alive",
581 ", \t\r\n"))
582 httpd->flags |= HTTPD_KEEPALIVE;
583 else
584 httpd->flags |= HTTPD_CLOSE;
585 }
586
587 /*
588 * Check for Accept-Encoding:
589 */
590 #ifdef HAVE_ZLIB
591 if (have_header(httpd, "Accept-Encoding:", "deflate", ";, \t\r\n"))
592 httpd->flags |= HTTPD_ACCEPT_DEFLATE;
593 #endif
594
595 /*
596 * Standards compliance hooks here.
597 */
598 if (strcmp(httpd->protocol, "HTTP/1.1") == 0
599 && ((httpd->flags & HTTPD_FOUNDHOST) == 0))
600 return (ISC_R_RANGE);
601
602 EXIT("request");
603
604 return (ISC_R_SUCCESS);
605 }
606
607 static void
608 isc_httpd_accept(isc_task_t *task, isc_event_t *ev) {
609 isc_result_t result;
610 isc_httpdmgr_t *httpdmgr = ev->ev_arg;
611 isc_httpd_t *httpd;
612 isc_region_t r;
613 isc_socket_newconnev_t *nev = (isc_socket_newconnev_t *)ev;
614 isc_sockaddr_t peeraddr;
615 char *headerdata;
616
617 ENTER("accept");
618
619 LOCK(&httpdmgr->lock);
620 if (MSHUTTINGDOWN(httpdmgr)) {
621 NOTICE("accept shutting down, goto out");
622 goto out;
623 }
624
625 if (nev->result == ISC_R_CANCELED) {
626 NOTICE("accept canceled, goto out");
627 goto out;
628 }
629
630 if (nev->result != ISC_R_SUCCESS) {
631 /* XXXMLG log failure */
632 NOTICE("accept returned failure, goto requeue");
633 goto requeue;
634 }
635
636 (void)isc_socket_getpeername(nev->newsocket, &peeraddr);
637 if (httpdmgr->client_ok != NULL &&
638 !(httpdmgr->client_ok)(&peeraddr, httpdmgr->cb_arg)) {
639 isc_socket_detach(&nev->newsocket);
640 goto requeue;
641 }
642
643 httpd = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpd_t));
644 if (httpd == NULL) {
645 /* XXXMLG log failure */
646 NOTICE("accept failed to allocate memory, goto requeue");
647 isc_socket_detach(&nev->newsocket);
648 goto requeue;
649 }
650
651 httpd->mgr = httpdmgr;
652 ISC_LINK_INIT(httpd, link);
653 ISC_LIST_APPEND(httpdmgr->running, httpd, link);
654 ISC_HTTPD_SETRECV(httpd);
655 httpd->sock = nev->newsocket;
656 isc_socket_setname(httpd->sock, "httpd", NULL);
657 httpd->flags = 0;
658
659 /*
660 * Initialize the buffer for our headers.
661 */
662 headerdata = isc_mem_get(httpdmgr->mctx, HTTP_SENDGROW);
663 if (headerdata == NULL) {
664 isc_mem_put(httpdmgr->mctx, httpd, sizeof(isc_httpd_t));
665 isc_socket_detach(&nev->newsocket);
666 goto requeue;
667 }
668 isc_buffer_init(&httpd->headerbuffer, headerdata, HTTP_SENDGROW);
669
670 ISC_LIST_INIT(httpd->bufflist);
671
672 isc_buffer_initnull(&httpd->compbuffer);
673 isc_buffer_initnull(&httpd->bodybuffer);
674 reset_client(httpd);
675
676 r.base = (unsigned char *)httpd->recvbuf;
677 r.length = HTTP_RECVLEN - 1;
678 result = isc_socket_recv(httpd->sock, &r, 1, task, isc_httpd_recvdone,
679 httpd);
680 /* FIXME!!! */
681 POST(result);
682 NOTICE("accept queued recv on socket");
683
684 requeue:
685 result = isc_socket_accept(httpdmgr->sock, task, isc_httpd_accept,
686 httpdmgr);
687 if (result != ISC_R_SUCCESS) {
688 /* XXXMLG what to do? Log failure... */
689 NOTICE("accept could not reaccept due to failure");
690 }
691
692 out:
693 UNLOCK(&httpdmgr->lock);
694
695 httpdmgr_destroy(httpdmgr);
696
697 isc_event_free(&ev);
698
699 EXIT("accept");
700 }
701
702 static isc_result_t
703 render_404(const char *url, isc_httpdurl_t *urlinfo,
704 const char *querystring, const char *headers, void *arg,
705 unsigned int *retcode, const char **retmsg,
706 const char **mimetype, isc_buffer_t *b,
707 isc_httpdfree_t **freecb, void **freecb_args)
708 {
709 static char msg[] = "No such URL.\r\n";
710
711 UNUSED(url);
712 UNUSED(urlinfo);
713 UNUSED(querystring);
714 UNUSED(headers);
715 UNUSED(arg);
716
717 *retcode = 404;
718 *retmsg = "No such URL";
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 static isc_result_t
729 render_500(const char *url, isc_httpdurl_t *urlinfo,
730 const char *querystring, const char *headers, void *arg,
731 unsigned int *retcode, const char **retmsg,
732 const char **mimetype, isc_buffer_t *b,
733 isc_httpdfree_t **freecb, void **freecb_args)
734 {
735 static char msg[] = "Internal server failure.\r\n";
736
737 UNUSED(url);
738 UNUSED(urlinfo);
739 UNUSED(querystring);
740 UNUSED(headers);
741 UNUSED(arg);
742
743 *retcode = 500;
744 *retmsg = "Internal server failure";
745 *mimetype = "text/plain";
746 isc_buffer_reinit(b, msg, strlen(msg));
747 isc_buffer_add(b, strlen(msg));
748 *freecb = NULL;
749 *freecb_args = NULL;
750
751 return (ISC_R_SUCCESS);
752 }
753
754 #ifdef HAVE_ZLIB
755 /*%<
756 * Reallocates compbuffer to size, does nothing if compbuffer is already
757 * larger than size.
758 *
759 * Requires:
760 *\li httpd a valid isc_httpd_t object
761 *
762 * Returns:
763 *\li #ISC_R_SUCCESS -- all is well.
764 *\li #ISC_R_NOMEMORY -- not enough memory to extend buffer
765 */
766 static isc_result_t
767 alloc_compspace(isc_httpd_t *httpd, unsigned int size) {
768 char *newspace;
769 isc_region_t r;
770
771 isc_buffer_region(&httpd->compbuffer, &r);
772 if (size < r.length) {
773 return (ISC_R_SUCCESS);
774 }
775
776 newspace = isc_mem_get(httpd->mgr->mctx, size);
777 if (newspace == NULL)
778 return (ISC_R_NOMEMORY);
779 isc_buffer_reinit(&httpd->compbuffer, newspace, size);
780
781 if (r.base != NULL) {
782 isc_mem_put(httpd->mgr->mctx, r.base, r.length);
783 }
784
785 return (ISC_R_SUCCESS);
786 }
787
788 /*%<
789 * Tries to compress httpd->bodybuffer to httpd->compbuffer, extending it
790 * if necessary.
791 *
792 * Requires:
793 *\li httpd a valid isc_httpd_t object
794 *
795 * Returns:
796 *\li #ISC_R_SUCCESS -- all is well.
797 *\li #ISC_R_NOMEMORY -- not enough memory to compress data
798 *\li #ISC_R_FAILURE -- error during compression or compressed
799 * data would be larger than input data
800 */
801 static isc_result_t
802 isc_httpd_compress(isc_httpd_t *httpd) {
803 z_stream zstr;
804 isc_region_t r;
805 isc_result_t result;
806 int ret;
807 int inputlen;
808
809 inputlen = isc_buffer_usedlength(&httpd->bodybuffer);
810 result = alloc_compspace(httpd, inputlen);
811 if (result != ISC_R_SUCCESS) {
812 return result;
813 }
814 isc_buffer_region(&httpd->compbuffer, &r);
815
816 /*
817 * We're setting output buffer size to input size so it fails if the
818 * compressed data size would be bigger than the input size.
819 */
820 memset(&zstr, 0, sizeof(zstr));
821 zstr.total_in = zstr.avail_in =
822 zstr.total_out = zstr.avail_out = inputlen;
823
824 zstr.next_in = isc_buffer_base(&httpd->bodybuffer);
825 zstr.next_out = r.base;
826
827 ret = deflateInit(&zstr, Z_DEFAULT_COMPRESSION);
828 if (ret == Z_OK) {
829 ret = deflate(&zstr, Z_FINISH);
830 }
831 deflateEnd(&zstr);
832 if (ret == Z_STREAM_END) {
833 isc_buffer_add(&httpd->compbuffer, inputlen - zstr.avail_out);
834 return (ISC_R_SUCCESS);
835 } else {
836 return (ISC_R_FAILURE);
837 }
838 }
839 #endif
840
841 static void
842 isc_httpd_recvdone(isc_task_t *task, isc_event_t *ev) {
843 isc_region_t r;
844 isc_result_t result;
845 isc_httpd_t *httpd = ev->ev_arg;
846 isc_socketevent_t *sev = (isc_socketevent_t *)ev;
847 isc_httpdurl_t *url;
848 isc_time_t now;
849 isc_boolean_t is_compressed = ISC_FALSE;
850 char datebuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
851
852 ENTER("recv");
853
854 INSIST(ISC_HTTPD_ISRECV(httpd));
855
856 if (sev->result != ISC_R_SUCCESS) {
857 NOTICE("recv destroying client");
858 destroy_client(&httpd);
859 goto out;
860 }
861
862 result = process_request(httpd, sev->n);
863 if (result == ISC_R_NOTFOUND) {
864 if (httpd->recvlen >= HTTP_RECVLEN - 1) {
865 destroy_client(&httpd);
866 goto out;
867 }
868 r.base = (unsigned char *)httpd->recvbuf + httpd->recvlen;
869 r.length = HTTP_RECVLEN - httpd->recvlen - 1;
870 /* check return code? */
871 (void)isc_socket_recv(httpd->sock, &r, 1, task,
872 isc_httpd_recvdone, httpd);
873 goto out;
874 } else if (result != ISC_R_SUCCESS) {
875 destroy_client(&httpd);
876 goto out;
877 }
878
879 ISC_HTTPD_SETSEND(httpd);
880
881 /*
882 * XXXMLG Call function here. Provide an add-header function
883 * which will append the common headers to a response we generate.
884 */
885 isc_buffer_initnull(&httpd->bodybuffer);
886 isc_time_now(&now);
887 isc_time_formathttptimestamp(&now, datebuf, sizeof(datebuf));
888 url = ISC_LIST_HEAD(httpd->mgr->urls);
889 while (url != NULL) {
890 if (strcmp(httpd->url, url->url) == 0)
891 break;
892 url = ISC_LIST_NEXT(url, link);
893 }
894 if (url == NULL)
895 result = httpd->mgr->render_404(httpd->url, NULL,
896 httpd->querystring,
897 NULL, NULL,
898 &httpd->retcode,
899 &httpd->retmsg,
900 &httpd->mimetype,
901 &httpd->bodybuffer,
902 &httpd->freecb,
903 &httpd->freecb_arg);
904 else
905 result = url->action(httpd->url, url,
906 httpd->querystring,
907 httpd->headers,
908 url->action_arg,
909 &httpd->retcode, &httpd->retmsg,
910 &httpd->mimetype, &httpd->bodybuffer,
911 &httpd->freecb, &httpd->freecb_arg);
912 if (result != ISC_R_SUCCESS) {
913 result = httpd->mgr->render_500(httpd->url, url,
914 httpd->querystring,
915 NULL, NULL,
916 &httpd->retcode,
917 &httpd->retmsg,
918 &httpd->mimetype,
919 &httpd->bodybuffer,
920 &httpd->freecb,
921 &httpd->freecb_arg);
922 RUNTIME_CHECK(result == ISC_R_SUCCESS);
923 }
924
925 #ifdef HAVE_ZLIB
926 if (httpd->flags & HTTPD_ACCEPT_DEFLATE) {
927 result = isc_httpd_compress(httpd);
928 if (result == ISC_R_SUCCESS) {
929 is_compressed = ISC_TRUE;
930 }
931 }
932 #endif
933
934 isc_httpd_response(httpd);
935 if ((httpd->flags & HTTPD_KEEPALIVE) != 0)
936 isc_httpd_addheader(httpd, "Connection", "Keep-Alive");
937 isc_httpd_addheader(httpd, "Content-Type", httpd->mimetype);
938 isc_httpd_addheader(httpd, "Date", datebuf);
939 isc_httpd_addheader(httpd, "Expires", datebuf);
940
941 if (url != NULL && url->isstatic) {
942 char loadbuf[ISC_FORMATHTTPTIMESTAMP_SIZE];
943 isc_time_formathttptimestamp(&url->loadtime,
944 loadbuf, sizeof(loadbuf));
945 isc_httpd_addheader(httpd, "Last-Modified", loadbuf);
946 isc_httpd_addheader(httpd, "Cache-Control: public", NULL);
947 } else {
948 isc_httpd_addheader(httpd, "Last-Modified", datebuf);
949 isc_httpd_addheader(httpd, "Pragma: no-cache", NULL);
950 isc_httpd_addheader(httpd, "Cache-Control: no-cache", NULL);
951 }
952
953 isc_httpd_addheader(httpd, "Server: libisc", NULL);
954
955 if (is_compressed == ISC_TRUE) {
956 isc_httpd_addheader(httpd, "Content-Encoding", "deflate");
957 isc_httpd_addheaderuint(httpd, "Content-Length",
958 isc_buffer_usedlength(&httpd->compbuffer));
959 } else {
960 isc_httpd_addheaderuint(httpd, "Content-Length",
961 isc_buffer_usedlength(&httpd->bodybuffer));
962 }
963
964 isc_httpd_endheaders(httpd); /* done */
965
966 ISC_LIST_APPEND(httpd->bufflist, &httpd->headerbuffer, link);
967 /*
968 * Link the data buffer into our send queue, should we have any data
969 * rendered into it. If no data is present, we won't do anything
970 * with the buffer.
971 */
972 if (is_compressed == ISC_TRUE) {
973 ISC_LIST_APPEND(httpd->bufflist, &httpd->compbuffer, link);
974 } else {
975 if (isc_buffer_length(&httpd->bodybuffer) > 0) {
976 ISC_LIST_APPEND(httpd->bufflist, &httpd->bodybuffer, link);
977 }
978 }
979
980 /* check return code? */
981 (void)isc_socket_sendv(httpd->sock, &httpd->bufflist, task,
982 isc_httpd_senddone, httpd);
983
984 out:
985 isc_event_free(&ev);
986 EXIT("recv");
987 }
988
989 void
990 isc_httpdmgr_shutdown(isc_httpdmgr_t **httpdmgrp) {
991 isc_httpdmgr_t *httpdmgr;
992 isc_httpd_t *httpd;
993 httpdmgr = *httpdmgrp;
994 *httpdmgrp = NULL;
995
996 ENTER("isc_httpdmgr_shutdown");
997
998 LOCK(&httpdmgr->lock);
999
1000 MSETSHUTTINGDOWN(httpdmgr);
1001
1002 isc_socket_cancel(httpdmgr->sock, httpdmgr->task, ISC_SOCKCANCEL_ALL);
1003
1004 httpd = ISC_LIST_HEAD(httpdmgr->running);
1005 while (httpd != NULL) {
1006 isc_socket_cancel(httpd->sock, httpdmgr->task,
1007 ISC_SOCKCANCEL_ALL);
1008 httpd = ISC_LIST_NEXT(httpd, link);
1009 }
1010
1011 UNLOCK(&httpdmgr->lock);
1012
1013 EXIT("isc_httpdmgr_shutdown");
1014 }
1015
1016 static isc_result_t
1017 grow_headerspace(isc_httpd_t *httpd) {
1018 char *newspace;
1019 unsigned int newlen;
1020 isc_region_t r;
1021
1022 isc_buffer_region(&httpd->headerbuffer, &r);
1023 newlen = r.length + HTTP_SENDGROW;
1024 if (newlen > HTTP_SEND_MAXLEN)
1025 return (ISC_R_NOSPACE);
1026
1027 newspace = isc_mem_get(httpd->mgr->mctx, newlen);
1028 if (newspace == NULL)
1029 return (ISC_R_NOMEMORY);
1030
1031 isc_buffer_reinit(&httpd->headerbuffer, newspace, newlen);
1032
1033 isc_mem_put(httpd->mgr->mctx, r.base, r.length);
1034
1035 return (ISC_R_SUCCESS);
1036 }
1037
1038 isc_result_t
1039 isc_httpd_response(isc_httpd_t *httpd) {
1040 isc_result_t result;
1041 unsigned int needlen;
1042
1043 needlen = strlen(httpd->protocol) + 1; /* protocol + space */
1044 needlen += 3 + 1; /* room for response code, always 3 bytes */
1045 needlen += strlen(httpd->retmsg) + 2; /* return msg + CRLF */
1046
1047 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
1048 result = grow_headerspace(httpd);
1049 if (result != ISC_R_SUCCESS)
1050 return (result);
1051 }
1052
1053 return (isc_buffer_printf(&httpd->headerbuffer, "%s %03u %s\r\n",
1054 httpd->protocol, httpd->retcode,
1055 httpd->retmsg));
1056 }
1057
1058 isc_result_t
1059 isc_httpd_addheader(isc_httpd_t *httpd, const char *name,
1060 const char *val)
1061 {
1062 isc_result_t result;
1063 unsigned int needlen;
1064
1065 needlen = strlen(name); /* name itself */
1066 if (val != NULL)
1067 needlen += 2 + strlen(val); /* :<space> and val */
1068 needlen += 2; /* CRLF */
1069
1070 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
1071 result = grow_headerspace(httpd);
1072 if (result != ISC_R_SUCCESS)
1073 return (result);
1074 }
1075
1076 if (val != NULL) {
1077 return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n",
1078 name, val));
1079 } else {
1080 return (isc_buffer_printf(&httpd->headerbuffer, "%s\r\n",
1081 name));
1082 }
1083 }
1084
1085 isc_result_t
1086 isc_httpd_endheaders(isc_httpd_t *httpd) {
1087 isc_result_t result;
1088
1089 while (isc_buffer_availablelength(&httpd->headerbuffer) < 2) {
1090 result = grow_headerspace(httpd);
1091 if (result != ISC_R_SUCCESS)
1092 return (result);
1093 }
1094
1095 return (isc_buffer_printf(&httpd->headerbuffer, "\r\n"));
1096 }
1097
1098 isc_result_t
1099 isc_httpd_addheaderuint(isc_httpd_t *httpd, const char *name, int val) {
1100 isc_result_t result;
1101 unsigned int needlen;
1102 char buf[sizeof "18446744073709551616"];
1103
1104 snprintf(buf, sizeof(buf), "%d", val);
1105
1106 needlen = strlen(name); /* name itself */
1107 needlen += 2 + strlen(buf); /* :<space> and val */
1108 needlen += 2; /* CRLF */
1109
1110 while (isc_buffer_availablelength(&httpd->headerbuffer) < needlen) {
1111 result = grow_headerspace(httpd);
1112 if (result != ISC_R_SUCCESS)
1113 return (result);
1114 }
1115
1116 return (isc_buffer_printf(&httpd->headerbuffer, "%s: %s\r\n", name,
1117 buf));
1118 }
1119
1120 static void
1121 isc_httpd_senddone(isc_task_t *task, isc_event_t *ev) {
1122 isc_httpd_t *httpd = ev->ev_arg;
1123 isc_region_t r;
1124 isc_socketevent_t *sev = (isc_socketevent_t *)ev;
1125
1126 ENTER("senddone");
1127 INSIST(ISC_HTTPD_ISSEND(httpd));
1128
1129 /*
1130 * First, unlink our header buffer from the socket's bufflist. This
1131 * is sort of an evil hack, since we know our buffer will be there,
1132 * and we know it's address, so we can just remove it directly.
1133 */
1134 NOTICE("senddone unlinked header");
1135 ISC_LIST_UNLINK(sev->bufferlist, &httpd->headerbuffer, link);
1136
1137 /*
1138 * We will always want to clean up our receive buffer, even if we
1139 * got an error on send or we are shutting down.
1140 *
1141 * We will pass in the buffer only if there is data in it. If
1142 * there is no data, we will pass in a NULL.
1143 */
1144 if (httpd->freecb != NULL) {
1145 isc_buffer_t *b = NULL;
1146 if (isc_buffer_length(&httpd->bodybuffer) > 0) {
1147 b = &httpd->bodybuffer;
1148 httpd->freecb(b, httpd->freecb_arg);
1149 }
1150 NOTICE("senddone free callback performed");
1151 }
1152 if (ISC_LINK_LINKED(&httpd->bodybuffer, link)) {
1153 ISC_LIST_UNLINK(sev->bufferlist, &httpd->bodybuffer, link);
1154 NOTICE("senddone body buffer unlinked");
1155 } else if (ISC_LINK_LINKED(&httpd->compbuffer, link)) {
1156 ISC_LIST_UNLINK(sev->bufferlist, &httpd->compbuffer, link);
1157 NOTICE("senddone compressed data unlinked and freed");
1158 }
1159
1160 if (sev->result != ISC_R_SUCCESS) {
1161 destroy_client(&httpd);
1162 goto out;
1163 }
1164
1165 if ((httpd->flags & HTTPD_CLOSE) != 0) {
1166 destroy_client(&httpd);
1167 goto out;
1168 }
1169
1170 ISC_HTTPD_SETRECV(httpd);
1171
1172 NOTICE("senddone restarting recv on socket");
1173
1174 reset_client(httpd);
1175
1176 r.base = (unsigned char *)httpd->recvbuf;
1177 r.length = HTTP_RECVLEN - 1;
1178 /* check return code? */
1179 (void)isc_socket_recv(httpd->sock, &r, 1, task,
1180 isc_httpd_recvdone, httpd);
1181
1182 out:
1183 isc_event_free(&ev);
1184 EXIT("senddone");
1185 }
1186
1187 static void
1188 reset_client(isc_httpd_t *httpd) {
1189 /*
1190 * Catch errors here. We MUST be in RECV mode, and we MUST NOT have
1191 * any outstanding buffers. If we have buffers, we have a leak.
1192 */
1193 INSIST(ISC_HTTPD_ISRECV(httpd));
1194 INSIST(!ISC_LINK_LINKED(&httpd->headerbuffer, link));
1195 INSIST(!ISC_LINK_LINKED(&httpd->bodybuffer, link));
1196
1197 httpd->recvbuf[0] = 0;
1198 httpd->recvlen = 0;
1199 httpd->headers = NULL;
1200 httpd->method = ISC_HTTPD_METHODUNKNOWN;
1201 httpd->url = NULL;
1202 httpd->querystring = NULL;
1203 httpd->protocol = NULL;
1204 httpd->flags = 0;
1205
1206 isc_buffer_clear(&httpd->headerbuffer);
1207 isc_buffer_clear(&httpd->compbuffer);
1208 isc_buffer_invalidate(&httpd->bodybuffer);
1209 }
1210
1211 isc_result_t
1212 isc_httpdmgr_addurl(isc_httpdmgr_t *httpdmgr, const char *url,
1213 isc_httpdaction_t *func, void *arg)
1214 {
1215 return (isc_httpdmgr_addurl2(httpdmgr, url, ISC_FALSE, func, arg));
1216 }
1217
1218 isc_result_t
1219 isc_httpdmgr_addurl2(isc_httpdmgr_t *httpdmgr, const char *url,
1220 isc_boolean_t isstatic,
1221 isc_httpdaction_t *func, void *arg)
1222 {
1223 isc_httpdurl_t *item;
1224
1225 if (url == NULL) {
1226 httpdmgr->render_404 = func;
1227 return (ISC_R_SUCCESS);
1228 }
1229
1230 item = isc_mem_get(httpdmgr->mctx, sizeof(isc_httpdurl_t));
1231 if (item == NULL)
1232 return (ISC_R_NOMEMORY);
1233
1234 item->url = isc_mem_strdup(httpdmgr->mctx, url);
1235 if (item->url == NULL) {
1236 isc_mem_put(httpdmgr->mctx, item, sizeof(isc_httpdurl_t));
1237 return (ISC_R_NOMEMORY);
1238 }
1239
1240 item->action = func;
1241 item->action_arg = arg;
1242 item->isstatic = isstatic;
1243 isc_time_now(&item->loadtime);
1244
1245 ISC_LINK_INIT(item, link);
1246 ISC_LIST_APPEND(httpdmgr->urls, item, link);
1247
1248 return (ISC_R_SUCCESS);
1249 }
1250
1251 void
1252 isc_httpd_setfinishhook(void (*fn)(void))
1253 {
1254 finishhook = fn;
1255 }
1256