]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
Change the syntax of FOREACH macros to be less dumb.
[user/henk/code/inspircd.git] / src / modules / m_httpd.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
6  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
7  *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
8  *   Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net>
9  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
10  *
11  * This file is part of InspIRCd.  InspIRCd is free software: you can
12  * redistribute it and/or modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation, version 2.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24
25 #include "inspircd.h"
26 #include "iohook.h"
27 #include "modules/httpd.h"
28
29 /* $ModDep: modules/httpd.h */
30
31 class ModuleHttpServer;
32
33 static ModuleHttpServer* HttpModule;
34 static bool claimed;
35
36 /** HTTP socket states
37  */
38 enum HttpState
39 {
40         HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */
41         HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */
42         HTTP_SERVE_SEND_DATA = 2 /* Sending response */
43 };
44
45 /** A socket used for HTTP transport
46  */
47 class HttpServerSocket : public BufferedSocket
48 {
49         HttpState InternalState;
50         std::string ip;
51
52         HTTPHeaders headers;
53         std::string reqbuffer;
54         std::string postdata;
55         unsigned int postsize;
56         std::string request_type;
57         std::string uri;
58         std::string http_version;
59
60  public:
61         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
62                 : BufferedSocket(newfd), ip(IP), postsize(0)
63         {
64                 InternalState = HTTP_SERVE_WAIT_REQUEST;
65
66                 FOREACH_MOD(OnHookIO, (this, via));
67                 if (GetIOHook())
68                         GetIOHook()->OnStreamSocketAccept(this, client, server);
69         }
70
71         void OnError(BufferedSocketError) CXX11_OVERRIDE
72         {
73                 ServerInstance->GlobalCulls.AddItem(this);
74         }
75
76         std::string Response(int response)
77         {
78                 switch (response)
79                 {
80                         case 100:
81                                 return "CONTINUE";
82                         case 101:
83                                 return "SWITCHING PROTOCOLS";
84                         case 200:
85                                 return "OK";
86                         case 201:
87                                 return "CREATED";
88                         case 202:
89                                 return "ACCEPTED";
90                         case 203:
91                                 return "NON-AUTHORITATIVE INFORMATION";
92                         case 204:
93                                 return "NO CONTENT";
94                         case 205:
95                                 return "RESET CONTENT";
96                         case 206:
97                                 return "PARTIAL CONTENT";
98                         case 300:
99                                 return "MULTIPLE CHOICES";
100                         case 301:
101                                 return "MOVED PERMENANTLY";
102                         case 302:
103                                 return "FOUND";
104                         case 303:
105                                 return "SEE OTHER";
106                         case 304:
107                                 return "NOT MODIFIED";
108                         case 305:
109                                 return "USE PROXY";
110                         case 307:
111                                 return "TEMPORARY REDIRECT";
112                         case 400:
113                                 return "BAD REQUEST";
114                         case 401:
115                                 return "UNAUTHORIZED";
116                         case 402:
117                                 return "PAYMENT REQUIRED";
118                         case 403:
119                                 return "FORBIDDEN";
120                         case 404:
121                                 return "NOT FOUND";
122                         case 405:
123                                 return "METHOD NOT ALLOWED";
124                         case 406:
125                                 return "NOT ACCEPTABLE";
126                         case 407:
127                                 return "PROXY AUTHENTICATION REQUIRED";
128                         case 408:
129                                 return "REQUEST TIMEOUT";
130                         case 409:
131                                 return "CONFLICT";
132                         case 410:
133                                 return "GONE";
134                         case 411:
135                                 return "LENGTH REQUIRED";
136                         case 412:
137                                 return "PRECONDITION FAILED";
138                         case 413:
139                                 return "REQUEST ENTITY TOO LARGE";
140                         case 414:
141                                 return "REQUEST-URI TOO LONG";
142                         case 415:
143                                 return "UNSUPPORTED MEDIA TYPE";
144                         case 416:
145                                 return "REQUESTED RANGE NOT SATISFIABLE";
146                         case 417:
147                                 return "EXPECTATION FAILED";
148                         case 500:
149                                 return "INTERNAL SERVER ERROR";
150                         case 501:
151                                 return "NOT IMPLEMENTED";
152                         case 502:
153                                 return "BAD GATEWAY";
154                         case 503:
155                                 return "SERVICE UNAVAILABLE";
156                         case 504:
157                                 return "GATEWAY TIMEOUT";
158                         case 505:
159                                 return "HTTP VERSION NOT SUPPORTED";
160                         default:
161                                 return "WTF";
162                         break;
163
164                 }
165         }
166
167         void SendHTTPError(int response)
168         {
169                 HTTPHeaders empty;
170                 std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
171                                    "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
172
173                 SendHeaders(data.length(), response, empty);
174                 WriteData(data);
175         }
176
177         void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
178         {
179
180                 WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
181
182                 time_t local = ServerInstance->Time();
183                 struct tm *timeinfo = gmtime(&local);
184                 char *date = asctime(timeinfo);
185                 date[strlen(date) - 1] = '\0';
186                 rheaders.CreateHeader("Date", date);
187
188                 rheaders.CreateHeader("Server", BRANCH);
189                 rheaders.SetHeader("Content-Length", ConvToStr(size));
190
191                 if (size)
192                         rheaders.CreateHeader("Content-Type", "text/html");
193                 else
194                         rheaders.RemoveHeader("Content-Type");
195
196                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
197                  * so remove it, its not essential for what we need.
198                  */
199                 rheaders.SetHeader("Connection", "Close");
200
201                 WriteData(rheaders.GetFormattedHeaders());
202                 WriteData("\r\n");
203         }
204
205         void OnDataReady()
206         {
207                 if (InternalState == HTTP_SERVE_RECV_POSTDATA)
208                 {
209                         postdata.append(recvq);
210                         if (postdata.length() >= postsize)
211                                 ServeData();
212                 }
213                 else
214                 {
215                         reqbuffer.append(recvq);
216
217                         if (reqbuffer.length() >= 8192)
218                         {
219                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "m_httpd dropped connection due to an oversized request buffer");
220                                 reqbuffer.clear();
221                                 SetError("Buffer");
222                         }
223
224                         if (InternalState == HTTP_SERVE_WAIT_REQUEST)
225                                 CheckRequestBuffer();
226                 }
227         }
228
229         void CheckRequestBuffer()
230         {
231                 std::string::size_type reqend = reqbuffer.find("\r\n\r\n");
232                 if (reqend == std::string::npos)
233                         return;
234
235                 // We have the headers; parse them all
236                 std::string::size_type hbegin = 0, hend;
237                 while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos)
238                 {
239                         if (hbegin == hend)
240                                 break;
241
242                         if (request_type.empty())
243                         {
244                                 std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin));
245                                 cheader >> request_type;
246                                 cheader >> uri;
247                                 cheader >> http_version;
248
249                                 if (request_type.empty() || uri.empty() || http_version.empty())
250                                 {
251                                         SendHTTPError(400);
252                                         return;
253                                 }
254
255                                 hbegin = hend + 2;
256                                 continue;
257                         }
258
259                         std::string cheader = reqbuffer.substr(hbegin, hend - hbegin);
260
261                         std::string::size_type fieldsep = cheader.find(':');
262                         if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1))
263                         {
264                                 SendHTTPError(400);
265                                 return;
266                         }
267
268                         headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2));
269
270                         hbegin = hend + 2;
271                 }
272
273                 reqbuffer.erase(0, reqend + 4);
274
275                 std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);
276                 std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);
277
278                 if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))
279                 {
280                         SendHTTPError(505);
281                         return;
282                 }
283
284                 if (headers.IsSet("Content-Length") && (postsize = ConvToInt(headers.GetHeader("Content-Length"))) > 0)
285                 {
286                         InternalState = HTTP_SERVE_RECV_POSTDATA;
287
288                         if (reqbuffer.length() >= postsize)
289                         {
290                                 postdata = reqbuffer.substr(0, postsize);
291                                 reqbuffer.erase(0, postsize);
292                         }
293                         else if (!reqbuffer.empty())
294                         {
295                                 postdata = reqbuffer;
296                                 reqbuffer.clear();
297                         }
298
299                         if (postdata.length() >= postsize)
300                                 ServeData();
301
302                         return;
303                 }
304
305                 ServeData();
306         }
307
308         void ServeData()
309         {
310                 InternalState = HTTP_SERVE_SEND_DATA;
311
312                 claimed = false;
313                 HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata);
314                 acl.Send();
315                 if (!claimed)
316                 {
317                         HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata);
318                         url.Send();
319                         if (!claimed)
320                         {
321                                 SendHTTPError(404);
322                         }
323                 }
324         }
325
326         void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
327         {
328                 SendHeaders(n->str().length(), response, *hheaders);
329                 WriteData(n->str());
330         }
331 };
332
333 class HTTPdAPIImpl : public HTTPdAPIBase
334 {
335  public:
336         HTTPdAPIImpl(Module* parent)
337                 : HTTPdAPIBase(parent)
338         {
339         }
340
341         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
342         {
343                 claimed = true;
344                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
345         }
346 };
347
348 class ModuleHttpServer : public Module
349 {
350         std::vector<HttpServerSocket *> httpsocks;
351         HTTPdAPIImpl APIImpl;
352
353  public:
354         ModuleHttpServer()
355                 : APIImpl(this)
356         {
357         }
358
359         void init() CXX11_OVERRIDE
360         {
361                 HttpModule = this;
362                 ServerInstance->Modules->AddService(APIImpl);
363                 ServerInstance->Modules->Attach(I_OnAcceptConnection, this);
364         }
365
366         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
367         {
368                 if (from->bind_tag->getString("type") != "httpd")
369                         return MOD_RES_PASSTHRU;
370                 int port;
371                 std::string incomingip;
372                 irc::sockets::satoap(*client, incomingip, port);
373                 new HttpServerSocket(nfd, incomingip, from, client, server);
374                 return MOD_RES_ALLOW;
375         }
376
377         ~ModuleHttpServer()
378         {
379                 for (size_t i = 0; i < httpsocks.size(); i++)
380                 {
381                         httpsocks[i]->cull();
382                         delete httpsocks[i];
383                 }
384         }
385
386         Version GetVersion() CXX11_OVERRIDE
387         {
388                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
389         }
390 };
391
392 MODULE_INIT(ModuleHttpServer)