Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes for RFC 2231 #726

Merged
merged 9 commits into from
Jun 13, 2015
18 changes: 16 additions & 2 deletions lib/rack/multipart.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,27 @@ module Multipart
MULTIPART = %r|\Amultipart/.*boundary=\"?([^\";,]+)\"?|ni
TOKEN = /[^\s()<>,;:\\"\/\[\]?=]+/
CONDISP = /Content-Disposition:\s*#{TOKEN}\s*/i
DISPPARM = /;\s*(#{TOKEN})=("(?:\\"|[^"])*"|#{TOKEN})/
RFC2183 = /^#{CONDISP}(#{DISPPARM})+$/i
VALUE = /"(?:\\"|[^"])*"|#{TOKEN}/
BROKEN_QUOTED = /^#{CONDISP}.*;\sfilename="(.*?)"(?:\s*$|\s*;\s*#{TOKEN}=)/i
BROKEN_UNQUOTED = /^#{CONDISP}.*;\sfilename=(#{TOKEN})/i
MULTIPART_CONTENT_TYPE = /Content-Type: (.*)#{EOL}/ni
MULTIPART_CONTENT_DISPOSITION = /Content-Disposition:.*\s+name="?([^\";]*)"?/ni
MULTIPART_CONTENT_ID = /Content-ID:\s*([^#{EOL}]*)/ni
# Updated definitions from RFC 2231
ATTRIBUTE_CHAR = %r{[^ \t)(><@,;:\\"/\[\]?=]}
ATTRIBUTE = /#{ATTRIBUTE_CHAR}+/
SECTION = /\*0|\*\d+/
REGULAR_PARAMETER_NAME = /#{ATTRIBUTE}#{SECTION}?/
REGULAR_PARAMETER = /(#{REGULAR_PARAMETER_NAME})=(#{VALUE})/
EXTENDED_OTHER_NAME = /#{ATTRIBUTE}\*[1-9]\d+\*/
EXTENDED_OTHER_VALUE = /%[0-9a-fA-F]{2}|#{ATTRIBUTE_CHAR}/
EXTENDED_OTHER_PARAMETER = /#{EXTENDED_OTHER_NAME}=#{EXTENDED_OTHER_VALUE}/
EXTENDED_INITIAL_NAME = /#{ATTRIBUTE}(?:\*0)?\*/
EXTENDED_INITIAL_VALUE = /(?:[a-zA-Z0-9\-]+)?'(?:[a-zA-Z0-9\-]+)?'(?:#{EXTENDED_OTHER_VALUE}+)/
EXTENDED_INITIAL_PARAMETER = /#{EXTENDED_INITIAL_NAME}=#{EXTENDED_INITIAL_VALUE}/
EXTENDED_PARAMETER = /#{EXTENDED_INITIAL_PARAMETER}|#{EXTENDED_OTHER_PARAMETER}/
DISPPARM = /;\s*#{REGULAR_PARAMETER}|#{EXTENDED_PARAMETER}/
RFC2183 = /#{CONDISP}(#{DISPPARM})+/i

class << self
def parse_multipart(env, params = Rack::Utils.default_query_parser)
Expand Down
19 changes: 15 additions & 4 deletions lib/rack/multipart/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -152,11 +152,13 @@ def get_current_head_and_filename_and_content_type_and_name_and_body
def get_filename(head)
filename = nil
case head
when RFC2183
filename = Hash[head.scan(DISPPARM)]['filename']
filename = $1 if filename and filename =~ /^"(.*)"$/
when BROKEN_QUOTED, BROKEN_UNQUOTED
filename = $1
when RFC2183
params = Hash[head.scan(DISPPARM)]
filename = params['filename']
filename ||= params['filename*']
filename = $1 if filename and filename =~ /^"(.*)"$/
end

return unless filename
Expand All @@ -170,7 +172,14 @@ def get_filename(head)
if filename !~ /\\[^\\"]/
filename = filename.gsub(/\\(.)/, '\1')
end
filename

encoding, locale, name = filename.split("'",3)

if locale.nil? && name.nil?
name = encoding
else
name.force_encoding ::Encoding.find(encoding)
end
end

def scrub_filename(filename)
Expand Down Expand Up @@ -234,6 +243,8 @@ def get_data(filename, body, content_type, name, head)
# Generic multipart cases, not coming from a form
data = {:type => content_type,
:name => name, :tempfile => body, :head => head}
elsif !filename && data.empty?
return
end

yield data
Expand Down
7 changes: 7 additions & 0 deletions test/multipart/filename_with_encoded_words
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
--AaB03x
Content-Type: image/jpeg
Content-Disposition: attachment; name="files"; filename*=utf-8''%D1%84%D0%B0%D0%B9%D0%BB
Content-Description: a complete map of the human genome

contents
--AaB03x--
8 changes: 8 additions & 0 deletions test/spec_multipart.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# coding: utf-8

require 'minitest/bacon'
require 'rack/utils'
require 'rack/mock'
Expand Down Expand Up @@ -274,6 +276,12 @@ def initialize(*)
params["files"][:tempfile].read.should.equal "contents"
end

should "parse multipart form with an encoded word filename" do
env = Rack::MockRequest.env_for '/', multipart_fixture(:filename_with_encoded_words)
params = Rack::Multipart.parse_multipart(env)
params["files"][:filename].should.equal "файл"
end

should "not include file params if no file was selected" do
env = Rack::MockRequest.env_for("/", multipart_fixture(:none))
params = Rack::Multipart.parse_multipart(env)
Expand Down