diff options
-rw-r--r-- | ext/unicorn_http/ext_help.h | 16 | ||||
-rw-r--r-- | ext/unicorn_http/extconf.rb | 1 | ||||
-rw-r--r-- | ext/unicorn_http/global_variables.h | 11 | ||||
-rw-r--r-- | ext/unicorn_http/unicorn_http.rl | 48 | ||||
-rw-r--r-- | lib/unicorn/const.rb | 1 | ||||
-rw-r--r-- | lib/unicorn/http_server.rb | 2 | ||||
-rw-r--r-- | t/t0019-max_header_len.sh | 49 |
7 files changed, 96 insertions, 32 deletions
diff --git a/ext/unicorn_http/ext_help.h b/ext/unicorn_http/ext_help.h index 1f76f54..0968080 100644 --- a/ext/unicorn_http/ext_help.h +++ b/ext/unicorn_http/ext_help.h @@ -36,6 +36,22 @@ static void rb_18_str_set_len(VALUE str, long len) # endif #endif /* ! defined(OFFT2NUM) */ +#if !defined(SIZET2NUM) +# if SIZEOF_SIZE_T == SIZEOF_LONG +# define SIZET2NUM(n) ULONG2NUM(n) +# else +# define SIZET2NUM(n) ULL2NUM(n) +# endif +#endif /* ! defined(SIZET2NUM) */ + +#if !defined(NUM2SIZET) +# if SIZEOF_SIZE_T == SIZEOF_LONG +# define NUM2SIZET(n) ((size_t)NUM2ULONG(n)) +# else +# define NUM2SIZET(n) ((size_t)NUM2ULL(n)) +# endif +#endif /* ! defined(NUM2SIZET) */ + #ifndef HAVE_RB_STR_MODIFY # define rb_str_modify(x) do {} while (0) #endif /* ! defined(HAVE_RB_STR_MODIFY) */ diff --git a/ext/unicorn_http/extconf.rb b/ext/unicorn_http/extconf.rb index 7da82e7..7a1b0cd 100644 --- a/ext/unicorn_http/extconf.rb +++ b/ext/unicorn_http/extconf.rb @@ -2,6 +2,7 @@ require 'mkmf' have_macro("SIZEOF_OFF_T", "ruby.h") or check_sizeof("off_t", "sys/types.h") +have_macro("SIZEOF_SIZE_T", "ruby.h") or check_sizeof("size_t", "sys/types.h") have_macro("SIZEOF_LONG", "ruby.h") or check_sizeof("long", "sys/types.h") have_func("rb_str_set_len", "ruby.h") have_func("gmtime_r", "time.h") diff --git a/ext/unicorn_http/global_variables.h b/ext/unicorn_http/global_variables.h index cda7f24..aa0d777 100644 --- a/ext/unicorn_http/global_variables.h +++ b/ext/unicorn_http/global_variables.h @@ -1,7 +1,8 @@ #ifndef global_variables_h #define global_variables_h static VALUE eHttpParserError; -static VALUE eRequestURITooLongError; +static VALUE e413; +static VALUE e414; static VALUE g_rack_url_scheme; static VALUE g_request_method; @@ -36,8 +37,7 @@ static VALUE g_http_11; static const char * const MAX_##N##_LENGTH_ERR = \ "HTTP element " # N " is longer than the " # length " allowed length." -NORETURN(static void parser_error(const char *)); -NORETURN(static void raise_414(const char *)); +NORETURN(static void parser_raise(VALUE klass, const char *)); /** * Validates the max length of given input and throws an HttpParserError @@ -45,12 +45,12 @@ NORETURN(static void raise_414(const char *)); */ #define VALIDATE_MAX_LENGTH(len, N) do { \ if (len > MAX_##N##_LENGTH) \ - parser_error(MAX_##N##_LENGTH_ERR); \ + parser_raise(eHttpParserError, MAX_##N##_LENGTH_ERR); \ } while (0) #define VALIDATE_MAX_URI_LENGTH(len, N) do { \ if (len > MAX_##N##_LENGTH) \ - raise_414(MAX_##N##_LENGTH_ERR); \ + parser_raise(e414, MAX_##N##_LENGTH_ERR); \ } while (0) /** Defines global strings in the init method. */ @@ -66,7 +66,6 @@ DEF_MAX_LENGTH(REQUEST_URI, 1024 * 12); DEF_MAX_LENGTH(FRAGMENT, 1024); /* Don't know if this length is specified somewhere or not */ DEF_MAX_LENGTH(REQUEST_PATH, 1024); DEF_MAX_LENGTH(QUERY_STRING, (1024 * 10)); -DEF_MAX_LENGTH(HEADER, (1024 * (80 + 32))); static void init_globals(void) { diff --git a/ext/unicorn_http/unicorn_http.rl b/ext/unicorn_http/unicorn_http.rl index 1d2705c..7a6e031 100644 --- a/ext/unicorn_http/unicorn_http.rl +++ b/ext/unicorn_http/unicorn_http.rl @@ -82,6 +82,14 @@ static VALUE xftrust(VALUE self) return trust_x_forward; } +static size_t MAX_HEADER_LEN = 1024 * (80 + 32); /* same as Mongrel */ + +/* this is only intended for use with Rainbows! */ +static VALUE set_maxhdrlen(VALUE self, VALUE len) +{ + return SIZET2NUM(MAX_HEADER_LEN = NUM2SIZET(len)); +} + /* keep this small for Rainbows! since every client has one */ struct http_parser { int cs; /* Ragel internal state */ @@ -110,30 +118,15 @@ static ID id_clear, id_set_backtrace; static void finalize_header(struct http_parser *hp); -NORETURN(static void raise_with_empty_bt(VALUE)); - -static void raise_with_empty_bt(VALUE exc) +static void parser_raise(VALUE klass, const char *msg) { + VALUE exc = rb_exc_new2(klass, msg); VALUE bt = rb_ary_new(); rb_funcall(exc, id_set_backtrace, 1, bt); rb_exc_raise(exc); } -static void parser_error(const char *msg) -{ - VALUE exc = rb_exc_new2(eHttpParserError, msg); - - raise_with_empty_bt(exc); -} - -static void raise_414(const char *msg) -{ - VALUE exc = rb_exc_new2(eRequestURITooLongError, msg); - - raise_with_empty_bt(exc); -} - #define REMAINING (unsigned long)(pe - p) #define LEN(AT, FPC) (FPC - buffer - hp->AT) #define MARK(M,FPC) (hp->M = (FPC) - buffer) @@ -201,7 +194,7 @@ http_version(struct http_parser *hp, const char *ptr, size_t len) static inline void hp_invalid_if_trailer(struct http_parser *hp) { if (HP_FL_TEST(hp, INTRAILER)) - parser_error("invalid Trailer"); + parser_raise(eHttpParserError, "invalid Trailer"); } static void write_cont_value(struct http_parser *hp, @@ -210,7 +203,7 @@ static void write_cont_value(struct http_parser *hp, char *vptr; if (hp->cont == Qfalse) - parser_error("invalid continuation line"); + parser_raise(eHttpParserError, "invalid continuation line"); if (NIL_P(hp->cont)) return; /* we're ignoring this header (probably Host:) */ @@ -261,7 +254,7 @@ static void write_value(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) - parser_error("invalid Content-Length"); + parser_raise(eHttpParserError, "invalid Content-Length"); if (hp->len.content != 0) HP_FL_SET(hp, HASBODY); hp_invalid_if_trailer(hp); @@ -351,7 +344,7 @@ static void write_value(struct http_parser *hp, action add_to_chunk_size { hp->len.chunk = step_incr(hp->len.chunk, fc, 16); if (hp->len.chunk < 0) - parser_error("invalid chunk size"); + parser_raise(eHttpParserError, "invalid chunk size"); } action header_done { finalize_header(hp); @@ -692,7 +685,8 @@ static VALUE HttpParser_parse(VALUE self) } http_parser_execute(hp, RSTRING_PTR(data), RSTRING_LEN(data)); - VALIDATE_MAX_LENGTH(hp->offset, HEADER); + if (hp->offset > MAX_HEADER_LEN) + parser_raise(e413, "HTTP header is too large"); if (hp->cs == http_parser_first_final || hp->cs == http_parser_en_ChunkedBody) { @@ -705,7 +699,7 @@ static VALUE HttpParser_parse(VALUE self) } if (hp->cs == http_parser_error) - parser_error("Invalid HTTP format, parsing fails."); + parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails."); return Qnil; } @@ -871,7 +865,7 @@ static VALUE HttpParser_filter_body(VALUE self, VALUE buf, VALUE data) hp->buf = data; http_parser_execute(hp, dptr, dlen); if (hp->cs == http_parser_error) - parser_error("Invalid HTTP format, parsing fails."); + parser_raise(eHttpParserError, "Invalid HTTP format, parsing fails."); assert(hp->s.dest_offset <= hp->offset && "destination buffer overflow"); @@ -919,8 +913,9 @@ void Init_unicorn_http(void) cHttpParser = rb_define_class_under(mUnicorn, "HttpParser", rb_cObject); eHttpParserError = rb_define_class_under(mUnicorn, "HttpParserError", rb_eIOError); - eRequestURITooLongError = - rb_define_class_under(mUnicorn, "RequestURITooLongError", + e413 = rb_define_class_under(mUnicorn, "RequestEntityTooLargeError", + eHttpParserError); + e414 = rb_define_class_under(mUnicorn, "RequestURITooLongError", eHttpParserError); init_globals(); @@ -964,6 +959,7 @@ void Init_unicorn_http(void) 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); + rb_define_singleton_method(cHttpParser, "max_header_len=", set_maxhdrlen, 1); init_common_fields(); SET_GLOBAL(g_http_host, "HOST"); diff --git a/lib/unicorn/const.rb b/lib/unicorn/const.rb index 6cf12a4..d90d358 100644 --- a/lib/unicorn/const.rb +++ b/lib/unicorn/const.rb @@ -32,6 +32,7 @@ module Unicorn::Const # common errors we'll send back ERROR_400_RESPONSE = "HTTP/1.1 400 Bad Request\r\n\r\n" ERROR_414_RESPONSE = "HTTP/1.1 414 Request-URI Too Long\r\n\r\n" + ERROR_413_RESPONSE = "HTTP/1.1 413 Request Entity Too Large\r\n\r\n" ERROR_500_RESPONSE = "HTTP/1.1 500 Internal Server Error\r\n\r\n" EXPECT_100_RESPONSE = "HTTP/1.1 100 Continue\r\n\r\n" diff --git a/lib/unicorn/http_server.rb b/lib/unicorn/http_server.rb index 994de67..dc29406 100644 --- a/lib/unicorn/http_server.rb +++ b/lib/unicorn/http_server.rb @@ -529,6 +529,8 @@ class Unicorn::HttpServer Unicorn::Const::ERROR_500_RESPONSE when Unicorn::RequestURITooLongError Unicorn::Const::ERROR_414_RESPONSE + when Unicorn::RequestEntityTooLargeError + Unicorn::Const::ERROR_413_RESPONSE when Unicorn::HttpParserError # try to tell the client they're bad Unicorn::Const::ERROR_400_RESPONSE else diff --git a/t/t0019-max_header_len.sh b/t/t0019-max_header_len.sh new file mode 100644 index 0000000..5ce1c69 --- /dev/null +++ b/t/t0019-max_header_len.sh @@ -0,0 +1,49 @@ +#!/bin/sh +. ./test-lib.sh +t_plan 5 "max_header_len setting (only intended for Rainbows!)" + +t_begin "setup and start" && { + unicorn_setup + req='GET / HTTP/1.0\r\n\r\n' + len=$(printf "$req" | wc -c) + echo Unicorn::HttpParser.max_header_len = $len >> $unicorn_config + unicorn -D -c $unicorn_config env.ru + unicorn_wait_start +} + +t_begin "minimal request succeeds" && { + rm -f $tmp + ( + cat $fifo > $tmp & + printf "$req" + wait + echo ok > $ok + ) | socat - TCP:$listen > $fifo + test xok = x$(cat $ok) + + fgrep "HTTP/1.1 200 OK" $tmp +} + +t_begin "big request fails" && { + rm -f $tmp + ( + cat $fifo > $tmp & + printf 'GET /xxxxxx HTTP/1.0\r\n\r\n' + wait + echo ok > $ok + ) | socat - TCP:$listen > $fifo + test xok = x$(cat $ok) + fgrep "HTTP/1.1 413" $tmp +} + +dbgcat tmp + +t_begin "killing succeeds" && { + kill $unicorn_pid +} + +t_begin "check stderr" && { + check_stderr +} + +t_done |