ViewVC Help
View File | Revision Log | Show Annotations | Download File
/cvs/cvsroot/ermyth/modules/rpc/httpd.C
Revision: 1.2
Committed: Sat Jul 21 01:29:10 2007 UTC (19 years, 2 months ago) by pippijn
Content type: text/plain
Branch: MAIN
Changes since 1.1: +3 -3 lines
Log Message:
- moved to new documentation system
- fixed small build error

File Contents

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