diff options
Diffstat (limited to 'ext/unicorn_http/unicorn_http.rl')
-rw-r--r-- | ext/unicorn_http/unicorn_http.rl | 461 |
1 files changed, 336 insertions, 125 deletions
diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl index 1ad2a5d..971cdc1 100644 --- a/ext/unicorn_http/unicorn_http.rl +++ b/ext/unicorn_http/unicorn_http.rl @@ -12,23 +12,81 @@ #include "global_variables.h" #include "c_util.h" +void init_unicorn_httpdate(void); + #define UH_FL_CHUNKED 0x1 #define UH_FL_HASBODY 0x2 #define UH_FL_INBODY 0x4 #define UH_FL_HASTRAILER 0x8 #define UH_FL_INTRAILER 0x10 #define UH_FL_INCHUNK 0x20 -#define UH_FL_KAMETHOD 0x40 +#define UH_FL_REQEOF 0x40 #define UH_FL_KAVERSION 0x80 #define UH_FL_HASHEADER 0x100 +#define UH_FL_TO_CLEAR 0x200 + +/* all of these flags need to be set for keepalive to be supported */ +#define UH_FL_KEEPALIVE (UH_FL_KAVERSION | UH_FL_REQEOF | UH_FL_HASHEADER) + +/* + * whether or not to trust X-Forwarded-Proto and X-Forwarded-SSL when + * setting rack.url_scheme + */ +static VALUE trust_x_forward = Qtrue; + +static unsigned long keepalive_requests = 100; /* same as nginx */ + +/* + * Returns the maximum number of keepalive requests a client may make + * before the parser refuses to continue. + */ +static VALUE ka_req(VALUE self) +{ + return ULONG2NUM(keepalive_requests); +} -/* both of these flags need to be set for keepalive to be supported */ -#define UH_FL_KEEPALIVE (UH_FL_KAMETHOD | UH_FL_KAVERSION) +/* + * Sets the maximum number of keepalive requests a client may make. + * A special value of +nil+ causes this to be the maximum value + * possible (this is architecture-dependent). + */ +static VALUE set_ka_req(VALUE self, VALUE val) +{ + keepalive_requests = NIL_P(val) ? ULONG_MAX : NUM2ULONG(val); + + return ka_req(self); +} + +/* + * Sets whether or not the parser will trust X-Forwarded-Proto and + * X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly. + * Rainbows!/Zbatery installations facing untrusted clients directly + * should set this to +false+ + */ +static VALUE set_xftrust(VALUE self, VALUE val) +{ + if (Qtrue == val || Qfalse == val) + trust_x_forward = val; + else + rb_raise(rb_eTypeError, "must be true or false"); + + return val; +} + +/* + * returns whether or not the parser will trust X-Forwarded-Proto and + * X-Forwarded-SSL headers and set "rack.url_scheme" to "https" accordingly + */ +static VALUE xftrust(VALUE self) +{ + return trust_x_forward; +} /* keep this small for Rainbows! since every client has one */ struct http_parser { int cs; /* Ragel internal state */ unsigned int flags; + unsigned long nr_requests; size_t mark; size_t offset; union { /* these 2 fields don't nest */ @@ -39,6 +97,8 @@ struct http_parser { size_t field_len; /* only used during header processing */ size_t dest_offset; /* only used during body processing */ } s; + VALUE buf; + VALUE env; VALUE cont; /* Qfalse: unset, Qnil: ignored header, T_STRING: append */ union { off_t content; @@ -46,7 +106,18 @@ struct http_parser { } len; }; -static void finalize_header(struct http_parser *hp, VALUE req); +static ID id_clear; + +static void finalize_header(struct http_parser *hp); + +static void parser_error(const char *msg) +{ + VALUE exc = rb_exc_new2(eHttpParserError, msg); + VALUE bt = rb_ary_new(); + + rb_funcall(exc, rb_intern("set_backtrace"), 1, bt); + rb_exc_raise(exc); +} #define REMAINING (unsigned long)(pe - p) #define LEN(AT, FPC) (FPC - buffer - hp->AT) @@ -66,51 +137,34 @@ static void finalize_header(struct http_parser *hp, VALUE req); */ static void hp_keepalive_connection(struct http_parser *hp, VALUE val) { - /* REQUEST_METHOD is always set before any headers */ - if (HP_FL_TEST(hp, KAMETHOD)) { - if (STR_CSTR_CASE_EQ(val, "keep-alive")) { - /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */ - HP_FL_SET(hp, KAVERSION); - } else if (STR_CSTR_CASE_EQ(val, "close")) { - /* - * it doesn't matter what HTTP version or request method we have, - * if a client says "Connection: close", we disable keepalive - */ - HP_FL_UNSET(hp, KEEPALIVE); - } else { - /* - * client could've sent anything, ignore it for now. Maybe - * "HP_FL_UNSET(hp, KEEPALIVE);" just in case? - * Raising an exception might be too mean... - */ - } + if (STR_CSTR_CASE_EQ(val, "keep-alive")) { + /* basically have HTTP/1.0 masquerade as HTTP/1.1+ */ + HP_FL_SET(hp, KAVERSION); + } else if (STR_CSTR_CASE_EQ(val, "close")) { + /* + * it doesn't matter what HTTP version or request method we have, + * if a client says "Connection: close", we disable keepalive + */ + HP_FL_UNSET(hp, KAVERSION); + } else { + /* + * client could've sent anything, ignore it for now. Maybe + * "HP_FL_UNSET(hp, KAVERSION);" just in case? + * Raising an exception might be too mean... + */ } } static void -request_method(struct http_parser *hp, VALUE req, const char *ptr, size_t len) +request_method(struct http_parser *hp, const char *ptr, size_t len) { - VALUE v; + VALUE v = rb_str_new(ptr, len); - /* - * we only support keepalive for GET and HEAD requests for now other - * methods are too rarely seen to be worth optimizing. POST is unsafe - * since some clients send extra bytes after POST bodies. - */ - if (CONST_MEM_EQ("GET", ptr, len)) { - HP_FL_SET(hp, KAMETHOD); - v = g_GET; - } else if (CONST_MEM_EQ("HEAD", ptr, len)) { - HP_FL_SET(hp, KAMETHOD); - v = g_HEAD; - } else { - v = rb_str_new(ptr, len); - } - rb_hash_aset(req, g_request_method, v); + rb_hash_aset(hp->env, g_request_method, v); } static void -http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len) +http_version(struct http_parser *hp, const char *ptr, size_t len) { VALUE v; @@ -125,14 +179,14 @@ http_version(struct http_parser *hp, VALUE req, const char *ptr, size_t len) } else { v = rb_str_new(ptr, len); } - rb_hash_aset(req, g_server_protocol, v); - rb_hash_aset(req, g_http_version, v); + rb_hash_aset(hp->env, g_server_protocol, v); + rb_hash_aset(hp->env, g_http_version, v); } static inline void hp_invalid_if_trailer(struct http_parser *hp) { if (HP_FL_TEST(hp, INTRAILER)) - rb_raise(eHttpParserError, "invalid Trailer"); + parser_error("invalid Trailer"); } static void write_cont_value(struct http_parser *hp, @@ -141,7 +195,7 @@ static void write_cont_value(struct http_parser *hp, char *vptr; if (hp->cont == Qfalse) - rb_raise(eHttpParserError, "invalid continuation line"); + parser_error("invalid continuation line"); if (NIL_P(hp->cont)) return; /* we're ignoring this header (probably Host:) */ @@ -163,7 +217,7 @@ static void write_cont_value(struct http_parser *hp, rb_str_buf_cat(hp->cont, vptr, LEN(mark, p)); } -static void write_value(VALUE req, struct http_parser *hp, +static void write_value(struct http_parser *hp, const char *buffer, const char *p) { VALUE f = find_common_field(PTR_TO(start.field), hp->s.field_len); @@ -192,8 +246,9 @@ static void write_value(VALUE req, struct http_parser *hp, } else if (f == g_content_length) { hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v)); if (hp->len.content < 0) - rb_raise(eHttpParserError, "invalid Content-Length"); - HP_FL_SET(hp, HASBODY); + parser_error("invalid Content-Length"); + if (hp->len.content != 0) + HP_FL_SET(hp, HASBODY); hp_invalid_if_trailer(hp); } else if (f == g_http_transfer_encoding) { if (STR_CSTR_CASE_EQ(v, "chunked")) { @@ -209,9 +264,9 @@ static void write_value(VALUE req, struct http_parser *hp, assert_frozen(f); } - e = rb_hash_aref(req, f); + e = rb_hash_aref(hp->env, f); if (NIL_P(e)) { - hp->cont = rb_hash_aset(req, f, v); + hp->cont = rb_hash_aset(hp->env, f, v); } else if (f == g_http_host) { /* * ignored, absolute URLs in REQUEST_URI take precedence over @@ -236,59 +291,55 @@ static void write_value(VALUE req, struct http_parser *hp, action downcase_char { downcase_char(deconst(fpc)); } action write_field { hp->s.field_len = LEN(start.field, fpc); } action start_value { MARK(mark, fpc); } - action write_value { write_value(req, hp, buffer, fpc); } + action write_value { write_value(hp, buffer, fpc); } action write_cont_value { write_cont_value(hp, buffer, fpc); } - action request_method { - request_method(hp, req, PTR_TO(mark), LEN(mark, fpc)); - } + action request_method { request_method(hp, PTR_TO(mark), LEN(mark, fpc)); } action scheme { - rb_hash_aset(req, g_rack_url_scheme, STR_NEW(mark, fpc)); - } - action host { - rb_hash_aset(req, g_http_host, STR_NEW(mark, fpc)); + rb_hash_aset(hp->env, g_rack_url_scheme, STR_NEW(mark, fpc)); } + action host { rb_hash_aset(hp->env, g_http_host, STR_NEW(mark, fpc)); } action request_uri { VALUE str; VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_URI); - str = rb_hash_aset(req, g_request_uri, STR_NEW(mark, fpc)); + str = rb_hash_aset(hp->env, g_request_uri, STR_NEW(mark, fpc)); /* * "OPTIONS * HTTP/1.1\r\n" is a valid request, but we can't have '*' * in REQUEST_PATH or PATH_INFO or else Rack::Lint will complain */ if (STR_CSTR_EQ(str, "*")) { str = rb_str_new(NULL, 0); - rb_hash_aset(req, g_path_info, str); - rb_hash_aset(req, g_request_path, str); + rb_hash_aset(hp->env, g_path_info, str); + rb_hash_aset(hp->env, g_request_path, str); } } action fragment { VALIDATE_MAX_LENGTH(LEN(mark, fpc), FRAGMENT); - rb_hash_aset(req, g_fragment, STR_NEW(mark, fpc)); + rb_hash_aset(hp->env, g_fragment, STR_NEW(mark, fpc)); } action start_query {MARK(start.query, fpc); } action query_string { VALIDATE_MAX_LENGTH(LEN(start.query, fpc), QUERY_STRING); - rb_hash_aset(req, g_query_string, STR_NEW(start.query, fpc)); + rb_hash_aset(hp->env, g_query_string, STR_NEW(start.query, fpc)); } - action http_version { http_version(hp, req, PTR_TO(mark), LEN(mark, fpc)); } + action http_version { http_version(hp, PTR_TO(mark), LEN(mark, fpc)); } action request_path { VALUE val; VALIDATE_MAX_LENGTH(LEN(mark, fpc), REQUEST_PATH); - val = rb_hash_aset(req, g_request_path, STR_NEW(mark, fpc)); + val = rb_hash_aset(hp->env, g_request_path, STR_NEW(mark, fpc)); /* rack says PATH_INFO must start with "/" or be empty */ if (!STR_CSTR_EQ(val, "*")) - rb_hash_aset(req, g_path_info, val); + rb_hash_aset(hp->env, g_path_info, val); } action add_to_chunk_size { hp->len.chunk = step_incr(hp->len.chunk, fc, 16); if (hp->len.chunk < 0) - rb_raise(eHttpParserError, "invalid chunk size"); + parser_error("invalid chunk size"); } action header_done { - finalize_header(hp, req); + finalize_header(hp); cs = http_parser_first_final; if (HP_FL_TEST(hp, HASBODY)) { @@ -296,6 +347,7 @@ static void write_value(VALUE req, struct http_parser *hp, if (HP_FL_TEST(hp, CHUNKED)) cs = http_parser_en_ChunkedBody; } else { + HP_FL_SET(hp, REQEOF); assert(!HP_FL_TEST(hp, CHUNKED) && "chunked encoding without body!"); } /* @@ -321,7 +373,7 @@ static void write_value(VALUE req, struct http_parser *hp, action skip_chunk_data { skip_chunk_data_hack: { size_t nr = MIN((size_t)hp->len.chunk, REMAINING); - memcpy(RSTRING_PTR(req) + hp->s.dest_offset, fpc, nr); + memcpy(RSTRING_PTR(hp->cont) + hp->s.dest_offset, fpc, nr); hp->s.dest_offset += nr; hp->len.chunk -= nr; p += nr; @@ -344,15 +396,20 @@ static void write_value(VALUE req, struct http_parser *hp, static void http_parser_init(struct http_parser *hp) { int cs = 0; - memset(hp, 0, sizeof(struct http_parser)); + hp->flags = 0; + hp->mark = 0; + hp->offset = 0; + hp->start.field = 0; + hp->s.field_len = 0; + hp->len.content = 0; hp->cont = Qfalse; /* zero on MRI, should be optimized away by above */ %% write init; hp->cs = cs; } /** exec **/ -static void http_parser_execute(struct http_parser *hp, - VALUE req, char *buffer, size_t len) +static void +http_parser_execute(struct http_parser *hp, char *buffer, size_t len) { const char *p, *pe; int cs = hp->cs; @@ -392,54 +449,110 @@ static struct http_parser *data_get(VALUE self) return hp; } -static void finalize_header(struct http_parser *hp, VALUE req) +/* + * set rack.url_scheme to "https" or "http", no others are allowed by Rack + * this resembles the Rack::Request#scheme method as of rack commit + * 35bb5ba6746b5d346de9202c004cc926039650c7 + */ +static void set_url_scheme(VALUE env, VALUE *server_port) { - VALUE temp = rb_hash_aref(req, g_rack_url_scheme); - VALUE server_name = g_localhost; - VALUE server_port = g_port_80; + VALUE scheme = rb_hash_aref(env, g_rack_url_scheme); - /* set rack.url_scheme to "https" or "http", no others are allowed by Rack */ - if (NIL_P(temp)) { - temp = rb_hash_aref(req, g_http_x_forwarded_proto); - if (!NIL_P(temp) && STR_CSTR_EQ(temp, "https")) - server_port = g_port_443; - else - temp = g_http; - rb_hash_aset(req, g_rack_url_scheme, temp); - } else if (STR_CSTR_EQ(temp, "https")) { - server_port = g_port_443; + if (NIL_P(scheme)) { + if (trust_x_forward == Qfalse) { + scheme = g_http; + } else { + scheme = rb_hash_aref(env, g_http_x_forwarded_ssl); + if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) { + *server_port = g_port_443; + scheme = g_https; + } else { + scheme = rb_hash_aref(env, g_http_x_forwarded_proto); + if (NIL_P(scheme)) { + scheme = g_http; + } else { + long len = RSTRING_LEN(scheme); + if (len >= 5 && !memcmp(RSTRING_PTR(scheme), "https", 5)) { + if (len != 5) + scheme = g_https; + *server_port = g_port_443; + } else { + scheme = g_http; + } + } + } + } + rb_hash_aset(env, g_rack_url_scheme, scheme); + } else if (STR_CSTR_EQ(scheme, "https")) { + *server_port = g_port_443; } else { - assert(server_port == g_port_80 && "server_port not set"); + assert(*server_port == g_port_80 && "server_port not set"); } +} + +/* + * Parse and set the SERVER_NAME and SERVER_PORT variables + * Not supporting X-Forwarded-Host/X-Forwarded-Port in here since + * anybody who needs them is using an unsupported configuration and/or + * incompetent. Rack::Request will handle X-Forwarded-{Port,Host} just + * fine. + */ +static void set_server_vars(VALUE env, VALUE *server_port) +{ + VALUE server_name = g_localhost; + VALUE host = rb_hash_aref(env, g_http_host); + + if (!NIL_P(host)) { + char *host_ptr = RSTRING_PTR(host); + long host_len = RSTRING_LEN(host); + char *colon; + + if (*host_ptr == '[') { /* ipv6 address format */ + char *rbracket = memchr(host_ptr + 1, ']', host_len - 1); + + if (rbracket) + colon = (rbracket[1] == ':') ? rbracket + 1 : NULL; + else + colon = memchr(host_ptr + 1, ':', host_len - 1); + } else { + colon = memchr(host_ptr, ':', host_len); + } - /* parse and set the SERVER_NAME and SERVER_PORT variables */ - temp = rb_hash_aref(req, g_http_host); - if (!NIL_P(temp)) { - char *colon = memchr(RSTRING_PTR(temp), ':', RSTRING_LEN(temp)); if (colon) { - long port_start = colon - RSTRING_PTR(temp) + 1; + long port_start = colon - host_ptr + 1; - server_name = rb_str_substr(temp, 0, colon - RSTRING_PTR(temp)); - if ((RSTRING_LEN(temp) - port_start) > 0) - server_port = rb_str_substr(temp, port_start, RSTRING_LEN(temp)); + server_name = rb_str_substr(host, 0, colon - host_ptr); + if ((host_len - port_start) > 0) + *server_port = rb_str_substr(host, port_start, host_len); } else { - server_name = temp; + server_name = host; } } - rb_hash_aset(req, g_server_name, server_name); - rb_hash_aset(req, g_server_port, server_port); + rb_hash_aset(env, g_server_name, server_name); + rb_hash_aset(env, g_server_port, *server_port); +} + +static void finalize_header(struct http_parser *hp) +{ + VALUE server_port = g_port_80; + + set_url_scheme(hp->env, &server_port); + set_server_vars(hp->env, &server_port); + if (!HP_FL_TEST(hp, HASHEADER)) - rb_hash_aset(req, g_server_protocol, g_http_09); + rb_hash_aset(hp->env, g_server_protocol, g_http_09); /* rack requires QUERY_STRING */ - if (NIL_P(rb_hash_aref(req, g_query_string))) - rb_hash_aset(req, g_query_string, rb_str_new(NULL, 0)); + if (NIL_P(rb_hash_aref(hp->env, g_query_string))) + rb_hash_aset(hp->env, g_query_string, rb_str_new(NULL, 0)); } static void hp_mark(void *ptr) { struct http_parser *hp = ptr; + rb_gc_mark(hp->buf); + rb_gc_mark(hp->env); rb_gc_mark(hp->cont); } @@ -458,7 +571,29 @@ static VALUE HttpParser_alloc(VALUE klass) */ static VALUE HttpParser_init(VALUE self) { - http_parser_init(data_get(self)); + struct http_parser *hp = data_get(self); + + http_parser_init(hp); + hp->buf = rb_str_new(NULL, 0); + hp->env = rb_hash_new(); + hp->nr_requests = keepalive_requests; + + return self; +} + +/** + * call-seq: + * parser.clear => parser + * + * Resets the parser to it's initial state so that you can reuse it + * rather than making new ones. + */ +static VALUE HttpParser_clear(VALUE self) +{ + struct http_parser *hp = data_get(self); + + http_parser_init(hp); + rb_funcall(hp->env, id_clear, 0); return self; } @@ -469,11 +604,18 @@ static VALUE HttpParser_init(VALUE self) * * Resets the parser to it's initial state so that you can reuse it * rather than making new ones. + * + * This method is deprecated and to be removed in Unicorn 4.x */ static VALUE HttpParser_reset(VALUE self) { - http_parser_init(data_get(self)); + static int warned; + if (!warned) { + rb_warn("Unicorn::HttpParser#reset is deprecated; " + "use Unicorn::HttpParser#clear instead"); + } + HttpParser_clear(self); return Qnil; } @@ -513,48 +655,67 @@ static VALUE HttpParser_content_length(VALUE self) } /** - * Document-method: trailers + * Document-method: parse * call-seq: - * parser.trailers(req, data) => req or nil - * - * This is an alias for HttpParser#headers - */ - -/** - * Document-method: headers - * call-seq: - * parser.headers(req, data) => req or nil + * parser.parse => env or nil * * Takes a Hash and a String of data, parses the String of data filling * in the Hash returning the Hash if parsing is finished, nil otherwise - * When returning the req Hash, it may modify data to point to where + * When returning the env Hash, it may modify data to point to where * body processing should begin. * * Raises HttpParserError if there are parsing errors. */ -static VALUE HttpParser_headers(VALUE self, VALUE req, VALUE data) +static VALUE HttpParser_parse(VALUE self) { struct http_parser *hp = data_get(self); + VALUE data = hp->buf; - rb_str_update(data); + if (HP_FL_TEST(hp, TO_CLEAR)) { + http_parser_init(hp); + rb_funcall(hp->env, id_clear, 0); + } - http_parser_execute(hp, req, RSTRING_PTR(data), RSTRING_LEN(data)); + http_parser_execute(hp, RSTRING_PTR(data), RSTRING_LEN(data)); VALIDATE_MAX_LENGTH(hp->offset, HEADER); if (hp->cs == http_parser_first_final || hp->cs == http_parser_en_ChunkedBody) { advance_str(data, hp->offset + 1); hp->offset = 0; + if (HP_FL_TEST(hp, INTRAILER)) + HP_FL_SET(hp, REQEOF); - return req; + return hp->env; } if (hp->cs == http_parser_error) - rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails."); + parser_error("Invalid HTTP format, parsing fails."); return Qnil; } +/** + * Document-method: trailers + * call-seq: + * parser.trailers(req, data) => req or nil + * + * This is an alias for HttpParser#headers + */ + +/** + * Document-method: headers + */ +static VALUE HttpParser_headers(VALUE self, VALUE env, VALUE buf) +{ + struct http_parser *hp = data_get(self); + + hp->env = env; + hp->buf = buf; + + return HttpParser_parse(self); +} + static int chunked_eof(struct http_parser *hp) { return ((hp->cs == http_parser_first_final) || HP_FL_TEST(hp, INTRAILER)); @@ -597,6 +758,26 @@ static VALUE HttpParser_keepalive(VALUE self) /** * call-seq: + * parser.next? => true or false + * + * Exactly like HttpParser#keepalive?, except it will reset the internal + * parser state on next parse if it returns true. It will also respect + * the maximum *keepalive_requests* value and return false if that is + * reached. + */ +static VALUE HttpParser_next(VALUE self) +{ + struct http_parser *hp = data_get(self); + + if ((HP_FL_ALL(hp, KEEPALIVE)) && (hp->nr_requests-- != 0)) { + HP_FL_SET(hp, TO_CLEAR); + return Qtrue; + } + return Qfalse; +} + +/** + * call-seq: * parser.headers? => true or false * * This should be used to detect if a request has headers (and if @@ -610,6 +791,16 @@ static VALUE HttpParser_has_headers(VALUE self) return HP_FL_TEST(hp, HASHEADER) ? Qtrue : Qfalse; } +static VALUE HttpParser_buf(VALUE self) +{ + return data_get(self)->buf; +} + +static VALUE HttpParser_env(VALUE self) +{ + return data_get(self)->env; +} + /** * call-seq: * parser.filter_body(buf, data) => nil/data @@ -630,7 +821,6 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data) char *dptr; long dlen; - rb_str_update(data); dptr = RSTRING_PTR(data); dlen = RSTRING_LEN(data); @@ -641,9 +831,11 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data) if (HP_FL_TEST(hp, CHUNKED)) { if (!chunked_eof(hp)) { hp->s.dest_offset = 0; - http_parser_execute(hp, buf, dptr, dlen); + hp->cont = buf; + hp->buf = data; + http_parser_execute(hp, dptr, dlen); if (hp->cs == http_parser_error) - rb_raise(eHttpParserError, "Invalid HTTP format, parsing fails."); + parser_error("Invalid HTTP format, parsing fails."); assert(hp->s.dest_offset <= hp->offset && "destination buffer overflow"); @@ -662,10 +854,13 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data) if (hp->len.content > 0) { long nr = MIN(dlen, hp->len.content); + hp->buf = data; memcpy(RSTRING_PTR(buf), dptr, nr); hp->len.content -= nr; - if (hp->len.content == 0) + if (hp->len.content == 0) { + HP_FL_SET(hp, REQEOF); hp->cs = http_parser_first_final; + } advance_str(data, nr); rb_str_set_len(buf, nr); data = Qnil; @@ -691,15 +886,20 @@ void Init_unicorn_http(void) init_globals(); rb_define_alloc_func(cHttpParser, HttpParser_alloc); - rb_define_method(cHttpParser, "initialize", HttpParser_init,0); - rb_define_method(cHttpParser, "reset", HttpParser_reset,0); + rb_define_method(cHttpParser, "initialize", HttpParser_init, 0); + rb_define_method(cHttpParser, "clear", HttpParser_clear, 0); + rb_define_method(cHttpParser, "reset", HttpParser_reset, 0); + rb_define_method(cHttpParser, "parse", HttpParser_parse, 0); rb_define_method(cHttpParser, "headers", HttpParser_headers, 2); - rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2); rb_define_method(cHttpParser, "trailers", HttpParser_headers, 2); + rb_define_method(cHttpParser, "filter_body", HttpParser_filter_body, 2); rb_define_method(cHttpParser, "content_length", HttpParser_content_length, 0); rb_define_method(cHttpParser, "body_eof?", HttpParser_body_eof, 0); rb_define_method(cHttpParser, "keepalive?", HttpParser_keepalive, 0); rb_define_method(cHttpParser, "headers?", HttpParser_has_headers, 0); + rb_define_method(cHttpParser, "next?", HttpParser_next, 0); + rb_define_method(cHttpParser, "buf", HttpParser_buf, 0); + rb_define_method(cHttpParser, "env", HttpParser_env, 0); /* * The maximum size a single chunk when using chunked transfer encoding. @@ -716,11 +916,22 @@ void Init_unicorn_http(void) */ rb_define_const(cHttpParser, "LENGTH_MAX", OFFT2NUM(UH_OFF_T_MAX)); + /* default value for keepalive_requests */ + rb_define_const(cHttpParser, "KEEPALIVE_REQUESTS_DEFAULT", + ULONG2NUM(keepalive_requests)); + + rb_define_singleton_method(cHttpParser, "keepalive_requests", ka_req, 0); + rb_define_singleton_method(cHttpParser, "keepalive_requests=", set_ka_req, 1); + rb_define_singleton_method(cHttpParser, "trust_x_forwarded=", set_xftrust, 1); + rb_define_singleton_method(cHttpParser, "trust_x_forwarded?", xftrust, 0); + init_common_fields(); SET_GLOBAL(g_http_host, "HOST"); SET_GLOBAL(g_http_trailer, "TRAILER"); SET_GLOBAL(g_http_transfer_encoding, "TRANSFER_ENCODING"); SET_GLOBAL(g_content_length, "CONTENT_LENGTH"); SET_GLOBAL(g_http_connection, "CONNECTION"); + id_clear = rb_intern("clear"); + init_unicorn_httpdate(); } #undef SET_GLOBAL |