diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb index 58b812520..4c79858e3 100644 --- a/lib/rack/multipart/parser.rb +++ b/lib/rack/multipart/parser.rb @@ -185,7 +185,7 @@ def initialize(boundary, tempfile, bufsize, query_parser) @collector = Collector.new tempfile @sbuf = StringScanner.new("".dup) - @body_regex = /(.*?)(#{EOL})?#{Regexp.quote(@boundary)}(#{EOL}|--)/m + @body_regex = /(?:#{EOL})?#{Regexp.quote(@boundary)}(?:#{EOL}|--)/m @rx_max_size = EOL.size + @boundary.bytesize + [EOL.size, '--'.size].max @head_regex = /(.*?#{EOL})#{EOL}/m end @@ -268,8 +268,8 @@ def handle_mime_head end def handle_mime_body - if @sbuf.check_until(@body_regex) # check but do not advance the pointer yet - body = @sbuf[1] + if (body_with_boundary = @sbuf.check_until(@body_regex)) # check but do not advance the pointer yet + body = body_with_boundary.sub(/#{@body_regex}\z/m, '') # remove the boundary from the string @collector.on_mime_body @mime_index, body @sbuf.pos += body.length + 2 # skip \r\n after the content @state = :CONSUME_TOKEN diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb index ae7e643ee..946e52c3c 100644 --- a/test/spec_multipart.rb +++ b/test/spec_multipart.rb @@ -6,6 +6,7 @@ require 'rack/multipart/parser' require 'rack/utils' require 'rack/mock' +require 'timeout' describe Rack::Multipart do def multipart_fixture(name, boundary = "AaB03x") @@ -152,6 +153,31 @@ def rd.rewind; end wr.close end + # see https://github.com/rack/rack/pull/1309 + it "parse strange multipart pdf" do + boundary = '---------------------------932620571087722842402766118' + + data = StringIO.new + data.write("--#{boundary}") + data.write("\r\n") + data.write('Content-Disposition: form-data; name="a"; filename="a.pdf"') + data.write("\r\n") + data.write("Content-Type:application/pdf\r\n") + data.write("\r\n") + data.write("-" * (1024 * 1024)) + data.write("\r\n") + data.write("--#{boundary}--\r\n") + + fixture = { + "CONTENT_TYPE" => "multipart/form-data; boundary=#{boundary}", + "CONTENT_LENGTH" => data.length.to_s, + :input => data, + } + + env = Rack::MockRequest.env_for '/', fixture + Timeout::timeout(10) { Rack::Multipart.parse_multipart(env) } + end + it 'raises an EOF error on content-length mistmatch' do env = Rack::MockRequest.env_for("/", multipart_fixture(:empty)) env['rack.input'] = StringIO.new