1
0
Fork 0
mirror of https://github.com/rails/rails.git synced 2022-11-09 12:12:34 -05:00

Parse url-encoded and multipart requests ourselves instead of delegating to CGI.

git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@6764 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
This commit is contained in:
Jeremy Kemper 2007-05-18 06:24:50 +00:00
parent b6541b8dcc
commit d2ed32d592
14 changed files with 784 additions and 818 deletions

View file

@ -1,5 +1,7 @@
*SVN*
* Parse url-encoded and multipart requests ourselves instead of delegating to CGI. [Jeremy Kemper]
* select :include_blank option can be set to a string instead of true, which just uses an empty string. #7664 [Wizard]
* Added url_for usage on render :location, which allows for record identification [DHH]. Example:

View file

@ -271,7 +271,9 @@ module ActionController #:nodoc:
# A YAML parser is also available and can be turned on with:
#
# ActionController::Base.param_parsers[Mime::YAML] = :yaml
@@param_parsers = { Mime::XML => :xml_simple }
@@param_parsers = { Mime::MULTIPART_FORM => :multipart_form,
Mime::URL_ENCODED_FORM => :url_encoded_form,
Mime::XML => :xml_simple }
cattr_accessor :param_parsers
# Controls the default charset for all renders.

View file

@ -1,12 +1,10 @@
require 'action_controller/cgi_ext/stdinput'
require 'action_controller/cgi_ext/parameters'
require 'action_controller/cgi_ext/query_extension'
require 'action_controller/cgi_ext/cookie'
require 'action_controller/cgi_ext/session'
class CGI #:nodoc:
include ActionController::CgiExt::Stdinput
include ActionController::CgiExt::Parameters
class << self
alias :escapeHTML_fail_on_nil :escapeHTML

View file

@ -1,208 +0,0 @@
require 'cgi'
require 'strscan'
module ActionController
module CgiExt
module Parameters
def self.included(base)
base.extend ClassMethods
end
# Merge POST and GET parameters from the request body and query string,
# with GET parameters taking precedence.
def parameters
request_parameters.update(query_parameters)
end
def query_parameters
self.class.parse_query_parameters(query_string)
end
def request_parameters
self.class.parse_request_parameters(params, env_table)
end
module ClassMethods
def parse_query_parameters(query_string)
return {} if query_string.blank?
pairs = query_string.split('&').collect do |chunk|
next if chunk.empty?
key, value = chunk.split('=', 2)
next if key.empty?
value = value.nil? ? nil : CGI.unescape(value)
[ CGI.unescape(key), value ]
end.compact
UrlEncodedPairParser.new(pairs).result
end
def parse_request_parameters(params)
parser = UrlEncodedPairParser.new
params = params.dup
until params.empty?
for key, value in params
if key.blank?
params.delete key
elsif !key.include?('[')
# much faster to test for the most common case first (GET)
# and avoid the call to build_deep_hash
parser.result[key] = get_typed_value(value[0])
params.delete key
elsif value.is_a?(Array)
parser.parse(key, get_typed_value(value.shift))
params.delete key if value.empty?
else
raise TypeError, "Expected array, found #{value.inspect}"
end
end
end
parser.result
end
private
def get_typed_value(value)
case value
when String
value
when NilClass
''
when Array
value.map { |v| get_typed_value(v) }
else
# Uploaded file provides content type and filename.
if value.respond_to?(:content_type) &&
!value.content_type.blank? &&
!value.original_filename.blank?
unless value.respond_to?(:full_original_filename)
class << value
alias_method :full_original_filename, :original_filename
# Take the basename of the upload's original filename.
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
# those which give the lone filename.
# The Windows regexp is adapted from Perl's File::Basename.
def original_filename
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
md.captures.first
else
File.basename full_original_filename
end
end
end
end
# Return the same value after overriding original_filename.
value
# Multipart values may have content type, but no filename.
elsif value.respond_to?(:read)
result = value.read
value.rewind
result
# Unknown value, neither string nor multipart.
else
raise "Unknown form value: #{value.inspect}"
end
end
end
end
class UrlEncodedPairParser < StringScanner #:nodoc:
attr_reader :top, :parent, :result
def initialize(pairs = [])
super('')
@result = {}
pairs.each { |key, value| parse(key, value) }
end
KEY_REGEXP = %r{([^\[\]=&]+)}
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
# Parse the query string
def parse(key, value)
self.string = key
@top, @parent = result, nil
# First scan the bare key
key = scan(KEY_REGEXP) or return
key = post_key_check(key)
# Then scan as many nestings as present
until eos?
r = scan(BRACKETED_KEY_REGEXP) or return
key = self[1]
key = post_key_check(key)
end
bind(key, value)
end
private
# After we see a key, we must look ahead to determine our next action. Cases:
#
# [] follows the key. Then the value must be an array.
# = follows the key. (A value comes next)
# & or the end of string follows the key. Then the key is a flag.
# otherwise, a hash follows the key.
def post_key_check(key)
if scan(/\[\]/) # a[b][] indicates that b is an array
container(key, Array)
nil
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
container(key, Hash)
nil
else # End of key? We do nothing.
key
end
end
# Add a container to the stack.
def container(key, klass)
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
value = bind(key, klass.new)
type_conflict! klass, value unless value.is_a?(klass)
push(value)
end
# Push a value onto the 'stack', which is actually only the top 2 items.
def push(value)
@parent, @top = @top, value
end
# Bind a key (which may be nil for items in an array) to the provided value.
def bind(key, value)
if top.is_a? Array
if key
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
top[-1][key] = value
else
top << {key => value}.with_indifferent_access
push top.last
end
else
top << value
end
elsif top.is_a? Hash
key = CGI.unescape(key)
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
return top[key] ||= value
else
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
end
return value
end
def type_conflict!(klass, value)
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value."
end
end
end
end
end

View file

