about summary refs log tree commit homepage
diff options
context:
space:
mode:
-rw-r--r--lib/rainbows/max_body/rewindable_wrapper.rb1
-rw-r--r--lib/rainbows/max_body/wrapper.rb66
-rw-r--r--t/sha1-random-size.ru24
-rwxr-xr-xt/t0104-rack-input-limit-tiny.sh134
-rwxr-xr-xt/t0105-rack-input-limit-bigger.sh2
5 files changed, 210 insertions, 17 deletions
diff --git a/lib/rainbows/max_body/rewindable_wrapper.rb b/lib/rainbows/max_body/rewindable_wrapper.rb
index b52726e..5693ead 100644
--- a/lib/rainbows/max_body/rewindable_wrapper.rb
+++ b/lib/rainbows/max_body/rewindable_wrapper.rb
@@ -8,6 +8,7 @@ class Rainbows::MaxBody::RewindableWrapper < Rainbows::MaxBody::Wrapper
 
   def rewind
     @limit = @orig_limit
+    @rbuf = ''
     @input.rewind
   end
 
diff --git a/lib/rainbows/max_body/wrapper.rb b/lib/rainbows/max_body/wrapper.rb
index 3c38ca6..15faeeb 100644
--- a/lib/rainbows/max_body/wrapper.rb
+++ b/lib/rainbows/max_body/wrapper.rb
@@ -1,26 +1,70 @@
 # -*- encoding: binary -*-
 # :enddoc:
+#
+# This is only used for chunked request bodies, which are rare
 class Rainbows::MaxBody::Wrapper
   def initialize(rack_input, limit)
-    @input, @limit = rack_input, limit
-  end
-
-  def check(rv)
-    throw :rainbows_EFBIG if rv && ((@limit -= rv.size) < 0)
-    rv
+    @input, @limit, @rbuf = rack_input, limit, ''
   end
 
   def each(&block)
-    while line = @input.gets
-      yield check(line)
+    while line = gets
+      yield line
     end
   end
 
-  def read(*args)
-    check(@input.read(*args))
+  # chunked encoding means this method behaves more like readpartial,
+  # since Rack does not support a method named "readpartial"
+  def read(length = nil, rv = '')
+    if length
+      if length <= @rbuf.size
+        length < 0 and raise ArgumentError, "negative length #{length} given"
+        rv.replace(@rbuf.slice!(0, length))
+      elsif @rbuf.empty?
+        checked_read(length, rv) or return
+      else
+        rv.replace(@rbuf.slice!(0, @rbuf.size))
+      end
+      rv.empty? && length != 0 ? nil : rv
+    else
+      rv.replace(read_all)
+    end
   end
 
   def gets
