aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
diff options
context:
space:
mode:
authorNicholas Seckar <nseckar@gmail.com>2006-08-04 16:03:21 +0000
committerNicholas Seckar <nseckar@gmail.com>2006-08-04 16:03:21 +0000
commitf39180eded41502ee8b323fb61c08662eb1de253 (patch)
tree2d965f5325e8bdbbdfdd163f2231cc4ae168e1b7 /actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
parent2d6ca64c457cfe87d9a15f77acf60470d6ac005f (diff)
downloadrails-f39180eded41502ee8b323fb61c08662eb1de253.tar.gz
rails-f39180eded41502ee8b323fb61c08662eb1de253.tar.bz2
rails-f39180eded41502ee8b323fb61c08662eb1de253.zip
Fix broken query parameter tests
git-svn-id: http://svn-commit.rubyonrails.org/rails/trunk@4660 5ecf4fe2-1ee6-0310-87b1-e25e094e27de
Diffstat (limited to 'actionpack/lib/action_controller/cgi_ext/cgi_methods.rb')
-rwxr-xr-xactionpack/lib/action_controller/cgi_ext/cgi_methods.rb156
1 files changed, 113 insertions, 43 deletions
diff --git a/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb b/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
index 1af837c39a..59ee9c1338 100755
--- a/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
+++ b/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
@@ -1,40 +1,16 @@
require 'cgi'
require 'action_controller/vendor/xml_node'
+require 'strscan'
# Static methods for parsing the query and request parameters that can be used in
# a CGI extension class or testing in isolation.
class CGIMethods #:nodoc:
+
class << self
# Returns a hash with the pairs from the query string. The implicit hash construction that is done in
# parse_request_params is not done here.
def parse_query_parameters(query_string)
- parsed_params = {}
-
- query_string.split(/[&;]/).each { |p|
- # Ignore repeated delimiters.
- next if p.empty?
-
- k, v = p.split('=',2)
- v = nil if (v && v.empty?)
-
- k = CGI.unescape(k) if k
- v = CGI.unescape(v) if v
-
- unless k.include?(?[)
- parsed_params[k] = v
- else
- keys = split_key(k)
- last_key = keys.pop
- last_key = keys.pop if (use_array = last_key.empty?)
- parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
-
- if use_array then (parent[last_key] ||= []) << v
- else parent[last_key] = v
- end
- end
- }
-
- parsed_params
+ QueryStringScanner.new(query_string).parse
end
# Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /
@@ -76,22 +52,6 @@ class CGIMethods #:nodoc:
end
private
- # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
- # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
- # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
- def split_key(key)
- if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
- keys = [$1]
-
- keys.concat($2[1..-2].split(']['))
- keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
-
- keys
- else
- [key]
- end
- end
-
def get_typed_value(value)
# test most frequent case first
if value.is_a?(String)
@@ -157,4 +117,114 @@ class CGIMethods #:nodoc:
end
end
end
+
+ class QueryStringScanner < StringScanner
+
+ attr_reader :top, :parent, :result
+
+ def initialize(string)
+ super(string)
+ end
+
+ KEY_REGEXP = %r{([^\[\]=&]+)}
+ BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
+
+ # Parse the query string
+ def parse
+ @result = {}
+ until eos?
+ # Parse each & delimited chunk
+ @parent, @top = nil, result
+
+ # First scan the bare key
+ key = scan(KEY_REGEXP) or (skip_term and next)
+ key = post_key_check(key)
+
+ # Then scan as many nestings as present
+ until check(/\=/) || eos?
+ r = scan(BRACKETED_KEY_REGEXP) or (skip_term and break)
+ key = self[1]
+ key = post_key_check(key)
+ end
+
+ # Scan the value if we see an =
+ if scan %r{=}
+ value = scan(/[^\&]+/) # scan_until doesn't handle \Z
+ value = CGI.unescape(value) if value # May be nil when eos?
+ bind key, value
+ end
+ scan %r/\&+/ # Ignore multiple adjacent &'s
+
+ end
+
+ return result
+ end
+
+ # Skip over the current term by scanning past the next &, or to
+ # then end of the string if there is no next &
+ def skip_term
+ scan_until(%r/\&+/) || scan(/.+/)
+ end
+
+ # 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 eos? || check(/\&/) # a& or a\Z indicates a is a flag.
+ bind key, nil # Curiously enough, the flag's value is nil
+ nil
+ elsif 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 # Presumably an = sign is next.
+ key
+ end
+ end
+
+ # Add a container to the stack.
+ #
+ def container(key, klass)
+ raise TypeError if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
+ value = bind(key, klass.new)
+ raise TypeError 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}
+ push top.last
+ end
+ else
+ top << value
+ end
+ elsif top.is_a? Hash
+ key = CGI.unescape(key)
+ if top.key?(key) && parent.is_a?(Array)
+ parent << (@top = {})
+ end
+ return top[key] ||= value
+ else
+ # Do nothing?
+ end
+ return value
+ end
+ end
+
end