@ -5,92 +5,18 @@ class CGI #:nodoc:
# Remove the old initialize_query method before redefining it.
remove_method :initialize_query
# Initialize the data from the query.
#
# Handles multipart forms (in particular, forms that involve file uploads).
# Reads query parameters in the @params field, and cookies into @cookies.
# Neuter CGI parameter parsing.
def initialize_query
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
# Fix some strange request environments.
if method = env_table['REQUEST_METHOD']
method = method.to_s.downcase.intern
else
method = :get
end
env_table['REQUEST_METHOD'] ||= 'GET'
# POST assumes missing Content-Type is application/x-www-form-urlencoded.
content_type = env_table['CONTENT_TYPE']
if content_type.blank? && method == :post
content_type = 'application/x-www-form-urlencoded'
if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST'
env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
end
# Force content length to zero if missing.
content_length = env_table['CONTENT_LENGTH'].to_i
# Set multipart to false by default.
@multipart = false
# POST and PUT may have params in entity body. If content type is missing
# or non-urlencoded, don't read the body or parse parameters: assume it's
# binary data.
if method == :post || method == :put
if boundary = extract_multipart_form_boundary(content_type)
@multipart = true
@params = read_multipart(boundary, content_length)
elsif content_type.blank? || content_type !~ %r{application/x-www-form-urlencoded}i
@params = {}
end
end
@params ||= CGI.parse(read_params(method, content_length))
@cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE'])
@params = {}
end
private
unless defined?(MULTIPART_FORM_BOUNDARY_RE)
MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #"
end
def extract_multipart_form_boundary(content_type)
MULTIPART_FORM_BOUNDARY_RE.match(content_type).to_a.pop
end
if defined? MOD_RUBY
def read_query
Apache::request.args || ''
end
else
def read_query
# fixes CGI querystring parsing for lighttpd
env_qs = env_table['QUERY_STRING']
if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank?
uri.split('?', 2)[1] || ''
else
env_qs || ''
end
end
end
def read_body(content_length)
stdinput.binmode if stdinput.respond_to?(:binmode)
content = stdinput.read(content_length) || ''
# Fix for Safari Ajax postings that always append \000
content.chop! if content[-1] == 0
content.gsub!(/&_=$/, '')
env_table['RAW_POST_DATA'] = content.freeze
end
def read_params(method, content_length)
case method
when :get
read_query
when :post, :put
read_body(content_length)
when :cmd
read_from_cmdline
else # :head, :delete, :options, :trace, :connect
read_query
end
end
end # module QueryExtension
end
end

View file

@ -47,12 +47,11 @@ module ActionController #:nodoc:
end
def query_string
if (qs = @cgi.query_string) && !qs.empty?
qs = @cgi.query_string
if !qs.blank?
qs
elsif uri = @env['REQUEST_URI']
parts = uri.split('?')
parts.shift
parts.join('?')
uri.split('?', 2).last
else
@env['QUERY_STRING'] || ''
end
@ -69,16 +68,11 @@ module ActionController #:nodoc:
end
def query_parameters
@query_parameters ||= CGI.parse_query_parameters(query_string)
@query_parameters ||= self.class.parse_query_parameters(query_string)
end
def request_parameters
@request_parameters ||=
if ActionController::Base.param_parsers.has_key?(content_type)
self.class.parse_formatted_request_parameters(content_type, body.read)
else
CGI.parse_request_parameters(@cgi.params)
end
@request_parameters ||= self.class.parse_formatted_request_parameters(body, content_type_with_parameters, content_length, env)
end
def cookies

View file

@ -306,7 +306,7 @@ module ActionController
"REQUEST_URI" => "/",
"HTTP_HOST" => host,
"SERVER_PORT" => https? ? "443" : "80",
"HTTPS" => https? ? "on" : "off")
"HTTPS" => https? ? "on" : "off")
ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {})
end

View file

