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

Changeset 4866

Show
Ignore:
Timestamp:
08/30/06 05:50:02 (2 years ago)
Author:
ulysses
Message:

Switch to using FormEncodedPairParser for parsing request parameters.

Files:

Legend:

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

    r4860 r4866  
    11*SVN* 
     2 
     3* Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, DHH] 
    24 
    35* respond_to .html now always renders #{action_name}.rhtml so that registered custom template handlers do not override it in priority. Custom mime types require a block and throw proper error now. [Tobias Luetke] 
  • trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb

    r4833 r4866  
    99    # DEPRECATED: Use parse_form_encoded_parameters 
    1010    def parse_query_parameters(query_string) 
    11       parse_form_encoded_parameters(query_string) 
     11      pairs = query_string.split('&').collect do |chunk| 
     12        next if chunk.empty? 
     13        key, value = chunk.split('=', 2) 
     14        value = (value.nil? || value.empty?) ? nil : CGI.unescape(value) 
     15        [ key, value ] 
     16      end.compact 
     17 
     18      FormEncodedPairParser.new(pairs).result 
    1219    end 
    1320 
    1421    # DEPRECATED: Use parse_form_encoded_parameters 
    1522    def parse_request_parameters(params) 
    16       parsed_params = {} 
     23      parser = FormEncodedPairParser.new 
    1724 
    18       for key, value in params 
    19         next unless key 
    20         value = [value] if key =~ /.*\[\]$/ 
    21         unless key.include?('[') 
    22           # much faster to test for the most common case first (GET) 
    23           # and avoid the call to build_deep_hash 
    24           parsed_params[key] = get_typed_value(value[0]) 
    25         else 
    26           build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key)) 
     25      finished = false 
     26      until finished 
     27        finished = true 
     28        for key, value in params 
     29          next unless key 
     30          if !key.include?('[') 
     31            # much faster to test for the most common case first (GET) 
     32            # and avoid the call to build_deep_hash 
     33            parser.result[key] = get_typed_value(value[0]) 
     34          elsif value.is_a?(Array) 
     35            parser.parse(key, get_typed_value(value.shift)) 
     36            finished = false unless value.empty? 
     37          else 
     38            raise TypeError, "Expected array, found #{value.inspect}" 
     39          end 
    2740        end 
    2841      end 
    2942     
    30       parsed_params 
    31     end 
    32      
    33     # TODO: Docs 
    34     def parse_form_encoded_parameters(form_encoded_string) 
    35       FormEncodedStringScanner.decode(form_encoded_string) 
     43      parser.result 
    3644    end 
    3745 
     
    94102        end 
    95103      end 
    96    
    97       PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/ 
    98       def get_levels(key) 
    99         all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a 
    100         if main.nil? 
    101           [] 
    102         elsif trailing 
    103           [key] 
    104         elsif bracketed 
    105           [main] + bracketed.slice(1...-1).split('][') 
    106         else 
    107           [main] 
    108         end 
    109       end 
    110  
    111       def build_deep_hash(value, hash, levels) 
    112         if levels.length == 0 
    113           value 
    114         elsif hash.nil? 
    115           { levels.first => build_deep_hash(value, nil, levels[1..-1]) } 
    116         else 
    117           hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) }) 
    118         end 
    119       end 
    120104  end 
    121105 
    122   class FormEncodedStringScanner < StringScanner 
     106  class FormEncodedPairParser < StringScanner 
    123107    attr_reader :top, :parent, :result 
    124108 
    125     def self.decode(form_encoded_string) 
    126       new(form_encoded_string).parse 
     109    def initialize(pairs = []) 
     110      super('') 
     111      @result = {} 
     112      pairs.each { |key, value| parse(key, value) } 
    127113    end 
    128  
    129     def initialize(form_encoded_string) 
    130       super(unescape_keys(form_encoded_string)) 
    131     end 
    132      
     114      
    133115    KEY_REGEXP = %r{([^\[\]=&]+)} 
    134116    BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} 
    135117     
    136      
    137     def parse 
    138       @result = {} 
    139  
    140       until eos? 
    141         # Parse each & delimited chunk 
    142         @parent, @top = nil, result 
    143          
    144         # First scan the bare key 
    145         key = scan(KEY_REGEXP) or (skip_term and next) 
     118    # Parse the query string 
     119    def parse(key, value) 
     120      self.string = key 
     121      @top, @parent = result, nil 
     122       
     123      # First scan the bare key 
     124      key = scan(KEY_REGEXP) or return 
     125      key = post_key_check(key) 
     126             
     127      # Then scan as many nestings as present 
     128      until eos?  
     129        r = scan(BRACKETED_KEY_REGEXP) or return 
     130        key = self[1] 
    146131        key = post_key_check(key) 
    147          
    148         # Then scan as many nestings as present 
    149         until check(/\=/) || eos?  
    150           r = scan(BRACKETED_KEY_REGEXP) or (skip_term and break) 
    151           key = self[1] 
    152           key = post_key_check(key) 
    153         end 
    154          
    155         # Scan the value if we see an = 
    156         if scan %r{=} 
    157           value = scan(/[^\&]+/) # scan_until doesn't handle \Z 
    158           value = CGI.unescape(value) if value # May be nil when eos? 
    159           bind(key, value) 
    160         end 
    161  
    162         scan(%r/\&+/) # Ignore multiple adjacent &'s 
    163132      end 
    164        
    165       return result 
     133  
     134      bind(key, value) 
    166135    end 
    167136 
    168137    private 
    169       # Turn keys like person%5Bname%5D into person[name], so they can be processed as hashes 
    170       def unescape_keys(query_string) 
    171         query_string.split('&').collect do |fragment| 
    172           key, value = fragment.split('=', 2) 
    173            
    174           if key 
    175             key = key.gsub(/%5D/, ']').gsub(/%5B/, '[') 
    176             [ key, value ].join("=") 
    177           else 
    178             fragment 
    179           end 
    180         end.join('&') 
    181       end 
    182  
    183       # Skip over the current term by scanning past the next &, or to 
    184       # then end of the string if there is no next & 
    185       def skip_term 
    186         scan_until(%r/\&+/) || scan(/.+/) 
    187       end 
    188      
    189138      # After we see a key, we must look ahead to determine our next action. Cases: 
    190139      #  
     
    194143      #   otherwise, a hash follows the key.  
    195144      def post_key_check(key) 
    196         if eos? || check(/\&/) # a& or a\Z indicates a is a flag. 
    197           bind key, nil # Curiously enough, the flag's value is nil 
    198           nil 
    199         elsif scan(/\[\]/) # a[b][] indicates that b is an array 
    200           container key, Array 
     145        if scan(/\[\]/) # a[b][] indicates that b is an array 
     146          container(key, Array) 
    201147          nil 
    202148        elsif check(/\[[^\]]/) # a[b] indicates that a is a hash 
    203           container key, Hash 
     149          container(key, Hash) 
    204150          nil 
    205         else # Presumably an = sign is next
     151        else # End of key? We do nothing
    206152          key 
    207153        end 
     
    213159        raise TypeError if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 
    214160        value = bind(key, klass.new) 
    215         raise TypeError unless value.is_a? klass 
    216         push value 
     161        raise TypeError unless value.is_a?(klass) 
     162        push(value) 
    217163      end 
    218164     
     
    237183        elsif top.is_a? Hash 
    238184          key = CGI.unescape(key) 
    239           if top.key?(key) && parent.is_a?(Array) 
    240             parent << (@top = {}) 
    241           end 
     185          parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) 
    242186          return top[key] ||= value 
    243187        else 
    244           # Do nothing? 
     188          raise ArgumentError, "Don't know what to do: top is #{top.inspect}" 
    245189        end 
     190 
    246191        return value 
    247192      end 
  • trunk/actionpack/lib/action_controller/cgi_process.rb

    r4833 r4866  
    146146            Module.const_missing($1) 
    147147          rescue LoadError, NameError => const_error 
    148             raise ActionController::SessionRestoreError, <<end_msg 
     148            raise ActionController::SessionRestoreError, <<-end_msg 
    149149Session contains objects whose class definition isn\'t available. 
    150150Remember to require the classes for all objects kept in the session. 
  • trunk/actionpack/test/controller/cgi_test.rb

    r4833 r4866  
    1717    @query_string_without_equal = "action" 
    1818    @query_string_with_many_ampersands = 
    19       "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" 
    20     @query_string_with_escaped_brackets = "subscriber%5Bfirst_name%5D=Jan5&subscriber%5B\ 
    21 last_name%5D=Waterman&subscriber%5Bemail%5D=drakn%40domain.tld&subscriber%5Bpassword\ 
    22 %5D=893ea&subscriber%5Bstreet%5D=&subscriber%5Bzip%5D=&subscriber%5Bcity%5D\ 
    23 =&commit=Update+info" 
    24      
     19      "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson"     
    2520  end 
    2621 
     
    3530    expected = {'x' => {'y' => {'z' => '10'}}} 
    3631    assert_equal(expected, CGIMethods.parse_query_parameters('x[y][z]=10')) 
    37     expected = {"commit"=>"Update info",  
    38       "subscriber" => { 
    39           "city" => nil, "zip"=>nil, 
    40           "last_name" => "Waterman", "street" => nil, 
    41           "password" =>"893ea", "first_name" =>  "Jan5" , 
    42           "email" => "drakn@domain.tld" 
    43       } 
    44     } 
    45     assert_equal(expected, CGIMethods.parse_query_parameters(@query_string_with_escaped_brackets)) 
    4632  end 
    4733   
     
    6046    assert_equal("10", CGIMethods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"]) 
    6147    assert_equal("10", CGIMethods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z]) 
     48  end 
     49   
     50  def test_request_hash_parsing 
     51    query = { 
     52      "note[viewers][viewer][][type]" => ["User", "Group"], 
     53      "note[viewers][viewer][][id]"   => ["1", "2"] 
     54    } 
     55 
     56    expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } 
     57 
     58    assert_equal(expected, CGIMethods.parse_request_parameters(query)) 
    6259  end 
    6360   
     
    240237  def test_parse_params_with_single_brackets_in_middle 
    241238    input     = { "a/b[c]d" =>  %w(e) } 
    242     expected  = { "a/b[c]d" => "e"
     239    expected  = { "a/b" => {}
    243240    assert_equal expected, CGIMethods.parse_request_parameters(input) 
    244241  end 
     
    246243  def test_parse_params_with_separated_brackets 
    247244    input     = { "a/b@[c]d[e]" =>  %w(f) } 
    248     expected  = { "a/b@" => { "c]d[e" => "f" }} 
     245    expected  = { "a/b@" => { }} 
    249246    assert_equal expected, CGIMethods.parse_request_parameters(input) 
    250247  end 
     
    252249  def test_parse_params_with_separated_brackets_and_array 
    253250    input     = { "a/b@[c]d[e][]" =>  %w(f) } 
    254     expected  = { "a/b@" => { "c]d[e" => ["f"] }} 
     251    expected  = { "a/b@" => { }} 
    255252    assert_equal expected , CGIMethods.parse_request_parameters(input) 
    256253  end 
     
    258255  def test_parse_params_with_unmatched_brackets_and_array 
    259256    input     = { "a/b@[c][d[e][]" =>  %w(f) } 
    260     expected  = { "a/b@" => { "c" => { "d[e" => ["f"] }}} 
     257    expected  = { "a/b@" => { "c" => { }}} 
    261258    assert_equal expected, CGIMethods.parse_request_parameters(input) 
    262259  end