-    check(@input.gets)
+    sep = $/
+    if sep.nil?
+      rv = read_all
+      return rv.empty? ? nil : rv
+    end
+    re = /\A(.*?#{Regexp.escape(sep)})/
+
+    begin
+      @rbuf.sub!(re, '') and return $1
+
+      if tmp = checked_read(16384)
+        @rbuf << tmp
+      elsif @rbuf.empty? # EOF
+        return nil
+      else # EOF, return whatever is left
+        return @rbuf.slice!(0, @rbuf.size)
+      end
+    end while true
+  end
+
+  def checked_read(length = 16384, buf = '')
+    if @input.read(length, buf)
+      throw :rainbows_EFBIG if ((@limit -= buf.size) < 0)
+      return buf
+    end
+  end
+
+  def read_all
+    rv = @rbuf.slice!(0, @rbuf.size)
+    tmp = ''
+    while checked_read(16384, tmp)
+      rv << tmp
+    end
+    rv
   end
 end
diff --git a/t/sha1-random-size.ru b/t/sha1-random-size.ru
index f86d017..4ef5d7b 100644
--- a/t/sha1-random-size.ru
+++ b/t/sha1-random-size.ru
@@ -7,11 +7,25 @@ app = lambda do |env|
     return [ 100, {}, [] ]
   digest = Digest::SHA1.new
   input = env['rack.input']
-  if buf = input.read(rand(cap))
-    begin
-      raise "#{buf.size} > #{cap}" if buf.size > cap
-      digest.update(buf)
-    end while input.read(rand(cap), buf)
+  case env["PATH_INFO"]
+  when "/gets_read_mix"
+    warn "GETS_READ_MIX #{env['HTTP_TRANSFER_ENCODING'].inspect}"
+    if buf = input.gets
+      warn "input.rbuf: #{input.instance_variable_get(:@rbuf).inspect}"
+      begin
+        digest.update(buf)
+        warn "buf.size : #{buf.size}"
+      end while input.read(rand(cap), buf)
+    end
+  when "/each"
+    input.each { |buf| digest.update(buf) }
+  else
+    if buf = input.read(rand(cap))
+      begin
+        raise "#{buf.size} > #{cap}" if buf.size > cap
+        digest.update(buf)
+      end while input.read(rand(cap), buf)
+    end
   end
 
   [ 200, {'Content-Type' => 'text/plain'}, [ digest.hexdigest << "\n" ] ]
diff --git a/t/t0104-rack-input-limit-tiny.sh b/t/t0104-rack-input-limit-tiny.sh
index 0cf9f73..284d7c0 100755
--- a/t/t0104-rack-input-limit-tiny.sh
+++ b/t/t0104-rack-input-limit-tiny.sh
@@ -3,7 +3,7 @@
 test -r random_blob || die "random_blob required, run with 'make $0'"
 req_curl_chunked_upload_err_check
 
-t_plan 6 "rack.input client_max_body_size tiny"
+t_plan 18 "rack.input client_max_body_size tiny"
 
 t_begin "setup and startup" && {
         rtmpfiles curl_out curl_err cmbs_config
@@ -21,6 +21,7 @@ t_begin "stops a regular request" && {
           http://$listen/ > $curl_out 2> $curl_err || > $ok
         dbgcat curl_err
         dbgcat curl_out
+        grep 413 $curl_err
         test -e $ok
 }
 
@@ -31,6 +32,7 @@ t_begin "stops a large chunked request" && {
           http://$listen/ > $curl_out 2> $curl_err || > $ok
         dbgcat curl_err
         dbgcat curl_out
+        grep 413 $curl_err
         test -e $ok
 }
 
@@ -56,6 +58,136 @@ t_begin "small size sha1 content-length ok" && {
         test "$(cat $curl_out)" = $blob_sha1
 }
 
+t_begin "stops a regular request (gets_read_mix)" && {
+        rm -f $ok
+        dd if=/dev/zero bs=257 count=1 of=$tmp
+        curl -vsSf -T $tmp -H Expect: \
+          http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
+        dbgcat curl_err
+        dbgcat curl_out
+        grep 413 $curl_err
+        test -e $ok
+}
+
+t_begin "stops a large chunked request (gets_read_mix)" && {
+        rm -f $ok
+        dd if=/dev/zero bs=257 count=1 | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
+        dbgcat curl_err
+        dbgcat curl_out
+        grep 413 $curl_err
+        test -e $ok
+}
+
+t_begin "stops a large line-based chunked request (gets_read_mix)" && {
+        rm -f $ok
+        </dev/null awk 'BEGIN{for(i=22;--i>=0;) print "hello world"}' | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/gets_read_mix > $curl_out 2> $curl_err || > $ok
+        dbgcat curl_err
+        dbgcat curl_out
+        grep 413 $curl_err
+        test -e $ok
+}
+
+t_begin "OK with line-based chunked request (gets_read_mix)" && {
+        rm -f $ok
+        </dev/null awk 'BEGIN{for(i=21;--i>=0;) print "hello world"}' | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/gets_read_mix > $curl_out 2> $curl_err
+        dbgcat curl_err
+        dbgcat curl_out
+        test x"$(cat $curl_out)" = x23eab3cebcbe22a0456c8462e3d3bb01ae761702
+}
+
+t_begin "small size sha1 chunked ok (gets_read_mix)" && {
+        blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
+        rm -f $ok
+        dd if=/dev/zero bs=256 count=1 | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/gets_read_mix > $curl_out 2> $curl_err
+        dbgcat curl_err
+        dbgcat curl_out
+        test "$(cat $curl_out)" = $blob_sha1
+}
+
+t_begin "small size sha1 content-length ok (gets_read_mix)" && {
+        blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
+        rm -f $ok
+        dd if=/dev/zero bs=256 count=1 of=$tmp
+        curl -vsSf -T $tmp -H Expect: \
+          http://$listen/gets_read_mix > $curl_out 2> $curl_err
+        dbgcat curl_err
+        dbgcat curl_out
+        test "$(cat $curl_out)" = $blob_sha1
+}
+
+t_begin "stops a regular request (each)" && {
+        rm -f $ok
+        dd if=/dev/zero bs=257 count=1 of=$tmp
+        curl -vsSf -T $tmp -H Expect: \
+          http://$listen/each > $curl_out 2> $curl_err || > $ok
+        dbgcat curl_err
+        dbgcat curl_out
+        grep 413 $curl_err
+        test -e $ok
+}
+
+t_begin "stops a large chunked request (each)" && {
+        rm -f $ok
+        dd if=/dev/zero bs=257 count=1 | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/each > $curl_out 2> $curl_err || > $ok
+        dbgcat curl_err
+        dbgcat curl_out
+        grep 413 $curl_err
+        test -e $ok
+}
+
+t_begin "small size sha1 chunked ok (each)" && {
+        blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
+        rm -f $ok
+        dd if=/dev/zero bs=256 count=1 | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/each > $curl_out 2> $curl_err
+        dbgcat curl_err
+        dbgcat curl_out
+        test "$(cat $curl_out)" = $blob_sha1
+}
+
+t_begin "small size sha1 content-length ok (each)" && {
+        blob_sha1=b376885ac8452b6cbf9ced81b1080bfd570d9b91
+        rm -f $ok
+        dd if=/dev/zero bs=256 count=1 of=$tmp
+        curl -vsSf -T $tmp -H Expect: \
+          http://$listen/each > $curl_out 2> $curl_err
+        dbgcat curl_err
+        dbgcat curl_out
+        test "$(cat $curl_out)" = $blob_sha1
+}
+
+t_begin "stops a large line-based chunked request (each)" && {
+        rm -f $ok
+        </dev/null awk 'BEGIN{for(i=22;--i>=0;) print "hello world"}' | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/each > $curl_out 2> $curl_err || > $ok
+        dbgcat curl_err
+        dbgcat curl_out
+        grep 413 $curl_err
+        test -e $ok
+}
+
+t_begin "OK with line-based chunked request (each)" && {
+        rm -f $ok
+        </dev/null awk 'BEGIN{for(i=21;--i>=0;) print "hello world"}' | \
+          curl -vsSf -T- -H Expect: \
+          http://$listen/each > $curl_out 2> $curl_err
+        dbgcat curl_err
+        dbgcat curl_out
+        test x"$(cat $curl_out)" = x23eab3cebcbe22a0456c8462e3d3bb01ae761702
+}
+
 t_begin "shutdown" && {
         kill $rainbows_pid
 }
diff --git a/t/t0105-rack-input-limit-bigger.sh b/t/t0105-rack-input-limit-bigger.sh
index e8cf95a..642dd2b 100755
--- a/t/t0105-rack-input-limit-bigger.sh
+++ b/t/t0105-rack-input-limit-bigger.sh
@@ -22,6 +22,7 @@ t_begin "stops a regular request" && {
         rm -f $tmp
         dbgcat curl_err
         dbgcat curl_out
+        grep 413 $curl_err
         test -e $ok
 }
 
@@ -32,6 +33,7 @@ t_begin "stops a large chunked request" && {
           http://$listen/ > $curl_out 2> $curl_err || > $ok
         dbgcat curl_err
         dbgcat curl_out
+        grep 413 $curl_err
         test -e $ok
 }