fotonauts-eventmachine_httpserver 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,566 @@
1
+ /*****************************************************************************
2
+
3
+ File: http.cpp
4
+ Date: 21Apr06
5
+
6
+ Copyright (C) 2006-07 by Francis Cianfrocca. All Rights Reserved.
7
+ Gmail: garbagecat10
8
+
9
+ This program is free software; you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation; either version 2 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program; if not, write to the Free Software
21
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
22
+
23
+ *****************************************************************************/
24
+
25
+
26
+ #include <iostream>
27
+ #include <string>
28
+ #include <string.h>
29
+ #include <sstream>
30
+ #include <stdexcept>
31
+ #include <stdio.h>
32
+ #include <stdlib.h>
33
+
34
+ #ifdef OS_WIN32
35
+ #include <windows.h>
36
+ #endif
37
+
38
+ using namespace std;
39
+
40
+ #include "http.h"
41
+
42
+
43
+ #ifdef OS_WIN32
44
+ #define strncasecmp _strnicmp
45
+ #define strcasecmp _stricmp
46
+ void setenv (const char *str, const char *value, bool replace)
47
+ {
48
+ SetEnvironmentVariable (str, value);
49
+ }
50
+ void unsetenv (const char *str)
51
+ {
52
+ SetEnvironmentVariable (str, NULL);
53
+ }
54
+ #endif
55
+
56
+
57
+ /**********************************
58
+ HttpConnection_t::HttpConnection_t
59
+ **********************************/
60
+
61
+ HttpConnection_t::HttpConnection_t()
62
+ {
63
+ ProtocolState = BaseState;
64
+ _Content = NULL;
65
+
66
+ // By default, we set the standard CGI environment strings.
67
+ // (This is primarily beneficial because it lets the caller use Ruby's CGI classes.)
68
+ // The caller can switch this off in Ruby code, which greatly improves performance.
69
+ bSetEnvironmentStrings = true;
70
+
71
+ // This flag was added by Kirk Haines (thanks, Kirk). It preserves the original
72
+ // behavior with respect to POST content, which was to accumulate it in a buffer
73
+ // allocated and managed in this class. Kirk's mods allow callers to specify that
74
+ // POST content be submitted directly to user code piece by piece as we receive it,
75
+ // instead of buffering it here. To get the latter behavior, user code must call
76
+ // dont_accumulate_post.
77
+ bAccumulatePost = true;
78
+ }
79
+
80
+
81
+ /***********************************
82
+ HttpConnection_t::~HttpConnection_t
83
+ ***********************************/
84
+
85
+ HttpConnection_t::~HttpConnection_t()
86
+ {
87
+ if (_Content)
88
+ free (_Content);
89
+ }
90
+
91
+
92
+
93
+ /**************************
94
+ HttpConnection_t::SendData
95
+ **************************/
96
+
97
+ void HttpConnection_t::SendData (const char *data, int length)
98
+ {
99
+ cerr << "UNIMPLEMENTED SendData" << endl;
100
+ }
101
+
102
+
103
+ /*********************************
104
+ HttpConnection_t::CloseConnection
105
+ *********************************/
106
+
107
+ void HttpConnection_t::CloseConnection (bool after_writing)
108
+ {
109
+ cerr << "UNIMPLEMENTED CloseConnection" << endl;
110
+ }
111
+
112
+
113
+ /********************************
114
+ HttpConnection_t::ProcessRequest
115
+ ********************************/
116
+
117
+ void HttpConnection_t::ProcessRequest (const char *method,
118
+ const char *cookie,
119
+ const char *ifnonematch,
120
+ const char *contenttype,
121
+ const char *query_string,
122
+ const char *path_info,
123
+ const char *request_uri,
124
+ const char *protocol,
125
+ int post_length,
126
+ const char *post_content,
127
+ const char *hdrblock,
128
+ int hdrblocksize)
129
+ {
130
+ cerr << "UNIMPLEMENTED ProcessRequest" << endl;
131
+ }
132
+
133
+
134
+ /*********************************
135
+ HttpConnection_t::ReceivePostData
136
+ *********************************/
137
+
138
+ void HttpConnection_t::ReceivePostData (const char *data, int len)
139
+ {
140
+ cerr << "UNIMPLEMENTED ReceivePostData" << endl;
141
+ }
142
+
143
+ /*****************************
144
+ HttpConnection_t::ConsumeData
145
+ *****************************/
146
+
147
+ void HttpConnection_t::ConsumeData (const char *data, int length)
148
+ {
149
+ if (ProtocolState == EndState)
150
+ return;
151
+
152
+ if ((length > 0) && !data)
153
+ throw std::runtime_error ("bad args consuming http data");
154
+
155
+ while (length > 0) {
156
+ //----------------------------------- BaseState
157
+ // Initialize for a new request. Don't consume any data.
158
+ // For anal-retentive security we may want to bzero the header block.
159
+ if (ProtocolState == BaseState) {
160
+ ProtocolState = PreheaderState;
161
+ nLeadingBlanks = 0;
162
+ HeaderLinePos = 0;
163
+ HeaderBlockPos = 0;
164
+ ContentLength = 0;
165
+ ContentPos = 0;
166
+ bRequestSeen = false;
167
+ bContentLengthSeen = false;
168
+ if (_Content) {
169
+ free ((void*)_Content);
170
+ _Content = NULL;
171
+ }
172
+ RequestMethod = NULL;
173
+ Cookie.clear();
174
+ IfNoneMatch.clear();
175
+ ContentType.clear();
176
+ PathInfo.clear();
177
+ RequestUri.clear();
178
+ QueryString.clear();
179
+ Protocol.clear();
180
+
181
+ if (bSetEnvironmentStrings) {
182
+ unsetenv ("REQUEST_METHOD");
183
+ unsetenv ("HTTP_COOKIE");
184
+ unsetenv ("IF_NONE_MATCH");
185
+ unsetenv ("CONTENT_TYPE");
186
+ unsetenv ("PATH_INFO");
187
+ unsetenv ("REQUEST_URI");
188
+ unsetenv ("QUERY_STRING");
189
+ unsetenv ("PROTOCOL");
190
+ }
191
+ }
192
+
193
+ //----------------------------------- PreheaderState
194
+ // Consume blank lines (but not too many of them)
195
+ while ((ProtocolState == PreheaderState) && (length > 0)) {
196
+ if ((*data == '\r') || (*data == '\n')) {
197
+ data++;
198
+ length--;
199
+ nLeadingBlanks++;
200
+ if (nLeadingBlanks > MaxLeadingBlanks) {
201
+ // TODO, log this.
202
+ goto fail_connection;
203
+ }
204
+ }
205
+ else
206
+ ProtocolState = HeaderState;
207
+ }
208
+
209
+ //----------------------------------- HeaderState
210
+ // Read HTTP headers.
211
+ // This processing depends on the fact that the end
212
+ // of the data buffer we receive will have a null terminator
213
+ // just after the last byte indicated by the length parameter.
214
+ // Cf notes in ConnectionDescriptor::Read.
215
+ while ((ProtocolState == HeaderState) && (length > 0)) {
216
+ if (*data == '\n') {
217
+ HeaderLine [HeaderLinePos] = 0;
218
+ if (!_InterpretHeaderLine (HeaderLine))
219
+ goto send_error;
220
+ if (HeaderLinePos == 0) {
221
+ if (ContentLength > 0) {
222
+ if (_Content)
223
+ free (_Content);
224
+ _Content = NULL;
225
+ if (bAccumulatePost) {
226
+ _Content = (char*) malloc (ContentLength + 1);
227
+ if (!_Content)
228
+ throw std::runtime_error ("resource exhaustion");
229
+ }
230
+ ContentPos = 0;
231
+ ProtocolState = ReadingContentState;
232
+ }
233
+ else
234
+ ProtocolState = DispatchState;
235
+ }
236
+ HeaderLinePos = 0;
237
+ data++;
238
+ length--;
239
+ }
240
+ else if (*data == '\r') {
241
+ // ignore \r
242
+ data++;
243
+ length--;
244
+ }
245
+ else {
246
+ const char *nl = strpbrk (data, "\r\n");
247
+ int len = nl ? (nl - data) : length;
248
+ if ((size_t)(HeaderLinePos + len) >= sizeof(HeaderLine)) {
249
+ // TODO, log this
250
+ goto fail_connection;
251
+ }
252
+ memcpy (HeaderLine + HeaderLinePos, data, len);
253
+ data += len;
254
+ length -= len;
255
+ HeaderLinePos += len;
256
+ }
257
+ }
258
+
259
+
260
+ //----------------------------------- ReadingContentState
261
+ // Read POST content.
262
+ while ((ProtocolState == ReadingContentState) && (length > 0)) {
263
+ int len = ContentLength - ContentPos;
264
+ if (len > length)
265
+ len = length;
266
+
267
+ if (bAccumulatePost)
268
+ memcpy (_Content + ContentPos, data, len);
269
+ else
270
+ ReceivePostData (data, len);
271
+
272
+ data += len;
273
+ length -= len;
274
+ ContentPos += len;
275
+ if (ContentPos == ContentLength) {
276
+ if (bAccumulatePost)
277
+ _Content[ContentPos] = 0;
278
+ ProtocolState = DispatchState;
279
+ }
280
+ }
281
+
282
+
283
+ //----------------------------------- DispatchState
284
+ if (ProtocolState == DispatchState) {
285
+ ProcessRequest (RequestMethod, Cookie.c_str(), IfNoneMatch.c_str(), ContentType.c_str(), QueryString.c_str(), PathInfo.c_str(), RequestUri.c_str(), Protocol.c_str(), ContentLength, _Content, HeaderBlock, HeaderBlockPos);
286
+ ProtocolState = BaseState;
287
+ }
288
+ }
289
+
290
+ return;
291
+
292
+ fail_connection:
293
+ // For protocol errors or security violations- kill the connection dead.
294
+ CloseConnection (false);
295
+ ProtocolState = EndState;
296
+ return;
297
+
298
+ send_error:
299
+ // for HTTP-level errors that will send back a response to the client.
300
+ CloseConnection (true);
301
+ ProtocolState = EndState;
302
+ return;
303
+
304
+ }
305
+
306
+
307
+ /**************************************
308
+ HttpConnection_t::_InterpretHeaderLine
309
+ **************************************/
310
+
311
+ bool HttpConnection_t::_InterpretHeaderLine (const char *header)
312
+ {
313
+ /* Return T/F to indicate whether we should continue processing
314
+ * this request. Return false to indicate that we detected a fatal
315
+ * error or other condition which should cause us to drop the
316
+ * connection.
317
+ * BY DEFINITION, this doesn't define any immediate fatal errors.
318
+ * That may need to change, in which case we'll have to return
319
+ * an error code rather than T/F, so the caller will know whether
320
+ * to drop the connection gracefully or not.
321
+ *
322
+ * There's something odd and possibly undesirable about how we're
323
+ * doing this. We fully process each header (including the request)
324
+ * _as we see it,_ and not at the end when all the headers have
325
+ * been seen. This saves us the trouble of keeping them all around
326
+ * and possibly parsing them twice, but it also means that when
327
+ * we emit errors from here (that generate HTTP responses other than
328
+ * 200 and therefore close the connection), we do so _immediately_
329
+ * and before looking at the rest of the headers. That might surprise
330
+ * and confuse some clients.
331
+ *
332
+ * Revised 27Sep06, we now store all the headers in one place, on a
333
+ * per-request basis, for the purpose of making them available to
334
+ * downstream users. At present this involves an undesirable extra
335
+ * memory copy. Eventually should rework the main header processing
336
+ * so it can be done in place.
337
+ */
338
+
339
+ if (!header) // an assert, really.
340
+ throw std::runtime_error ("bad arg interpreting headers");
341
+
342
+ if (!bRequestSeen) {
343
+ bRequestSeen = true;
344
+ return _InterpretRequest (header);
345
+ }
346
+
347
+ if (!strncasecmp (header, "content-length:", 15)) {
348
+ if (bContentLengthSeen) {
349
+ // TODO, log this. There are some attacks that depend
350
+ // on sending more than one content-length header.
351
+ _SendError (406);
352
+ return false;
353
+ }
354
+ bContentLengthSeen = true;
355
+ const char *s = header + 15;
356
+ while (*s && ((*s==' ') || (*s=='\t')))
357
+ s++;
358
+ ContentLength = atoi (s);
359
+ if (ContentLength > MaxContentLength) {
360
+ // TODO, log this.
361
+ _SendError (406);
362
+ return false;
363
+ }
364
+ }
365
+ else if (!strncasecmp (header, "cookie:", 7)) {
366
+ const char *s = header + 7;
367
+ while (*s && ((*s==' ') || (*s=='\t')))
368
+ s++;
369
+ Cookie = s;
370
+ if (bSetEnvironmentStrings)
371
+ setenv ("HTTP_COOKIE", s, true);
372
+ }
373
+ else if (!strncasecmp (header, "If-none-match:", 14)) {
374
+ const char *s = header + 14;
375
+ while (*s && ((*s==' ') || (*s=='\t')))
376
+ s++;
377
+ IfNoneMatch = s;
378
+ if (bSetEnvironmentStrings)
379
+ setenv ("IF_NONE_MATCH", s, true);
380
+ }
381
+ else if (!strncasecmp (header, "Content-type:", 13)) {
382
+ const char *s = header + 13;
383
+ while (*s && ((*s==' ') || (*s=='\t')))
384
+ s++;
385
+ ContentType = s;
386
+ if (bSetEnvironmentStrings)
387
+ setenv ("CONTENT_TYPE", s, true);
388
+ }
389
+
390
+
391
+ // Copy the incoming header into a block
392
+ if ((HeaderBlockPos + strlen(header) + 1) < HeaderBlockSize) {
393
+ int len = strlen(header);
394
+ memcpy (HeaderBlock+HeaderBlockPos, header, len);
395
+ HeaderBlockPos += len;
396
+ HeaderBlock [HeaderBlockPos++] = 0;
397
+ }
398
+ else {
399
+ // TODO, log this.
400
+ _SendError (406);
401
+ return false;
402
+ }
403
+
404
+ return true;
405
+ }
406
+
407
+
408
+ /***********************************
409
+ HttpConnection_t::_InterpretRequest
410
+ ***********************************/
411
+
412
+ bool HttpConnection_t::_InterpretRequest (const char *header)
413
+ {
414
+ /* Return T/F to indicate whether we should continue processing
415
+ * this request. Return false to indicate that we detected a fatal
416
+ * error or other condition which should cause us to drop the
417
+ * connection.
418
+ * Interpret the contents of the given line as an HTTP request string.
419
+ * WE ASSUME the passed-in header is not null.
420
+ *
421
+ * In preparation for a CGI-style call, we set the following
422
+ * environment strings here (other code will DEPEND ON ALL OF
423
+ * THESE BEING SET HERE in case there are no errors):
424
+ * REQUEST_METHOD, PATH_INFO, QUERY_STRING.
425
+ *
426
+ * Oh and by the way, this code sucks. It's reasonably fast
427
+ * but not terribly fast, and it's ugly. Refactor someday.
428
+ */
429
+
430
+ const char *blank = strchr (header, ' ');
431
+ if (!blank) {
432
+ _SendError (406);
433
+ return false;
434
+ }
435
+
436
+ if (!_DetectVerbAndSetEnvString (header, blank - header))
437
+ return false;
438
+
439
+ blank++;
440
+ if (*blank != '/') {
441
+ _SendError (406);
442
+ return false;
443
+ }
444
+
445
+ const char *blank2 = strchr (blank, ' ');
446
+ if (!blank2) {
447
+ _SendError (406);
448
+ return false;
449
+ }
450
+ if (strcasecmp (blank2 + 1, "HTTP/1.0") && strcasecmp (blank2 + 1, "HTTP/1.1")) {
451
+ _SendError (505);
452
+ return false;
453
+ }
454
+
455
+ string prot (blank2+1);
456
+ Protocol = prot.c_str();
457
+
458
+ // Here, the request starts at blank and ends just before blank2.
459
+ // Find the query-string (?) and/or fragment (#,;), if either are present.
460
+ const char *questionmark = strchr (blank, '?');
461
+ if (questionmark && (questionmark >= blank2))
462
+ questionmark = NULL;
463
+ const char *fragment = strpbrk ((questionmark ? (questionmark+1) : blank), "#;");
464
+ if (fragment && (fragment >= blank2))
465
+ fragment = NULL;
466
+
467
+ if (questionmark) {
468
+ string req (blank, questionmark - blank);
469
+ PathInfo = req.c_str();
470
+ RequestUri = req.c_str();
471
+ string qs (questionmark+1, fragment ? (fragment - (questionmark+1)) : (blank2 - (questionmark+1)));
472
+ QueryString = qs.c_str();
473
+
474
+ if (bSetEnvironmentStrings) {
475
+ setenv ("PATH_INFO", req.c_str(), true);
476
+ setenv ("REQUEST_URI", req.c_str(), true);
477
+ setenv ("QUERY_STRING", qs.c_str(), true);
478
+ setenv ("PROTOCOL", prot.c_str(), true);
479
+ }
480
+ }
481
+ else if (fragment) {
482
+ string req (blank, fragment - blank);
483
+ PathInfo = req.c_str();
484
+ RequestUri = req.c_str();
485
+ QueryString.clear();
486
+ if (bSetEnvironmentStrings) {
487
+ setenv ("PATH_INFO", req.c_str(), true);
488
+ setenv ("REQUEST_URI", req.c_str(), true);
489
+ setenv ("QUERY_STRING", "", true);
490
+ setenv ("PROTOCOL", prot.c_str(), true);
491
+ }
492
+ }
493
+ else {
494
+ string req (blank, blank2 - blank);
495
+ PathInfo = req.c_str();
496
+ RequestUri = req.c_str();
497
+ QueryString.clear();
498
+ if (bSetEnvironmentStrings) {
499
+ setenv ("PATH_INFO", req.c_str(), true);
500
+ setenv ("REQUEST_URI", req.c_str(), true);
501
+ setenv ("QUERY_STRING", "", true);
502
+ setenv ("PROTOCOL", prot.c_str(), true);
503
+ }
504
+ }
505
+
506
+ return true;
507
+ }
508
+
509
+
510
+ /********************************************
511
+ HttpConnection_t::_DetectVerbAndSetEnvString
512
+ ********************************************/
513
+
514
+ bool HttpConnection_t::_DetectVerbAndSetEnvString (const char *request, int verblength)
515
+ {
516
+ /* Helper method for _InterpretRequest.
517
+ * WE MUST SET THE ENV STRING "REQUEST_METHOD" HERE
518
+ * unless there is an error.
519
+ * The hardcoded verbs MUST be static, as we'll carry around pointers to them.
520
+ */
521
+
522
+ static const char *verbs[] = {
523
+ "GET",
524
+ "POST",
525
+ "PUT",
526
+ "DELETE",
527
+ "HEAD"
528
+ };
529
+
530
+ int n_verbs = sizeof(verbs) / sizeof(const char*);
531
+
532
+ // Warning, this algorithm is vulnerable to head-matches,
533
+ // so compare the longer head-matching strings first.
534
+ // We could fix this if we included the blank in the search
535
+ // string but then we'd have to lop it off in the env string.
536
+ // ALSO NOTICE the early return on success.
537
+ for (int i=0; i < n_verbs; i++) {
538
+ if (!strncasecmp (request, verbs[i], verblength) && (strlen(verbs[i]) == (size_t)verblength)) {
539
+ RequestMethod = verbs[i];
540
+ if (bSetEnvironmentStrings)
541
+ setenv ("REQUEST_METHOD", verbs[i], 1);
542
+ return true;
543
+ }
544
+ }
545
+
546
+ _SendError (405);
547
+ return false;
548
+ }
549
+
550
+
551
+
552
+ /****************************
553
+ HttpConnection_t::_SendError
554
+ ****************************/
555
+
556
+ void HttpConnection_t::_SendError (int code)
557
+ {
558
+ stringstream ss;
559
+ ss << "HTTP/1.1 " << code << " ...\r\n";
560
+ ss << "Connection: close\r\n";
561
+ ss << "Content-type: text/plain\r\n";
562
+ ss << "\r\n";
563
+ ss << "Detected error: HTTP code " << code;
564
+
565
+ SendData (ss.str().c_str(), ss.str().length());
566
+ }