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:
parent
b6541b8dcc
commit
d2ed32d592
14 changed files with 784 additions and 818 deletions
|
@ -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:
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue