about summary refs log tree commit homepage
diff options
context:
space:
mode:
authorEric Wong <normalperson@yhbt.net>2011-05-05 16:42:26 -0700
committerEric Wong <normalperson@yhbt.net>2011-05-05 16:42:26 -0700
commit77a951c5da518dda471282635c98f3b572ca15db (patch)
tree7e87f472c615c6e5522d5a5efc79fe4c5cb7bd18
parent733cb68e444a6f324bb1ffda3839da98ef010c74 (diff)
downloadunicorn-77a951c5da518dda471282635c98f3b572ca15db.tar.gz
Rainbows! wants to be able to lower this eventually...
-rw-r--r--ext/unicorn_http/ext_help.h16
-rw-r--r--ext/unicorn_http/extconf.rb1
-rw-r--r--ext/unicorn_http/global_variables.h11
-rw-r--r--ext/unicorn_http/unicorn_http.rl48
-rw-r--r--lib/unicorn/const.rb1
-rw-r--r--lib/unicorn/http_server.rb2
-rw-r--r--t/t0019-max_header_len.sh49
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