ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/ermyth/modules/rpc/httpd.C
Revision: 1.10
Committed: Sat Sep 22 14:27:28 2007 UTC (16 years, 8 months ago) by pippijn
Content type: text/plain
Branch: MAIN
CVS Tags: HEAD
Changes since 1.9: +3 -2 lines
Log Message:
split up ermyth into ermyth-modules, libermyth (currently just ermyth-util) and ermyth-core

File Contents

# Content
1 /**
2 * httpd.C: Generic HTTPd
3 *
4 * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team
5 * Rights to this code are as documented in COPYING.
6 *
7 *
8 * Portions of this file were derived from sources bearing the following license:
9 * Copyright © 2007 Pippijn van Steenhoven / The Ermyth Team
10 * Copyright © 2005-2007 Jilles Tjoelker et al.
11 * Rights to this code are as documented in doc/pod/license.pod.
12 *
13 * $Id: httpd.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $
14 */
15
16 #include "atheme.h"
17 #include <libermyth.h>
18 #include <ermyth/module.h>
19 #include "httpd.h"
20 #include "datastream.h"
21 #include "connection.h"
22 #include "confparse.h"
23
24 #define REQUEST_MAX 65536 /* maximum size of one call */
25
26 static char const rcsid[] = "$Id: httpd.C,v 1.9 2007-09-16 18:54:44 pippijn Exp $";
27
28 static void on_config_ready (void);
29
30 REGISTER_MODULE ("rpc/httpd", false, "The Ermyth Team <http://ermyth.one09.net>");
31
32 static connection_t *listener;
33 pathvec pathhandlers;
34
35 /* conf stuff */
36 ConfTable::list_type conf_httpd_table;
37
38 static struct httpd_configuration
39 {
40 char *host;
41 char *www_root;
42 int port;
43
44 void clear ()
45 {
46 if (host != NULL)
47 sfree (host);
48 if (www_root != NULL)
49 sfree (www_root);
50 host = NULL;
51 www_root = NULL;
52 }
53 } httpd_config;
54
55 static int
56 conf_httpd_host (config_entry_t *ce)
57 {
58 if (!ce->valid ())
59 return -1;
60
61 if (httpd_config.host != NULL)
62 sfree (httpd_config.host);
63 httpd_config.host = sstrdup (ce->vardata<char *> ());
64
65 return 0;
66 }
67
68 static int
69 conf_httpd_www_root (config_entry_t *ce)
70 {
71 if (!ce->valid ())
72 return -1;
73
74 if (httpd_config.www_root != NULL)
75 sfree (httpd_config.www_root);
76 httpd_config.www_root = sstrdup (ce->vardata<char *> ());
77
78 return 0;
79 }
80
81 static int
82 conf_httpd_port (config_entry_t *ce)
83 {
84 if (!ce->valid ())
85 return -1;
86
87 httpd_config.port = ce->vardata<int> ();
88
89 return 0;
90 }
91
92 static int
93 conf_httpd (config_entry_t *ce)
94 {
95 subblock_handler (ce, conf_httpd_table);
96 return 0;
97 }
98
99 void
100 httpddata::clear (void)
101 {
102 method[0] = '\0';
103 filename[0] = '\0';
104 if (requestbuf != NULL)
105 {
106 sfree (requestbuf);
107 requestbuf = NULL;
108 }
109 if (replybuf != NULL)
110 {
111 sfree (replybuf);
112 replybuf = NULL;
113 }
114 length = 0;
115 lengthdone = 0;
116 correct_content_type = false;
117 expect_100_continue = false;
118 sent_reply = false;
119 }
120
121 static int
122 open_file (char const *filename)
123 {
124 char fname[256];
125
126 if (strstr (filename, ".."))
127 return -1;
128 if (!strcmp (filename, "/"))
129 filename = "/index.html";
130 snprintf (fname, sizeof fname, "%s/%s", httpd_config.www_root, filename);
131 return open (fname, O_RDONLY);
132 }
133
134 static void
135 process_header (connection_t *cptr, char *line)
136 {
137 httpddata *hd;
138 char *p;
139
140 hd = static_cast<httpddata *> (cptr->userdata);
141 p = strchr (line, ':');
142 if (p == NULL)
143 return;
144 *p = '\0';
145 p++;
146 while (*p == ' ')
147 p++;
148 if (!strcasecmp (line, "Connection"))
149 {
150 p = strtok (p, ", \t");
151 while (p != NULL)
152 {
153 if (!strcasecmp (p, "close"))
154 {
155 slog (LG_DEBUG, "process_header(): Connection: close requested by fd %d", cptr->fd);
156 hd->connection_close = true;
157 }
158 p = strtok (NULL, ", \t");
159 }
160 }
161 else if (!strcasecmp (line, "Content-Length"))
162 hd->length = atoi (p);
163 else if (!strcasecmp (line, "Content-Type"))
164 {
165 p = strtok (p, "; \t");
166 hd->correct_content_type = p != NULL && (!strcasecmp (p, "text/xml") || !strcasecmp (p, "application/json"));
167 }
168 else if (!strcasecmp (line, "Expect"))
169 hd->expect_100_continue = !strcasecmp (p, "100-continue");
170 }
171
172 static void
173 check_close (connection_t *cptr)
174 {
175 httpddata *hd;
176
177 hd = static_cast<httpddata *> (cptr->userdata);
178 if (hd->connection_close)
179 sendq_add_eof (cptr);
180 }
181
182 static void
183 send_error (connection_t *cptr, int errorcode, char const * const text, bool const sendentity)
184 {
185 httpddata *hd;
186 char buf1[300];
187 char buf2[700];
188
189 hd = static_cast<httpddata *> (cptr->userdata);
190 if (errorcode < 100 || errorcode > 999)
191 errorcode = 500;
192 snprintf (buf2, sizeof buf2, "HTTP/1.1 %d %s\r\n", errorcode, text);
193 snprintf (buf1, sizeof buf1, "HTTP/1.1 %d %s\r\n"
194 "Server: " PACKAGE_NAME "/%s\r\n"
195 "Content-Type: text/plain\r\n"
196 "Content-Length: %" PRIsize "\r\n\r\n%s",
197 errorcode, text, version, strlen (buf2), sendentity ? buf2 : "");
198 sendq_add (cptr, buf1, strlen (buf1));
199 }
200
201 static char const * const
202 content_type (char const * const filename)
203 {
204 char const *p;
205
206 if (!strcmp (filename, "/"))
207 return "text/html";
208
209 p = strrchr (filename, '.');
210 if (p == NULL)
211 return "text/plain";
212 p++;
213 if (!strcasecmp (p, "html") || !strcasecmp (p, "htm"))
214 return "text/html";
215 else if (!strcasecmp (p, "txt"))
216 return "text/plain";
217 else if (!strcasecmp (p, "jpg") || !strcasecmp (p, "jpeg"))
218 return "image/jpeg";
219 else if (!strcasecmp (p, "gif"))
220 return "image/gif";
221 else if (!strcasecmp (p, "png"))
222 return "image/png";
223 return "application/octet-stream";
224 }
225
226 static void
227 httpd_recvqhandler (connection_t *cptr)
228 {
229 char buf[BUFSIZE * 2];
230 char outbuf[BUFSIZE * 2];
231 int count;
232 httpddata *hd;
233 char *p;
234 int in;
235 struct stat sb;
236 off_t count1;
237 path_handler_t ph;
238 bool is_get, is_post, handling_done = false;
239 pathvec::iterator found, pvend = pathhandlers.end ();
240
241 hd = static_cast<httpddata *> (cptr->userdata);
242
243 if ((found = (std::find_if (pathhandlers.begin (), pvend, pathhandler_eq (hd->filename))))
244 != pvend)
245 {
246 handling_done = true;
247 ph = *found;
248 }
249
250 if (handling_done == true)
251 {
252 if (hd->requestbuf != NULL)
253 {
254 count = recvq_get (cptr, hd->requestbuf + hd->lengthdone, hd->length - hd->lengthdone);
255 if (count <= 0)
256 return;
257 hd->lengthdone += count;
258 if (hd->lengthdone != hd->length)
259 return;
260 hd->requestbuf[hd->length] = '\0';
261
262 ph.handler (cptr, hd->requestbuf);
263
264 hd->clear ();
265 return;
266 }
267 }
268
269 count = recvq_getline (cptr, buf, sizeof buf - 1);
270 if (count <= 0)
271 return;
272 if (cptr->flags & CF_NONEWLINE)
273 {
274 slog (LG_INFO, "httpd_recvqhandler(): throwing out fd %d (%s) for excessive line length", cptr->fd, cptr->hbuf);
275 send_error (cptr, 400, "Bad request", true);
276 sendq_add_eof (cptr);
277 return;
278 }
279
280 cnt.bin += count;
281 if (buf[count - 1] == '\n')
282 count--;
283 if (count > 0 && buf[count - 1] == '\r')
284 count--;
285 buf[count] = '\0';
286
287 if (hd->method[0] == '\0')
288 {
289 /* make sure they're not sending more requests after
290 * declaring they're not sending any more */
291 if (hd->connection_close)
292 return;
293 p = strtok (buf, " ");
294 if (p == NULL)
295 return;
296 strlcpy (hd->method, p, sizeof hd->method);
297 p = strtok (NULL, " ");
298 if (p == NULL)
299 return;
300 strlcpy (hd->filename, p, sizeof hd->filename);
301 p = strtok (NULL, "");
302 if (p == NULL || !strcmp (p, "HTTP/1.0"))
303 hd->connection_close = true;
304 slog (LG_DEBUG, "httpd_recvqhandler(): request %s for %s", hd->method, hd->filename);
305 }
306 else if (count == 0)
307 {
308 is_get = !strcmp (hd->method, "GET");
309 is_post = !strcmp (hd->method, "POST");
310
311 if (!is_post && !is_get)
312 {
313 send_error (cptr, 501, "Method Not Implemented", true);
314 sendq_add_eof (cptr);
315 return;
316 }
317
318 hd->method[0] = '\0';
319
320 if (!handling_done)
321 {
322 in = open_file (hd->filename);
323 if (in == -1 || fstat (in, &sb) == -1 || !S_ISREG (sb.st_mode))
324 {
325 if (in != -1)
326 close (in);
327 slog (LG_INFO, "httpd_recvqhandler(): 404 for %s", hd->filename);
328 send_error (cptr, 404, "Not Found", is_get);
329 check_close (cptr);
330 return;
331 }
332 slog (LG_INFO, "httpd_recvqhandler(): 200 for %s", hd->filename);
333 snprintf (outbuf, sizeof outbuf, "HTTP/1.1 200 OK\r\n"
334 "Server: " PACKAGE_NAME "/%s\r\n"
335 "Content-Type: %s\r\n"
336 "Content-Length: %lu\r\n"
337 "\r\n",
338 version, content_type (hd->filename), sb.st_size);
339 sendq_add (cptr, outbuf, strlen (outbuf));
340 count1 = is_get ? sb.st_size : 0;
341 while (count1 > 0)
342 {
343 count = sizeof outbuf;
344 if (count > count1)
345 count = count1;
346 count = read (in, outbuf, count);
347 if (count <= 0)
348 break;
349 sendq_add (cptr, outbuf, count);
350 count1 -= count;
351 }
352 close (in);
353 if (count1 > 0)
354 {
355 slog (LG_INFO, "httpd_recvqhandler(): disconnecting fd %d (%s), read failed on %s", cptr->fd, cptr->hbuf, hd->filename);
356 cptr->flags |= CF_DEAD;
357 }
358 else
359 check_close (cptr);
360 }
361 else
362 {
363 if (hd->length <= 0)
364 {
365 send_error (cptr, 411, "Length Required", true);
366 sendq_add_eof (cptr);
367 return;
368 }
369 if (hd->length > REQUEST_MAX)
370 {
371 send_error (cptr, 413, "Request Entity Too Large", true);
372 sendq_add_eof (cptr);
373 return;
374 }
375 if (!hd->correct_content_type)
376 {
377 send_error (cptr, 415, "Unsupported Media Type", true);
378 sendq_add_eof (cptr);
379 return;
380 }
381 if (hd->expect_100_continue)
382 {
383 snprintf (outbuf, sizeof outbuf, "HTTP/1.1 100 Continue\r\n"
384 "Server: " PACKAGE_NAME "/%s\r\n"
385 "\r\n", version);
386 sendq_add (cptr, outbuf, strlen (outbuf));
387 }
388 hd->requestbuf = salloc<char> (hd->length + 1);
389 }
390 }
391 else
392 process_header (cptr, buf);
393 }
394
395 static void
396 httpd_closehandler (connection_t *cptr)
397 {
398 httpddata *hd;
399
400 slog (LG_DEBUG, "httpd_closehandler(): fd %d (%s) closed", cptr->fd, cptr->hbuf);
401 hd = static_cast<httpddata *> (cptr->userdata);
402 if (hd != NULL)
403 {
404 sfree (hd->requestbuf);
405 delete hd;
406 }
407 cptr->userdata = NULL;
408 }
409
410 static void
411 do_listen (connection_t *cptr)
412 {
413 connection_t *newptr;
414 httpddata *hd;
415
416 newptr = connection_t::accept_tcp (cptr, recvq_put, sendq_flush);
417 slog (LG_DEBUG, "do_listen(): accepted httpd from %s fd %d", newptr->hbuf, newptr->fd);
418 hd = new httpddata;
419 newptr->userdata = hd;
420 newptr->recvq_handler = httpd_recvqhandler;
421 newptr->close_handler = httpd_closehandler;
422 }
423
424 static void
425 httpd_checkidle (void *arg)
426 {
427 connection_t::list_type::iterator it, it_end = connection_t::list.end ();
428 connection_t *cptr;
429
430 if (listener == NULL)
431 return;
432
433 for (it = connection_t::list.begin (); it != it_end; ++it)
434 {
435 cptr = *it;
436 if (cptr->listener == listener && cptr->last_recv + 300 < NOW)
437 {
438 if (sendq_nonempty (cptr))
439 cptr->last_recv = NOW;
440 else
441 /* from a timeout function,
442 * connection_t::close_soon() may take quite
443 * a while, and connection_t::close() is safe
444 * -- jilles */
445 cptr->close ();
446 }
447 }
448 }
449
450 static void
451 on_config_ready (void)
452 {
453 if (httpd_config.host != NULL && httpd_config.port != 0)
454 {
455 listener = connection_t::open_listener_tcp (httpd_config.host, httpd_config.port, do_listen);
456 if (listener == NULL)
457 slog (LG_ERROR, "httpd_config_ready(): failed to open listener on host %s port %d", httpd_config.host, httpd_config.port);
458 }
459 else
460 slog (LG_ERROR, "httpd_config_ready(): httpd {} block missing or invalid");
461 httpd_config.clear ();
462 }
463
464 bool
465 _modinit (module *m)
466 {
467 event_add ("httpd_checkidle", httpd_checkidle, NULL, 60);
468
469 /* This module needs a rehash to initialize fully if loaded
470 * at run time */
471 ConfTable::callback.ready.attach (on_config_ready);
472
473 add_top_conf ("HTTPD", conf_httpd);
474 add_conf_item ("HOST", conf_httpd_table, conf_httpd_host);
475 add_conf_item ("WWW_ROOT", conf_httpd_table, conf_httpd_www_root);
476 add_conf_item ("PORT", conf_httpd_table, conf_httpd_port);
477
478 return true;
479 }
480
481 void
482 _moddeinit (void)
483 {
484 event_delete (httpd_checkidle, NULL);
485 listener->close_soon_children ();
486 del_conf_item ("HOST", conf_httpd_table);
487 del_conf_item ("WWW_ROOT", conf_httpd_table);
488 del_conf_item ("PORT", conf_httpd_table);
489 del_top_conf ("HTTPD");
490 httpd_config.clear ();
491
492 ConfTable::callback.ready.detach (on_config_ready);
493 }