Permalink
Browse files

Make Rack::Request read multipart form data

darcs-hash:20070228125648-4fc50-ce858f8df7bdc362bd689cbc84c5a92805e03ebe.gz
  • Loading branch information...
1 parent 758f928 commit 4fe5360acf4f1aa680ee00df589bba10b0b56634 @chneukirchen chneukirchen committed Feb 28, 2007
Showing with 193 additions and 3 deletions.
  1. +6 −3 lib/rack/request.rb
  2. +88 −0 lib/rack/utils.rb
  3. +99 −0 test/spec_rack_request.rb
View
@@ -40,9 +40,12 @@ def POST
@env["rack.request.form_hash"]
else
@env["rack.request.form_input"] = @env["rack.input"]
- @env["rack.request.form_vars"] = body.read
- @env["rack.request.form_hash"] =
- Utils.parse_query(@env["rack.request.form_vars"])
+ unless @env["rack.request.form_hash"] =
+ Utils::Multipart.parse_multipart(env)
+ @env["rack.request.form_vars"] = @env["rack.input"].read
+ @env["rack.request.form_hash"] = Utils.parse_query(@env["rack.request.form_vars"])
+ end
+ @env["rack.request.form_hash"]
end
end
View
@@ -1,3 +1,5 @@
+require 'tempfile'
+
module Rack
module Utils
# Performs URI escaping so that you can construct proper
@@ -73,5 +75,91 @@ def capitalize(k)
k.to_s.downcase.gsub(/^.|[-_\s]./) { |x| x.upcase }
end
end
+
+ # Adapted from IOWA.
+ module Multipart
+ EOL = "\r\n"
+
+ def self.parse_multipart(env)
+ unless env['CONTENT_TYPE'] =~ %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
+ nil
+ else
+ boundary = "--#{$1}"
+
+ params = {}
+ buf = ""
+ content_length = env['CONTENT_LENGTH'].to_i
+ input = env['rack.input']
+
+ boundary_size = boundary.size + EOL.size
+ bufsize = 16384
+
+ content_length -= boundary_size
+
+ status = input.read(boundary_size)
+ raise EOFError, "bad content body" unless status == boundary + EOL
+
+ loop {
+ head = nil
+ body = ''
+ filename = content_type = name = nil
+
+ until head && buf =~ /#{boundary}(?:#{EOL}|--)/ # /
+ if !head && i = buf.index("\r\n\r\n")
+ head = buf.slice!(0, i+2) # First \r\n
+ buf.slice!(0, 2) # Second \r\n
+
+ filename = head[/Content-Disposition:.* filename="?([^\";]*)"?/ni, 1]
+ content_type = head[/Content-Type: (.*)\r\n/ni, 1]
+ name = head[/Content-Disposition:.* name="?([^\";]*)"?/ni, 1]
+
+ body = Tempfile.new("RackMultipart") if filename
+
+ next
+ end
+
+ # Save the read body part.
+ if head && (boundary.size+4 < buf.size)
+ body << buf.slice!(0, buf.size - (boundary.size+4))
+ end
+
+ c = input.read(bufsize < content_length ? bufsize : content_length)
+ raise EOFError, "bad content body" if c.nil? || c.empty?
+ buf << c
+ content_length -= c.size
+ end
+
+ # Save the rest.
+ if i = buf.index(/(?:#{EOL})?#{boundary}(#{EOL}|--)/n)
+ body << buf.slice!(0, i)
+ buf.slice!(0, boundary_size+2)
+
+ content_length = -1 if $1 == "--"
+ end
+
+ if filename
+ body.rewind
+ data = {:filename => filename, :type => content_type,
+ :name => name, :tempfile => body, :head => head}
+ else
+ data = body
+ end
+
+ if name
+ if name =~ /\[\]\z/
+ params[name] ||= []
+ params[name] << data
+ else
+ params[name] = data
+ end
+ end
+
+ break if buf.empty? || content_length == -1
+ }
+
+ params
+ end
+ end
+ end
end
end
View
@@ -113,4 +113,103 @@
"rack.url_scheme" => "https"})).url.
should.equal "https://example.org/"
end
+
+ specify "can parse multipart form data" do
+ # Adapted from RFC 1867.
+ input = StringIO.new(<<EOF)
+--AaB03x\r
+content-disposition: form-data; name="reply"\r
+\r
+yes\r
+--AaB03x\r
+content-disposition: form-data; name="fileupload"; filename="dj.jpg"\r
+Content-Type: image/jpeg\r
+Content-Transfer-Encoding: base64\r
+\r
+/9j/4AAQSkZJRgABAQAAAQABAAD//gA+Q1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcg\r
+--AaB03x--\r
+EOF
+ input.rewind
+ req = Rack::Request.new \
+ TestRequest.env("CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+ "CONTENT_LENGTH" => input.size,
+ "rack.input" => input)
+
+ req.POST.should.include "fileupload"
+ req.POST.should.include "reply"
+
+ req.POST["reply"].should.equal "yes"
+
+ f = req.POST["fileupload"]
+ f.should.be.kind_of Hash
+ f[:type].should.equal "image/jpeg"
+ f[:filename].should.equal "dj.jpg"
+ f.should.include :tempfile
+ f[:tempfile].size.should.equal 76
+ end
+
+ specify "can parse big multipart form data" do
+ input = StringIO.new(<<EOF)
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+\r
+#{"x"*32768}\r
+--AaB03x\r
+content-disposition: form-data; name="mean"; filename="mean"\r
+\r
+--AaB03xha\r
+--AaB03x--\r
+EOF
+ input.rewind
+ req = Rack::Request.new \
+ TestRequest.env("CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+ "CONTENT_LENGTH" => input.size,
+ "rack.input" => input)
+
+ req.POST["huge"][:tempfile].size.should.equal 32768
+ req.POST["mean"][:tempfile].size.should.equal 10
+ req.POST["mean"][:tempfile].read.should.equal "--AaB03xha"
+ end
+
+ specify "can detect invalid multipart form data" do
+ input = StringIO.new(<<EOF)
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+EOF
+ input.rewind
+ req = Rack::Request.new \
+ TestRequest.env("CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+ "CONTENT_LENGTH" => input.size,
+ "rack.input" => input)
+
+ lambda { req.POST }.should.raise(EOFError)
+
+ input = StringIO.new(<<EOF)
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+\r
+foo\r
+EOF
+ input.rewind
+ req = Rack::Request.new \
+ TestRequest.env("CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+ "CONTENT_LENGTH" => input.size,
+ "rack.input" => input)
+
+ lambda { req.POST }.should.raise(EOFError)
+
+ input = StringIO.new(<<EOF)
+--AaB03x\r
+content-disposition: form-data; name="huge"; filename="huge"\r
+\r
+foo\r
+EOF
+ input.rewind
+ req = Rack::Request.new \
+ TestRequest.env("CONTENT_TYPE" => "multipart/form-data, boundary=AaB03x",
+ "CONTENT_LENGTH" => input.size,
+ "rack.input" => input)
+
+ lambda { req.POST }.should.raise(EOFError)
+ end
end

0 comments on commit 4fe5360

Please sign in to comment.