Changeset 4866
- Timestamp:
- 08/30/06 05:50:02 (2 years ago)
- Files:
-
- trunk/actionpack/CHANGELOG (modified) (1 diff)
- trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb (modified) (5 diffs)
- trunk/actionpack/lib/action_controller/cgi_process.rb (modified) (1 diff)
- trunk/actionpack/test/controller/cgi_test.rb (modified) (7 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/CHANGELOG
r4860 r4866 1 1 *SVN* 2 3 * Switch to using FormEncodedPairParser for parsing request parameters. [Nicholas Seckar, DHH] 2 4 3 5 * 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 9 9 # DEPRECATED: Use parse_form_encoded_parameters 10 10 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 12 19 end 13 20 14 21 # DEPRECATED: Use parse_form_encoded_parameters 15 22 def parse_request_parameters(params) 16 parse d_params = {}23 parser = FormEncodedPairParser.new 17 24 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 27 40 end 28 41 end 29 42 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 36 44 end 37 45 … … 94 102 end 95 103 end 96 97 PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/98 def get_levels(key)99 all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a100 if main.nil?101 []102 elsif trailing103 [key]104 elsif bracketed105 [main] + bracketed.slice(1...-1).split('][')106 else107 [main]108 end109 end110 111 def build_deep_hash(value, hash, levels)112 if levels.length == 0113 value114 elsif hash.nil?115 { levels.first => build_deep_hash(value, nil, levels[1..-1]) }116 else117 hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) })118 end119 end120 104 end 121 105 122 class FormEncoded StringScanner < StringScanner106 class FormEncodedPairParser < StringScanner 123 107 attr_reader :top, :parent, :result 124 108 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) } 127 113 end 128 129 def initialize(form_encoded_string) 130 super(unescape_keys(form_encoded_string)) 131 end 132 114 133 115 KEY_REGEXP = %r{([^\[\]=&]+)} 134 116 BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} 135 117 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] 146 131 key = post_key_check(key) 147 148 # Then scan as many nestings as present149 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 end154 155 # Scan the value if we see an =156 if scan %r{=}157 value = scan(/[^\&]+/) # scan_until doesn't handle \Z158 value = CGI.unescape(value) if value # May be nil when eos?159 bind(key, value)160 end161 162 scan(%r/\&+/) # Ignore multiple adjacent &'s163 132 end 164 165 return result133 134 bind(key, value) 166 135 end 167 136 168 137 private 169 # Turn keys like person%5Bname%5D into person[name], so they can be processed as hashes170 def unescape_keys(query_string)171 query_string.split('&').collect do |fragment|172 key, value = fragment.split('=', 2)173 174 if key175 key = key.gsub(/%5D/, ']').gsub(/%5B/, '[')176 [ key, value ].join("=")177 else178 fragment179 end180 end.join('&')181 end182 183 # Skip over the current term by scanning past the next &, or to184 # then end of the string if there is no next &185 def skip_term186 scan_until(%r/\&+/) || scan(/.+/)187 end188 189 138 # After we see a key, we must look ahead to determine our next action. Cases: 190 139 # … … 194 143 # otherwise, a hash follows the key. 195 144 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) 201 147 nil 202 148 elsif check(/\[[^\]]/) # a[b] indicates that a is a hash 203 container key, Hash149 container(key, Hash) 204 150 nil 205 else # Presumably an = sign is next.151 else # End of key? We do nothing. 206 152 key 207 153 end … … 213 159 raise TypeError if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 214 160 value = bind(key, klass.new) 215 raise TypeError unless value.is_a? klass216 push value161 raise TypeError unless value.is_a?(klass) 162 push(value) 217 163 end 218 164 … … 237 183 elsif top.is_a? Hash 238 184 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) 242 186 return top[key] ||= value 243 187 else 244 # Do nothing?188 raise ArgumentError, "Don't know what to do: top is #{top.inspect}" 245 189 end 190 246 191 return value 247 192 end trunk/actionpack/lib/action_controller/cgi_process.rb
r4833 r4866 146 146 Module.const_missing($1) 147 147 rescue LoadError, NameError => const_error 148 raise ActionController::SessionRestoreError, << end_msg148 raise ActionController::SessionRestoreError, <<-end_msg 149 149 Session contains objects whose class definition isn\'t available. 150 150 Remember to require the classes for all objects kept in the session. trunk/actionpack/test/controller/cgi_test.rb
r4833 r4866 17 17 @query_string_without_equal = "action" 18 18 @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" 25 20 end 26 21 … … 35 30 expected = {'x' => {'y' => {'z' => '10'}}} 36 31 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))46 32 end 47 33 … … 60 46 assert_equal("10", CGIMethods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"]) 61 47 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)) 62 59 end 63 60 … … 240 237 def test_parse_params_with_single_brackets_in_middle 241 238 input = { "a/b[c]d" => %w(e) } 242 expected = { "a/b [c]d" => "e"}239 expected = { "a/b" => {} } 243 240 assert_equal expected, CGIMethods.parse_request_parameters(input) 244 241 end … … 246 243 def test_parse_params_with_separated_brackets 247 244 input = { "a/b@[c]d[e]" => %w(f) } 248 expected = { "a/b@" => { "c]d[e" => "f"}}245 expected = { "a/b@" => { }} 249 246 assert_equal expected, CGIMethods.parse_request_parameters(input) 250 247 end … … 252 249 def test_parse_params_with_separated_brackets_and_array 253 250 input = { "a/b@[c]d[e][]" => %w(f) } 254 expected = { "a/b@" => { "c]d[e" => ["f"]}}251 expected = { "a/b@" => { }} 255 252 assert_equal expected , CGIMethods.parse_request_parameters(input) 256 253 end … … 258 255 def test_parse_params_with_unmatched_brackets_and_array 259 256 input = { "a/b@[c][d[e][]" => %w(f) } 260 expected = { "a/b@" => { "c" => { "d[e" => ["f"]}}}257 expected = { "a/b@" => { "c" => { }}} 261 258 assert_equal expected, CGIMethods.parse_request_parameters(input) 262 259 end