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

Changeset 4833

Show
Ignore:
Timestamp:
08/28/06 14:05:05 (2 years ago)
Author:
david
Message:

FormEncodedStringParser needs a tad more work before it can handle POST data (like file handling), so were backing out for a bit

Files:

Legend:

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

    r4824 r4833  
    22 
    33* Deprecation: test deprecated instance vars in partials. [Jeremy Kemper] 
    4  
    5 * Changed the POST parameter processing to use the new QueryStringParser and make the result a indifferent hash [DHH] 
    64 
    75* Add UrlWriter to allow writing urls from Mailers and scripts. [Nicholas Seckar] 
  • trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb

    r4823 r4833  
    66# a CGI extension class or testing in isolation. 
    77class CGIMethods #:nodoc: 
    8    
    98  class << self 
    10     # Returns a hash with the pairs from the query string. The implicit hash construction that is done in 
    11     # parse_request_params is not done here. 
     9    # DEPRECATED: Use parse_form_encoded_parameters 
    1210    def parse_query_parameters(query_string) 
    13       QueryStringScanner.new(query_string).parse 
    14     end 
    15  
    16     # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" /  
    17     # "Somewhere cool!" are translated into a full hash hierarchy, like 
    18     # { "customer" => { "address" => { "street" => "Somewhere cool!" } } } 
     11      parse_form_encoded_parameters(query_string) 
     12    end 
     13 
     14    # DEPRECATED: Use parse_form_encoded_parameters 
    1915    def parse_request_parameters(params) 
    2016      parsed_params = {} 
     
    3329     
    3430      parsed_params 
     31    end 
     32     
     33    # TODO: Docs 
     34    def parse_form_encoded_parameters(form_encoded_string) 
     35      FormEncodedStringScanner.decode(form_encoded_string) 
    3536    end 
    3637 
     
    119120  end 
    120121 
    121   class QueryStringScanner < StringScanner 
     122  class FormEncodedStringScanner < StringScanner 
    122123    attr_reader :top, :parent, :result 
    123124 
    124     def initialize(string) 
    125       super(string) 
     125    def self.decode(form_encoded_string) 
     126      new(form_encoded_string).parse 
     127    end 
     128 
     129    def initialize(form_encoded_string) 
     130      super(unescape_keys(form_encoded_string)) 
    126131    end 
    127132     
     
    129134    BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} 
    130135     
    131     # Parse the query string 
     136     
    132137    def parse 
    133138      @result = {} 
     139 
    134140      until eos? 
    135141        # Parse each & delimited chunk 
     
    151157          value = scan(/[^\&]+/) # scan_until doesn't handle \Z 
    152158          value = CGI.unescape(value) if value # May be nil when eos? 
    153           bind key, value 
    154         end 
    155         scan %r/\&+/ # Ignore multiple adjacent &'s 
    156          
     159          bind(key, value) 
     160        end 
     161 
     162        scan(%r/\&+/) # Ignore multiple adjacent &'s 
    157163      end 
    158164       
    159165      return result 
    160166    end 
    161      
    162     # Skip over the current term by scanning past the next &, or to 
    163     # then end of the string if there is no next & 
    164     def skip_term 
    165       scan_until(%r/\&+/) || scan(/.+/) 
    166     end 
    167      
    168     # After we see a key, we must look ahead to determine our next action. Cases: 
    169     #  
    170     #   [] follows the key. Then the value must be an array. 
    171     #   = follows the key. (A value comes next) 
    172     #   & or the end of string follows the key. Then the key is a flag. 
    173     #   otherwise, a hash follows the key.  
    174     def post_key_check(key) 
    175       if eos? || check(/\&/) # a& or a\Z indicates a is a flag. 
    176         bind key, nil # Curiously enough, the flag's value is nil 
    177         nil 
    178       elsif scan(/\[\]/) # a[b][] indicates that b is an array 
    179         container key, Array 
    180         nil 
    181       elsif check(/\[[^\]]/) # a[b] indicates that a is a hash 
    182         container key, Hash 
    183         nil 
    184       else # Presumably an = sign is next. 
    185         key 
    186       end 
    187     end 
    188      
    189     # Add a container to the stack. 
    190     #  
    191     def container(key, klass) 
    192       raise TypeError if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 
    193       value = bind(key, klass.new) 
    194       raise TypeError unless value.is_a? klass 
    195       push value 
    196     end 
    197      
    198     # Push a value onto the 'stack', which is actually only the top 2 items. 
    199     def push(value) 
    200       @parent, @top = @top, value 
    201     end 
    202      
    203     # Bind a key (which may be nil for items in an array) to the provided value. 
    204     def bind(key, value) 
    205       if top.is_a? Array 
    206         if key 
    207           if top[-1].is_a?(Hash) && ! top[-1].key?(key) 
    208             top[-1][key] = value 
     167 
     168    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("=") 
    209177          else 
    210             top << {key => value}.with_indifferent_access 
    211             push top.last 
    212           end 
    213         else 
    214           top << value 
    215         end 
    216       elsif top.is_a? Hash 
    217         key = CGI.unescape(key) 
    218         if top.key?(key) && parent.is_a?(Array) 
    219           parent << (@top = {}) 
    220         end 
    221         return top[key] ||= value 
    222       else 
    223         # Do nothing? 
    224       end 
    225       return value 
    226     end 
    227   end 
     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     
     189      # After we see a key, we must look ahead to determine our next action. Cases: 
     190      #  
     191      #   [] follows the key. Then the value must be an array. 
     192      #   = follows the key. (A value comes next) 
     193      #   & or the end of string follows the key. Then the key is a flag. 
     194      #   otherwise, a hash follows the key.  
     195      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 
     201          nil 
     202        elsif check(/\[[^\]]/) # a[b] indicates that a is a hash 
     203          container key, Hash 
     204          nil 
     205        else # Presumably an = sign is next. 
     206          key 
     207        end 
     208      end 
     209     
     210      # Add a container to the stack. 
     211      #  
     212      def container(key, klass) 
     213        raise TypeError if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 
     214        value = bind(key, klass.new) 
     215        raise TypeError unless value.is_a? klass 
     216        push value 
     217      end 
     218     
     219      # Push a value onto the 'stack', which is actually only the top 2 items. 
     220      def push(value) 
     221        @parent, @top = @top, value 
     222      end 
     223     
     224      # Bind a key (which may be nil for items in an array) to the provided value. 
     225      def bind(key, value) 
     226        if top.is_a? Array 
     227          if key 
     228            if top[-1].is_a?(Hash) && ! top[-1].key?(key) 
     229              top[-1][key] = value 
     230            else 
     231              top << {key => value}.with_indifferent_access 
     232              push top.last 
     233            end 
     234          else 
     235            top << value 
     236          end 
     237        elsif top.is_a? Hash 
     238          key = CGI.unescape(key) 
     239          if top.key?(key) && parent.is_a?(Array) 
     240            parent << (@top = {}) 
     241          end 
     242          return top[key] ||= value 
     243        else 
     244          # Do nothing? 
     245        end 
     246        return value 
     247      end 
     248    end 
    228249end 
  • trunk/actionpack/lib/action_controller/cgi_process.rb

    r4821 r4833  
    6969          CGIMethods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA']) 
    7070        else 
    71           CGIMethods.parse_query_parameters(@env['RAW_POST_DATA'] || ""
     71          CGIMethods.parse_request_parameters(@cgi.params
    7272        end 
    7373    end 
  • trunk/actionpack/test/controller/cgi_test.rb

    r4823 r4833  
    1818    @query_string_with_many_ampersands = 
    1919      "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" 
     20    @query_string_with_escaped_brackets = "subscriber%5Bfirst_name%5D=Jan5&subscriber%5B\ 
     21last_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     
    2025  end 
    2126 
     
    3035    expected = {'x' => {'y' => {'z' => '10'}}} 
    3136    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)) 
    3246  end 
    3347