monorail 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/README ADDED
@@ -0,0 +1,37 @@
1
+ # $Id: README 2373 2006-04-24 02:43:57Z francis $
2
+ #
3
+ #
4
+
5
+ Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
6
+ Monorail is written and maintained by Francis Cianfrocca.
7
+ Gmail: garbagecat10.
8
+
9
+
10
+ == Welcome to Monorail
11
+
12
+ Monorail is a lightweight web-application framework that emphasizes
13
+ performance and security. It includes a native-code engine for
14
+ fast network I/O that eliminates the need for an outboard web server.
15
+
16
+ Monorail applications are developed using a controller-view model
17
+ that is reminiscent of other development frameworks. Monorail
18
+ applications can be integrated with components from other frameworks,
19
+ including ActiveRecord. (Monorail does _not_ incorporate functionality
20
+ for database-persistance.)
21
+
22
+ Monorail's built-in web-server can also be used to serve applications
23
+ other than Monorail applications. A single instance of Monorail's
24
+ web-server can simultaneously serve a mixture of static pages,
25
+ CGI applications, Monorail applications, and applications from other
26
+ frameworks such as Rails.
27
+
28
+ Monorail's web server includes native-code handling of the HTTP protocol,
29
+ layered above the Ruby/EventMachine event-handling library. It also
30
+ incorporates SSL/TLS functionality, and integration with outboard
31
+ authentication/authorization systems.
32
+
33
+ Monorail is currently licensed under GPL, but we expect that a future
34
+ version will be released under the MIT and/or Ruby licenses.
35
+
36
+ Please read the accompanying file COPYING for license information.
37
+
@@ -0,0 +1,6 @@
1
+ # $Id: RELEASE_NOTES 2335 2006-04-20 07:43:52Z francis $
2
+ #
3
+ #
4
+
5
+ Monorail release-notes stub
6
+
@@ -0,0 +1,8 @@
1
+ begin
2
+ require 'monorail'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'monorail'
6
+ end
7
+ Monorail.application.run
8
+
@@ -0,0 +1,33 @@
1
+ # $Id: extconf.rb 2358 2006-04-22 19:50:36Z francis $
2
+ #
3
+ #----------------------------------------------------------------------------
4
+ #
5
+ # Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
6
+ #
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
+ # extconf.rb for Ruby/EventMachine
26
+ # We have to munge LDSHARED because this code needs a C++ link.
27
+ #
28
+
29
+ require 'mkmf'
30
+ CONFIG['LDSHARED'] = "$(CXX) -shared"
31
+ $LOCAL_LIBS << "-lpthread"
32
+ create_makefile "rubyhttpmachine"
33
+
@@ -0,0 +1,419 @@
1
+ /*****************************************************************************
2
+
3
+ $Id: http.cpp 2357 2006-04-22 19:49:00Z francis $
4
+
5
+ File: http.cpp
6
+ Date: 21Apr06
7
+
8
+ Copyright (C) 2006 by Francis Cianfrocca. All Rights Reserved.
9
+ Gmail: garbagecat10
10
+
11
+ This program is free software; you can redistribute it and/or modify
12
+ it under the terms of the GNU General Public License as published by
13
+ the Free Software Foundation; either version 2 of the License, or
14
+ (at your option) any later version.
15
+
16
+ This program is distributed in the hope that it will be useful,
17
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ GNU General Public License for more details.
20
+
21
+ You should have received a copy of the GNU General Public License
22
+ along with this program; if not, write to the Free Software
23
+ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
24
+
25
+ *****************************************************************************/
26
+
27
+
28
+ #include <iostream>
29
+ #include <string>
30
+ #include <sstream>
31
+ #include <stdexcept>
32
+
33
+ using namespace std;
34
+
35
+ #include "http.h"
36
+
37
+
38
+ /**********************************
39
+ HttpConnection_t::HttpConnection_t
40
+ **********************************/
41
+
42
+ HttpConnection_t::HttpConnection_t()
43
+ {
44
+ ProtocolState = BaseState;
45
+ _Content = NULL;
46
+ }
47
+
48
+
49
+ /***********************************
50
+ HttpConnection_t::~HttpConnection_t
51
+ ***********************************/
52
+
53
+ HttpConnection_t::~HttpConnection_t()
54
+ {
55
+ if (_Content)
56
+ free (_Content);
57
+ }
58
+
59
+
60
+
61
+ /**************************
62
+ HttpConnection_t::SendData
63
+ **************************/
64
+
65
+ void HttpConnection_t::SendData (const char *data, int length)
66
+ {
67
+ cerr << "UNIMPLEMENTED SendData" << endl;
68
+ }
69
+
70
+
71
+ /*********************************
72
+ HttpConnection_t::CloseConnection
73
+ *********************************/
74
+
75
+ void HttpConnection_t::CloseConnection (bool after_writing)
76
+ {
77
+ cerr << "UNIMPLEMENTED CloseConnection" << endl;
78
+ }
79
+
80
+
81
+ /********************************
82
+ HttpConnection_t::ProcessRequest
83
+ ********************************/
84
+
85
+ void HttpConnection_t::ProcessRequest()
86
+ {
87
+ cerr << "UNIMPLEMENTED ProcessRequest" << endl;
88
+ }
89
+
90
+
91
+
92
+
93
+ /*****************************
94
+ HttpConnection_t::ConsumeData
95
+ *****************************/
96
+
97
+ void HttpConnection_t::ConsumeData (const char *data, int length)
98
+ {
99
+ if (ProtocolState == EndState)
100
+ return;
101
+
102
+ if ((length > 0) && !data)
103
+ throw std::runtime_error ("bad args consuming http data");
104
+
105
+ while (length > 0) {
106
+ //----------------------------------- BaseState
107
+ // Initialize for a new request. Don't consume any data.
108
+ if (ProtocolState == BaseState) {
109
+ ProtocolState = PreheaderState;
110
+ nLeadingBlanks = 0;
111
+ HeaderLinePos = 0;
112
+ ContentLength = 0;
113
+ ContentPos = 0;
114
+ bRequestSeen = false;
115
+ bContentLengthSeen = false;
116
+ if (_Content) {
117
+ free ((void*)_Content);
118
+ _Content = NULL;
119
+ }
120
+ unsetenv ("HTTP_COOKIE");
121
+ }
122
+
123
+ //----------------------------------- PreheaderState
124
+ // Consume blank lines (but not too many of them)
125
+ while ((ProtocolState == PreheaderState) && (length > 0)) {
126
+ if ((*data == '\r') || (*data == '\n')) {
127
+ data++;
128
+ length--;
129
+ nLeadingBlanks++;
130
+ if (nLeadingBlanks > MaxLeadingBlanks) {
131
+ // TODO, log this.
132
+ goto fail_connection;
133
+ }
134
+ }
135
+ else
136
+ ProtocolState = HeaderState;
137
+ }
138
+
139
+ //----------------------------------- HeaderState
140
+ // Read HTTP headers.
141
+ // This processing depends on the fact that the end
142
+ // of the data buffer we receive will have a null terminator
143
+ // just after the last byte indicated by the length parameter.
144
+ // Cf notes in ConnectionDescriptor::Read.
145
+ while ((ProtocolState == HeaderState) && (length > 0)) {
146
+ if (*data == '\n') {
147
+ HeaderLine [HeaderLinePos] = 0;
148
+ if (!_InterpretHeaderLine (HeaderLine))
149
+ goto send_error;
150
+ if (HeaderLinePos == 0) {
151
+ if (ContentLength > 0) {
152
+ if (_Content)
153
+ free (_Content);
154
+ _Content = (char*) malloc (ContentLength + 1);
155
+ if (!_Content)
156
+ throw std::runtime_error ("resource exhaustion");
157
+ ContentPos = 0;
158
+ ProtocolState = ReadingContentState;
159
+ }
160
+ else
161
+ ProtocolState = DispatchState;
162
+ }
163
+ HeaderLinePos = 0;
164
+ data++;
165
+ length--;
166
+ }
167
+ else if (*data == '\r') {
168
+ // ignore \r
169
+ data++;
170
+ length--;
171
+ }
172
+ else {
173
+ const char *nl = strpbrk (data, "\r\n");
174
+ int len = nl ? (nl - data) : length;
175
+ if ((HeaderLinePos + len) >= sizeof(HeaderLine)) {
176
+ // TODO, log this
177
+ goto fail_connection;
178
+ }
179
+ memcpy (HeaderLine + HeaderLinePos, data, len);
180
+ data += len;
181
+ length -= len;
182
+ HeaderLinePos += len;
183
+ }
184
+ }
185
+
186
+
187
+ //----------------------------------- ReadingContentState
188
+ // Read POST content.
189
+ while ((ProtocolState == ReadingContentState) && (length > 0)) {
190
+ int len = ContentLength - ContentPos;
191
+ if (len > length)
192
+ len = length;
193
+ memcpy (_Content + ContentPos, data, len);
194
+ data += len;
195
+ length -= len;
196
+ ContentPos += len;
197
+ if (ContentPos == ContentLength) {
198
+ _Content[ContentPos] = 0;
199
+ ProtocolState = DispatchState;
200
+ }
201
+ }
202
+
203
+
204
+ //----------------------------------- DispatchState
205
+ if (ProtocolState == DispatchState) {
206
+ ProcessRequest();
207
+ ProtocolState = BaseState;
208
+ }
209
+ }
210
+
211
+ return;
212
+
213
+ fail_connection:
214
+ // For protocol errors or security violations- kill the connection dead.
215
+ CloseConnection (false);
216
+ ProtocolState = EndState;
217
+ return;
218
+
219
+ send_error:
220
+ // for HTTP-level errors that will send back a response to the client.
221
+ CloseConnection (true);
222
+ ProtocolState = EndState;
223
+ return;
224
+
225
+ }
226
+
227
+
228
+ /**************************************
229
+ HttpConnection_t::_InterpretHeaderLine
230
+ **************************************/
231
+
232
+ bool HttpConnection_t::_InterpretHeaderLine (const char *header)
233
+ {
234
+ /* Return T/F to indicate whether we should continue processing
235
+ * this request. Return false to indicate that we detected a fatal
236
+ * error or other condition which should cause us to drop the
237
+ * connection.
238
+ * BY DEFINITION, this doesn't define any immediate fatal errors.
239
+ * That may need to change, in which case we'll have to return
240
+ * an error code rather than T/F, so the caller will know whether
241
+ * to drop the connection gracefully or not.
242
+ *
243
+ * There's something odd and possibly undesirable about how we're
244
+ * doing this. We fully process each header (including the request)
245
+ * _as we see it,_ and not at the end when all the headers have
246
+ * been seen. This saves us the trouble of keeping them all around
247
+ * and possibly parsing them twice, but it also means that when
248
+ * we emit errors from here (that generate HTTP responses other than
249
+ * 200 and therefore close the connection), we do so _immediately_
250
+ * and before looking at the rest of the headers. That might surprise
251
+ * and confuse some clients.
252
+ */
253
+
254
+ if (!bRequestSeen) {
255
+ bRequestSeen = true;
256
+ return _InterpretRequest (header);
257
+ }
258
+
259
+ if (!strncasecmp (header, "content-length:", 15)) {
260
+ if (bContentLengthSeen) {
261
+ // TODO, log this. There are some attacks that depend
262
+ // on sending more than one content-length header.
263
+ _SendError (406);
264
+ return false;
265
+ }
266
+ bContentLengthSeen = true;
267
+ const char *s = header + 15;
268
+ while (*s && ((*s==' ') || (*s=='\t')))
269
+ s++;
270
+ ContentLength = atoi (s);
271
+ if (ContentLength > MaxContentLength) {
272
+ // TODO, log this.
273
+ _SendError (406);
274
+ return false;
275
+ }
276
+ }
277
+ else if (!strncasecmp (header, "cookie:", 7)) {
278
+ const char *s = header + 7;
279
+ while (*s && ((*s==' ') || (*s=='\t')))
280
+ s++;
281
+ setenv ("HTTP_COOKIE", s, true);
282
+ }
283
+
284
+ return true;
285
+ }
286
+
287
+
288
+ /***********************************
289
+ HttpConnection_t::_InterpretRequest
290
+ ***********************************/
291
+
292
+ bool HttpConnection_t::_InterpretRequest (const char *header)
293
+ {
294
+ /* Return T/F to indicate whether we should continue processing
295
+ * this request. Return false to indicate that we detected a fatal
296
+ * error or other condition which should cause us to drop the
297
+ * connection.
298
+ * Interpret the contents of the given line as an HTTP request string.
299
+ * WE ASSUME the passed-in header is not null.
300
+ *
301
+ * In preparation for a CGI-style call, we set the following
302
+ * environment strings here (other code will DEPEND ON ALL OF
303
+ * THESE BEING SET HERE in case there are no errors):
304
+ * REQUEST_METHOD, PATH_INFO, QUERY_STRING.
305
+ *
306
+ * Oh and by the way, this code sucks. It's reasonably fast
307
+ * but not terribly fast, and it's ugly. Refactor someday.
308
+ */
309
+
310
+ const char *blank = strchr (header, ' ');
311
+ if (!blank) {
312
+ _SendError (406);
313
+ return false;
314
+ }
315
+
316
+ if (!_DetectVerbAndSetEnvString (header, blank - header))
317
+ return false;
318
+
319
+ blank++;
320
+ if (*blank != '/') {
321
+ _SendError (406);
322
+ return false;
323
+ }
324
+
325
+ const char *blank2 = strchr (blank, ' ');
326
+ if (!blank2) {
327
+ _SendError (406);
328
+ return false;
329
+ }
330
+ if (strcasecmp (blank2 + 1, "HTTP/1.0") && strcasecmp (blank2 + 1, "HTTP/1.1")) {
331
+ _SendError (505);
332
+ return false;
333
+ }
334
+
335
+ // Here, the request starts at blank and ends just before blank2.
336
+ // Find the query-string (?) and/or fragment (#,;), if either are present.
337
+ const char *questionmark = strchr (blank, '?');
338
+ if (questionmark && (questionmark >= blank2))
339
+ questionmark = NULL;
340
+ const char *fragment = strpbrk ((questionmark ? (questionmark+1) : blank), "#;");
341
+ if (fragment && (fragment >= blank2))
342
+ fragment = NULL;
343
+
344
+ if (questionmark) {
345
+ string req (blank, questionmark - blank);
346
+ setenv ("PATH_INFO", req.c_str(), true);
347
+ string qs (questionmark+1, fragment ? (fragment - (questionmark+1)) : (blank2 - (questionmark+1)));
348
+ setenv ("QUERY_STRING", qs.c_str(), true);
349
+ }
350
+ else if (fragment) {
351
+ string req (blank, fragment - blank);
352
+ setenv ("PATH_INFO", req.c_str(), true);
353
+ setenv ("QUERY_STRING", "", true);
354
+ }
355
+ else {
356
+ string req (blank, blank2 - blank);
357
+ setenv ("PATH_INFO", req.c_str(), true);
358
+ setenv ("QUERY_STRING", "", true);
359
+ }
360
+
361
+
362
+ return true;
363
+ }
364
+
365
+
366
+ /********************************************
367
+ HttpConnection_t::_DetectVerbAndSetEnvString
368
+ ********************************************/
369
+
370
+ bool HttpConnection_t::_DetectVerbAndSetEnvString (const char *request, int verblength)
371
+ {
372
+ /* Helper method for _InterpretRequest.
373
+ * WE MUST SET THE ENV STRING "REQUEST_METHOD" HERE
374
+ * unless there is an error.
375
+ */
376
+
377
+ const char *verbs[] = {
378
+ "GET",
379
+ "POST",
380
+ "HEAD"
381
+ };
382
+
383
+ int n_verbs = sizeof(verbs) / sizeof(const char*);
384
+
385
+ // Warning, this algorithm is vulnerable to head-matches,
386
+ // so compare the longer head-matching strings first.
387
+ // We could fix this if we included the blank in the search
388
+ // string but then we'd have to lop it off in the env string.
389
+ // ALSO NOTICE the early return on success.
390
+ for (int i=0; i < n_verbs; i++) {
391
+ if (!strncasecmp (request, verbs[i], verblength) && (strlen(verbs[i]) == verblength)) {
392
+ setenv ("REQUEST_METHOD", verbs[i], 1);
393
+ return true;
394
+ }
395
+ }
396
+
397
+ _SendError (405);
398
+ return false;
399
+ }
400
+
401
+
402
+
403
+ /****************************
404
+ HttpConnection_t::_SendError
405
+ ****************************/
406
+
407
+ void HttpConnection_t::_SendError (int code)
408
+ {
409
+ stringstream ss;
410
+ ss << "HTTP/1.1 " << code << " ...\r\n";
411
+ ss << "Connection: close\r\n";
412
+ ss << "Content-type: text/plain\r\n";
413
+ ss << "\r\n";
414
+ ss << "Detected error: HTTP code " << code;
415
+
416
+ SendData (ss.str().c_str(), ss.str().length());
417
+ }
418
+
419
+