@ -1,3 +1,7 @@
require 'tempfile'
require 'stringio'
require 'strscan'
module ActionController
# CgiRequest and TestRequest provide concrete implementations.
class AbstractRequest
@ -55,6 +59,14 @@ module ActionController
@env
end
def content_length
@content_length ||= env['CONTENT_LENGTH'].to_i
end
def content_type_with_parameters
@content_type_with_parameters ||= env['CONTENT_TYPE'].to_s
end
# Determine whether the body of a HTTP call is URL-encoded (default)
# or matches one of the registered param_parsers.
#
@ -64,7 +76,7 @@ module ActionController
@content_type ||=
begin
# Receive header sans any charset information.
content_type = @env['CONTENT_TYPE'].to_s.sub(/\s*\;.*$/, '').strip.downcase
content_type = content_type_with_parameters.sub(/\s*\;.*$/, '').strip.downcase
if x_post_format = @env['HTTP_X_POST_DATA_FORMAT']
case x_post_format.to_s.downcase
@ -297,20 +309,350 @@ module ActionController
end
def self.parse_formatted_request_parameters(mime_type, body)
case strategy = ActionController::Base.param_parsers[mime_type]
when Proc
strategy.call(body)
when :xml_simple, :xml_node
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
when :yaml
YAML.load(body)
else
{}
class << self
def parse_formatted_request_parameters(body, content_type, content_length, env = {})
content_length = content_length.to_i
return {} if content_length.zero?
content_type, boundary = extract_multipart_boundary(content_type.to_s)
return {} if content_type.blank?
mime_type = Mime::Type.lookup(content_type)
strategy = ActionController::Base.param_parsers[mime_type]
raise [content_type, content_length, mime_type, ActionController::Base.param_parsers].inspect unless strategy
# Only multipart form parsing expects a stream.
if strategy && strategy != :multipart_form
body = body.read(content_length)
end
case strategy
when Proc
strategy.call(body)
when :url_encoded_form
clean_up_ajax_request_body! body
parse_query_parameters(body)
when :multipart_form
parse_multipart_form_parameters(body, boundary, content_length, env)
when :xml_simple, :xml_node
body.blank? ? {} : Hash.from_xml(body).with_indifferent_access
when :yaml
YAML.load(body)
else
{}
end
rescue Exception => e # YAML, XML or Ruby code block errors
raise
{ "body" => body,
"content_type" => content_type,
"content_length" => content_length,
"exception" => "#{e.message} (#{e.class})",
"backtrace" => e.backtrace }
end
rescue Exception => e # YAML, XML or Ruby code block errors
{ "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
"body" => body, "format" => mime_type }
def parse_query_parameters(query_string)
return {} if query_string.blank?
pairs = query_string.split('&').collect do |chunk|
next if chunk.empty?
key, value = chunk.split('=', 2)
next if key.empty?
value = value.nil? ? nil : CGI.unescape(value)
[ CGI.unescape(key), value ]
end.compact
UrlEncodedPairParser.new(pairs).result
end
def parse_request_parameters(params)
parser = UrlEncodedPairParser.new
params = params.dup
until params.empty?
for key, value in params
if key.blank?
params.delete key
elsif !key.include?('[')
# much faster to test for the most common case first (GET)
# and avoid the call to build_deep_hash
parser.result[key] = get_typed_value(value[0])
params.delete key
elsif value.is_a?(Array)
parser.parse(key, get_typed_value(value.shift))
params.delete key if value.empty?
else
raise TypeError, "Expected array, found #{value.inspect}"
end
end
end
parser.result
end
def parse_multipart_form_parameters(body, boundary, content_length, env)
parse_request_parameters(read_multipart(body, boundary, content_length, env))
end
private
def get_typed_value(value)
case value
when String
value
when NilClass
''
when Array
value.map { |v| get_typed_value(v) }
else
# Uploaded file provides content type and filename.
if value.respond_to?(:content_type) &&
!value.content_type.blank? &&
!value.original_filename.blank?
unless value.respond_to?(:full_original_filename)
class << value
alias_method :full_original_filename, :original_filename
# Take the basename of the upload's original filename.
# This handles the full Windows paths given by Internet Explorer
# (and perhaps other broken user agents) without affecting
# those which give the lone filename.
# The Windows regexp is adapted from Perl's File::Basename.
def original_filename
if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
md.captures.first
else
File.basename full_original_filename
end
end
end
end
# Return the same value after overriding original_filename.
value
# Multipart values may have content type, but no filename.
elsif value.respond_to?(:read)
result = value.read
value.rewind
result
# Unknown value, neither string nor multipart.
else
raise "Unknown form value: #{value.inspect}"
end
end
end
MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n
def extract_multipart_boundary(content_type)
if content_type =~ MULTIPART_BOUNDARY
['multipart/form-data', $1.dup]
else
content_type
end
end
def clean_up_ajax_request_body!(body)
body.chop! if body[-1] == 0
body.gsub!(/&_=$/, '')
end
EOL = "\015\012"
def read_multipart(body, boundary, content_length, env)
params = Hash.new([])
boundary = "--" + boundary
quoted_boundary = Regexp.quote(boundary, "n")
buf = ""
bufsize = 10 * 1024
boundary_end=""
# start multipart/form-data
body.binmode if defined? body.binmode
boundary_size = boundary.size + EOL.size
content_length -= boundary_size
status = body.read(boundary_size)
if nil == status
raise EOFError, "no content body"
elsif boundary + EOL != status
raise EOFError, "bad content body"
end
loop do
head = nil
content =
if 10240 < content_length
Tempfile.new("CGI")
else
StringIO.new
end
content.binmode if defined? content.binmode
until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf)
if (not head) and /#{EOL}#{EOL}/n.match(buf)
buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do
head = $1.dup
""
end
next
end
if head and ( (EOL + boundary + EOL).size < buf.size )
content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)]
buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = ""
end
c = if bufsize < content_length
body.read(bufsize)
else
body.read(content_length)
end
if c.nil? || c.empty?
raise EOFError, "bad content body"
end
buf.concat(c)
content_length -= c.size
end
buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do
content.print $1
if "--" == $2
content_length = -1
end
boundary_end = $2.dup
""
end
content.rewind
/Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni.match(head)
filename = ($1 or $2 or "")
if /Mac/ni.match(env['HTTP_USER_AGENT']) and
/Mozilla/ni.match(env['HTTP_USER_AGENT']) and
(not /MSIE/ni.match(env['HTTP_USER_AGENT']))
filename = CGI.unescape(filename)
end
/Content-Type: (.*)/ni.match(head)
content_type = ($1 or "")
(class << content; self; end).class_eval do
alias local_path path
define_method(:original_filename) {filename.dup.taint}
define_method(:content_type) {content_type.dup.taint}
end
/Content-Disposition:.* name="?([^\";]*)"?/ni.match(head)
name = $1.dup
if params.has_key?(name)
params[name].push(content)
else
params[name] = [content]
end
break if buf.size == 0
break if content_length == -1
end
raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/
params
end
end
end
class UrlEncodedPairParser < StringScanner #:nodoc:
attr_reader :top, :parent, :result
def initialize(pairs = [])
super('')
@result = {}
pairs.each { |key, value| parse(key, value) }
end
KEY_REGEXP = %r{([^\[\]=&]+)}
BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
# Parse the query string
def parse(key, value)
self.string = key
@top, @parent = result, nil
# First scan the bare key
key = scan(KEY_REGEXP) or return
key = post_key_check(key)
# Then scan as many nestings as present
until eos?
r = scan(BRACKETED_KEY_REGEXP) or return
key = self[1]
key = post_key_check(key)
end
bind(key, value)
end
private
# After we see a key, we must look ahead to determine our next action. Cases:
#
# [] follows the key. Then the value must be an array.
# = follows the key. (A value comes next)
# & or the end of string follows the key. Then the key is a flag.
# otherwise, a hash follows the key.
def post_key_check(key)
if scan(/\[\]/) # a[b][] indicates that b is an array
container(key, Array)
nil
elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
container(key, Hash)
nil
else # End of key? We do nothing.
key
end
end
# Add a container to the stack.
def container(key, klass)
type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
value = bind(key, klass.new)
type_conflict! klass, value unless value.is_a?(klass)
push(value)
end
# Push a value onto the 'stack', which is actually only the top 2 items.
def push(value)
@parent, @top = @top, value
end
# Bind a key (which may be nil for items in an array) to the provided value.
def bind(key, value)
if top.is_a? Array
if key
if top[-1].is_a?(Hash) && ! top[-1].key?(key)
top[-1][key] = value
else
top << {key => value}.with_indifferent_access
push top.last
end
else
top << value
end
elsif top.is_a? Hash
key = CGI.unescape(key)
parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
return top[key] ||= value
else
raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
end
return value
end
def type_conflict!(klass, value)
raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value."
end
end
end

