license: upgrade from GPLv2-only to GPLv2-or-later
[kcar.git] / ext / kcar / kcar.rl
blob500dc3c7992d64b0f243b32c0e4fbfa68755ed34
1 /**
2  * Copyright (c) 2009, 2010 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 1.8
5  * or the GPLv2 or later.
6  */
7 #include "ruby.h"
8 #include "ext_help.h"
9 #include <assert.h>
10 #include <stdlib.h>
11 #include <string.h>
12 #include <sys/types.h>
13 #include "c_util.h"
15 static VALUE eParserError;
16 static ID id_sq, id_sq_set;
18 /** Defines common length and error messages for input length validation. */
19 #define DEF_MAX_LENGTH(N, length) \
20   static const size_t MAX_##N##_LENGTH = length; \
21   static const char MAX_##N##_LENGTH_ERR[] = \
22     "HTTP element " # N  " is longer than the " # length " allowed length."
24 /**
25  * Validates the max length of given input and throws an ParserError
26  * exception if over.
27  */
28 #define VALIDATE_MAX_LENGTH(len, N) do { \
29   if (len > MAX_##N##_LENGTH) \
30     rb_raise(eParserError, MAX_##N##_LENGTH_ERR); \
31 } while (0)
33 /* Defines the maximum allowed lengths for various input elements.*/
34 DEF_MAX_LENGTH(FIELD_NAME, 256);
35 DEF_MAX_LENGTH(FIELD_VALUE, 80 * 1024);
36 DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32)));
37 DEF_MAX_LENGTH(REASON, 256);
39 #define UH_FL_CHUNKED  0x1
40 #define UH_FL_HASBODY  0x2
41 #define UH_FL_INBODY   0x4
42 #define UH_FL_HASTRAILER   0x8
43 #define UH_FL_INTRAILER 0x10
44 #define UH_FL_INCHUNK  0x20
45 #define UH_FL_KEEPALIVE 0x40
46 #define UH_FL_HASHEADER 0x80
48 struct http_parser {
49   int cs; /* Ragel internal state */
50   unsigned int flags;
51   size_t mark;
52   size_t offset;
53   union { /* these 2 fields don't nest */
54     size_t field;
55     size_t query;
56   } start;
57   union {
58     size_t field_len; /* only used during header processing */
59     size_t dest_offset; /* only used during body processing */
60   } s;
61   VALUE cont; /* Qfalse: unset, Qnil: ignored header, T_STRING: append */
62   VALUE status; /* String or Qnil */
63   union {
64     off_t content;
65     off_t chunk;
66   } len;
69 #define REMAINING (unsigned long)(pe - p)
70 #define LEN(AT, FPC) (FPC - buffer - hp->AT)
71 #define MARK(M,FPC) (hp->M = (FPC) - buffer)
72 #define PTR_TO(F) (buffer + hp->F)
73 #define STR_NEW(M,FPC) rb_str_new(PTR_TO(M), LEN(M, FPC))
74 #define STRIPPED_STR_NEW(M,FPC) stripped_str_new(PTR_TO(M), LEN(M, FPC))
76 #define HP_FL_TEST(hp,fl) ((hp)->flags & (UH_FL_##fl))
77 #define HP_FL_SET(hp,fl) ((hp)->flags |= (UH_FL_##fl))
78 #define HP_FL_UNSET(hp,fl) ((hp)->flags &= ~(UH_FL_##fl))
79 #define HP_FL_ALL(hp,fl) (HP_FL_TEST(hp, fl) == (UH_FL_##fl))
81 static int is_lws(char c)
83   return (c == ' ' || c == '\t');
86 static VALUE stripped_str_new(const char *str, long len)
88   long end;
90   for (end = len - 1; end >= 0 && is_lws(str[end]); end--);
92   return rb_str_new(str, end + 1);
95 static void finalize_header(struct http_parser *hp)
97   if ((HP_FL_TEST(hp, HASTRAILER) && ! HP_FL_TEST(hp, CHUNKED)))
98     rb_raise(eParserError, "trailer but not chunked");
102  * handles values of the "Connection:" header, keepalive is implied
103  * for HTTP/1.1 but needs to be explicitly enabled with HTTP/1.0
104  * Additionally, we require GET/HEAD requests to support keepalive.
105  */
106 static void hp_keepalive_connection(struct http_parser *hp, VALUE val)
108   /* REQUEST_METHOD is always set before any headers */
109   if (STR_CSTR_CASE_EQ(val, "keep-alive")) {
110     /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */
111     HP_FL_SET(hp, KEEPALIVE);
112   } else if (STR_CSTR_CASE_EQ(val, "close")) {
113     /*
114      * it doesn't matter what HTTP version or request method we have,
115      * if a server says "Connection: close", we disable keepalive
116      */
117     HP_FL_UNSET(hp, KEEPALIVE);
118   } else {
119     /*
120      * server could've sent anything, ignore it for now.  Maybe
121      * "HP_FL_UNSET(hp, KEEPALIVE);" just in case?
122      * Raising an exception might be too mean...
123      */
124   }
127 static void
128 http_version(struct http_parser *hp, VALUE hdr, const char *ptr, size_t len)
130   if (CONST_MEM_EQ("HTTP/1.1", ptr, len)) {
131     /* HTTP/1.1 implies keepalive unless "Connection: close" is set */
132     HP_FL_SET(hp, KEEPALIVE);
133   }
136 static void
137 status_phrase(struct http_parser *hp, VALUE hdr, const char *ptr, size_t len)
139   long nr;
141   hp->status = rb_str_new(ptr, len);
143   /* RSTRING_PTR is null terminated, ptr is not */
144   nr = strtol(RSTRING_PTR(hp->status), NULL, 10);
146   if (nr < 100 || nr > 999)
147     rb_raise(eParserError, "invalid status: %s", RSTRING_PTR(hp->status));
149   if ( !((nr >= 100 && nr <= 199) || nr == 204 || nr == 304) )
150     HP_FL_SET(hp, HASBODY);
153 static inline void invalid_if_trailer(struct http_parser *hp)
155   if (HP_FL_TEST(hp, INTRAILER))
156     rb_raise(eParserError, "invalid Trailer");
159 static void write_cont_value(struct http_parser *hp,
160                              char *buffer, const char *p)
162   char *vptr;
163   long end;
164   long len = LEN(mark, p);
165   long cont_len;
167   if (hp->cont == Qfalse)
168     rb_raise(eParserError, "invalid continuation line");
170   if (NIL_P(hp->cont))
171     return; /* we're ignoring this header (probably Status:) */
173   assert(TYPE(hp->cont) == T_STRING && "continuation line is not a string");
174   assert(hp->mark > 0 && "impossible continuation line offset");
176   if (len == 0)
177     return;
179   cont_len = RSTRING_LEN(hp->cont);
180   if (cont_len > 0) {
181     --hp->mark;
182     len = LEN(mark, p);
183   }
185   vptr = PTR_TO(mark);
187   /* normalize tab to space */
188   if (cont_len > 0) {
189     assert((' ' == *vptr || '\t' == *vptr) && "invalid leading white space");
190     *vptr = ' ';
191   }
192   for (end = len - 1; end >= 0 && is_lws(vptr[end]); end--);
193   rb_str_buf_cat(hp->cont, vptr, end + 1);
196 static void write_value(VALUE hdr, struct http_parser *hp,
197                         const char *buffer, const char *p)
199   VALUE f, v;
200   VALUE hclass;
201   const char *fptr = PTR_TO(start.field);
202   size_t flen = hp->s.field_len;
203   const char *vptr;
204   size_t vlen;
206   HP_FL_SET(hp, HASHEADER);
208   /* Rack does not like Status headers, so we never send them */
209   if (CSTR_CASE_EQ(fptr, flen, "status")) {
210     hp->cont = Qnil;
211     return;
212   }
214   vptr = PTR_TO(mark);
215   vlen = LEN(mark, p);
216   VALIDATE_MAX_LENGTH(vlen, FIELD_VALUE);
217   VALIDATE_MAX_LENGTH(flen, FIELD_NAME);
218   f = rb_str_new(fptr, (long)flen);
219   v = stripped_str_new(vptr, (long)vlen);
221   /* needs more tests for error-checking here */
222   /*
223    * TODO:
224    * some of these tests might be too strict for real-world HTTP servers,
225    * report real-world examples as we find them:
226    */
227   if (STR_CSTR_CASE_EQ(f, "connection")) {
228     hp_keepalive_connection(hp, v);
229   } else if (STR_CSTR_CASE_EQ(f, "content-length")) {
230     if (! HP_FL_TEST(hp, HASBODY))
231       rb_raise(eParserError, "Content-Length with no body expected");
232     if (HP_FL_TEST(hp, CHUNKED))
233       rb_raise(eParserError,
234                "Content-Length when chunked Transfer-Encoding is set");
235     hp->len.content = parse_length(vptr, vlen);
237     if (hp->len.content < 0)
238       rb_raise(eParserError, "invalid Content-Length");
240     invalid_if_trailer(hp);
241   } else if (STR_CSTR_CASE_EQ(f, "transfer-encoding")) {
242     if (STR_CSTR_CASE_EQ(v, "chunked")) {
243       if (! HP_FL_TEST(hp, HASBODY))
244         rb_raise(eParserError,
245                  "chunked Transfer-Encoding with no body expected");
246       if (hp->len.content >= 0)
247         rb_raise(eParserError,
248                  "chunked Transfer-Encoding when Content-Length is set");
250       hp->len.chunk = 0;
251       HP_FL_SET(hp, CHUNKED);
252     }
253     invalid_if_trailer(hp);
254   } else if (STR_CSTR_CASE_EQ(f, "trailer")) {
255     if (! HP_FL_TEST(hp, HASBODY))
256       rb_raise(eParserError, "trailer with no body");
257     HP_FL_SET(hp, HASTRAILER);
258     invalid_if_trailer(hp);
259   }
261   hclass = CLASS_OF(hdr);
262   if (hclass == rb_cArray) {
263     rb_ary_push(hdr, rb_ary_new3(2, f, v));
264     hp->cont = v;
265   } else {
266     /* hash-ish, try rb_hash_* first and fall back to slow rb_funcall */
267     VALUE e;
269     /* try to read the existing value */
270     if (hclass == rb_cHash)
271       e = rb_hash_aref(hdr, f);
272     else
273       e = rb_funcall(hdr, id_sq, 1, f);
275     if (NIL_P(e)) {
276       /* new value, freeze it since it speeds up MRI slightly */
277       OBJ_FREEZE(f);
279       if (hclass == rb_cHash)
280         rb_hash_aset(hdr, f, v);
281       else
282         rb_funcall(hdr, id_sq_set, 2, f, v);
284       hp->cont = v;
285     } else {
286       /*
287        * existing value, append to it, Rack 1.x uses newlines to represent
288        * repeated cookies:
289        *    { 'Set-Cookie' => "a=b\nc=d" }
290        * becomes:
291        *    "Set-Cookie: a=b\r\nSet-Cookie: c=d\r\n"
292        */
293       rb_str_buf_cat(e, "\n", 1);
294       hp->cont = rb_str_buf_append(e, v);
295     }
296   }
299 /** Machine **/
302   machine http_parser;
304   action mark {MARK(mark, fpc); }
306   action start_field { MARK(start.field, fpc); }
307   action write_field { hp->s.field_len = LEN(start.field, fpc); }
308   action start_value { MARK(mark, fpc); }
309   action write_value { write_value(hdr, hp, buffer, fpc); }
310   action write_cont_value { write_cont_value(hp, buffer, fpc); }
311   action http_version { http_version(hp, hdr, PTR_TO(mark), LEN(mark, fpc)); }
312   action status_phrase { status_phrase(hp, hdr, PTR_TO(mark), LEN(mark, fpc)); }
314   action add_to_chunk_size {
315     hp->len.chunk = step_incr(hp->len.chunk, fc, 16);
316     if (hp->len.chunk < 0)
317       rb_raise(eParserError, "invalid chunk size");
318   }
319   action header_done {
320     finalize_header(hp);
321     cs = http_parser_first_final;
323     if (HP_FL_TEST(hp, CHUNKED))
324       cs = http_parser_en_ChunkedBody;
326     /*
327      * go back to Ruby so we can call the Rack application, we'll reenter
328      * the parser iff the body needs to be processed.
329      */
330     goto post_exec;
331   }
333   action end_trailers {
334     cs = http_parser_first_final;
335     goto post_exec;
336   }
338   action end_chunked_body {
339     HP_FL_SET(hp, INTRAILER);
340     cs = http_parser_en_Trailers;
341     ++p;
342     assert(p <= pe && "buffer overflow after chunked body");
343     goto post_exec;
344   }
346   action skip_chunk_data {
347   skip_chunk_data_hack: {
348     size_t nr = MIN((size_t)hp->len.chunk, REMAINING);
349     memcpy(RSTRING_PTR(hdr) + hp->s.dest_offset, fpc, nr);
350     hp->s.dest_offset += nr;
351     hp->len.chunk -= nr;
352     p += nr;
353     assert(hp->len.chunk >= 0 && "negative chunk length");
354     if ((size_t)hp->len.chunk > REMAINING) {
355       HP_FL_SET(hp, INCHUNK);
356       goto post_exec;
357     } else {
358       fhold;
359       fgoto chunk_end;
360     }
361   }}
363   include kcar_http_common "kcar_http_common.rl";
366 /** Data **/
367 %% write data;
369 static void http_parser_init(struct http_parser *hp)
371   int cs = 0;
372   memset(hp, 0, sizeof(struct http_parser));
373   hp->cont = Qfalse; /* zero on MRI, should be optimized away by above */
374   hp->status = Qnil;
375   hp->len.content = -1;
376   %% write init;
377   hp->cs = cs;
380 /** exec **/
381 static void http_parser_execute(struct http_parser *hp,
382   VALUE hdr, char *buffer, size_t len)
384   const char *p, *pe;
385   int cs = hp->cs;
386   size_t off = hp->offset;
388   if (cs == http_parser_first_final)
389     return;
391   assert(off <= len && "offset past end of buffer");
393   p = buffer+off;
394   pe = buffer+len;
396   assert((void *)(pe - p) == (void *)(len - off) &&
397          "pointers aren't same distance");
399   if (HP_FL_TEST(hp, INCHUNK)) {
400     HP_FL_UNSET(hp, INCHUNK);
401     goto skip_chunk_data_hack;
402   }
403   %% write exec;
404 post_exec: /* "_out:" also goes here */
405   if (hp->cs != http_parser_error)
406     hp->cs = cs;
407   hp->offset = p - buffer;
409   assert(p <= pe && "buffer overflow after parsing execute");
410   assert(hp->offset <= len && "offset longer than length");
413 static struct http_parser *data_get(VALUE self)
415   struct http_parser *hp;
417   Data_Get_Struct(self, struct http_parser, hp);
418   assert(hp && "failed to extract http_parser struct");
419   return hp;
422 static void mark(void *ptr)
424   struct http_parser *hp = ptr;
426   rb_gc_mark(hp->cont);
427   rb_gc_mark(hp->status);
430 static VALUE alloc(VALUE klass)
432   struct http_parser *hp;
433   return Data_Make_Struct(klass, struct http_parser, mark, -1, hp);
437  * call-seq:
438  *    Kcar::Parser.new => parser
440  * Creates a new parser.
442  * Document-method: reset
444  * call-seq:
445  *    parser.reset => parser
447  * Resets the parser so it can be reused by another client
448  */
449 static VALUE initialize(VALUE self)
451   http_parser_init(data_get(self));
453   return self;
456 static void advance_str(VALUE str, off_t nr)
458   long len = RSTRING_LEN(str);
460   if (len == 0)
461     return;
463   rb_str_modify(str);
465   assert(nr <= len && "trying to advance past end of buffer");
466   len -= nr;
467   if (len > 0) /* unlikely, len is usually 0 */
468     memmove(RSTRING_PTR(str), RSTRING_PTR(str) + nr, len);
469   rb_str_set_len(str, len);
473  * call-seq:
474  *   parser.body_bytes_left => nil or Integer
476  * Returns the number of bytes left to run through Parser#filter_body.
477  * This will initially be the value of the "Content-Length" HTTP header
478  * after header parsing is complete and will decrease in value as
479  * Parser#filter_body is called for each chunk.  This should return
480  * zero for responses with no body.
482  * This will return nil on "Transfer-Encoding: chunked" responses as
483  * well as HTTP/1.0 responses where Content-Length is not set
484  */
485 static VALUE body_bytes_left(VALUE self)
487   struct http_parser *hp = data_get(self);
489   if (HP_FL_TEST(hp, CHUNKED))
490     return Qnil;
491   if (hp->len.content >= 0)
492     return OFFT2NUM(hp->len.content);
494   return Qnil;
498  * call-seq:
499  *   parser.body_bytes_left = Integer
501  * Sets the number of bytes left to download for HTTP responses
502  * with "Content-Length".  This raises RuntimeError for chunked
503  * responses.
504  */
505 static VALUE body_bytes_left_set(VALUE self, VALUE bytes)
507   struct http_parser *hp = data_get(self);
509   if (HP_FL_TEST(hp, CHUNKED))
510     rb_raise(rb_eRuntimeError, "body_bytes_left= is not for chunked bodies");
511   hp->len.content = NUM2OFFT(bytes);
512   return bytes;
516  * Document-method: chunked
517  * call-seq:
518  *    parser.chunked? => true or false
520  * This is used to detect if a response uses chunked Transfer-Encoding or not.
521  */
522 static VALUE chunked(VALUE self)
524   struct http_parser *hp = data_get(self);
526   return HP_FL_TEST(hp, CHUNKED) ? Qtrue : Qfalse;
530  * Document-method: headers
531  * call-seq:
532  *    parser.headers(hdr, data) => hdr or nil
534  * Takes a Hash and a String of data, parses the String of data filling
535  * in the Hash returning the Hash if parsing is finished, nil otherwise
536  * When returning the hdr Hash, it may modify data to point to where
537  * body processing should begin.
539  * Raises ParserError if there are parsing errors.
540  */
541 static VALUE headers(VALUE self, VALUE hdr, VALUE data)
543   struct http_parser *hp = data_get(self);
545   http_parser_execute(hp, hdr, RSTRING_PTR(data), RSTRING_LEN(data));
546   VALIDATE_MAX_LENGTH(hp->offset, HEADER);
548   if (hp->cs == http_parser_first_final ||
549       hp->cs == http_parser_en_ChunkedBody) {
550     advance_str(data, hp->offset + 1);
551     hp->offset = 0;
552     if (HP_FL_TEST(hp, INTRAILER))
553       return hdr;
554     else
555       return rb_ary_new3(2, hp->status, hdr);
556   }
558   if (hp->cs == http_parser_error)
559     rb_raise(eParserError, "Invalid HTTP format, parsing fails.");
561   return Qnil;
564 static int chunked_eof(struct http_parser *hp)
566   return ((hp->cs == http_parser_first_final) || HP_FL_TEST(hp, INTRAILER));
570  * call-seq:
571  *    parser.body_eof? => true or false
573  * Detects if we're done filtering the body or not.  This can be used
574  * to detect when to stop calling Parser#filter_body.
575  */
576 static VALUE body_eof(VALUE self)
578   struct http_parser *hp = data_get(self);
580   if (!HP_FL_TEST(hp, HASHEADER) && HP_FL_ALL(hp, KEEPALIVE))
581     return Qtrue;
583   if (HP_FL_TEST(hp, CHUNKED))
584     return chunked_eof(hp) ? Qtrue : Qfalse;
586   if (! HP_FL_TEST(hp, HASBODY))
587     return Qtrue;
589   return hp->len.content == 0 ? Qtrue : Qfalse;
593  * call-seq:
594  *    parser.keepalive? => true or false
596  * This should be used to detect if a request can really handle
597  * keepalives and pipelining.  Currently, the rules are:
599  * 1. MUST be HTTP/1.1 +or+ HTTP/1.0 with "Connection: keep-alive"
600  * 2. MUST NOT have "Connection: close" set
601  * 3. If there is a response body, either a) Content-Length is set
602  *    or b) chunked encoding is used
603  */
604 static VALUE keepalive(VALUE self)
606   struct http_parser *hp = data_get(self);
608   if (HP_FL_ALL(hp, KEEPALIVE)) {
609     if (HP_FL_TEST(hp, HASHEADER) && HP_FL_TEST(hp, HASBODY) ) {
610       if (HP_FL_TEST(hp, CHUNKED) || (hp->len.content >= 0))
611         return Qtrue;
613       /* unknown Content-Length and not chunked, we must assume close */
614       return Qfalse;
615     } else {
616       /* 100 Continue, 304 Not Modified, etc... */
617       return Qtrue;
618     }
619   }
620   return Qfalse;
624  * call-seq:
625  *    parser.filter_body(buf, data) => nil/data
627  * Takes a String of +data+, will modify data if dechunking is done.
628  * Returns +nil+ if there is more data left to process.  Returns
629  * +data+ if body processing is complete. When returning +data+,
630  * it may modify +data+ so the start of the string points to where
631  * the body ended so that trailer processing can begin.
633  * Raises ParserError if there are dechunking errors.
634  * Basically this is a glorified memcpy(3) that copies +data+
635  * into +buf+ while filtering it through the dechunker.
636  */
637 static VALUE filter_body(VALUE self, VALUE buf, VALUE data)
639   struct http_parser *hp = data_get(self);
640   char *dptr;
641   long dlen;
643   dptr = RSTRING_PTR(data);
644   dlen = RSTRING_LEN(data);
646   StringValue(buf);
647   rb_str_modify(buf);
648   rb_str_resize(buf, dlen); /* we can never copy more than dlen bytes */
649   OBJ_TAINT(buf); /* keep weirdo $SAFE users happy */
651   if (!HP_FL_TEST(hp, CHUNKED))
652     rb_raise(rb_eRuntimeError, "filter_body is only for chunked bodies");
654   if (!chunked_eof(hp)) {
655     hp->s.dest_offset = 0;
656     http_parser_execute(hp, buf, dptr, dlen);
657     if (hp->cs == http_parser_error)
658       rb_raise(eParserError, "Invalid HTTP format, parsing fails.");
660     assert(hp->s.dest_offset <= hp->offset &&
661            "destination buffer overflow");
662     advance_str(data, hp->offset);
663     rb_str_set_len(buf, hp->s.dest_offset);
665     if (RSTRING_LEN(buf) == 0 && chunked_eof(hp)) {
666       assert(hp->len.chunk == 0 && "chunk at EOF but more to parse");
667     } else {
668       data = Qnil;
669     }
670   }
671   hp->offset = 0; /* for trailer parsing */
672   return data;
675 void Init_kcar_ext(void)
677   VALUE mKcar = rb_define_module("Kcar");
678   VALUE cParser = rb_define_class_under(mKcar, "Parser", rb_cObject);
680   eParserError = rb_define_class_under(mKcar, "ParserError", rb_eIOError);
682   rb_define_alloc_func(cParser, alloc);
683   rb_define_method(cParser, "initialize", initialize, 0);
684   rb_define_method(cParser, "reset", initialize, 0);
685   rb_define_method(cParser, "headers", headers, 2);
686   rb_define_method(cParser, "trailers", headers, 2);
687   rb_define_method(cParser, "filter_body", filter_body, 2);
688   rb_define_method(cParser, "body_bytes_left", body_bytes_left, 0);
689   rb_define_method(cParser, "body_bytes_left=", body_bytes_left_set, 1);
690   rb_define_method(cParser, "body_eof?", body_eof, 0);
691   rb_define_method(cParser, "keepalive?", keepalive, 0);
692   rb_define_method(cParser, "chunked?", chunked, 0);
694   /*
695    * The maximum size a single chunk when using chunked transfer encoding.
696    * This is only a theoretical maximum used to detect errors in clients,
697    * it is highly unlikely to encounter clients that send more than
698    * several kilobytes at once.
699    */
700   rb_define_const(cParser, "CHUNK_MAX", OFFT2NUM(UH_OFF_T_MAX));
702   /*
703    * The maximum size of the body as specified by Content-Length.
704    * This is only a theoretical maximum, the actual limit is subject
705    * to the limits of the file system used for +Dir.tmpdir+.
706    */
707   rb_define_const(cParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX));
708   id_sq = rb_intern("[]");
709   id_sq_set = rb_intern("[]=");