diff options
Diffstat (limited to 'ext/unicorn_http/unicorn_http.rl')
-rw-r--r-- | ext/unicorn_http/unicorn_http.rl | 87 |
1 files changed, 66 insertions, 21 deletions
diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl index 8ef23bc..fb5dcde 100644 --- a/ext/unicorn_http/unicorn_http.rl +++ b/ext/unicorn_http/unicorn_http.rl @@ -12,6 +12,7 @@ #include "common_field_optimization.h" #include "global_variables.h" #include "c_util.h" +#include "epollexclusive.h" void init_unicorn_httpdate(void); @@ -27,10 +28,15 @@ void init_unicorn_httpdate(void); #define UH_FL_TO_CLEAR 0x200 #define UH_FL_RESSTART 0x400 /* for check_client_connection */ #define UH_FL_HIJACK 0x800 +#define UH_FL_RES_CHUNK_VER (1U << 12) +#define UH_FL_RES_CHUNK_METHOD (1U << 13) /* 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) +/* we can only chunk responses for non-HEAD HTTP/1.1 requests */ +#define UH_FL_RES_CHUNKABLE (UH_FL_RES_CHUNK_VER | UH_FL_RES_CHUNK_METHOD) + static unsigned int MAX_HEADER_LEN = 1024 * (80 + 32); /* same as Mongrel */ /* this is only intended for use with Rainbows! */ @@ -62,19 +68,8 @@ struct http_parser { } len; }; -static ID id_set_backtrace; - -#ifdef HAVE_RB_HASH_CLEAR /* Ruby >= 2.0 */ -# define my_hash_clear(h) (void)rb_hash_clear(h) -#else /* !HAVE_RB_HASH_CLEAR - Ruby <= 1.9.3 */ - -static ID id_clear; - -static void my_hash_clear(VALUE h) -{ - rb_funcall(h, id_clear, 0); -} -#endif /* HAVE_RB_HASH_CLEAR */ +static ID id_set_backtrace, id_is_chunked_p; +static VALUE cHttpParser; static void finalize_header(struct http_parser *hp); @@ -155,6 +150,9 @@ request_method(struct http_parser *hp, const char *ptr, size_t len) { VALUE v = rb_str_new(ptr, len); + if (len != 4 || memcmp(ptr, "HEAD", 4)) + HP_FL_SET(hp, RES_CHUNK_METHOD); + rb_hash_aset(hp->env, g_request_method, v); } @@ -168,6 +166,7 @@ http_version(struct http_parser *hp, const char *ptr, size_t len) if (CONST_MEM_EQ("HTTP/1.1", ptr, len)) { /* HTTP/1.1 implies keepalive unless "Connection: close" is set */ HP_FL_SET(hp, KAVERSION); + HP_FL_SET(hp, RES_CHUNK_VER); v = g_http_11; } else if (CONST_MEM_EQ("HTTP/1.0", ptr, len)) { v = g_http_10; @@ -220,6 +219,19 @@ static void write_cont_value(struct http_parser *hp, rb_str_buf_cat(hp->cont, vptr, end + 1); } +static int is_chunked(VALUE v) +{ + /* common case first */ + if (STR_CSTR_CASE_EQ(v, "chunked")) + return 1; + + /* + * call Ruby function in unicorn/http_request.rb to deal with unlikely + * comma-delimited case + */ + return rb_funcall(cHttpParser, id_is_chunked_p, 1, v) != Qfalse; +} + static void write_value(struct http_parser *hp, const char *buffer, const char *p) { @@ -246,7 +258,9 @@ static void write_value(struct http_parser *hp, f = uncommon_field(field, flen); } else if (f == g_http_connection) { hp_keepalive_connection(hp, v); - } else if (f == g_content_length) { + } else if (f == g_content_length && !HP_FL_TEST(hp, CHUNKED)) { + if (hp->len.content) + parser_raise(eHttpParserError, "Content-Length already set"); hp->len.content = parse_length(RSTRING_PTR(v), RSTRING_LEN(v)); if (hp->len.content < 0) parser_raise(eHttpParserError, "invalid Content-Length"); @@ -254,9 +268,30 @@ static void write_value(struct http_parser *hp, HP_FL_SET(hp, HASBODY); hp_invalid_if_trailer(hp); } else if (f == g_http_transfer_encoding) { - if (STR_CSTR_CASE_EQ(v, "chunked")) { + if (is_chunked(v)) { + if (HP_FL_TEST(hp, CHUNKED)) + /* + * RFC 7230 3.3.1: + * A sender MUST NOT apply chunked more than once to a message body + * (i.e., chunking an already chunked message is not allowed). + */ + parser_raise(eHttpParserError, "Transfer-Encoding double chunked"); + HP_FL_SET(hp, CHUNKED); HP_FL_SET(hp, HASBODY); + + /* RFC 7230 3.3.3, 3: favor chunked if Content-Length exists */ + hp->len.content = 0; + } else if (HP_FL_TEST(hp, CHUNKED)) { + /* + * RFC 7230 3.3.3, point 3 states: + * If a Transfer-Encoding header field is present in a request and + * the chunked transfer coding is not the final encoding, the + * message body length cannot be determined reliably; the server + * MUST respond with the 400 (Bad Request) status code and then + * close the connection. + */ + parser_raise(eHttpParserError, "invalid Transfer-Encoding"); } hp_invalid_if_trailer(hp); } else if (f == g_http_trailer) { @@ -487,7 +522,7 @@ static void set_url_scheme(VALUE env, VALUE *server_port) * and X-Forwarded-Proto handling from this parser? We've had it * forever and nobody has said anything against it, either. * Anyways, please send comments to our public mailing list: - * unicorn-public@bogomips.org (no HTML mail, no subscription necessary) + * unicorn-public@yhbt.net (no HTML mail, no subscription necessary) */ scheme = rb_hash_aref(env, g_http_x_forwarded_ssl); if (!NIL_P(scheme) && STR_CSTR_EQ(scheme, "on")) { @@ -613,7 +648,7 @@ static VALUE HttpParser_clear(VALUE self) return HttpParser_init(self); http_parser_init(hp); - my_hash_clear(hp->env); + rb_hash_clear(hp->env); return self; } @@ -775,6 +810,14 @@ static VALUE HttpParser_keepalive(VALUE self) return HP_FL_ALL(hp, KEEPALIVE) ? Qtrue : Qfalse; } +/* :nodoc: */ +static VALUE chunkable_response_p(VALUE self) +{ + const struct http_parser *hp = data_get(self); + + return HP_FL_ALL(hp, RES_CHUNKABLE) ? Qtrue : Qfalse; +} + /** * call-seq: * parser.next? => true or false @@ -931,7 +974,7 @@ static VALUE HttpParser_rssget(VALUE self) void Init_unicorn_http(void) { - VALUE mUnicorn, cHttpParser; + VALUE mUnicorn; mUnicorn = rb_define_module("Unicorn"); cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject); @@ -942,6 +985,7 @@ void Init_unicorn_http(void) e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError", eHttpParserError); + id_uminus = rb_intern("-@"); init_globals(); rb_define_alloc_func(cHttpParser, HttpParser_alloc); rb_define_method(cHttpParser, "initialize", HttpParser_init, 0); @@ -954,6 +998,7 @@ void Init_unicorn_http(void) 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, "chunkable_response?", chunkable_response_p, 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); @@ -988,8 +1033,8 @@ void Init_unicorn_http(void) id_set_backtrace = rb_intern("set_backtrace"); init_unicorn_httpdate(); -#ifndef HAVE_RB_HASH_CLEAR - id_clear = rb_intern("clear"); -#endif + id_is_chunked_p = rb_intern("is_chunked?"); + + init_epollexclusive(mUnicorn); } #undef SET_GLOBAL |