http: generic C string vs VALUEs comparison function
[unicorn.git] / ext / unicorn_http / unicorn_http.rl
blob76831bbdc24e8f5f428e865635b3ca2246a969a3
1 /**
2  * Copyright (c) 2009 Eric Wong (all bugs are Eric's fault)
3  * Copyright (c) 2005 Zed A. Shaw
4  * You can redistribute it and/or modify it under the same terms as Ruby.
5  */
6 #include "ruby.h"
7 #include "ext_help.h"
8 #include <assert.h>
9 #include <string.h>
10 #include <sys/types.h>
11 #include "common_field_optimization.h"
12 #include "global_variables.h"
13 #include "c_util.h"
15 #define UH_FL_CHUNKED  0x1
16 #define UH_FL_HASBODY  0x2
17 #define UH_FL_INBODY   0x4
18 #define UH_FL_HASTRAILER 0x8
20 struct http_parser {
21   int cs; /* Ragel internal state */
22   unsigned int flags;
23   size_t mark;
24   union { /* these 3 fields don't nest */
25     size_t field;
26     size_t query;
27     size_t offset;
28   } start;
29   union {
30     size_t field_len; /* only used during header processing */
31     long tmpfd; /* only used during body processing */
32   } s;
33   union {
34     off_t content;
35     off_t chunk;
36   } len;
39 static void http_field(VALUE req, const char *field, size_t flen, VALUE val);
40 static void header_done(VALUE req, const char *at, size_t length);
42 #define LEN(AT, FPC) (FPC - buffer - hp->AT)
43 #define MARK(M,FPC) (hp->M = (FPC) - buffer)
44 #define PTR_TO(F) (buffer + hp->F)
45 #define STR_NEW(M,FPC) rb_str_new(PTR_TO(M), LEN(M, FPC))
47 /** Machine **/
49 %%{
50   machine http_parser;
52   action mark {MARK(mark, fpc); }
54   action start_field { MARK(start.field, fpc); }
55   action snake_upcase_field { snake_upcase_char((char *)fpc); }
56   action downcase_char { downcase_char((char *)fpc); }
57   action write_field { hp->s.field_len = LEN(start.field, fpc); }
58   action start_value { MARK(mark, fpc); }
59   action write_value {
60     VALIDATE_MAX_LENGTH(LEN(mark, fpc), FIELD_VALUE);
61     http_field(req, PTR_TO(start.field), hp->s.field_len, STR_NEW(mark, fpc));
62   }
63   action request_method {
64     rb_hash_aset(req, g_request_method, STR_NEW(mark, fpc));
65   }
66   action scheme {
67     rb_hash_aset(req, g_rack_url_scheme, STR_NEW(mark, fpc));
68   }
69   action host {
70     rb_hash_aset(req, g_http_host, STR_NEW(mark, fpc));
71   }
72   action request_uri {
73     size_t len = LEN(mark, fpc);
74     VALUE str;
76     VALIDATE_MAX_LENGTH(len, REQUEST_URI);
77     str = rb_hash_aset(req, g_request_uri, STR_NEW(mark, fpc));
78     /*
79      * "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*'
80      * in REQUEST_PATH or PATH_INFO or else Rack::Lint will complain
81      */
82     if (STR_CSTR_EQ(str, "*")) {
83       str = rb_str_new(NULL, 0);
84       rb_hash_aset(req, g_path_info, str);
85       rb_hash_aset(req, g_request_path, str);
86     }
87   }
88   action fragment {
89     VALIDATE_MAX_LENGTH(LEN(mark, fpc), FRAGMENT);
90     rb_hash_aset(req, g_fragment, STR_NEW(mark, fpc));
91   }
92   action start_query {MARK(start.query, fpc); }
93   action query_string {
94     VALIDATE_MAX_LENGTH(LEN(start.query, fpc), QUERY_STRING);
95     rb_hash_aset(req, g_query_string, STR_NEW(start.query, fpc));
96   }
97   action http_version {
98     rb_hash_aset(req, g_http_version, STR_NEW(mark, fpc));
99   }
100   action request_path {
101     VALUE val;
102     size_t len = LEN(mark, fpc);
104     VALIDATE_MAX_LENGTH(len, REQUEST_PATH);
105     val = rb_hash_aset(req, g_request_path, STR_NEW(mark, fpc));
107     /* rack says PATH_INFO must start with "/" or be empty */
108     if (!STR_CSTR_EQ(val, "*"))
109       rb_hash_aset(req, g_path_info, val);
110   }
111   action done {
112     hp->start.offset = fpc - buffer + 1;
113     header_done(req, fpc + 1, pe - fpc - 1);
114     fbreak;
115   }
117   include unicorn_http_common "unicorn_http_common.rl";
120 /** Data **/
121 %% write data;
123 static void http_parser_init(struct http_parser *hp)
125   int cs = 0;
126   memset(hp, 0, sizeof(struct http_parser));
127   %% write init;
128   hp->cs = cs;
131 /** exec **/
132 static void http_parser_execute(struct http_parser *hp,
133   VALUE req, const char *buffer, size_t len)
135   const char *p, *pe;
136   int cs = hp->cs;
137   size_t off = hp->start.offset;
139   assert(off <= len && "offset past end of buffer");
141   p = buffer+off;
142   pe = buffer+len;
144   assert(pe - p == len - off && "pointers aren't same distance");
146   %% write exec;
148   if (hp->cs != http_parser_error)
149     hp->cs = cs;
150   hp->start.offset = p - buffer;
152   assert(p <= pe && "buffer overflow after parsing execute");
153   assert(hp->start.offset <= len && "start.offset longer than length");
154   assert(hp->mark < len && "mark is after buffer end");
155   assert(hp->s.field_len <= len && "field has length longer than whole buffer");
158 static struct http_parser *data_get(VALUE self)
160   struct http_parser *hp;
162   Data_Get_Struct(self, struct http_parser, hp);
163   assert(hp);
164   return hp;
167 static void http_field(VALUE req, const char *field, size_t flen, VALUE val)
169   VALUE f = find_common_field(field, flen);
171   if (f == Qnil) {
172     VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
173     f = uncommon_field(field, flen);
174   } else if (f == g_http_host && rb_hash_aref(req, f) != Qnil) {
175     return;
176   }
178   rb_hash_aset(req, f, val);
181 static void set_server_params(VALUE req)
183   VALUE temp = rb_hash_aref(req, g_rack_url_scheme);
184   VALUE server_name = g_localhost;
185   VALUE server_port = g_port_80;
187   /* set rack.url_scheme to "https" or "http", no others are allowed by Rack */
188   if (temp == Qnil) {
189     temp = rb_hash_aref(req, g_http_x_forwarded_proto);
190     if (temp != Qnil && STR_CSTR_EQ(temp, "https"))
191       server_port = g_port_443;
192     else
193       temp = g_http;
194     rb_hash_aset(req, g_rack_url_scheme, temp);
195   } else if (STR_CSTR_EQ(temp, "https")) {
196     server_port = g_port_443;
197   }
199   /* parse and set the SERVER_NAME and SERVER_PORT variables */
200   temp = rb_hash_aref(req, g_http_host);
201   if (temp != Qnil) {
202     char *colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp));
203     if (colon) {
204       long port_start = colon - RSTRING_PTR(temp) + 1;
206       server_name = rb_str_substr(temp, 0, colon - RSTRING_PTR(temp));
207       if ((RSTRING_LEN(temp) - port_start) > 0)
208         server_port = rb_str_substr(temp, port_start, RSTRING_LEN(temp));
209     } else {
210       server_name = temp;
211     }
212   }
213   rb_hash_aset(req, g_server_name, server_name);
214   rb_hash_aset(req, g_server_port, server_port);
217 /** Finalizes the request header to have a bunch of stuff that's needed. */
218 static void header_done(VALUE req, const char *at, size_t length)
220   VALUE temp;
222   /* rack requires QUERY_STRING */
223   if (rb_hash_aref(req, g_query_string) == Qnil)
224     rb_hash_aset(req, g_query_string, rb_str_new(NULL, 0));
226   set_server_params(req);
227   rb_hash_aset(req, g_server_protocol, g_server_protocol_value);
229   /* grab the initial body and stuff it into the hash */
230   temp = rb_hash_aref(req, g_request_method);
231   if (temp != Qnil) {
232     long len = RSTRING_LEN(temp);
233     char *ptr = RSTRING_PTR(temp);
235     if (memcmp(ptr, "HEAD", len) && memcmp(ptr, "GET", len))
236       rb_hash_aset(req, sym_http_body, rb_str_new(at, length));
237   }
240 static VALUE HttpParser_alloc(VALUE klass)
242   struct http_parser *hp;
243   return Data_Make_Struct(klass, struct http_parser, NULL, NULL, hp);
248  * call-seq:
249  *    parser.new -> parser
251  * Creates a new parser.
252  */
253 static VALUE HttpParser_init(VALUE self)
255   http_parser_init(data_get(self));
257   return self;
262  * call-seq:
263  *    parser.reset -> nil
265  * Resets the parser to it's initial state so that you can reuse it
266  * rather than making new ones.
267  */
268 static VALUE HttpParser_reset(VALUE self)
270   http_parser_init(data_get(self));
272   return Qnil;
277  * call-seq:
278  *    parser.execute(req, data) -> true/false
280  * Takes a Hash and a String of data, parses the String of data filling
281  * in the Hash returning a boolean to indicate whether or not parsing
282  * is finished.
284  * This function now throws an exception when there is a parsing error.
285  * This makes the logic for working with the parser much easier.  You
286  * will need to wrap the parser with an exception handling block.
287  */
289 static VALUE HttpParser_execute(VALUE self, VALUE req, VALUE data)
291   struct http_parser *hp = data_get(self);
292   char *dptr = RSTRING_PTR(data);
293   long dlen = RSTRING_LEN(data);
295   if (hp->start.offset < dlen) {
296     http_parser_execute(hp, req, dptr, dlen);
298     VALIDATE_MAX_LENGTH(hp->start.offset, HEADER);
300     if (hp->cs != http_parser_error)
301       return hp->cs == http_parser_first_final ? Qtrue : Qfalse;
303     rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails.");
304   }
305   rb_raise(eHttpParserError, "Requested start is after data buffer end.");
308 #define SET_GLOBAL(var,str) do { \
309   var = find_common_field(str, sizeof(str) - 1); \
310   assert(var != Qnil); \
311 } while (0)
313 void Init_unicorn_http(void)
315   init_globals();
316   rb_define_alloc_func(cHttpParser, HttpParser_alloc);
317   rb_define_method(cHttpParser, "initialize", HttpParser_init,0);
318   rb_define_method(cHttpParser, "reset", HttpParser_reset,0);
319   rb_define_method(cHttpParser, "execute", HttpParser_execute,2);
320   init_common_fields();
321   SET_GLOBAL(g_http_host, "HOST");
323 #undef SET_GLOBAL