View file

@ -1,373 +1,7 @@
require File.dirname(__FILE__) + '/../abstract_unit'
require 'action_controller/cgi_process'
class CGITest < Test::Unit::TestCase
def setup
@query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
@query_string_with_empty = "action=create_customer&full_name="
@query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
@query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
@query_string_with_multiple_of_same_name =
"action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
@query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
@query_string_without_equal = "action"
@query_string_with_many_ampersands =
"&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
@query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
end
def test_query_string
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
CGI.parse_query_parameters(@query_string)
)
end
def test_deep_query_string
expected = {'x' => {'y' => {'z' => '10'}}}
assert_equal(expected, CGI.parse_query_parameters('x[y][z]=10'))
end
def test_deep_query_string_with_array
assert_equal({'x' => {'y' => {'z' => ['10']}}}, CGI.parse_query_parameters('x[y][z][]=10'))
assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, CGI.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
end
def test_deep_query_string_with_array_of_hash
assert_equal({'x' => {'y' => [{'z' => '10'}]}}, CGI.parse_query_parameters('x[y][][z]=10'))
assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, CGI.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
end
def test_deep_query_string_with_array_of_hashes_with_one_pair
assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, CGI.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
assert_equal("10", CGI.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
assert_equal("10", CGI.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
end
def test_request_hash_parsing
query = {
"note[viewers][viewer][][type]" => ["User", "Group"],
"note[viewers][viewer][][id]" => ["1", "2"]
}
expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } }
assert_equal(expected, CGI.parse_request_parameters(query))
end
def test_deep_query_string_with_array_of_hashes_with_multiple_pairs
assert_equal(
{'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
CGI.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
)
end
def test_query_string_with_nil
assert_equal(
{ "action" => "create_customer", "full_name" => ''},
CGI.parse_query_parameters(@query_string_with_empty)
)
end
def test_query_string_with_array
assert_equal(
{ "action" => "create_customer", "selected" => ["1", "2", "3"]},
CGI.parse_query_parameters(@query_string_with_array)
)
end
def test_query_string_with_amps
assert_equal(
{ "action" => "create_customer", "name" => "Don't & Does"},
CGI.parse_query_parameters(@query_string_with_amps)
)
end
def test_query_string_with_many_equal
assert_equal(
{ "action" => "create_customer", "full_name" => "abc=def=ghi"},
CGI.parse_query_parameters(@query_string_with_many_equal)
)
end
def test_query_string_without_equal
assert_equal(
{ "action" => nil },
CGI.parse_query_parameters(@query_string_without_equal)
)
end
def test_query_string_with_empty_key
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
CGI.parse_query_parameters(@query_string_with_empty_key)
)
end
def test_query_string_with_many_ampersands
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
CGI.parse_query_parameters(@query_string_with_many_ampersands)
)
end
def test_parse_params
input = {
"customers[boston][first][name]" => [ "David" ],
"customers[boston][first][url]" => [ "http://David" ],
"customers[boston][second][name]" => [ "Allan" ],
"customers[boston][second][url]" => [ "http://Allan" ],
"something_else" => [ "blah" ],
"something_nil" => [ nil ],
"something_empty" => [ "" ],
"products[first]" => [ "Apple Computer" ],
"products[second]" => [ "Pc" ],
"" => [ 'Save' ]
}
expected_output = {
"customers" => {
"boston" => {
"first" => {
"name" => "David",
"url" => "http://David"
},
"second" => {
"name" => "Allan",
"url" => "http://Allan"
}
}
},
"something_else" => "blah",
"something_empty" => "",
"something_nil" => "",
"products" => {
"first" => "Apple Computer",
"second" => "Pc"
}
}
assert_equal expected_output, CGI.parse_request_parameters(input)
end
def test_parse_params_from_multipart_upload
mockup = Struct.new(:content_type, :original_filename, :read, :rewind)
file = mockup.new('img/jpeg', 'foo.jpg')
ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
non_file_text_part = mockup.new('text/plain', '', 'abc')
input = {
"something" => [ StringIO.new("") ],
"array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]],
"mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]],
"mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]],
"ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]],
"products[string]" => [ StringIO.new("Apple Computer") ],
"products[file]" => [ file ],
"ie_products[string]" => [ StringIO.new("Microsoft") ],
"ie_products[file]" => [ ie_file ],
"text_part" => [non_file_text_part]
}
expected_output = {
"something" => "",
"array_of_stringios" => ["One", "Two"],
"mixed_types_array" => [ "Three", "NotStringIO" ],
"mixed_types_as_checkboxes" => {
"strings" => {
"nested" => [ file, "String", "StringIO" ]
},
},
"ie_mixed_types_as_checkboxes" => {
"strings" => {
"nested" => [ ie_file, "String", "StringIO" ]
},
},
"products" => {
"string" => "Apple Computer",
"file" => file
},
"ie_products" => {
"string" => "Microsoft",
"file" => ie_file
},
"text_part" => "abc"
}
params = CGI.parse_request_parameters(input)
assert_equal expected_output, params
# Lone filenames are preserved.
assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
assert_equal 'foo.jpg', params['products']['file'].original_filename
# But full Windows paths are reduced to their basename.
assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
assert_equal 'bar.jpg', params['ie_products']['file'].original_filename
end
def test_parse_params_with_file
input = {
"customers[boston][first][name]" => [ "David" ],
"something_else" => [ "blah" ],
"logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
}
expected_output = {
"customers" => {
"boston" => {
"first" => {
"name" => "David"
}
}
},
"something_else" => "blah",
"logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
}
assert_equal expected_output, CGI.parse_request_parameters(input)
end
def test_parse_params_with_array
input = { "selected[]" => [ "1", "2", "3" ] }
expected_output = { "selected" => [ "1", "2", "3" ] }
assert_equal expected_output, CGI.parse_request_parameters(input)
end
def test_parse_params_with_non_alphanumeric_name
input = { "a/b[c]" => %w(d) }
expected = { "a/b" => { "c" => "d" }}
assert_equal expected, CGI.parse_request_parameters(input)
end
def test_parse_params_with_single_brackets_in_middle
input = { "a/b[c]d" => %w(e) }
expected = { "a/b" => {} }
assert_equal expected, CGI.parse_request_parameters(input)
end
def test_parse_params_with_separated_brackets
input = { "a/b@[c]d[e]" => %w(f) }
expected = { "a/b@" => { }}
assert_equal expected, CGI.parse_request_parameters(input)
end
def test_parse_params_with_separated_brackets_and_array
input = { "a/b@[c]d[e][]" => %w(f) }
expected = { "a/b@" => { }}
assert_equal expected , CGI.parse_request_parameters(input)
end
def test_parse_params_with_unmatched_brackets_and_array
input = { "a/b@[c][d[e][]" => %w(f) }
expected = { "a/b@" => { "c" => { }}}
assert_equal expected, CGI.parse_request_parameters(input)
end
def test_parse_params_with_nil_key
input = { nil => nil, "test2" => %w(value1) }
expected = { "test2" => "value1" }
assert_equal expected, CGI.parse_request_parameters(input)
end
end
class MultipartCGITest < Test::Unit::TestCase
FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart'
def setup
ENV['REQUEST_METHOD'] = 'POST'
ENV['CONTENT_LENGTH'] = '0'
ENV['CONTENT_TYPE'] = 'multipart/form-data, boundary=AaB03x'
end
def test_single_parameter
params = process('single_parameter')
assert_equal({ 'foo' => 'bar' }, params)
end
def test_text_file
params = process('text_file')
assert_equal %w(file foo), params.keys.sort
assert_equal 'bar', params['foo']
file = params['file']
assert_kind_of StringIO, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain\r", file.content_type
assert_equal 'contents', file.read
end
def test_large_text_file
params = process('large_text_file')
assert_equal %w(file foo), params.keys.sort
assert_equal 'bar', params['foo']
file = params['file']
assert_kind_of Tempfile, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain\r", file.content_type
assert ('a' * 20480) == file.read
end
def test_binary_file
params = process('binary_file')
assert_equal %w(file flowers foo), params.keys.sort
assert_equal 'bar', params['foo']
file = params['file']
assert_kind_of StringIO, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain\r", file.content_type
assert_equal 'contents', file.read
file = params['flowers']
assert_kind_of StringIO, file
assert_equal 'flowers.jpg', file.original_filename
assert_equal "image/jpeg\r", file.content_type
assert_equal 19512, file.size
#assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read
end
def test_mixed_files
params = process('mixed_files')
assert_equal %w(files foo), params.keys.sort
assert_equal 'bar', params['foo']
# Ruby CGI doesn't handle multipart/mixed for us.
assert_kind_of String, params['files']
assert_equal 19756, params['files'].size
end
# Rewind readable cgi params so others may reread them (such as CGI::Session
# when passing the session id in a multipart form).
def test_multipart_param_rewound
params = process('text_file')
assert_equal 'bar', @cgi.params['foo'][0].read
end
private
def process(name)
File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
ENV['CONTENT_LENGTH'] = file.stat.size.to_s
@cgi = CGI.new('query', file)
CGI.parse_request_parameters @cgi.params
end
end
end
# Ensures that PUT works with multipart as well as POST.
class PutMultipartCGITest < MultipartCGITest
def setup
super
ENV['REQUEST_METHOD'] = 'PUT'
end
end
class CGIRequestTest < Test::Unit::TestCase
class CgiRequestTest < Test::Unit::TestCase
def setup
@request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"}
# cookie as returned by some Nokia phone browsers (no space after semicolon separator)
@ -375,20 +9,20 @@ class CGIRequestTest < Test::Unit::TestCase
@fake_cgi = Struct.new(:env_table).new(@request_hash)
@request = ActionController::CgiRequest.new(@fake_cgi)
end
def test_proxy_request
assert_equal 'glu.ttono.us', @request.host_with_port
end
def test_http_host
@request_hash.delete "HTTP_X_FORWARDED_HOST"
@request_hash['HTTP_HOST'] = "rubyonrails.org:8080"
assert_equal "rubyonrails.org:8080", @request.host_with_port
@request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org"
assert_equal "www.secondhost.org", @request.host
end
def test_http_host_with_default_port_overrides_server_port
@request_hash.delete "HTTP_X_FORWARDED_HOST"
@request_hash['HTTP_HOST'] = "rubyonrails.org"
@ -412,21 +46,9 @@ class CGIRequestTest < Test::Unit::TestCase
cookies = CGI::Cookie::parse(@request_hash["HTTP_COOKIE"]);
assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"]
assert_equal ["yes"], cookies["is_admin"]
alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]);
assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"]
assert_equal ["yes"], alt_cookies["is_admin"]
end
def test_unbalanced_query_string_with_array
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
CGI.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
)
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
CGI.parse_request_parameters({'location[]' => ["1", "2"],
'age_group[]' => ["2"]})
)
end
end

