Changeset 6733
- Timestamp:
- 05/15/07 00:08:05 (1 year ago)
- Files:
-
- trunk/actionpack/lib/action_controller/cgi_ext.rb (added)
- trunk/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb (deleted)
- trunk/actionpack/lib/action_controller/cgi_ext/cookie.rb (moved) (moved from trunk/actionpack/lib/action_controller/cgi_ext/cookie_performance_fix.rb) (3 diffs)
- trunk/actionpack/lib/action_controller/cgi_ext/parameters.rb (moved) (moved from trunk/actionpack/lib/action_controller/cgi_ext/cgi_ext.rb) (2 diffs)
- trunk/actionpack/lib/action_controller/cgi_ext/pstore_performance_fix.rb (deleted)
- trunk/actionpack/lib/action_controller/cgi_ext/query_extension.rb (moved) (moved from trunk/actionpack/lib/action_controller/cgi_ext/raw_post_data_fix.rb) (1 diff)
- trunk/actionpack/lib/action_controller/cgi_ext/session.rb (moved) (moved from trunk/actionpack/lib/action_controller/cgi_ext/session_performance_fix.rb) (2 diffs)
- trunk/actionpack/lib/action_controller/cgi_process.rb (modified) (3 diffs)
- trunk/actionpack/test/controller/cgi_test.rb (modified) (26 diffs)
- trunk/actionpack/test/controller/raw_post_test.rb (modified) (1 diff)
- trunk/actionpack/test/controller/session/cookie_store_test.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/lib/action_controller/cgi_ext/cookie.rb
r3039 r6733 1 1 CGI.module_eval { remove_const "Cookie" } 2 2 3 # TODO: document how this differs from stdlib CGI::Cookie 3 4 class CGI #:nodoc: 4 # This is a cookie class that fixes the performance problems with the default one that ships with 1.8.1 and below.5 # It replaces the inheritance on SimpleDelegator with DelegateClass(Array) following the suggestion from Matz on6 # http://groups.google.com/groups?th=e3a4e68ba042f842&seekm=c3sioe%241qvm%241%40news.cybercity.dk#link147 5 class Cookie < DelegateClass(Array) 8 6 # Create a new CGI::Cookie object. … … 20 18 # expires:: the time at which this cookie expires, as a +Time+ object. 21 19 # secure:: whether this cookie is a secure cookie or not (default to 22 # false). Secure cookies are only transmitted to HTTPS 20 # false). Secure cookies are only transmitted to HTTPS 23 21 # servers. 24 22 # … … 40 38 @path = name['path'] 41 39 end 42 40 43 41 unless @name 44 42 raise ArgumentError, "`name' required" trunk/actionpack/lib/action_controller/cgi_ext/parameters.rb
r6732 r6733 1 1 require 'cgi' 2 require 'cgi/session' 3 require 'cgi/session/pstore' 4 require 'action_controller/cgi_ext/cgi_methods' 5 6 # Wrapper around the CGIMethods that have been secluded to allow testing without 7 # an instantiated CGI object 2 require 'strscan' 3 8 4 class CGI #:nodoc: 9 5 class << self … … 14 10 end 15 11 end 16 17 # Returns a parameter hash including values from both the request (POST/GET) 18 # and the query string with the latter taking precedence. 19 def parameters 20 request_parameters.update(query_parameters) 21 end 22 23 def query_parameters 24 CGIMethods.parse_query_parameters(query_string) 25 end 26 27 def request_parameters 28 CGIMethods.parse_request_parameters(params, env_table) 29 end 30 31 def session(parameters = nil) 32 parameters = {} if parameters.nil? 33 parameters['database_manager'] = CGI::Session::PStore 34 CGI::Session.new(self, parameters) 12 end 13 14 module ActionController 15 module CgiExt 16 module Parameters 17 def self.included(base) 18 base.extend ClassMethods 19 end 20 21 # Merge POST and GET parameters from the request body and query string, 22 # with GET parameters taking precedence. 23 def parameters 24 request_parameters.update(query_parameters) 25 end 26 27 def query_parameters 28 self.class.parse_query_parameters(query_string) 29 end 30 31 def request_parameters 32 self.class.parse_request_parameters(params, env_table) 33 end 34 35 module ClassMethods 36 # DEPRECATED: Use parse_form_encoded_parameters 37 def parse_query_parameters(query_string) 38 pairs = query_string.split('&').collect do |chunk| 39 next if chunk.empty? 40 key, value = chunk.split('=', 2) 41 next if key.empty? 42 value = value.nil? ? nil : CGI.unescape(value) 43 [ CGI.unescape(key), value ] 44 end.compact 45 46 FormEncodedPairParser.new(pairs).result 47 end 48 49 # DEPRECATED: Use parse_form_encoded_parameters 50 def parse_request_parameters(params) 51 parser = FormEncodedPairParser.new 52 53 params = params.dup 54 until params.empty? 55 for key, value in params 56 if key.blank? 57 params.delete key 58 elsif !key.include?('[') 59 # much faster to test for the most common case first (GET) 60 # and avoid the call to build_deep_hash 61 parser.result[key] = get_typed_value(value[0]) 62 params.delete key 63 elsif value.is_a?(Array) 64 parser.parse(key, get_typed_value(value.shift)) 65 params.delete key if value.empty? 66 else 67 raise TypeError, "Expected array, found #{value.inspect}" 68 end 69 end 70 end 71 72 parser.result 73 end 74 75 def parse_formatted_request_parameters(mime_type, raw_post_data) 76 case strategy = ActionController::Base.param_parsers[mime_type] 77 when Proc 78 strategy.call(raw_post_data) 79 when :xml_simple, :xml_node 80 raw_post_data.blank? ? {} : Hash.from_xml(raw_post_data).with_indifferent_access 81 when :yaml 82 YAML.load(raw_post_data) 83 end 84 rescue Exception => e # YAML, XML or Ruby code block errors 85 { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace, 86 "raw_post_data" => raw_post_data, "format" => mime_type } 87 end 88 89 private 90 def get_typed_value(value) 91 case value 92 when String 93 value 94 when NilClass 95 '' 96 when Array 97 value.map { |v| get_typed_value(v) } 98 else 99 # Uploaded file provides content type and filename. 100 if value.respond_to?(:content_type) && 101 !value.content_type.blank? && 102 !value.original_filename.blank? 103 unless value.respond_to?(:full_original_filename) 104 class << value 105 alias_method :full_original_filename, :original_filename 106 107 # Take the basename of the upload's original filename. 108 # This handles the full Windows paths given by Internet Explorer 109 # (and perhaps other broken user agents) without affecting 110 # those which give the lone filename. 111 # The Windows regexp is adapted from Perl's File::Basename. 112 def original_filename 113 if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename) 114 md.captures.first 115 else 116 File.basename full_original_filename 117 end 118 end 119 end 120 end 121 122 # Return the same value after overriding original_filename. 123 value 124 125 # Multipart values may have content type, but no filename. 126 elsif value.respond_to?(:read) 127 result = value.read 128 value.rewind 129 result 130 131 # Unknown value, neither string nor multipart. 132 else 133 raise "Unknown form value: #{value.inspect}" 134 end 135 end 136 end 137 end 138 139 class FormEncodedPairParser < StringScanner #:nodoc: 140 attr_reader :top, :parent, :result 141 142 def initialize(pairs = []) 143 super('') 144 @result = {} 145 pairs.each { |key, value| parse(key, value) } 146 end 147 148 KEY_REGEXP = %r{([^\[\]=&]+)} 149 BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} 150 151 # Parse the query string 152 def parse(key, value) 153 self.string = key 154 @top, @parent = result, nil 155 156 # First scan the bare key 157 key = scan(KEY_REGEXP) or return 158 key = post_key_check(key) 159 160 # Then scan as many nestings as present 161 until eos? 162 r = scan(BRACKETED_KEY_REGEXP) or return 163 key = self[1] 164 key = post_key_check(key) 165 end 166 167 bind(key, value) 168 end 169 170 private 171 # After we see a key, we must look ahead to determine our next action. Cases: 172 # 173 # [] follows the key. Then the value must be an array. 174 # = follows the key. (A value comes next) 175 # & or the end of string follows the key. Then the key is a flag. 176 # otherwise, a hash follows the key. 177 def post_key_check(key) 178 if 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 # End of key? We do nothing. 185 key 186 end 187 end 188 189 # Add a container to the stack. 190 def container(key, klass) 191 type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 192 value = bind(key, klass.new) 193 type_conflict! klass, value unless value.is_a?(klass) 194 push(value) 195 end 196 197 # Push a value onto the 'stack', which is actually only the top 2 items. 198 def push(value) 199 @parent, @top = @top, value 200 end 201 202 # Bind a key (which may be nil for items in an array) to the provided value. 203 def bind(key, value) 204 if top.is_a? Array 205 if key 206 if top[-1].is_a?(Hash) && ! top[-1].key?(key) 207 top[-1][key] = value 208 else 209 top << {key => value}.with_indifferent_access 210 push top.last 211 end 212 else 213 top << value 214 end 215 elsif top.is_a? Hash 216 key = CGI.unescape(key) 217 parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) 218 return top[key] ||= value 219 else 220 raise ArgumentError, "Don't know what to do: top is #{top.inspect}" 221 end 222 223 return value 224 end 225 226 def type_conflict!(klass, value) 227 raise TypeError, "Conflicting types for parameter containers. Expected an instance of #{klass} but found an instance of #{value.class}. This can be caused by colliding Array and Hash parameters like qs[]=value&qs[key]=value." 228 end 229 end 230 end 35 231 end 36 232 end trunk/actionpack/lib/action_controller/cgi_ext/query_extension.rb
r6446 r6733 1 require 'cgi' 2 1 3 class CGI #:nodoc: 2 4 module QueryExtension trunk/actionpack/lib/action_controller/cgi_ext/session.rb
r6342 r6733 1 # CGI::Session#create_new_id requires 'digest/md5' on every call. This makes 2 # sense when spawning processes per request, but is unnecessarily expensive 3 # when serving requests from a long-lived process. 4 # 5 # http://railsexpress.de/blog/articles/2005/11/22/speeding-up-the-creation-of-new-sessions 6 # 7 # Also expose the CGI instance to session stores. 1 require 'digest/md5' 8 2 require 'cgi/session' 9 require ' digest/md5'3 require 'cgi/session/pstore' 10 4 11 class CGI 5 class CGI #:nodoc: 6 # * Expose the CGI instance to session stores. 7 # * Don't require 'digest/md5' whenever a new session id is generated. 12 8 class Session #:nodoc: 13 9 # Generate an MD5 hash including the time, a random number, the process id, … … 40 36 self.class.generate_unique_id 41 37 end 38 39 # * Don't require 'digest/md5' whenever a new session is started. 40 class PStore #:nodoc: 41 def initialize(session, option={}) 42 dir = option['tmpdir'] || Dir::tmpdir 43 prefix = option['prefix'] || '' 44 id = session.session_id 45 md5 = Digest::MD5.hexdigest(id)[0,16] 46 path = dir+"/"+prefix+md5 47 path.untaint 48 if File::exist?(path) 49 @hash = nil 50 else 51 unless session.new_session 52 raise CGI::Session::NoSession, "uninitialized session" 53 end 54 @hash = {} 55 end 56 @p = ::PStore.new(path) 57 @p.transaction do |p| 58 File.chmod(0600, p.path) 59 end 60 end 61 end 42 62 end 43 63 end trunk/actionpack/lib/action_controller/cgi_process.rb
r6431 r6733 1 require 'action_controller/cgi_ext/cgi_ext' 2 require 'action_controller/cgi_ext/cookie_performance_fix' 3 require 'action_controller/cgi_ext/raw_post_data_fix' 4 require 'action_controller/cgi_ext/session_performance_fix' 5 require 'action_controller/cgi_ext/pstore_performance_fix' 1 require 'action_controller/cgi_ext' 6 2 require 'action_controller/session/cookie_store' 7 3 … … 65 61 def query_parameters 66 62 @query_parameters ||= 67 (qs = self.query_string).empty? ? {} : CGI Methods.parse_query_parameters(qs)63 (qs = self.query_string).empty? ? {} : CGI.parse_query_parameters(qs) 68 64 end 69 65 … … 71 67 @request_parameters ||= 72 68 if ActionController::Base.param_parsers.has_key?(content_type) 73 CGI Methods.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA'])74 else 75 CGI Methods.parse_request_parameters(@cgi.params)69 CGI.parse_formatted_request_parameters(content_type, @env['RAW_POST_DATA']) 70 else 71 CGI.parse_request_parameters(@cgi.params) 76 72 end 77 73 end trunk/actionpack/test/controller/cgi_test.rb
r6578 r6733 1 1 require File.dirname(__FILE__) + '/../abstract_unit' 2 2 require 'action_controller/cgi_process' 3 require 'action_controller/cgi_ext/cgi_ext'4 5 require 'stringio'6 3 7 4 class CGITest < Test::Unit::TestCase … … 23 20 assert_equal( 24 21 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, 25 CGI Methods.parse_query_parameters(@query_string)22 CGI.parse_query_parameters(@query_string) 26 23 ) 27 24 end … … 29 26 def test_deep_query_string 30 27 expected = {'x' => {'y' => {'z' => '10'}}} 31 assert_equal(expected, CGI Methods.parse_query_parameters('x[y][z]=10'))28 assert_equal(expected, CGI.parse_query_parameters('x[y][z]=10')) 32 29 end 33 30 34 31 def test_deep_query_string_with_array 35 assert_equal({'x' => {'y' => {'z' => ['10']}}}, CGI Methods.parse_query_parameters('x[y][z][]=10'))36 assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, CGI Methods.parse_query_parameters('x[y][z][]=10&x[y][z][]=5'))32 assert_equal({'x' => {'y' => {'z' => ['10']}}}, CGI.parse_query_parameters('x[y][z][]=10')) 33 assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, CGI.parse_query_parameters('x[y][z][]=10&x[y][z][]=5')) 37 34 end 38 35 39 36 def test_deep_query_string_with_array_of_hash 40 assert_equal({'x' => {'y' => [{'z' => '10'}]}}, CGI Methods.parse_query_parameters('x[y][][z]=10'))41 assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, CGI Methods.parse_query_parameters('x[y][][z]=10&x[y][][w]=10'))37 assert_equal({'x' => {'y' => [{'z' => '10'}]}}, CGI.parse_query_parameters('x[y][][z]=10')) 38 assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, CGI.parse_query_parameters('x[y][][z]=10&x[y][][w]=10')) 42 39 end 43 40 44 41 def test_deep_query_string_with_array_of_hashes_with_one_pair 45 assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, CGI Methods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20'))46 assert_equal("10", CGI Methods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"])47 assert_equal("10", CGI Methods.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z])42 assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, CGI.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')) 43 assert_equal("10", CGI.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"]) 44 assert_equal("10", CGI.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z]) 48 45 end 49 46 … … 56 53 expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } 57 54 58 assert_equal(expected, CGI Methods.parse_request_parameters(query))55 assert_equal(expected, CGI.parse_request_parameters(query)) 59 56 end 60 57 … … 62 59 assert_equal( 63 60 {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, 64 CGI Methods.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b')61 CGI.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') 65 62 ) 66 63 end … … 69 66 assert_equal( 70 67 { "action" => "create_customer", "full_name" => ''}, 71 CGI Methods.parse_query_parameters(@query_string_with_empty)68 CGI.parse_query_parameters(@query_string_with_empty) 72 69 ) 73 70 end … … 76 73 assert_equal( 77 74 { "action" => "create_customer", "selected" => ["1", "2", "3"]}, 78 CGI Methods.parse_query_parameters(@query_string_with_array)75 CGI.parse_query_parameters(@query_string_with_array) 79 76 ) 80 77 end … … 83 80 assert_equal( 84 81 { "action" => "create_customer", "name" => "Don't & Does"}, 85 CGI Methods.parse_query_parameters(@query_string_with_amps)82 CGI.parse_query_parameters(@query_string_with_amps) 86 83 ) 87 84 end … … 90 87 assert_equal( 91 88 { "action" => "create_customer", "full_name" => "abc=def=ghi"}, 92 CGI Methods.parse_query_parameters(@query_string_with_many_equal)89 CGI.parse_query_parameters(@query_string_with_many_equal) 93 90 ) 94 91 end … … 97 94 assert_equal( 98 95 { "action" => nil }, 99 CGI Methods.parse_query_parameters(@query_string_without_equal)96 CGI.parse_query_parameters(@query_string_without_equal) 100 97 ) 101 98 end … … 104 101 assert_equal( 105 102 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" }, 106 CGI Methods.parse_query_parameters(@query_string_with_empty_key)103 CGI.parse_query_parameters(@query_string_with_empty_key) 107 104 ) 108 105 end … … 111 108 assert_equal( 112 109 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, 113 CGI Methods.parse_query_parameters(@query_string_with_many_ampersands)110 CGI.parse_query_parameters(@query_string_with_many_ampersands) 114 111 ) 115 112 end … … 151 148 } 152 149 153 assert_equal expected_output, CGI Methods.parse_request_parameters(input)150 assert_equal expected_output, CGI.parse_request_parameters(input) 154 151 end 155 152 … … 198 195 } 199 196 200 params = CGI Methods.parse_request_parameters(input)197 params = CGI.parse_request_parameters(input) 201 198 assert_equal expected_output, params 202 199 … … 229 226 } 230 227 231 assert_equal expected_output, CGI Methods.parse_request_parameters(input)228 assert_equal expected_output, CGI.parse_request_parameters(input) 232 229 end 233 230 … … 237 234 expected_output = { "selected" => [ "1", "2", "3" ] } 238 235 239 assert_equal expected_output, CGI Methods.parse_request_parameters(input)236 assert_equal expected_output, CGI.parse_request_parameters(input) 240 237 end 241 238 … … 243 240 input = { "a/b[c]" => %w(d) } 244 241 expected = { "a/b" => { "c" => "d" }} 245 assert_equal expected, CGI Methods.parse_request_parameters(input)242 assert_equal expected, CGI.parse_request_parameters(input) 246 243 end 247 244 … … 249 246 input = { "a/b[c]d" => %w(e) } 250 247 expected = { "a/b" => {} } 251 assert_equal expected, CGI Methods.parse_request_parameters(input)248 assert_equal expected, CGI.parse_request_parameters(input) 252 249 end 253 250 … … 255 252 input = { "a/b@[c]d[e]" => %w(f) } 256 253 expected = { "a/b@" => { }} 257 assert_equal expected, CGI Methods.parse_request_parameters(input)254 assert_equal expected, CGI.parse_request_parameters(input) 258 255 end 259 256 … … 261 258 input = { "a/b@[c]d[e][]" => %w(f) } 262 259 expected = { "a/b@" => { }} 263 assert_equal expected , CGI Methods.parse_request_parameters(input)260 assert_equal expected , CGI.parse_request_parameters(input) 264 261 end 265 262 … … 267 264 input = { "a/b@[c][d[e][]" => %w(f) } 268 265 expected = { "a/b@" => { "c" => { }}} 269 assert_equal expected, CGI Methods.parse_request_parameters(input)266 assert_equal expected, CGI.parse_request_parameters(input) 270 267 end 271 268 … … 273 270 input = { nil => nil, "test2" => %w(value1) } 274 271 expected = { "test2" => "value1" } 275 assert_equal expected, CGI Methods.parse_request_parameters(input)272 assert_equal expected, CGI.parse_request_parameters(input) 276 273 end 277 274 end … … 282 279 raw_post_data = 283 280 "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{Base64.encode64('ABC')}</avatar></person>" 284 person = CGI Methods.parse_formatted_request_parameters(Mime::XML, raw_post_data)281 person = CGI.parse_formatted_request_parameters(Mime::XML, raw_post_data) 285 282 assert_equal "image/jpg", person['person']['avatar'].content_type 286 283 assert_equal "me.jpg", person['person']['avatar'].original_filename … … 294 291 "<avatar type='file' name='you.gif' content_type='image/gif'>#{Base64.encode64('DEF')}</avatar>" + 295 292 "</avatars></person>" 296 person = CGI Methods.parse_formatted_request_parameters(Mime::XML, raw_post_data)293 person = CGI.parse_formatted_request_parameters(Mime::XML, raw_post_data) 297 294 298 295 assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type … … 388 385 $stdin = file 389 386 @cgi = CGI.new 390 CGI Methods.parse_request_parameters @cgi.params387 CGI.parse_request_parameters @cgi.params 391 388 end 392 389 ensure … … 458 455 assert_equal( 459 456 {'location' => ["1", "2"], 'age_group' => ["2"]}, 460 CGI Methods.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")457 CGI.parse_query_parameters("location[]=1&location[]=2&age_group[]=2") 461 458 ) 462 459 assert_equal( 463 460 {'location' => ["1", "2"], 'age_group' => ["2"]}, 464 CGI Methods.parse_request_parameters({'location[]' => ["1", "2"],461 CGI.parse_request_parameters({'location[]' => ["1", "2"], 465 462 'age_group[]' => ["2"]}) 466 463 ) trunk/actionpack/test/controller/raw_post_test.rb
r6057 r6733 1 1 require "#{File.dirname(__FILE__)}/../abstract_unit" 2 require 'cgi'3 2 require 'stringio' 4 require 'action_controller/cgi_ext/ raw_post_data_fix'3 require 'action_controller/cgi_ext/query_extension' 5 4 6 5 class RawPostDataTest < Test::Unit::TestCase trunk/actionpack/test/controller/session/cookie_store_test.rb
r6424 r6733 1 1 require "#{File.dirname(__FILE__)}/../../abstract_unit" 2 2 require 'action_controller/cgi_process' 3 require 'action_controller/cgi_ext /cgi_ext'3 require 'action_controller/cgi_ext' 4 4 5 5 require 'stringio'