fotonauts-eventmachine_httpserver 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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
+ }