Put fewer slashes into the slashed_url.
[darkstat] / http.c
1 /* darkstat 3
2 * copyright (c) 2001-2008 Emil Mikulic.
3 *
4 * http.c: embedded webserver.
5 * This borrows a lot of code from darkhttpd.
6 *
7 * You may use, modify and redistribute this file under the terms of the
8 * GNU General Public License version 2. (see COPYING.GPL)
9 */
10
11 #include "darkstat.h"
12 #include "http.h"
13 #include "conv.h"
14 #include "hosts_db.h"
15 #include "graph_db.h"
16 #include "err.h"
17 #include "queue.h"
18 #include "str.h"
19 #include "now.h"
20
21 #include <sys/uio.h>
22 #include <sys/socket.h>
23 #include <arpa/inet.h>
24 #include <netinet/in.h>
25 #include <netdb.h>
26 #include <assert.h>
27 #include <ctype.h>
28 #include <errno.h>
29 #include <fcntl.h>
30 #include <signal.h>
31 #include <stdarg.h>
32 #include <stdio.h>
33 #include <stdlib.h>
34 #include <string.h>
35 #include <unistd.h>
36 #include <zlib.h>
37
38 char *base_url = NULL;
39
40 static const char mime_type_xml[] = "text/xml";
41 static const char mime_type_html[] = "text/html; charset=us-ascii";
42 static const char mime_type_css[] = "text/css";
43 static const char mime_type_js[] = "text/javascript";
44 static const char encoding_identity[] = "identity";
45 static const char encoding_gzip[] = "gzip";
46
47 static const char server[] = PACKAGE_NAME "/" PACKAGE_VERSION;
48 static int idletime = 60;
49 static int sockin = -1; /* socket to accept connections from */
50 #define MAX_REQUEST_LENGTH 4000
51
52 #ifndef min
53 #define min(a,b) (((a) < (b)) ? (a) : (b))
54 #endif
55
56 struct connection {
57 LIST_ENTRY(connection) entries;
58
59 int socket;
60 struct sockaddr_storage client;
61 time_t last_active;
62 enum {
63 RECV_REQUEST, /* receiving request */
64 SEND_HEADER_AND_REPLY, /* try to send header+reply together */
65 SEND_HEADER, /* sending generated header */
66 SEND_REPLY, /* sending reply */
67 DONE /* conn closed, need to remove from queue */
68 } state;
69
70 /* char request[request_length+1] is null-terminated */
71 char *request;
72 size_t request_length;
73 int accept_gzip;
74
75 /* request fields */
76 char *method, *uri, *query; /* query can be NULL */
77
78 char *header;
79 const char *mime_type, *encoding, *header_extra;
80 size_t header_length, header_sent;
81 int header_dont_free, header_only, http_code;
82
83 char *reply;
84 int reply_dont_free;
85 size_t reply_length, reply_sent;
86
87 unsigned int total_sent; /* header + body = total, for logging */
88 };
89
90 static LIST_HEAD(conn_list_head, connection) connlist =
91 LIST_HEAD_INITIALIZER(conn_list_head);
92
93 /* ---------------------------------------------------------------------------
94 * Decode URL by converting %XX (where XX are hexadecimal digits) to the
95 * character it represents. Don't forget to free the return value.
96 */
97 static char *urldecode(const char *url)
98 {
99 size_t i, len = strlen(url);
100 char *out = xmalloc(len+1);
101 int pos;
102
103 for (i=0, pos=0; i<len; i++)
104 {
105 if (url[i] == '%' && i+2 < len &&
106 isxdigit(url[i+1]) && isxdigit(url[i+2]))
107 {
108 /* decode %XX */
109 #define HEX_TO_DIGIT(hex) ( \
110 ((hex) >= 'A' && (hex) <= 'F') ? ((hex)-'A'+10): \
111 ((hex) >= 'a' && (hex) <= 'f') ? ((hex)-'a'+10): \
112 ((hex)-'0') )
113
114 out[pos++] = HEX_TO_DIGIT(url[i+1]) * 16 +
115 HEX_TO_DIGIT(url[i+2]);
116 i += 2;
117
118 #undef HEX_TO_DIGIT
119 }
120 else
121 {
122 /* straight copy */
123 out[pos++] = url[i];
124 }
125 }
126 out[pos] = 0;
127 #if 0
128 /* don't really need to realloc here - it's probably a performance hit */
129 out = xrealloc(out, strlen(out)+1); /* dealloc what we don't need */
130 #endif
131 return (out);
132 }
133
134
135
136 /* ---------------------------------------------------------------------------
137 * Consolidate slashes in-place by shifting parts of the string over repeated
138 * slashes.
139 */
140 static void consolidate_slashes(char *s)
141 {
142 size_t left = 0, right = 0;
143 int saw_slash = 0;
144
145 assert(s != NULL);
146
147 while (s[right] != '\0')
148 {
149 if (saw_slash)
150 {
151 if (s[right] == '/') right++;
152 else
153 {
154 saw_slash = 0;
155 s[left++] = s[right++];
156 }
157 }
158 else
159 {
160 if (s[right] == '/') saw_slash++;
161 s[left++] = s[right++];
162 }
163 }
164 s[left] = '\0';
165 }
166
167
168
169 /* ---------------------------------------------------------------------------
170 * Resolve /./ and /../ in a URI, returing a new, safe URI, or NULL if the URI
171 * is invalid/unsafe. Returned buffer needs to be deallocated.
172 */
173 static char *make_safe_uri(char *uri)
174 {
175 char **elem, *out;
176 unsigned int slashes = 0, elements = 0;
177 size_t urilen, i, j, pos;
178
179 assert(uri != NULL);
180 if (uri[0] != '/')
181 return (NULL);
182 consolidate_slashes(uri);
183 urilen = strlen(uri);
184
185 /* count the slashes */
186 for (i=0, slashes=0; i<urilen; i++)
187 if (uri[i] == '/') slashes++;
188
189 /* make an array for the URI elements */
190 elem = xmalloc(sizeof(*elem) * slashes);
191 for (i=0; i<slashes; i++)
192 elem[i] = (NULL);
193
194 /* split by slashes and build elem[] array */
195 for (i=1; i<urilen;)
196 {
197 /* look for the next slash */
198 for (j=i; j<urilen && uri[j] != '/'; j++)
199 ;
200
201 /* process uri[i,j) */
202 if ((j == i+1) && (uri[i] == '.'))
203 /* "." */;
204 else if ((j == i+2) && (uri[i] == '.') && (uri[i+1] == '.'))
205 {
206 /* ".." */
207 if (elements == 0)
208 {
209 /*
210 * Unsafe string so free elem[]. All its elements are free
211 * at this point.
212 */
213 free(elem);
214 return (NULL);
215 }
216 else
217 {
218 elements--;
219 free(elem[elements]);
220 }
221 }
222 else elem[elements++] = split_string(uri, i, j);
223
224 i = j + 1; /* uri[j] is a slash - move along one */
225 }
226
227 /* reassemble */
228 out = xmalloc(urilen+1); /* it won't expand */
229 pos = 0;
230 for (i=0; i<elements; i++)
231 {
232 size_t delta = strlen(elem[i]);
233
234 assert(pos <= urilen);
235 out[pos++] = '/';
236
237 assert(pos+delta <= urilen);
238 memcpy(out+pos, elem[i], delta);
239 free(elem[i]);
240 pos += delta;
241 }
242 free(elem);
243
244 if ((elements == 0) || (uri[urilen-1] == '/')) out[pos++] = '/';
245 assert(pos <= urilen);
246 out[pos] = '\0';
247
248 #if 0
249 /* don't really need to do this and it's probably a performance hit: */
250 /* shorten buffer if necessary */
251 if (pos != urilen) out = xrealloc(out, strlen(out)+1);
252 #endif
253 return (out);
254 }
255
256 /* ---------------------------------------------------------------------------
257 * Allocate and initialize an empty connection.
258 */
259 static struct connection *new_connection(void)
260 {
261 struct connection *conn = xmalloc(sizeof(*conn));
262
263 conn->socket = -1;
264 memset(&conn->client, 0, sizeof(conn->client));
265 conn->last_active = now;
266 conn->request = NULL;
267 conn->request_length = 0;
268 conn->accept_gzip = 0;
269 conn->method = NULL;
270 conn->uri = NULL;
271 conn->query = NULL;
272 conn->header = NULL;
273 conn->mime_type = NULL;
274 conn->encoding = NULL;
275 conn->header_extra = "";
276 conn->header_length = 0;
277 conn->header_sent = 0;
278 conn->header_dont_free = 0;
279 conn->header_only = 0;
280 conn->http_code = 0;
281 conn->reply = NULL;
282 conn->reply_dont_free = 0;
283 conn->reply_length = 0;
284 conn->reply_sent = 0;
285 conn->total_sent = 0;
286
287 /* Make it harmless so it gets garbage-collected if it should, for some
288 * reason, fail to be correctly filled out.
289 */
290 conn->state = DONE;
291
292 return (conn);
293 }
294
295
296
297 /* ---------------------------------------------------------------------------
298 * Accept a connection from sockin and add it to the connection queue.
299 */
300 static void accept_connection(void)
301 {
302 struct sockaddr_storage addrin;
303 socklen_t sin_size;
304 struct connection *conn;
305 char ipaddr[INET6_ADDRSTRLEN], portstr[12];
306 int sock;
307
308 sin_size = (socklen_t)sizeof(addrin);
309 sock = accept(sockin, (struct sockaddr *)&addrin, &sin_size);
310 if (sock == -1)
311 {
312 if (errno == ECONNABORTED || errno == EINTR)
313 {
314 verbosef("accept() failed: %s", strerror(errno));
315 return;
316 }
317 /* else */ err(1, "accept()");
318 }
319
320 fd_set_nonblock(sock);
321
322 /* allocate and initialise struct connection */
323 conn = new_connection();
324 conn->socket = sock;
325 conn->state = RECV_REQUEST;
326 memcpy(&conn->client, &addrin, sizeof(conn->client));
327 LIST_INSERT_HEAD(&connlist, conn, entries);
328
329 getnameinfo((struct sockaddr *) &addrin, sin_size,
330 ipaddr, sizeof(ipaddr), portstr, sizeof(portstr),
331 NI_NUMERICHOST | NI_NUMERICSERV);
332 verbosef("accepted connection from %s:%s", ipaddr, portstr);
333 }
334
335
336
337 /* ---------------------------------------------------------------------------
338 * Log a connection, then cleanly deallocate its internals.
339 */
340 static void free_connection(struct connection *conn)
341 {
342 dverbosef("free_connection(%d)", conn->socket);
343 if (conn->socket != -1) close(conn->socket);
344 if (conn->request != NULL) free(conn->request);
345 if (conn->method != NULL) free(conn->method);
346 if (conn->uri != NULL) free(conn->uri);
347 if (conn->query != NULL) free(conn->query);
348 if (conn->header != NULL && !conn->header_dont_free) free(conn->header);
349 if (conn->reply != NULL && !conn->reply_dont_free) free(conn->reply);
350 }
351
352
353
354 /* ---------------------------------------------------------------------------
355 * Format [when] as an RFC1123 date, stored in the specified buffer. The same
356 * buffer is returned for convenience.
357 */
358 #define DATE_LEN 30 /* strlen("Fri, 28 Feb 2003 00:02:08 GMT")+1 */
359 static char *rfc1123_date(char *dest, const time_t when)
360 {
361 time_t tmp = when;
362 if (strftime(dest, DATE_LEN,
363 "%a, %d %b %Y %H:%M:%S %Z", gmtime(&tmp) ) == 0)
364 errx(1, "strftime() failed [%s]", dest);
365 return (dest);
366 }
367
368 static void generate_header(struct connection *conn,
369 const int code, const char *text)
370 {
371 char date[DATE_LEN];
372
373 assert(conn->header == NULL);
374 assert(conn->mime_type != NULL);
375 if (conn->encoding == NULL)
376 conn->encoding = encoding_identity;
377
378 verbosef("http: %d %s (%s: %d bytes)", code, text,
379 conn->encoding, conn->reply_length);
380 conn->header_length = xasprintf(&(conn->header),
381 "HTTP/1.1 %d %s\r\n"
382 "Date: %s\r\n"
383 "Server: %s\r\n"
384 "Vary: Accept-Encoding\r\n"
385 "Content-Type: %s\r\n"
386 "Content-Length: %d\r\n"
387 "Content-Encoding: %s\r\n"
388 "X-Robots-Tag: noindex, noarchive\r\n"
389 "%s"
390 "\r\n"
391 ,
392 code, text,
393 rfc1123_date(date, now), server,
394 conn->mime_type, conn->reply_length, conn->encoding,
395 conn->header_extra);
396 conn->http_code = code;
397 }
398
399
400
401 /* ---------------------------------------------------------------------------
402 * A default reply for any (erroneous) occasion.
403 */
404 static void default_reply(struct connection *conn,
405 const int errcode, const char *errname, const char *format, ...)
406 {
407 char *reason;
408 va_list va;
409
410 va_start(va, format);
411 xvasprintf(&reason, format, va);
412 va_end(va);
413
414 conn->reply_length = xasprintf(&(conn->reply),
415 "<html><head><title>%d %s</title></head><body>\n"
416 "<h1>%s</h1>\n" /* errname */
417 "%s\n" /* reason */
418 "<hr>\n"
419 "Generated by %s"
420 "</body></html>\n",
421 errcode, errname, errname, reason, server);
422 free(reason);
423
424 /* forget any dangling metadata */
425 conn->mime_type = mime_type_html;
426 conn->encoding = encoding_identity;
427
428 generate_header(conn, errcode, errname);
429 }
430
431
432
433 /* ---------------------------------------------------------------------------
434 * Parses a single HTTP request field. Returns string from end of [field] to
435 * first \r, \n or end of request string. Returns NULL if [field] can't be
436 * matched.
437 *
438 * You need to remember to deallocate the result.
439 * example: parse_field(conn, "Referer: ");
440 */
441 static char *parse_field(const struct connection *conn, const char *field)
442 {
443 size_t bound1, bound2;
444 char *pos;
445
446 /* find start */
447 pos = strstr(conn->request, field);
448 if (pos == NULL)
449 return (NULL);
450 bound1 = pos - conn->request + strlen(field);
451
452 /* find end */
453 for (bound2 = bound1;
454 conn->request[bound2] != '\r' &&
455 bound2 < conn->request_length; bound2++)
456 ;
457
458 /* copy to buffer */
459 return (split_string(conn->request, bound1, bound2));
460 }
461
462
463
464 /* ---------------------------------------------------------------------------
465 * Parse an HTTP request like "GET /hosts/?sort=in HTTP/1.1" to get the method
466 * (GET), the uri (/hosts/), the query (sort=in) and whether the UA will
467 * accept gzip encoding. Remember to deallocate all these buffers. Query
468 * can be NULL. The method will be returned in uppercase.
469 */
470 static int parse_request(struct connection *conn)
471 {
472 size_t bound1, bound2, mid;
473 char *accept_enc;
474
475 /* parse method */
476 for (bound1 = 0; bound1 < conn->request_length &&
477 conn->request[bound1] != ' '; bound1++)
478 ;
479
480 conn->method = split_string(conn->request, 0, bound1);
481 strntoupper(conn->method, bound1);
482
483 /* parse uri */
484 for (; bound1 < conn->request_length &&
485 conn->request[bound1] == ' '; bound1++)
486 ;
487
488 if (bound1 == conn->request_length)
489 return (0); /* fail */
490
491 for (bound2=bound1+1; bound2 < conn->request_length &&
492 conn->request[bound2] != ' ' &&
493 conn->request[bound2] != '\r'; bound2++)
494 ;
495
496 /* find query string */
497 for (mid=bound1; mid<bound2 && conn->request[mid] != '?'; mid++)
498 ;
499
500 if (conn->request[mid] == '?') {
501 conn->query = split_string(conn->request, mid+1, bound2);
502 bound2 = mid;
503 }
504
505 conn->uri = split_string(conn->request, bound1, bound2);
506
507 /* parse important fields */
508 accept_enc = parse_field(conn, "Accept-Encoding: ");
509 if (accept_enc != NULL) {
510 if (strstr(accept_enc, "gzip") != NULL)
511 conn->accept_gzip = 1;
512 free(accept_enc);
513 }
514 return (1);
515 }
516
517 /* FIXME: maybe we need a smarter way of doing static pages: */
518
519 /* ---------------------------------------------------------------------------
520 * Web interface: static stylesheet.
521 */
522 static void
523 static_style_css(struct connection *conn)
524 {
525 #include "stylecss.h"
526
527 conn->reply = style_css;
528 conn->reply_length = style_css_len;
529 conn->reply_dont_free = 1;
530 conn->mime_type = mime_type_css;
531 }
532
533 /* ---------------------------------------------------------------------------
534 * Web interface: static JavaScript.
535 */
536 static void
537 static_graph_js(struct connection *conn)
538 {
539 #include "graphjs.h"
540
541 conn->reply = graph_js;
542 conn->reply_length = graph_js_len;
543 conn->reply_dont_free = 1;
544 conn->mime_type = mime_type_js;
545 }
546
547 /* ---------------------------------------------------------------------------
548 * gzip a reply, if requested and possible. Don't bother with a minimum
549 * length requirement, I've never seen a page fail to compress.
550 */
551 static void
552 process_gzip(struct connection *conn)
553 {
554 char *buf;
555 size_t len;
556 z_stream zs;
557
558 if (!conn->accept_gzip)
559 return;
560
561 buf = xmalloc(conn->reply_length);
562 len = conn->reply_length;
563
564 zs.zalloc = Z_NULL;
565 zs.zfree = Z_NULL;
566 zs.opaque = Z_NULL;
567
568 if (deflateInit2(&zs, Z_BEST_COMPRESSION, Z_DEFLATED,
569 15+16, /* 15 = biggest window, 16 = add gzip header+trailer */
570 8 /* default */,
571 Z_DEFAULT_STRATEGY) != Z_OK)
572 return;
573
574 zs.avail_in = conn->reply_length;
575 zs.next_in = (unsigned char *)conn->reply;
576
577 zs.avail_out = conn->reply_length;
578 zs.next_out = (unsigned char *)buf;
579
580 if (deflate(&zs, Z_FINISH) != Z_STREAM_END) {
581 deflateEnd(&zs);
582 free(buf);
583 verbosef("failed to compress %u bytes", (unsigned int)len);
584 return;
585 }
586
587 if (conn->reply_dont_free)
588 conn->reply_dont_free = 0;
589 else
590 free(conn->reply);
591
592 conn->reply = buf;
593 conn->reply_length -= zs.avail_out;
594 conn->encoding = encoding_gzip;
595 deflateEnd(&zs);
596 }
597
598 /* ---------------------------------------------------------------------------
599 * Process a GET/HEAD request
600 */
601 static void process_get(struct connection *conn)
602 {
603 char *decoded_url, *safe_url;
604
605 verbosef("http: %s \"%s\" %s", conn->method, conn->uri,
606 (conn->query == NULL)?"":conn->query);
607
608 /* work out path of file being requested */
609 decoded_url = urldecode(conn->uri);
610
611 /* make sure it's safe */
612 safe_url = make_safe_uri(decoded_url);
613 free(decoded_url);
614 if (safe_url == NULL)
615 {
616 default_reply(conn, 400, "Bad Request",
617 "You requested an invalid URI: %s", conn->uri);
618 return;
619 }
620
621 /* make relative (or fail) */
622 decoded_url = safe_url;
623 if (!str_starts_with(decoded_url, base_url))
624 {
625 default_reply(conn, 404, "Not Found",
626 "The page you requested could not be found.");
627 free(decoded_url);
628 return;
629 }
630 safe_url = decoded_url + strlen(base_url) - 1;
631
632 if (strcmp(safe_url, "/") == 0) {
633 struct str *buf = html_front_page();
634 str_extract(buf, &(conn->reply_length), &(conn->reply));
635 conn->mime_type = mime_type_html;
636 }
637 else if (str_starts_with(safe_url, "/hosts/")) {
638 /* FIXME here - make this saner */
639 struct str *buf = html_hosts(safe_url, conn->query);
640 if (buf == NULL) {
641 default_reply(conn, 404, "Not Found",
642 "The page you requested could not be found.");
643 free(decoded_url);
644 return;
645 }
646 str_extract(buf, &(conn->reply_length), &(conn->reply));
647 conn->mime_type = mime_type_html;
648 }
649 else if (str_starts_with(safe_url, "/graphs.xml")) {
650 struct str *buf = xml_graphs();
651 str_extract(buf, &(conn->reply_length), &(conn->reply));
652 conn->mime_type = mime_type_xml;
653 /* hack around Opera caching the XML */
654 conn->header_extra = "Pragma: no-cache\r\n";
655 }
656 else if (strcmp(safe_url, "/style.css") == 0)
657 static_style_css(conn);
658 else if (strcmp(safe_url, "/graph.js") == 0)
659 static_graph_js(conn);
660 else {
661 default_reply(conn, 404, "Not Found",
662 "The page you requested could not be found.");
663 free(decoded_url);
664 return;
665 }
666 free(decoded_url);
667
668 process_gzip(conn);
669 assert(conn->mime_type != NULL);
670 generate_header(conn, 200, "OK");
671 }
672
673
674
675 /* ---------------------------------------------------------------------------
676 * Process a request: build the header and reply, advance state.
677 */
678 static void process_request(struct connection *conn)
679 {
680 if (!parse_request(conn))
681 {
682 default_reply(conn, 400, "Bad Request",
683 "You sent a request that the server couldn't understand.");
684 }
685 else if (strcmp(conn->method, "GET") == 0)
686 {
687 process_get(conn);
688 }
689 else if (strcmp(conn->method, "HEAD") == 0)
690 {
691 process_get(conn);
692 conn->header_only = 1;
693 }
694 else
695 {
696 default_reply(conn, 501, "Not Implemented",
697 "The method you specified (%s) is not implemented.",
698 conn->method);
699 }
700
701 /* advance state */
702 if (conn->header_only)
703 conn->state = SEND_HEADER;
704 else
705 conn->state = SEND_HEADER_AND_REPLY;
706
707 /* request not needed anymore */
708 free(conn->request);
709 conn->request = NULL; /* important: don't free it again later */
710 }
711
712
713
714 /* ---------------------------------------------------------------------------
715 * Receiving request.
716 */
717 static void poll_recv_request(struct connection *conn)
718 {
719 #define BUFSIZE 65536
720 char buf[BUFSIZE];
721 ssize_t recvd;
722
723 recvd = recv(conn->socket, buf, BUFSIZE, 0);
724 dverbosef("poll_recv_request(%d) got %d bytes", conn->socket, (int)recvd);
725 if (recvd <= 0)
726 {
727 if (recvd == -1)
728 verbosef("recv(%d) error: %s", conn->socket, strerror(errno));
729 conn->state = DONE;
730 return;
731 }
732 conn->last_active = now;
733 #undef BUFSIZE
734
735 /* append to conn->request */
736 conn->request = xrealloc(conn->request, conn->request_length+recvd+1);
737 memcpy(conn->request+conn->request_length, buf, (size_t)recvd);
738 conn->request_length += recvd;
739 conn->request[conn->request_length] = 0;
740
741 /* process request if we have all of it */
742 if (conn->request_length > 4 &&
743 memcmp(conn->request+conn->request_length-4, "\r\n\r\n", 4) == 0)
744 process_request(conn);
745
746 /* die if it's too long */
747 if (conn->request_length > MAX_REQUEST_LENGTH)
748 {
749 default_reply(conn, 413, "Request Entity Too Large",
750 "Your request was dropped because it was too long.");
751 conn->state = SEND_HEADER;
752 }
753 }
754
755
756
757 /* ---------------------------------------------------------------------------
758 * Try to send header and [a part of the] reply in one packet.
759 */
760 static void poll_send_header_and_reply(struct connection *conn)
761 {
762 ssize_t sent;
763 struct iovec iov[2];
764
765 assert(!conn->header_only);
766 assert(conn->reply_length > 0);
767 assert(conn->header_sent == 0);
768
769 assert(conn->reply_sent == 0);
770
771 /* Fill out iovec */
772 iov[0].iov_base = conn->header;
773 iov[0].iov_len = conn->header_length;
774
775 iov[1].iov_base = conn->reply;
776 iov[1].iov_len = conn->reply_length;
777
778 sent = writev(conn->socket, iov, 2);
779 conn->last_active = now;
780
781 /* handle any errors (-1) or closure (0) in send() */
782 if (sent < 1) {
783 if (sent == -1)
784 verbosef("writev(%d) error: %s", conn->socket, strerror(errno));
785 conn->state = DONE;
786 return;
787 }
788
789 /* Figure out what we've sent. */
790 conn->total_sent += (unsigned int)sent;
791 if (sent < (ssize_t)conn->header_length) {
792 verbosef("partially sent header");
793 conn->header_sent = sent;
794 conn->state = SEND_HEADER;
795 return;
796 }
797 /* else */
798 conn->header_sent = conn->header_length;
799 sent -= conn->header_length;
800
801 if (sent < (ssize_t)conn->reply_length) {
802 verbosef("partially sent reply");
803 conn->reply_sent += sent;
804 conn->state = SEND_REPLY;
805 return;
806 }
807 /* else */
808 conn->reply_sent = conn->reply_length;
809 conn->state = DONE;
810 }
811
812 /* ---------------------------------------------------------------------------
813 * Sending header. Assumes conn->header is not NULL.
814 */
815 static void poll_send_header(struct connection *conn)
816 {
817 ssize_t sent;
818
819 sent = send(conn->socket, conn->header + conn->header_sent,
820 conn->header_length - conn->header_sent, 0);
821 conn->last_active = now;
822 dverbosef("poll_send_header(%d) sent %d bytes", conn->socket, (int)sent);
823
824 /* handle any errors (-1) or closure (0) in send() */
825 if (sent < 1)
826 {
827 if (sent == -1)
828 verbosef("send(%d) error: %s", conn->socket, strerror(errno));
829 conn->state = DONE;
830 return;
831 }
832 conn->header_sent += (unsigned int)sent;
833 conn->total_sent += (unsigned int)sent;
834
835 /* check if we're done sending */
836 if (conn->header_sent == conn->header_length)
837 {
838 if (conn->header_only)
839 conn->state = DONE;
840 else
841 conn->state = SEND_REPLY;
842 }
843 }
844
845
846
847 /* ---------------------------------------------------------------------------
848 * Sending reply.
849 */
850 static void poll_send_reply(struct connection *conn)
851 {
852 ssize_t sent;
853
854 sent = send(conn->socket,
855 conn->reply + conn->reply_sent,
856 conn->reply_length - conn->reply_sent, 0);
857 conn->last_active = now;
858 dverbosef("poll_send_reply(%d) sent %d: [%d-%d] of %d",
859 conn->socket, (int)sent,
860 (int)conn->reply_sent,
861 (int)(conn->reply_sent + sent - 1),
862 (int)conn->reply_length);
863
864 /* handle any errors (-1) or closure (0) in send() */
865 if (sent < 1)
866 {
867 if (sent == -1)
868 verbosef("send(%d) error: %s", conn->socket, strerror(errno));
869 else if (sent == 0)
870 verbosef("send(%d) closure", conn->socket);
871 conn->state = DONE;
872 return;
873 }
874 conn->reply_sent += (unsigned int)sent;
875 conn->total_sent += (unsigned int)sent;
876
877 /* check if we're done sending */
878 if (conn->reply_sent == conn->reply_length) conn->state = DONE;
879 }
880
881
882
883 /* --------------------------------------------------------------------------
884 * Initialize the base path.
885 */
886 static void http_init_base(const char *url)
887 {
888 char *slashed_url, *safe_url;
889 size_t urllen;
890
891 if (url == NULL) {
892 base_url = strdup("/");
893 return;
894 }
895
896 /* make sure that the url has leading and trailing slashes */
897 urllen = strlen(url);
898 slashed_url = xmalloc(urllen+3);
899 slashed_url[0] = '/';
900 memcpy(slashed_url+1, url, urllen); /* don't copy NUL */
901 slashed_url[urllen+1] = '/';
902 slashed_url[urllen+2] = '\0';
903
904 /* clean the url */
905 safe_url = make_safe_uri(slashed_url);
906 free(slashed_url);
907 if (safe_url == NULL) {
908 verbosef("invalid base \"%s\", ignored", url);
909 base_url = strdup("/"); /* set to default */
910 return;
911 }
912 else
913 base_url = safe_url;
914 }
915
916 /* --------------------------------------------------------------------------
917 * Initialize the sockin global. This is the socket that we accept
918 * connections from. Pass -1 as max_conn for system limit.
919 */
920 void http_init(const char *base, const char *bindaddr,
921 const unsigned short bindport, const int max_conn)
922 {
923 struct sockaddr_storage addrin;
924 struct addrinfo hints, *ai, *aiptr;
925 char ipaddr[INET6_ADDRSTRLEN], portstr[12];
926 int sockopt, ret;
927
928 http_init_base(base);
929
930 memset(&hints, 0, sizeof(hints));
931 hints.ai_family = AF_UNSPEC;
932 hints.ai_socktype = SOCK_STREAM;
933 hints.ai_flags = AI_PASSIVE;
934 #ifdef AI_ADDRCONFIG
935 hints.ai_flags |= AI_ADDRCONFIG;
936 #endif
937 snprintf(portstr, sizeof(portstr), "%u", bindport);
938
939 if ((ret = getaddrinfo(bindaddr, portstr, &hints, &aiptr)))
940 err(1, "getaddrinfo(): %s", gai_strerror(ret));
941
942 for (ai = aiptr; ai; ai = ai->ai_next) {
943 /* create incoming socket */
944 sockin = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol);
945 if (sockin == -1)
946 continue;
947
948 /* reuse address */
949 sockopt = 1;
950 if (setsockopt(sockin, SOL_SOCKET, SO_REUSEADDR,
951 &sockopt, sizeof(sockopt)) == -1) {
952 close(sockin);
953 continue;
954 }
955
956 /* Recover address and port strings. */
957 getnameinfo(ai->ai_addr, ai->ai_addrlen, ipaddr, sizeof(ipaddr),
958 NULL, 0, NI_NUMERICHOST);
959
960 /* bind socket */
961 memcpy(&addrin, ai->ai_addr, ai->ai_addrlen);
962 if (bind(sockin, (struct sockaddr *)&addrin, ai->ai_addrlen) == -1) {
963 close(sockin);
964 continue;
965 }
966
967 verbosef("listening on %s:%u", ipaddr, bindport);
968
969 /* listen on socket */
970 if (listen(sockin, max_conn) >= 0)
971 /* Successfully bound and now listening. */
972 break;
973
974 /* Next candidate. */
975 continue;
976 }
977
978 freeaddrinfo(aiptr);
979
980 if (ai == NULL)
981 err(1, "getaddrinfo() unable to locate address");
982
983 /* ignore SIGPIPE */
984 if (signal(SIGPIPE, SIG_IGN) == SIG_ERR)
985 err(1, "can't ignore SIGPIPE");
986 }
987
988
989
990 /* ---------------------------------------------------------------------------
991 * Set recv/send fd_sets and calculate timeout length.
992 */
993 void
994 http_fd_set(fd_set *recv_set, fd_set *send_set, int *max_fd,
995 struct timeval *timeout, int *need_timeout)
996 {
997 struct connection *conn, *next;
998 int minidle = idletime + 1;
999
1000 #define MAX_FD_SET(sock, fdset) do { \
1001 FD_SET(sock, fdset); *max_fd = max(*max_fd, sock); } while(0)
1002
1003 MAX_FD_SET(sockin, recv_set);
1004
1005 LIST_FOREACH_SAFE(conn, &connlist, entries, next)
1006 {
1007 int idlefor = now - conn->last_active;
1008
1009 /* Time out dead connections. */
1010 if (idlefor >= idletime) {
1011 char ipaddr[INET6_ADDRSTRLEN];
1012 /* FIXME: this is too late on FreeBSD, socket is invalid */
1013 int ret = getnameinfo((struct sockaddr *)&conn->client,
1014 sizeof(conn->client), ipaddr, sizeof(ipaddr),
1015 NULL, 0, NI_NUMERICHOST);
1016 if (ret == 0)
1017 verbosef("http socket timeout from %s (fd %d)",
1018 ipaddr, conn->socket);
1019 else
1020 warn("http socket timeout: getnameinfo error: %s",
1021 gai_strerror(ret));
1022 conn->state = DONE;
1023 }
1024
1025 /* Connections that need a timeout. */
1026 if (conn->state != DONE)
1027 minidle = min(minidle, (idletime - idlefor));
1028
1029 switch (conn->state)
1030 {
1031 case DONE:
1032 /* clean out stale connection */
1033 LIST_REMOVE(conn, entries);
1034 free_connection(conn);
1035 free(conn);
1036 break;
1037
1038 case RECV_REQUEST:
1039 MAX_FD_SET(conn->socket, recv_set);
1040 break;
1041
1042 case SEND_HEADER_AND_REPLY:
1043 case SEND_HEADER:
1044 case SEND_REPLY:
1045 MAX_FD_SET(conn->socket, send_set);
1046 break;
1047
1048 default: errx(1, "invalid state");
1049 }
1050 }
1051 #undef MAX_FD_SET
1052
1053 /* Only set timeout if cap hasn't already. */
1054 if ((*need_timeout == 0) && (minidle <= idletime)) {
1055 *need_timeout = 1;
1056 timeout->tv_sec = minidle;
1057 timeout->tv_usec = 0;
1058 }
1059 }
1060
1061
1062
1063 /* ---------------------------------------------------------------------------
1064 * poll connections that select() says need attention
1065 */
1066 void http_poll(fd_set *recv_set, fd_set *send_set)
1067 {
1068 struct connection *conn;
1069
1070 if (FD_ISSET(sockin, recv_set)) accept_connection();
1071
1072 LIST_FOREACH(conn, &connlist, entries)
1073 switch (conn->state)
1074 {
1075 case RECV_REQUEST:
1076 if (FD_ISSET(conn->socket, recv_set)) poll_recv_request(conn);
1077 break;
1078
1079 case SEND_HEADER_AND_REPLY:
1080 if (FD_ISSET(conn->socket, send_set)) poll_send_header_and_reply(conn);
1081 break;
1082
1083 case SEND_HEADER:
1084 if (FD_ISSET(conn->socket, send_set)) poll_send_header(conn);
1085 break;
1086
1087 case SEND_REPLY:
1088 if (FD_ISSET(conn->socket, send_set)) poll_send_reply(conn);
1089 break;
1090
1091 case DONE: /* fallthrough */
1092 default: errx(1, "invalid state");
1093 }
1094 }
1095
1096 void http_stop(void) {
1097 free(base_url);
1098 close(sockin);
1099 }
1100
1101 /* vim:set ts=4 sw=4 et tw=78: */