Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Improve multipart parsing support for UAs that don't correctly escape

Content-Disposition filenames.

Signed-off-by: Joshua Peek <josh@joshpeek.com>
  • Loading branch information...
commit a36ac970fd682f07c3e57756542791679f5fcf84 1 parent 799fa0d
@rubys rubys authored josh committed
View
25 lib/rack/utils.rb
@@ -478,7 +478,27 @@ def self.parse_multipart(env)
head = buf.slice!(0, i+2) # First \r\n
buf.slice!(0, 2) # Second \r\n
- filename = head[/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;\s]*))/ni, 1]
+ token = /[^\s()<>,;:\\"\/\[\]?=]+/
+ condisp = /Content-Disposition:\s*#{token}\s*/i
+ dispparm = /;\s*(#{token})=("(?:\\"|[^"])*"|#{token})*/
+
+ rfc2183 = /^#{condisp}(#{dispparm})+$/i
+ broken_quoted = /^#{condisp}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*\w+=)/i
+ broken_unquoted = /^#{condisp}.*;\sfilename=(#{token})/
+
+ if head =~ rfc2183
+ filename = Hash[head.scan(dispparm)]['filename']
+ filename = $1 if filename and filename =~ /^"(.*)"$/
+ elsif head =~ broken_quoted
+ filename = $1
+ elsif head =~ broken_unquoted
+ filename = $1
+ end
+
+ if filename && filename !~ /\\[^\\"]/
+ filename = Utils.unescape(filename).gsub(/\\(.)/, '\1')
+ end
+
content_type = head[/Content-Type: (.*)#{EOL}/ni, 1]
name = head[/Content-Disposition:.*\s+name="?([^\";]*)"?/ni, 1] || head[/Content-ID:\s*([^#{EOL}]*)/ni, 1]
@@ -519,8 +539,7 @@ def self.parse_multipart(env)
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
# those which give the lone filename.
- filename =~ /^(?:.*[:\\\/])?(.*)/m
- filename = $1
+ filename = filename.split(/[\/\\]/).last
data = {:filename => filename, :type => content_type,
:name => name, :tempfile => body, :head => head}
View
6 test/multipart/filename_with_escaped_qoutes
@@ -0,0 +1,6 @@
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="escape \"quotes"
+Content-Type: application/octet-stream
+
+contents
+--AaB03x--
View
6 test/multipart/filename_with_percent_escaped_qoutes
@@ -0,0 +1,6 @@
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="escape %22quotes"
+Content-Type: application/octet-stream
+
+contents
+--AaB03x--
View
6 test/multipart/filename_with_unescaped_qoutes
@@ -0,0 +1,6 @@
+--AaB03x
+Content-Disposition: form-data; name="files"; filename="escape "quotes"
+Content-Type: application/octet-stream
+
+contents
+--AaB03x--
View
39 test/spec_rack_utils.rb
@@ -449,6 +449,45 @@ def context env, app=@app; app.call(env); end
params["files"][:tempfile].read.should.equal "contents"
end
+ specify "should parse filename with escaped qoutes" do
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_escaped_qoutes))
+ params = Rack::Utils::Multipart.parse_multipart(env)
+ params["files"][:type].should.equal "application/octet-stream"
+ params["files"][:filename].should.equal "escape \"quotes"
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
+ "name=\"files\"; " +
+ "filename=\"escape \\\"quotes\"\r\n" +
+ "Content-Type: application/octet-stream\r\n"
+ params["files"][:name].should.equal "files"
+ params["files"][:tempfile].read.should.equal "contents"
+ end
+
+ specify "should parse filename with percent escaped qoutes" do
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_percent_escaped_qoutes))
+ params = Rack::Utils::Multipart.parse_multipart(env)
+ params["files"][:type].should.equal "application/octet-stream"
+ params["files"][:filename].should.equal "escape \"quotes"
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
+ "name=\"files\"; " +
+ "filename=\"escape %22quotes\"\r\n" +
+ "Content-Type: application/octet-stream\r\n"
+ params["files"][:name].should.equal "files"
+ params["files"][:tempfile].read.should.equal "contents"
+ end
+
+ specify "should parse filename with unescaped qoutes" do
+ env = Rack::MockRequest.env_for("/", multipart_fixture(:filename_with_unescaped_qoutes))
+ params = Rack::Utils::Multipart.parse_multipart(env)
+ params["files"][:type].should.equal "application/octet-stream"
+ params["files"][:filename].should.equal "escape \"quotes"
+ params["files"][:head].should.equal "Content-Disposition: form-data; " +
+ "name=\"files\"; " +
+ "filename=\"escape \"quotes\"\r\n" +
+ "Content-Type: application/octet-stream\r\n"
+ params["files"][:name].should.equal "files"
+ params["files"][:tempfile].read.should.equal "contents"
+ end
+
specify "rewinds input after parsing upload" do
options = multipart_fixture(:text)
input = options[:input]
Please sign in to comment.
Something went wrong with that request. Please try again.