View file

@ -232,11 +232,13 @@ class MimeControllerTest < Test::Unit::TestCase
assert_equal "<p>Hello world!</p>\n", @response.body
end
def test_with_content_type
def test_with_atom_content_type
@request.env["CONTENT_TYPE"] = "application/atom+xml"
get :made_for_content_type
assert_equal "ATOM", @response.body
end
def test_with_rss_content_type
@request.env["CONTENT_TYPE"] = "application/rss+xml"
get :made_for_content_type
assert_equal "RSS", @response.body

View file

@ -1,74 +0,0 @@
require "#{File.dirname(__FILE__)}/../abstract_unit"
class RawPostDataTest < Test::Unit::TestCase
def setup
ENV.delete('RAW_POST_DATA')
@request_body = 'a=1'
end
def test_post_with_urlencoded_body
ENV['REQUEST_METHOD'] = 'POST'
ENV['CONTENT_TYPE'] = ' apPlication/x-Www-form-urlEncoded; charset=utf-8'
assert_equal ['1'], cgi.params['a']
assert_raw_post_data
end
def test_post_with_empty_content_type_treated_as_urlencoded
ENV['REQUEST_METHOD'] = 'POST'
ENV['CONTENT_TYPE'] = ''
assert_equal ['1'], cgi.params['a']
assert_raw_post_data
end
def test_post_with_unrecognized_content_type_ignores_body
ENV['REQUEST_METHOD'] = 'POST'
ENV['CONTENT_TYPE'] = 'foo/bar'
assert cgi.params.empty?
assert_no_raw_post_data
end
def test_put_with_urlencoded_body
ENV['REQUEST_METHOD'] = 'PUT'
ENV['CONTENT_TYPE'] = 'application/x-www-form-urlencoded'
assert_equal ['1'], cgi.params['a']
assert_raw_post_data
end
def test_put_with_empty_content_type_ignores_body
ENV['REQUEST_METHOD'] = 'PUT'
ENV['CONTENT_TYPE'] = ''
assert cgi.params.empty?
assert_no_raw_post_data
end
def test_put_with_unrecognized_content_type_ignores_body
ENV['REQUEST_METHOD'] = 'PUT'
ENV['CONTENT_TYPE'] = 'foo/bar'
assert cgi.params.empty?
assert_no_raw_post_data
end
private
def cgi
unless defined? @cgi
ENV['CONTENT_LENGTH'] = @request_body.size.to_s
@cgi = CGI.new('query', StringIO.new(@request_body.dup))
end
@cgi
end
def assert_raw_post_data
assert_not_nil ENV['RAW_POST_DATA']
assert ENV['RAW_POST_DATA'].frozen?
assert_equal @request_body, ENV['RAW_POST_DATA']
assert_equal '', cgi.stdinput.read
end
def assert_no_raw_post_data
assert_nil ENV['RAW_POST_DATA']
assert_equal @request_body, cgi.stdinput.read
end
end

