ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/httpd.C
Revision: 1.9
Committed: Sun Sep 16 18:54:44 2007 UTC (19 years ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.8: +10 -5 lines
Log Message:
#defines to enum

File Contents

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