Ruby on Rails | Screencasts | Download | Documentation | Weblog | Community | Source

Changeset 4660

Show
Ignore:
Timestamp:
08/04/06 16:03:21 (2 years ago)
Author:
ulysses
Message:

Fix broken query parameter tests

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/actionpack/CHANGELOG

    r4656 r4660  
    11*SVN* 
     2 
     3* Update query parser to support adjacent hashes. [Nicholas Seckar] 
    24 
    35* Make action caching aware of different formats for the same action so that, e.g.  foo.xml is cached separately from foo.html. Implicitly set content type when reading in cached content with mime revealing extensions so the entire onous isn't on the webserver. [Marcel Molina Jr.] 
  • trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb

    r4453 r4660  
    11require 'cgi' 
    22require 'action_controller/vendor/xml_node' 
     3require 'strscan' 
    34 
    45# Static methods for parsing the query and request parameters that can be used in 
    56# a CGI extension class or testing in isolation. 
    67class CGIMethods #:nodoc: 
     8   
    79  class << self 
    810    # Returns a hash with the pairs from the query string. The implicit hash construction that is done in 
    911    # parse_request_params is not done here. 
    1012    def parse_query_parameters(query_string) 
    11       parsed_params = {} 
    12    
    13       query_string.split(/[&;]/).each { |p|  
    14         # Ignore repeated delimiters. 
    15         next if p.empty? 
    16  
    17         k, v = p.split('=',2) 
    18         v = nil if (v && v.empty?) 
    19  
    20         k = CGI.unescape(k) if k 
    21         v = CGI.unescape(v) if v 
    22  
    23         unless k.include?(?[) 
    24           parsed_params[k] = v 
    25         else 
    26           keys = split_key(k) 
    27           last_key = keys.pop 
    28           last_key = keys.pop if (use_array = last_key.empty?) 
    29           parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}} 
    30            
    31           if use_array then (parent[last_key] ||= []) << v 
    32           else parent[last_key] = v 
    33           end 
    34         end 
    35       } 
    36    
    37       parsed_params 
     13      QueryStringScanner.new(query_string).parse 
    3814    end 
    3915 
     
    7753 
    7854    private 
    79       # Splits the given key into several pieces. Example keys are 'name', 'person[name]', 
    80       # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned. 
    81       # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', ''] 
    82       def split_key(key) 
    83         if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key 
    84           keys = [$1] 
    85          
    86           keys.concat($2[1..-2].split('][')) 
    87           keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings 
    88          
    89           keys 
    90         else 
    91           [key] 
    92         end 
    93       end 
    94      
    9555      def get_typed_value(value) 
    9656        # test most frequent case first 
     
    158118      end 
    159119  end 
     120 
     121  class QueryStringScanner < StringScanner 
     122 
     123    attr_reader :top, :parent, :result 
     124 
     125    def initialize(string) 
     126      super(string) 
     127    end 
     128     
     129    KEY_REGEXP = %r{([^\[\]=&]+)} 
     130    BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} 
     131     
     132    # Parse the query string 
     133    def parse 
     134      @result = {} 
     135      until eos? 
     136        # Parse each & delimited chunk 
     137        @parent, @top = nil, result 
     138         
     139        # First scan the bare key 
     140        key = scan(KEY_REGEXP) or (skip_term and next) 
     141        key = post_key_check(key) 
     142         
     143        # Then scan as many nestings as present 
     144        until check(/\=/) || eos?  
     145          r = scan(BRACKETED_KEY_REGEXP) or (skip_term and break) 
     146          key = self[1] 
     147          key = post_key_check(key) 
     148        end 
     149         
     150        # Scan the value if we see an = 
     151        if scan %r{=} 
     152          value = scan(/[^\&]+/) # scan_until doesn't handle \Z 
     153          value = CGI.unescape(value) if value # May be nil when eos? 
     154          bind key, value 
     155        end 
     156        scan %r/\&+/ # Ignore multiple adjacent &'s 
     157         
     158      end 
     159       
     160      return result 
     161    end 
     162     
     163    # Skip over the current term by scanning past the next &, or to 
     164    # then end of the string if there is no next & 
     165    def skip_term 
     166      scan_until(%r/\&+/) || scan(/.+/) 
     167    end 
     168     
     169    # After we see a key, we must look ahead to determine our next action. Cases: 
     170    #  
     171    #   [] follows the key. Then the value must be an array. 
     172    #   = follows the key. (A value comes next) 
     173    #   & or the end of string follows the key. Then the key is a flag. 
     174    #   otherwise, a hash follows the key.  
     175    def post_key_check(key) 
     176      if eos? || check(/\&/) # a& or a\Z indicates a is a flag. 
     177        bind key, nil # Curiously enough, the flag's value is nil 
     178        nil 
     179      elsif scan(/\[\]/) # a[b][] indicates that b is an array 
     180        container key, Array 
     181        nil 
     182      elsif check(/\[[^\]]/) # a[b] indicates that a is a hash 
     183        container key, Hash 
     184        nil 
     185      else # Presumably an = sign is next. 
     186        key 
     187      end 
     188    end 
     189     
     190    # Add a container to the stack. 
     191    #  
     192    def container(key, klass) 
     193      raise TypeError if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 
     194      value = bind(key, klass.new) 
     195      raise TypeError unless value.is_a? klass 
     196      push value 
     197    end 
     198     
     199    # Push a value onto the 'stack', which is actually only the top 2 items. 
     200    def push(value) 
     201      @parent, @top = @top, value 
     202    end 
     203     
     204    # Bind a key (which may be nil for items in an array) to the provided value. 
     205    def bind(key, value) 
     206      if top.is_a? Array 
     207        if key 
     208          if top[-1].is_a?(Hash) && ! top[-1].key?(key) 
     209            top[-1][key] = value 
     210          else 
     211            top << {key => value} 
     212            push top.last 
     213          end 
     214        else 
     215          top << value 
     216        end 
     217      elsif top.is_a? Hash 
     218        key = CGI.unescape(key) 
     219        if top.key?(key) && parent.is_a?(Array) 
     220          parent << (@top = {}) 
     221        end 
     222        return top[key] ||= value 
     223      else 
     224        # Do nothing? 
     225      end 
     226      return value 
     227    end 
     228  end 
     229   
    160230end