View file

@ -44,7 +44,7 @@ class RequestTest < Test::Unit::TestCase
@request.host = "www.rubyonrails.co.uk"
assert_equal "rubyonrails.co.uk", @request.domain(2)
@request.host = "192.168.1.200"
assert_nil @request.domain
@ -68,7 +68,7 @@ class RequestTest < Test::Unit::TestCase
@request.host = nil
assert_equal [], @request.subdomains
end
def test_port_string
@request.port = 80
assert_equal "", @request.port_string
@ -76,14 +76,14 @@ class RequestTest < Test::Unit::TestCase
@request.port = 8080
assert_equal ":8080", @request.port_string
end
def test_relative_url_root
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
@request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
assert_equal '', @request.relative_url_root, "relative_url_root should be disabled on lighttpd"
@request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text'
@request.env['SCRIPT_NAME'] = nil
assert_equal "", @request.relative_url_root
@ -99,19 +99,19 @@ class RequestTest < Test::Unit::TestCase
@request.relative_url_root = nil
@request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
assert_equal "/collaboration/hieraki", @request.relative_url_root
assert_equal "/collaboration/hieraki", @request.relative_url_root
# apache/scgi case
@request.relative_url_root = nil
@request.env['SCRIPT_NAME'] = "/collaboration/hieraki"
assert_equal "/collaboration/hieraki", @request.relative_url_root
assert_equal "/collaboration/hieraki", @request.relative_url_root
@request.relative_url_root = nil
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
@request.env['SERVER_SOFTWARE'] = 'lighttpd/1.2.3'
@request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki"
assert_equal "/hieraki", @request.relative_url_root
# @env overrides path guess
@request.relative_url_root = nil
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
@ -119,15 +119,15 @@ class RequestTest < Test::Unit::TestCase
@request.env['RAILS_RELATIVE_URL_ROOT'] = "/real_url"
assert_equal "/real_url", @request.relative_url_root
end
def test_request_uri
@request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432'
@request.relative_url_root = nil
@request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1"
assert_equal "/path/of/some/uri?mapped=1", @request.request_uri
assert_equal "/path/of/some/uri", @request.path
@request.relative_url_root = nil
@request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri"
assert_equal "/path/of/some/uri", @request.request_uri
@ -147,25 +147,25 @@ class RequestTest < Test::Unit::TestCase
@request.set_REQUEST_URI "/?m=b"
assert_equal "/?m=b", @request.request_uri
assert_equal "/", @request.path
@request.relative_url_root = nil
@request.set_REQUEST_URI "/"
@request.env['SCRIPT_NAME'] = "/dispatch.cgi"
assert_equal "/", @request.request_uri
assert_equal "/", @request.path
assert_equal "/", @request.path
@request.relative_url_root = nil
@request.set_REQUEST_URI "/hieraki/"
@request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi"
assert_equal "/hieraki/", @request.request_uri
assert_equal "/", @request.path
assert_equal "/", @request.path
@request.relative_url_root = nil
@request.set_REQUEST_URI "/collaboration/hieraki/books/edit/2"
@request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi"
assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri
assert_equal "/books/edit/2", @request.path
# The following tests are for when REQUEST_URI is not supplied (as in IIS)
@request.relative_url_root = nil
@request.set_REQUEST_URI nil
@ -238,30 +238,30 @@ class RequestTest < Test::Unit::TestCase
@request.host = "rubyonrails.org"
@request.port = 80
assert_equal "rubyonrails.org", @request.host_with_port
@request.host = "rubyonrails.org"
@request.port = 81
assert_equal "rubyonrails.org:81", @request.host_with_port
end
def test_server_software
assert_equal nil, @request.server_software
@request.env['SERVER_SOFTWARE'] = 'Apache3.422'
assert_equal 'apache', @request.server_software
@request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)'
assert_equal 'lighttpd', @request.server_software
end
def test_xml_http_request
assert !@request.xml_http_request?
assert !@request.xhr?
@request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0"
assert !@request.xml_http_request?
assert !@request.xhr?
@request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest"
assert @request.xml_http_request?
assert @request.xhr?
@ -301,7 +301,7 @@ class RequestTest < Test::Unit::TestCase
assert_equal method, @request.method
end
end
def test_head_masquarading_as_get
set_request_method_to :head
assert_equal :get, @request.method
@ -313,12 +313,12 @@ class RequestTest < Test::Unit::TestCase
@request.instance_eval { @parameters = { :format => 'xml' } }
assert_equal Mime::XML, @request.format
end
def test_xhtml_format
@request.instance_eval { @parameters = { :format => 'xhtml' } }
assert_equal Mime::HTML, @request.format
end
def test_txt_format
@request.instance_eval { @parameters = { :format => 'txt' } }
assert_equal Mime::TEXT, @request.format
@ -329,7 +329,7 @@ class RequestTest < Test::Unit::TestCase
@request.env["HTTP_ACCEPT"] = "text/javascript"
assert_equal Mime::JS, @request.format
end
def test_content_type
@request.env["CONTENT_TYPE"] = "text/html"
assert_equal Mime::HTML, @request.content_type
@ -338,7 +338,7 @@ class RequestTest < Test::Unit::TestCase
def test_content_no_type
assert_equal nil, @request.content_type
end
def test_content_type_xml
@request.env["CONTENT_TYPE"] = "application/xml"
assert_equal Mime::XML, @request.content_type
@ -357,18 +357,377 @@ class RequestTest < Test::Unit::TestCase
end
class RequestParameterParsingTest < Test::Unit::TestCase
def test_xml_with_single_file
class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase
def setup
@query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1"
@query_string_with_empty = "action=create_customer&full_name="
@query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3"
@query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does"
@query_string_with_multiple_of_same_name =
"action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3"
@query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi"
@query_string_without_equal = "action"
@query_string_with_many_ampersands =
"&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"
@query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save"
end
def test_query_string
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"},
ActionController::AbstractRequest.parse_query_parameters(@query_string)
)
end
def test_deep_query_string
expected = {'x' => {'y' => {'z' => '10'}}}
assert_equal(expected, ActionController::AbstractRequest.parse_query_parameters('x[y][z]=10'))
end
def test_deep_query_string_with_array
assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10'))
assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))
end
def test_deep_query_string_with_array_of_hash
assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10'))
assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))
end
def test_deep_query_string_with_array_of_hashes_with_one_pair
assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))
assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])
assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])
end
def test_deep_query_string_with_array_of_hashes_with_multiple_pairs
assert_equal(
{'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}},
ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')
)
end
def test_query_string_with_nil
assert_equal(
{ "action" => "create_customer", "full_name" => ''},
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty)
)
end
def test_query_string_with_array
assert_equal(
{ "action" => "create_customer", "selected" => ["1", "2", "3"]},
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_array)
)
end
def test_query_string_with_amps
assert_equal(
{ "action" => "create_customer", "name" => "Don't & Does"},
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_amps)
)
end
def test_query_string_with_many_equal
assert_equal(
{ "action" => "create_customer", "full_name" => "abc=def=ghi"},
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_equal)
)
end
def test_query_string_without_equal
assert_equal(
{ "action" => nil },
ActionController::AbstractRequest.parse_query_parameters(@query_string_without_equal)
)
end
def test_query_string_with_empty_key
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson" },
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty_key)
)
end
def test_query_string_with_many_ampersands
assert_equal(
{ "action" => "create_customer", "full_name" => "David Heinemeier Hansson"},
ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_ampersands)
)
end
def test_unbalanced_query_string_with_array
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
ActionController::AbstractRequest.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")
)
assert_equal(
{'location' => ["1", "2"], 'age_group' => ["2"]},
ActionController::AbstractRequest.parse_request_parameters({'location[]' => ["1", "2"],
'age_group[]' => ["2"]})
)
end
def test_request_hash_parsing
query = {
"note[viewers][viewer][][type]" => ["User", "Group"],
"note[viewers][viewer][][id]" => ["1", "2"]
}
expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } }
assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query))
end
def test_parse_params
input = {
"customers[boston][first][name]" => [ "David" ],
"customers[boston][first][url]" => [ "http://David" ],
"customers[boston][second][name]" => [ "Allan" ],
"customers[boston][second][url]" => [ "http://Allan" ],
"something_else" => [ "blah" ],
"something_nil" => [ nil ],
"something_empty" => [ "" ],
"products[first]" => [ "Apple Computer" ],
"products[second]" => [ "Pc" ],
"" => [ 'Save' ]
}
expected_output = {
"customers" => {
"boston" => {
"first" => {
"name" => "David",
"url" => "http://David"
},
"second" => {
"name" => "Allan",
"url" => "http://Allan"
}
}
},
"something_else" => "blah",
"something_empty" => "",
"something_nil" => "",
"products" => {
"first" => "Apple Computer",
"second" => "Pc"
}
}
assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_from_multipart_upload
mockup = Struct.new(:content_type, :original_filename, :read, :rewind)
file = mockup.new('img/jpeg', 'foo.jpg')
ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg')
non_file_text_part = mockup.new('text/plain', '', 'abc')
input = {
"something" => [ StringIO.new("") ],
"array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]],
"mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]],
"mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]],
"ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]],
"products[string]" => [ StringIO.new("Apple Computer") ],
"products[file]" => [ file ],
"ie_products[string]" => [ StringIO.new("Microsoft") ],
"ie_products[file]" => [ ie_file ],
"text_part" => [non_file_text_part]
}
expected_output = {
"something" => "",
"array_of_stringios" => ["One", "Two"],
"mixed_types_array" => [ "Three", "NotStringIO" ],
"mixed_types_as_checkboxes" => {
"strings" => {
"nested" => [ file, "String", "StringIO" ]
},
},
"ie_mixed_types_as_checkboxes" => {
"strings" => {
"nested" => [ ie_file, "String", "StringIO" ]
},
},
"products" => {
"string" => "Apple Computer",
"file" => file
},
"ie_products" => {
"string" => "Microsoft",
"file" => ie_file
},
"text_part" => "abc"
}
params = ActionController::AbstractRequest.parse_request_parameters(input)
assert_equal expected_output, params
# Lone filenames are preserved.
assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
assert_equal 'foo.jpg', params['products']['file'].original_filename
# But full Windows paths are reduced to their basename.
assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename
assert_equal 'bar.jpg', params['ie_products']['file'].original_filename
end
def test_parse_params_with_file
input = {
"customers[boston][first][name]" => [ "David" ],
"something_else" => [ "blah" ],
"logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ]
}
expected_output = {
"customers" => {
"boston" => {
"first" => {
"name" => "David"
}
}
},
"something_else" => "blah",
"logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path,
}
assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_array
input = { "selected[]" => [ "1", "2", "3" ] }
expected_output = { "selected" => [ "1", "2", "3" ] }
assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_non_alphanumeric_name
input = { "a/b[c]" => %w(d) }
expected = { "a/b" => { "c" => "d" }}
assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_single_brackets_in_middle
input = { "a/b[c]d" => %w(e) }
expected = { "a/b" => {} }
assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_separated_brackets
input = { "a/b@[c]d[e]" => %w(f) }
expected = { "a/b@" => { }}
assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_separated_brackets_and_array
input = { "a/b@[c]d[e][]" => %w(f) }
expected = { "a/b@" => { }}
assert_equal expected , ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_unmatched_brackets_and_array
input = { "a/b@[c][d[e][]" => %w(f) }
expected = { "a/b@" => { "c" => { }}}
assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
end
def test_parse_params_with_nil_key
input = { nil => nil, "test2" => %w(value1) }
expected = { "test2" => "value1" }
assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input)
end
end
class MultipartRequestParameterParsingTest < Test::Unit::TestCase
FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart'
def test_single_parameter
params = process('single_parameter')
assert_equal({ 'foo' => 'bar' }, params)
end
def test_text_file
params = process('text_file')
assert_equal %w(file foo), params.keys.sort
assert_equal 'bar', params['foo']
file = params['file']
assert_kind_of StringIO, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain\r", file.content_type
assert_equal 'contents', file.read
end
def test_large_text_file
params = process('large_text_file')
assert_equal %w(file foo), params.keys.sort
assert_equal 'bar', params['foo']
file = params['file']
assert_kind_of Tempfile, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain\r", file.content_type
assert ('a' * 20480) == file.read
end
def test_binary_file
params = process('binary_file')
assert_equal %w(file flowers foo), params.keys.sort
assert_equal 'bar', params['foo']
file = params['file']
assert_kind_of StringIO, file
assert_equal 'file.txt', file.original_filename
assert_equal "text/plain\r", file.content_type
assert_equal 'contents', file.read
file = params['flowers']
assert_kind_of StringIO, file
assert_equal 'flowers.jpg', file.original_filename
assert_equal "image/jpeg\r", file.content_type
assert_equal 19512, file.size
#assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read
end
def test_mixed_files
params = process('mixed_files')
assert_equal %w(files foo), params.keys.sort
assert_equal 'bar', params['foo']
# Ruby CGI doesn't handle multipart/mixed for us.
assert_kind_of String, params['files']
assert_equal 19756, params['files'].size
end
private
def process(name)
File.open(File.join(FIXTURE_PATH, name), 'rb') do |file|
content_length = file.stat.size.to_s
content_type = 'multipart/form-data, boundary=AaB03x'
ActionController::AbstractRequest.parse_formatted_request_parameters(file, content_type, content_length)
end
end
end
class XmlParamsParsingTest < Test::Unit::TestCase
def test_single_file
body = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{Base64.encode64('ABC')}</avatar></person>"
person = ActionController::AbstractRequest.parse_formatted_request_parameters(Mime::XML, body)
person = ActionController::AbstractRequest.parse_formatted_request_parameters(StringIO.new(body), 'application/xml', body.size)
assert_equal "image/jpg", person['person']['avatar'].content_type
assert_equal "me.jpg", person['person']['avatar'].original_filename
assert_equal "ABC", person['person']['avatar'].read
end
def test_xml_with_multiple_files
def test_multiple_files
body = <<-end_body
<person>
<name>David</name>
@ -379,7 +738,7 @@ class RequestParameterParsingTest < Test::Unit::TestCase
</person>
end_body
person = ActionController::AbstractRequest.parse_formatted_request_parameters(Mime::XML, body)
person = ActionController::AbstractRequest.parse_formatted_request_parameters(StringIO.new(body), 'application/xml', body.size)
assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type
assert_equal "me.jpg", person['person']['avatars']['avatar'].first.original_filename
@ -390,4 +749,3 @@ class RequestParameterParsingTest < Test::Unit::TestCase
assert_equal "DEF", person['person']['avatars']['avatar'].last.read
end
end

View file

@ -199,10 +199,9 @@ class CookieStoreTest < Test::Unit::TestCase
ENV['HTTP_HOST'] = 'example.com'
ENV['QUERY_STRING'] = ''
$stdin, old_stdin = StringIO.new(''), $stdin
yield CGI.new
ensure
$stdin = old_stdin
cgi = CGI.new('query', StringIO.new(''))
yield cgi if block_given?
cgi
end
end

View file

@ -36,10 +36,13 @@ class WebServiceTest < Test::Unit::TestCase
def setup
@controller = TestController.new
ActionController::Base.param_parsers.clear
ActionController::Base.param_parsers[Mime::XML] = :xml_simple
@default_param_parsers = ActionController::Base.param_parsers.dup
end
def teardown
ActionController::Base.param_parsers = @default_param_parsers
end
def test_check_parameters
process('GET')
assert_equal '', @controller.response.body