summary refs log tree commit
diff options
context:
space:
mode:
authorBrian Palmer <brianp@instructure.com>2014-01-06 16:14:21 -0700
committerSantiago Pastorino <santiago@wyeworks.com>2014-11-27 11:14:37 -0200
commita7cb4302dd4b3fe532799b8303af2e6a8ad0a85c (patch)
tree42b4611238687ba3ea535a5f4d1b39209d28b332
parent99a1a62572e6d75d29ef569c58eae70ed9ad3255 (diff)
downloadrack-a7cb4302dd4b3fe532799b8303af2e6a8ad0a85c.tar.gz
allow overriding the rack multipart parser tempfile class
This allows for more flexibility in how to buffer (or stream) multipart
file uploads, rather than always using Tempfile and buffering to local
TMPDIR.
-rw-r--r--lib/rack/multipart/parser.rb20
-rw-r--r--test/spec_multipart.rb9
2 files changed, 21 insertions, 8 deletions
diff --git a/lib/rack/multipart/parser.rb b/lib/rack/multipart/parser.rb
index fa90ca96..faa98c7e 100644
--- a/lib/rack/multipart/parser.rb
+++ b/lib/rack/multipart/parser.rb
@@ -18,10 +18,13 @@ module Rack
         content_length = env['CONTENT_LENGTH']
         content_length = content_length.to_i if content_length
 
-        new($1, io, content_length, env)
+        tempfile = env['rack.multipart.tempfile_factory'] || lambda { |filename, content_type| Tempfile.new("RackMultipart") }
+        bufsize = env['rack.multipart.buffer_size'] || BUFSIZE
+
+        new($1, io, content_length, env, tempfile, bufsize)
       end
 
-      def initialize(boundary, io, content_length, env)
+      def initialize(boundary, io, content_length, env, tempfile, bufsize)
         @buf            = ""
 
         if @buf.respond_to? :force_encoding
@@ -34,6 +37,8 @@ module Rack
         @content_length = content_length
         @boundary_size  = Utils.bytesize(@boundary) + EOL.size
         @env = env
+        @tempfile       = tempfile
+        @bufsize        = bufsize
 
         if @content_length
           @content_length -= @boundary_size
@@ -86,7 +91,7 @@ module Rack
 
       def fast_forward_to_first_boundary
         loop do
-          content = @io.read(BUFSIZE)
+          content = @io.read(@bufsize)
           raise EOFError, "bad content body" unless content
           @buf << content
 
@@ -95,7 +100,7 @@ module Rack
             return if read_buffer == full_boundary
           end
 
-          raise EOFError, "bad content body" if Utils.bytesize(@buf) >= BUFSIZE
+          raise EOFError, "bad content body" if Utils.bytesize(@buf) >= @bufsize
         end
       end
 
@@ -125,8 +130,7 @@ module Rack
             end
 
             if filename
-              extname = ::File.extname(filename)
-              (@env['rack.tempfiles'] ||= []) << body = Tempfile.new(["RackMultipart", extname])
+              (@env['rack.tempfiles'] ||= []) << body = @tempfile.call(filename, content_type)
               body.binmode  if body.respond_to?(:binmode)
             end
 
@@ -138,7 +142,7 @@ 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(@content_length && @bufsize >= @content_length ? @content_length : @bufsize)
           raise EOFError, "bad content body"  if content.nil? || content.empty?
 
           @buf << content
@@ -223,7 +227,7 @@ module Rack
           # filename is blank which means no file has been selected
           return
         elsif filename
-          body.rewind
+          body.rewind if body.respond_to?(:rewind)
 
           # Take the basename of the upload's original filename.
           # This handles the full Windows paths given by Internet Explorer
diff --git a/test/spec_multipart.rb b/test/spec_multipart.rb
index ca733dae..327c6a2a 100644
--- a/test/spec_multipart.rb
+++ b/test/spec_multipart.rb
@@ -171,6 +171,15 @@ describe Rack::Multipart do
     params["file1.txt"][:tempfile].read.should.equal "contents"
   end
 
+  should "parse multipart upload file using custom tempfile class" do
+    env = Rack::MockRequest.env_for("/", multipart_fixture(:text))
+    my_tempfile = ""
+    env['rack.multipart.tempfile_factory'] = lambda { |filename, content_type| my_tempfile }
+    params = Rack::Multipart.parse_multipart(env)
+    params["files"][:tempfile].object_id.should.equal my_tempfile.object_id
+    my_tempfile.should.equal "contents"
+  end
+
   should "parse multipart upload with nested parameters" do
     env = Rack::MockRequest.env_for("/", multipart_fixture(:nested))
     params = Rack::Multipart.parse_multipart(env)