mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
5c99f241a0
max_multipart_length of multipart data. [Feature #8370] patch by Leif Eriksen <leif.eriksen.au@gmail.com> git-svn-id: svn+ssh://ci.ruby-lang.org/ruby/trunk@46392 b2dd03c8-39d4-4d8f-98ff-823fe69b080e
385 lines
11 KiB
Ruby
385 lines
11 KiB
Ruby
require 'test/unit'
|
|
require 'cgi'
|
|
require 'tempfile'
|
|
require 'stringio'
|
|
require_relative '../ruby/envutil'
|
|
|
|
|
|
##
|
|
## usage:
|
|
## boundary = 'foobar1234' # or nil
|
|
## multipart = MultiPart.new(boundary)
|
|
## multipart.append('name1', 'value1')
|
|
## multipart.append('file1', File.read('file1.html'), 'file1.html')
|
|
## str = multipart.close()
|
|
## str.each_line {|line| p line }
|
|
## ## output:
|
|
## # "--foobar1234\r\n"
|
|
## # "Content-Disposition: form-data: name=\"name1\"\r\n"
|
|
## # "\r\n"
|
|
## # "value1\r\n"
|
|
## # "--foobar1234\r\n"
|
|
## # "Content-Disposition: form-data: name=\"file1\"; filename=\"file1.html\"\r\n"
|
|
## # "Content-Type: text/html\r\n"
|
|
## # "\r\n"
|
|
## # "<html>\n"
|
|
## # "<body><p>Hello</p></body>\n"
|
|
## # "</html>\n"
|
|
## # "\r\n"
|
|
## # "--foobar1234--\r\n"
|
|
##
|
|
class MultiPart
|
|
|
|
def initialize(boundary=nil)
|
|
@boundary = boundary || create_boundary()
|
|
@buf = ''
|
|
@buf.force_encoding(::Encoding::ASCII_8BIT) if defined?(::Encoding)
|
|
end
|
|
attr_reader :boundary
|
|
|
|
def append(name, value, filename=nil, content_type=nil)
|
|
content_type = detect_content_type(filename) if filename && content_type.nil?
|
|
s = filename ? "; filename=\"#{filename}\"" : ''
|
|
buf = @buf
|
|
buf << "--#{boundary}\r\n"
|
|
buf << "Content-Disposition: form-data: name=\"#{name}\"#{s}\r\n"
|
|
buf << "Content-Type: #{content_type}\r\n" if content_type
|
|
buf << "\r\n"
|
|
value = value.dup.force_encoding(::Encoding::ASCII_8BIT) if defined?(::Encoding)
|
|
buf << value
|
|
buf << "\r\n"
|
|
return self
|
|
end
|
|
|
|
def close
|
|
buf = @buf
|
|
@buf = ''
|
|
return buf << "--#{boundary}--\r\n"
|
|
end
|
|
|
|
def create_boundary() #:nodoc:
|
|
return "--boundary#{rand().to_s[2..-1]}"
|
|
end
|
|
|
|
def detect_content_type(filename) #:nodoc:
|
|
filename =~ /\.(\w+)\z/
|
|
return MIME_TYPES[$1] || 'application/octet-stream'
|
|
end
|
|
|
|
MIME_TYPES = {
|
|
'gif' => 'image/gif',
|
|
'jpg' => 'image/jpeg',
|
|
'jpeg' => 'image/jpeg',
|
|
'png' => 'image/png',
|
|
'bmp' => 'image/bmp',
|
|
'tif' => 'image/tiff',
|
|
'tiff' => 'image/tiff',
|
|
'htm' => 'text/html',
|
|
'html' => 'text/html',
|
|
'xml' => 'text/xml',
|
|
'txt' => 'text/plain',
|
|
'text' => 'text/plain',
|
|
'css' => 'text/css',
|
|
'mpg' => 'video/mpeg',
|
|
'mpeg' => 'video/mpeg',
|
|
'mov' => 'video/quicktime',
|
|
'avi' => 'video/x-msvideo',
|
|
'mp3' => 'audio/mpeg',
|
|
'mid' => 'audio/midi',
|
|
'wav' => 'audio/x-wav',
|
|
'zip' => 'application/zip',
|
|
#'tar.gz' => 'application/gtar',
|
|
'gz' => 'application/gzip',
|
|
'bz2' => 'application/bzip2',
|
|
'rtf' => 'application/rtf',
|
|
'pdf' => 'application/pdf',
|
|
'ps' => 'application/postscript',
|
|
'js' => 'application/x-javascript',
|
|
'xls' => 'application/vnd.ms-excel',
|
|
'doc' => 'application/msword',
|
|
'ppt' => 'application/vnd.ms-powerpoint',
|
|
}
|
|
|
|
end
|
|
|
|
|
|
|
|
class CGIMultipartTest < Test::Unit::TestCase
|
|
|
|
def setup
|
|
ENV['REQUEST_METHOD'] = 'POST'
|
|
@tempfiles = []
|
|
end
|
|
|
|
def teardown
|
|
%w[ REQUEST_METHOD CONTENT_TYPE CONTENT_LENGTH REQUEST_METHOD ].each do |name|
|
|
ENV.delete(name)
|
|
end
|
|
$stdin.close() if $stdin.is_a?(Tempfile)
|
|
$stdin = STDIN
|
|
@tempfiles.each {|t|
|
|
t.close!
|
|
}
|
|
end
|
|
|
|
def _prepare(data)
|
|
## create multipart input
|
|
multipart = MultiPart.new(defined?(@boundary) ? @boundary : nil)
|
|
data.each do |hash|
|
|
multipart.append(hash[:name], hash[:value], hash[:filename])
|
|
end
|
|
input = multipart.close()
|
|
input = yield(input) if block_given?
|
|
#$stderr.puts "*** debug: input=\n#{input.collect{|line| line.inspect}.join("\n")}"
|
|
@boundary ||= multipart.boundary
|
|
## set environment
|
|
ENV['CONTENT_TYPE'] = "multipart/form-data; boundary=#{@boundary}"
|
|
ENV['CONTENT_LENGTH'] = input.length.to_s
|
|
ENV['REQUEST_METHOD'] = 'POST'
|
|
## set $stdin
|
|
tmpfile = Tempfile.new('test_cgi_multipart')
|
|
@tempfiles << tmpfile
|
|
tmpfile.binmode
|
|
tmpfile << input
|
|
tmpfile.rewind()
|
|
$stdin = tmpfile
|
|
end
|
|
|
|
def _test_multipart(cgi_options={})
|
|
caller(0).find {|s| s =~ /in `test_(.*?)'/ }
|
|
#testname = $1
|
|
#$stderr.puts "*** debug: testname=#{testname.inspect}"
|
|
_prepare(@data)
|
|
options = {:accept_charset=>"UTF-8"}
|
|
options.merge! cgi_options
|
|
cgi = RUBY_VERSION>="1.9" ? CGI.new(options) : CGI.new
|
|
expected_names = @data.collect{|hash| hash[:name] }.sort
|
|
assert_equal(expected_names, cgi.params.keys.sort)
|
|
threshold = 1024*10
|
|
@data.each do |hash|
|
|
name = hash[:name]
|
|
expected = hash[:value]
|
|
if RUBY_VERSION>="1.9"
|
|
if hash[:filename] #if file
|
|
expected_class = @expected_class || (hash[:value].length < threshold ? StringIO : Tempfile)
|
|
assert(cgi.files.keys.member?(hash[:name]))
|
|
else
|
|
expected_class = String
|
|
assert_equal(expected, cgi[name])
|
|
assert_equal(false,cgi.files.keys.member?(hash[:name]))
|
|
end
|
|
else
|
|
expected_class = @expected_class || (hash[:value].length < threshold ? StringIO : Tempfile)
|
|
end
|
|
assert_kind_of(expected_class, cgi[name])
|
|
assert_equal(expected, cgi[name].read())
|
|
assert_equal(hash[:filename] || '', cgi[name].original_filename) #if hash[:filename]
|
|
assert_equal(hash[:content_type] || '', cgi[name].content_type) #if hash[:content_type]
|
|
end
|
|
ensure
|
|
if cgi
|
|
cgi.params.each {|name, vals|
|
|
vals.each {|val|
|
|
if val.kind_of?(Tempfile) && val.path
|
|
val.close!
|
|
end
|
|
}
|
|
}
|
|
end
|
|
end
|
|
|
|
|
|
def _read(basename)
|
|
filename = File.join(File.dirname(__FILE__), 'testdata', basename)
|
|
s = File.open(filename, 'rb') {|f| f.read() }
|
|
|
|
return s
|
|
end
|
|
|
|
|
|
def test_cgi_multipart_stringio
|
|
@boundary = '----WebKitFormBoundaryAAfvAII+YL9102cX'
|
|
@data = [
|
|
{:name=>'hidden1', :value=>'foobar'},
|
|
{:name=>'text1', :value=>"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"},
|
|
{:name=>'file1', :value=>_read('file1.html'),
|
|
:filename=>'file1.html', :content_type=>'text/html'},
|
|
{:name=>'image1', :value=>_read('small.png'),
|
|
:filename=>'small.png', :content_type=>'image/png'}, # small image
|
|
]
|
|
@data[1][:value].force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
|
|
@expected_class = StringIO
|
|
_test_multipart()
|
|
end
|
|
|
|
|
|
def test_cgi_multipart_tempfile
|
|
@boundary = '----WebKitFormBoundaryAAfvAII+YL9102cX'
|
|
@data = [
|
|
{:name=>'hidden1', :value=>'foobar'},
|
|
{:name=>'text1', :value=>"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"},
|
|
{:name=>'file1', :value=>_read('file1.html'),
|
|
:filename=>'file1.html', :content_type=>'text/html'},
|
|
{:name=>'image1', :value=>_read('large.png'),
|
|
:filename=>'large.png', :content_type=>'image/png'}, # large image
|
|
]
|
|
@data[1][:value].force_encoding(::Encoding::UTF_8) if defined?(::Encoding)
|
|
@expected_class = Tempfile
|
|
_test_multipart()
|
|
end
|
|
|
|
|
|
def _set_const(klass, name, value)
|
|
old = nil
|
|
klass.class_eval do
|
|
old = const_get(name)
|
|
remove_const(name)
|
|
const_set(name, value)
|
|
end
|
|
return old
|
|
end
|
|
|
|
|
|
def test_cgi_multipart_maxmultipartlength
|
|
@data = [
|
|
{:name=>'image1', :value=>_read('large.png'),
|
|
:filename=>'large.png', :content_type=>'image/png'}, # large image
|
|
]
|
|
begin
|
|
ex = assert_raise(StandardError) do
|
|
_test_multipart(:max_multipart_length=>2 * 1024) # set via simple scalar
|
|
end
|
|
assert_equal("too large multipart data.", ex.message)
|
|
ensure
|
|
end
|
|
end
|
|
|
|
|
|
def test_cgi_multipart_maxmultipartlength_lambda
|
|
@data = [
|
|
{:name=>'image1', :value=>_read('large.png'),
|
|
:filename=>'large.png', :content_type=>'image/png'}, # large image
|
|
]
|
|
begin
|
|
ex = assert_raise(StandardError) do
|
|
_test_multipart(:max_multipart_length=>lambda{2*1024}) # set via lambda
|
|
end
|
|
assert_equal("too large multipart data.", ex.message)
|
|
ensure
|
|
end
|
|
end
|
|
|
|
|
|
def test_cgi_multipart_maxmultipartcount
|
|
@data = [
|
|
{:name=>'file1', :value=>_read('file1.html'),
|
|
:filename=>'file1.html', :content_type=>'text/html'},
|
|
]
|
|
item = @data.first
|
|
500.times { @data << item }
|
|
#original = _set_const(CGI, :MAX_MULTIPART_COUNT, 128)
|
|
begin
|
|
ex = assert_raise(StandardError) do
|
|
_test_multipart()
|
|
end
|
|
assert_equal("too many parameters.", ex.message)
|
|
ensure
|
|
#_set_const(CGI, :MAX_MULTIPART_COUNT, original)
|
|
end
|
|
end if CGI.const_defined?(:MAX_MULTIPART_COUNT)
|
|
|
|
|
|
def test_cgi_multipart_badbody ## [ruby-dev:28470]
|
|
@data = [
|
|
{:name=>'file1', :value=>_read('file1.html'),
|
|
:filename=>'file1.html', :content_type=>'text/html'},
|
|
]
|
|
_prepare(@data) do |input|
|
|
input2 = input.sub(/--(\r\n)?\z/, "\r\n")
|
|
assert input2 != input
|
|
#p input2
|
|
input2
|
|
end
|
|
ex = assert_raise(EOFError) do
|
|
RUBY_VERSION>="1.9" ? CGI.new(:accept_charset=>"UTF-8") : CGI.new
|
|
end
|
|
assert_equal("bad content body", ex.message)
|
|
#
|
|
_prepare(@data) do |input|
|
|
input2 = input.sub(/--(\r\n)?\z/, "")
|
|
assert input2 != input
|
|
#p input2
|
|
input2
|
|
end
|
|
ex = assert_raise(EOFError) do
|
|
RUBY_VERSION>="1.9" ? CGI.new(:accept_charset=>"UTF-8") : CGI.new
|
|
end
|
|
assert_equal("bad content body", ex.message)
|
|
end
|
|
|
|
|
|
def test_cgi_multipart_quoteboundary ## [JVN#84798830]
|
|
@boundary = '(.|\n)*'
|
|
@data = [
|
|
{:name=>'hidden1', :value=>'foobar'},
|
|
{:name=>'text1', :value=>"\xE3\x81\x82\xE3\x81\x84\xE3\x81\x86\xE3\x81\x88\xE3\x81\x8A"},
|
|
{:name=>'file1', :value=>_read('file1.html'),
|
|
:filename=>'file1.html', :content_type=>'text/html'},
|
|
{:name=>'image1', :value=>_read('small.png'),
|
|
:filename=>'small.png', :content_type=>'image/png'}, # small image
|
|
]
|
|
@data[1][:value].force_encoding("UTF-8") if RUBY_VERSION>="1.9"
|
|
_prepare(@data)
|
|
cgi = RUBY_VERSION>="1.9" ? CGI.new(:accept_charset=>"UTF-8") : CGI.new
|
|
assert_equal('file1.html', cgi['file1'].original_filename)
|
|
end
|
|
|
|
def test_cgi_multipart_boundary_10240 # [Bug #3866]
|
|
@boundary = 'AaB03x'
|
|
@data = [
|
|
{:name=>'file', :value=>"b"*10134,
|
|
:filename=>'file.txt', :content_type=>'text/plain'},
|
|
{:name=>'foo', :value=>"bar"},
|
|
]
|
|
_prepare(@data)
|
|
cgi = RUBY_VERSION>="1.9" ? CGI.new(:accept_charset=>"UTF-8") : CGI.new
|
|
assert_equal(cgi['foo'], 'bar')
|
|
assert_equal(cgi['file'].read, 'b'*10134)
|
|
cgi['file'].close! if cgi['file'].kind_of? Tempfile
|
|
end
|
|
|
|
def test_cgi_multipart_without_tempfile
|
|
assert_in_out_err([], <<-'EOM')
|
|
require 'cgi'
|
|
require 'stringio'
|
|
ENV['REQUEST_METHOD'] = 'POST'
|
|
ENV['CONTENT_TYPE'] = 'multipart/form-data; boundary=foobar1234'
|
|
body = <<-BODY
|
|
--foobar1234
|
|
Content-Disposition: form-data: name=\"name1\"
|
|
|
|
value1
|
|
--foobar1234
|
|
Content-Disposition: form-data: name=\"file1\"; filename=\"file1.html\"
|
|
Content-Type: text/html
|
|
|
|
<html>
|
|
<body><p>Hello</p></body>
|
|
</html>
|
|
|
|
--foobar1234--
|
|
BODY
|
|
body.gsub!(/\n/, "\r\n")
|
|
ENV['CONTENT_LENGTH'] = body.size.to_s
|
|
$stdin = StringIO.new(body)
|
|
CGI.new
|
|
EOM
|
|
end
|
|
|
|
###
|
|
|
|
self.instance_methods.each do |method|
|
|
private method if method =~ /^test_(.*)/ && $1 != ENV['TEST']
|
|
end if ENV['TEST']
|
|
|
|
end
|