Re-enable spec/library for full CI runs.
[rbx.git] / lib / ext / mongrel / http11.c
blobcb35bc03f81b8742f720fbba021d657418644a78
1 /**
2 * Copyright (c) 2005 Zed A. Shaw
3 * You can redistribute it and/or modify it under the same terms as Ruby.
4 */
5 #include "ruby.h"
6 #include "ext_help.h"
7 #include <assert.h>
8 #include <string.h>
9 #include "http11_parser.h"
10 #include <ctype.h>
12 static VALUE mMongrel;
13 static VALUE cHttpParser;
14 static VALUE eHttpParserError;
16 #define id_handler_map rb_intern("@handler_map")
17 #define id_http_body rb_intern("@http_body")
19 static VALUE global_http_prefix;
20 static VALUE global_request_method;
21 static VALUE global_request_uri;
22 static VALUE global_fragment;
23 static VALUE global_query_string;
24 static VALUE global_http_version;
25 static VALUE global_content_length;
26 static VALUE global_http_content_length;
27 static VALUE global_request_path;
28 static VALUE global_content_type;
29 static VALUE global_http_content_type;
30 static VALUE global_gateway_interface;
31 static VALUE global_gateway_interface_value;
32 static VALUE global_server_name;
33 static VALUE global_server_port;
34 static VALUE global_server_protocol;
35 static VALUE global_server_protocol_value;
36 static VALUE global_http_host;
37 static VALUE global_mongrel_version;
38 static VALUE global_server_software;
39 static VALUE global_port_80;
41 #define TRIE_INCREASE 30
43 /** Defines common length and error messages for input length validation. */
44 #define DEF_MAX_LENGTH(N,length) const size_t MAX_##N##_LENGTH = length; const char *MAX_##N##_LENGTH_ERR = "HTTP element " # N " is longer than the " # length " allowed length."
46 /** Validates the max length of given input and throws an HttpParserError exception if over. */
47 #define VALIDATE_MAX_LENGTH(len, N) if(len > MAX_##N##_LENGTH) { rb_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); }
49 /** Defines global strings in the init method. */
50 #define DEF_GLOBAL(N, val) global_##N = ID2SYM(rb_intern(val));
53 /* Defines the maximum allowed lengths for various input elements.*/
54 DEF_MAX_LENGTH(FIELD_NAME, 256);
55 DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
56 DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12);
57 DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */
58 DEF_MAX_LENGTH(REQUEST_PATH, 1024);
59 DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10));
60 DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
63 void http_field(void *data, const char *field, size_t flen, const char *value, size_t vlen)
65 char *ch, *end;
66 VALUE req = (VALUE)data;
67 VALUE v = Qnil;
68 VALUE f = Qnil;
70 VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
71 VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
73 v = rb_str_new(value, vlen);
74 f = rb_str_dup(global_http_prefix);
75 f = rb_str_buf_cat(f, field, flen);
77 for(ch = rb_str_get_char_ptr(f), end = ch + rb_str_get_char_len(f); ch < end; ch++) {
78 if(*ch == '-') {
79 *ch = '_';
80 } else {
81 *ch = toupper(*ch);
85 rb_str_flush_char_ptr(f, ch);
87 rb_hash_aset(req, f, v);
90 void request_method(void *data, const char *at, size_t length)
92 VALUE req = (VALUE)data;
93 VALUE val = Qnil;
95 val = rb_str_new(at, length);
96 rb_hash_aset(req, global_request_method, val);
99 void request_uri(void *data, const char *at, size_t length)
101 VALUE req = (VALUE)data;
102 VALUE val = Qnil;
104 VALIDATE_MAX_LENGTH(length, REQUEST_URI);
106 val = rb_str_new(at, length);
107 rb_hash_aset(req, global_request_uri, val);
110 void fragment(void *data, const char *at, size_t length)
112 VALUE req = (VALUE)data;
113 VALUE val = Qnil;
115 VALIDATE_MAX_LENGTH(length, FRAGMENT);
117 val = rb_str_new(at, length);
118 rb_hash_aset(req, global_fragment, val);
121 void request_path(void *data, const char *at, size_t length)
123 VALUE req = (VALUE)data;
124 VALUE val = Qnil;
126 VALIDATE_MAX_LENGTH(length, REQUEST_PATH);
128 val = rb_str_new(at, length);
129 rb_hash_aset(req, global_request_path, val);
132 void query_string(void *data, const char *at, size_t length)
134 VALUE req = (VALUE)data;
135 VALUE val = Qnil;
137 VALIDATE_MAX_LENGTH(length, QUERY_STRING);
139 val = rb_str_new(at, length);
140 rb_hash_aset(req, global_query_string, val);
143 void http_version(void *data, const char *at, size_t length)
145 VALUE req = (VALUE)data;
146 VALUE val = rb_str_new(at, length);
147 rb_hash_aset(req, global_http_version, val);
150 /** Finalizes the request header to have a bunch of stuff that's
151 needed. */
153 void header_done(void *data, const char *at, size_t length)
155 VALUE req = (VALUE)data;
156 VALUE temp = Qnil;
157 VALUE ctype = Qnil;
158 VALUE clen = Qnil;
159 char *colon = NULL, *temp_ptr = NULL;
161 clen = rb_hash_aref(req, global_http_content_length);
162 if(clen != Qnil) {
163 rb_hash_aset(req, global_content_length, clen);
166 ctype = rb_hash_aref(req, global_http_content_type);
167 if(ctype != Qnil) {
168 rb_hash_aset(req, global_content_type, ctype);
171 rb_hash_aset(req, global_gateway_interface, global_gateway_interface_value);
172 if((temp = rb_hash_aref(req, global_http_host)) != Qnil) {
173 temp_ptr = rb_str_get_char_ptr(temp);
174 /* ruby better close strings off with a '\0' dammit */
175 colon = strchr(temp_ptr, ':');
176 if(colon != NULL) {
177 rb_hash_aset(req, global_server_name, rb_str_substr(temp, 0, colon - temp_ptr));
178 rb_hash_aset(req, global_server_port,
179 rb_str_substr(temp, colon - temp_ptr+1,
180 rb_str_get_char_len(temp)));
181 } else {
182 rb_hash_aset(req, global_server_name, temp);
183 rb_hash_aset(req, global_server_port, global_port_80);
185 free(temp_ptr);
188 /* grab the initial body and stuff it into an ivar */
189 rb_ivar_set(req, id_http_body, rb_str_new(at, length));
190 rb_hash_aset(req, global_server_protocol, global_server_protocol_value);
191 rb_hash_aset(req, global_server_software, global_mongrel_version);
195 void HttpParser_free(void *data) {
196 TRACE();
198 if(data) {
199 free(data);
204 VALUE HttpParser_alloc(VALUE klass)
206 VALUE obj;
207 http_parser *hp = malloc(sizeof(http_parser)); // ALLOC_N(http_parser, 1);
208 TRACE();
209 hp->http_field = http_field;
210 hp->request_method = request_method;
211 hp->request_uri = request_uri;
212 hp->fragment = fragment;
213 hp->request_path = request_path;
214 hp->query_string = query_string;
215 hp->http_version = http_version;
216 hp->header_done = header_done;
217 http_parser_init(hp);
219 obj = Data_Wrap_Struct(klass, NULL, HttpParser_free, hp);
221 return obj;
226 * call-seq:
227 * parser.new -> parser
229 * Creates a new parser.
231 VALUE HttpParser_init(VALUE self)
233 http_parser *http = NULL;
234 DATA_GET(self, http_parser, http);
235 http_parser_init(http);
237 return self;
242 * call-seq:
243 * parser.reset -> nil
245 * Resets the parser to it's initial state so that you can reuse it
246 * rather than making new ones.
248 VALUE HttpParser_reset(VALUE self)
250 http_parser *http = NULL;
251 DATA_GET(self, http_parser, http);
252 http_parser_init(http);
254 return Qnil;
259 * call-seq:
260 * parser.finish -> true/false
262 * Finishes a parser early which could put in a "good" or bad state.
263 * You should call reset after finish it or bad things will happen.
265 VALUE HttpParser_finish(VALUE self)
267 http_parser *http = NULL;
268 DATA_GET(self, http_parser, http);
269 http_parser_finish(http);
271 return http_parser_is_finished(http) ? Qtrue : Qfalse;
276 * call-seq:
277 * parser.execute(req_hash, data, start) -> Integer
279 * Takes a Hash and a String of data, parses the String of data filling in the Hash
280 * returning an Integer to indicate how much of the data has been read. No matter
281 * what the return value, you should call HttpParser#finished? and HttpParser#error?
282 * to figure out if it's done parsing or there was an error.
284 * This function now throws an exception when there is a parsing error. This makes
285 * the logic for working with the parser much easier. You can still test for an
286 * error, but now you need to wrap the parser with an exception handling block.
288 * The third argument allows for parsing a partial request and then continuing
289 * the parsing from that position. It needs all of the original data as well
290 * so you have to append to the data buffer as you read.
292 VALUE HttpParser_execute(VALUE self, VALUE req_hash, VALUE data, VALUE start)
294 http_parser *http = NULL;
295 int from = 0;
296 char *dptr = NULL;
297 long dlen = 0;
299 DATA_GET(self, http_parser, http);
301 from = FIX2INT(start);
302 dptr = rb_str_get_char_ptr(data);
303 dlen = rb_str_get_char_len(data);
305 if(from >= dlen) {
306 rb_raise(eHttpParserError, "Requested start is after data buffer end.");
307 } else {
308 http->data = (void *)req_hash;
309 http_parser_execute(http, dptr, dlen, from);
311 VALIDATE_MAX_LENGTH(http_parser_nread(http), HEADER);
313 if(http_parser_has_error(http)) {
314 rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
315 } else {
316 return INT2FIX(http_parser_nread(http));
320 rb_str_flush_char_ptr(data, dptr);
326 * call-seq:
327 * parser.error? -> true/false
329 * Tells you whether the parser is in an error state.
331 VALUE HttpParser_has_error(VALUE self)
333 http_parser *http = NULL;
334 DATA_GET(self, http_parser, http);
336 return http_parser_has_error(http) ? Qtrue : Qfalse;
341 * call-seq:
342 * parser.finished? -> true/false
344 * Tells you whether the parser is finished or not and in a good state.
346 VALUE HttpParser_is_finished(VALUE self)
348 http_parser *http = NULL;
349 DATA_GET(self, http_parser, http);
351 return http_parser_is_finished(http) ? Qtrue : Qfalse;
356 * call-seq:
357 * parser.nread -> Integer
359 * Returns the amount of data processed so far during this processing cycle. It is
360 * set to 0 on initialize or reset calls and is incremented each time execute is called.
362 VALUE HttpParser_nread(VALUE self)
364 http_parser *http = NULL;
365 DATA_GET(self, http_parser, http);
367 return INT2FIX(http->nread);
370 void Init_http11()
373 mMongrel = rb_define_module("Mongrel");
375 DEF_GLOBAL(http_prefix, "HTTP_");
376 DEF_GLOBAL(request_method, "REQUEST_METHOD");
377 DEF_GLOBAL(request_uri, "REQUEST_URI");
378 DEF_GLOBAL(fragment, "FRAGMENT");
379 DEF_GLOBAL(query_string, "QUERY_STRING");
380 DEF_GLOBAL(http_version, "HTTP_VERSION");
381 DEF_GLOBAL(request_path, "REQUEST_PATH");
382 DEF_GLOBAL(content_length, "CONTENT_LENGTH");
383 DEF_GLOBAL(http_content_length, "HTTP_CONTENT_LENGTH");
384 DEF_GLOBAL(content_type, "CONTENT_TYPE");
385 DEF_GLOBAL(http_content_type, "HTTP_CONTENT_TYPE");
386 DEF_GLOBAL(gateway_interface, "GATEWAY_INTERFACE");
387 DEF_GLOBAL(gateway_interface_value, "CGI/1.2");
388 DEF_GLOBAL(server_name, "SERVER_NAME");
389 DEF_GLOBAL(server_port, "SERVER_PORT");
390 DEF_GLOBAL(server_protocol, "SERVER_PROTOCOL");
391 DEF_GLOBAL(server_protocol_value, "HTTP/1.1");
392 DEF_GLOBAL(http_host, "HTTP_HOST");
393 DEF_GLOBAL(mongrel_version, "Mongrel 1.1.1"); /* XXX Why is this defined here? */
394 DEF_GLOBAL(server_software, "SERVER_SOFTWARE");
395 DEF_GLOBAL(port_80, "80");
397 eHttpParserError = rb_define_class_under(mMongrel, "HttpParserError", rb_eIOError);
399 cHttpParser = rb_define_class_under(mMongrel, "HttpParser", rb_cObject);
400 rb_define_alloc_func(cHttpParser, HttpParser_alloc);
401 rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
402 rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
403 rb_define_method(cHttpParser, "finish", HttpParser_finish,0);
404 rb_define_method(cHttpParser, "execute", HttpParser_execute,3);
405 rb_define_method(cHttpParser, "error?", HttpParser_has_error,0);
406 rb_define_method(cHttpParser, "finished?", HttpParser_is_finished,0);
407 rb_define_method(cHttpParser, "nread", HttpParser_nread,0);