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