summary refs log tree commit
diff options
context:
space:
mode:
authorAaron Patterson <aaron.patterson@gmail.com>2015-08-28 09:32:22 -0700
committerAaron Patterson <aaron.patterson@gmail.com>2015-08-28 09:32:22 -0700
commit3b510485e3cddaf07bdc54550d3075e8957fe6ad (patch)
treef55ae5c5dc116eb53f7786cb91304a511ba9079d
parente00488827a0c100712f4f8ac6c17b2ce414c8bb7 (diff)
downloadrack-3b510485e3cddaf07bdc54550d3075e8957fe6ad.tar.gz
wrap bounded IO objects
If we have a content length, wrap the IO object with a new object that
knows about the content length and will act like an IO object with the
length specified
-rw-r--r--lib/rack/multipart.rb1
-rw-r--r--lib/rack/multipart/parser.rb57
2 files changed, 41 insertions, 17 deletions
diff --git a/lib/rack/multipart.rb b/lib/rack/multipart.rb
index b1625437..1c7c4c35 100644
--- a/lib/rack/multipart.rb
+++ b/lib/rack/multipart.rb
@@ -36,6 +36,7 @@ module Rack
 
     class << self
       def parse_multipart(env, params = Rack::Utils.default_query_parser)
+        return if env['CONTENT_LENGTH'] == '0'
         Parser.create(env, params).parse
       end
 
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index b04d39bb..7fab856e 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -10,6 +10,41 @@ module Rack
 
       DUMMY = Struct.new(:parse).new
 
+      class BoundedIO # :nodoc:
+        def initialize(io, content_length)
+          @io             = io
+          @content_length = content_length
+          @cursor = 0
+        end
+
+        def read(size)
+          return if @cursor >= @content_length
+
+          left = @content_length - @cursor
+
+          str = if left < size
+                  @io.read left
+                else
+                 @io.read size
+                end
+
+          if str
+            @cursor += str.bytesize
+          else
+            # Raise an error for mismatching Content-Length and actual contents
+            raise EOFError, "bad content body"
+          end
+
+          str
+        end
+
+        def eof?; @content_length == @cursor; end
+
+        def rewind
+          @io.rewind
+        end
+      end
+
       def self.create(env, query_parser)
         return DUMMY unless env['CONTENT_TYPE'] =~ MULTIPART
 
@@ -22,27 +57,23 @@ module Rack
         tempfile = env[RACK_MULTIPART_TEMPFILE_FACTORY] ||
           lambda { |filename, content_type| Tempfile.new(["RackMultipart", ::File.extname(filename)]) }
         bufsize = env[RACK_MULTIPART_BUFFER_SIZE] || BUFSIZE
+        io = BoundedIO.new(io, content_length) if content_length
 
-        new($1, io, content_length, env, tempfile, bufsize, query_parser)
+        new($1, io, env, tempfile, bufsize, query_parser)
       end
 
-      def initialize(boundary, io, content_length, env, tempfile, bufsize, query_parser)
+      def initialize(boundary, io, env, tempfile, bufsize, query_parser)
         @buf            = "".force_encoding(Encoding::ASCII_8BIT)
 
         @query_parser   = query_parser
         @params         = query_parser.make_params
         @boundary       = "--#{boundary}"
         @io             = io
-        @content_length = content_length
         @boundary_size  = @boundary.bytesize + EOL.size
         @env = env
         @tempfile       = tempfile
         @bufsize        = bufsize
 
-        if @content_length
-          @content_length -= @boundary_size
-        end
-
         @rx = /(?:#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/n
         @full_boundary = @boundary + EOL
       end
@@ -148,11 +179,10 @@ module Rack
             body << @buf.slice!(0, @buf.size - (@boundary_size+4))
           end
 
-          content = @io.read(@content_length && @bufsize >= @content_length ? @content_length : @bufsize)
+          content = @io.read(@bufsize)
           handle_empty_content!(content) and break
 
           @buf << content
-          @content_length -= content.size if @content_length
         end
 
         [head, filename, content_type, name, body, file]
@@ -262,14 +292,7 @@ module Rack
 
       def handle_empty_content!(content)
         if content.nil? || content.empty?
-          # Raise an error for mismatching Content-Length and actual contents
-          raise EOFError, "bad content body" if @content_length.to_i > 0
-
-          # In case we receive a POST request with empty body, reset @content_length
-          # and return empty string
-          @content_length = 0
-          @buf = ""
-
+          raise EOFError if @io.eof?
           return true
         end
       end