Changeset 6764
- Timestamp:
- 05/18/07 06:24:50 (3 years ago)
- Files:
-
- trunk/actionpack/CHANGELOG (modified) (1 diff)
- trunk/actionpack/lib/action_controller/base.rb (modified) (1 diff)
- trunk/actionpack/lib/action_controller/cgi_ext.rb (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/cgi_ext/parameters.rb (deleted)
- trunk/actionpack/lib/action_controller/cgi_ext/query_extension.rb (modified) (1 diff)
- trunk/actionpack/lib/action_controller/cgi_process.rb (modified) (2 diffs)
- trunk/actionpack/lib/action_controller/integration.rb (modified) (1 diff)
- trunk/actionpack/lib/action_controller/request.rb (modified) (4 diffs)
- trunk/actionpack/test/controller/cgi_test.rb (modified) (3 diffs)
- trunk/actionpack/test/controller/mime_responds_test.rb (modified) (1 diff)
- trunk/actionpack/test/controller/raw_post_test.rb (deleted)
- trunk/actionpack/test/controller/request_test.rb (modified) (19 diffs)
- trunk/actionpack/test/controller/session/cookie_store_test.rb (modified) (1 diff)
- trunk/actionpack/test/controller/webservice_test.rb (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
trunk/actionpack/CHANGELOG
r6763 r6764 1 1 *SVN* 2 3 * Parse url-encoded and multipart requests ourselves instead of delegating to CGI. [Jeremy Kemper] 2 4 3 5 * select :include_blank option can be set to a string instead of true, which just uses an empty string. #7664 [Wizard] trunk/actionpack/lib/action_controller/base.rb
r6758 r6764 272 272 # 273 273 # ActionController::Base.param_parsers[Mime::YAML] = :yaml 274 @@param_parsers = { Mime::XML => :xml_simple } 274 @@param_parsers = { Mime::MULTIPART_FORM => :multipart_form, 275 Mime::URL_ENCODED_FORM => :url_encoded_form, 276 Mime::XML => :xml_simple } 275 277 cattr_accessor :param_parsers 276 278 trunk/actionpack/lib/action_controller/cgi_ext.rb
r6740 r6764 1 1 require 'action_controller/cgi_ext/stdinput' 2 require 'action_controller/cgi_ext/parameters'3 2 require 'action_controller/cgi_ext/query_extension' 4 3 require 'action_controller/cgi_ext/cookie' … … 7 6 class CGI #:nodoc: 8 7 include ActionController::CgiExt::Stdinput 9 include ActionController::CgiExt::Parameters10 8 11 9 class << self trunk/actionpack/lib/action_controller/cgi_ext/query_extension.rb
r6740 r6764 6 6 remove_method :initialize_query 7 7 8 # Initialize the data from the query. 9 # 10 # Handles multipart forms (in particular, forms that involve file uploads). 11 # Reads query parameters in the @params field, and cookies into @cookies. 8 # Neuter CGI parameter parsing. 12 9 def initialize_query 13 @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE']) 10 # Fix some strange request environments. 11 env_table['REQUEST_METHOD'] ||= 'GET' 14 12 15 # Fix some strange request environments. 16 if method = env_table['REQUEST_METHOD'] 17 method = method.to_s.downcase.intern 18 else 19 method = :get 13 # POST assumes missing Content-Type is application/x-www-form-urlencoded. 14 if env_table['CONTENT_TYPE'].blank? && env_table['REQUEST_METHOD'] == 'POST' 15 env_table['CONTENT_TYPE'] = 'application/x-www-form-urlencoded' 20 16 end 21 17 22 # POST assumes missing Content-Type is application/x-www-form-urlencoded. 23 content_type = env_table['CONTENT_TYPE'] 24 if content_type.blank? && method == :post 25 content_type = 'application/x-www-form-urlencoded' 26 end 27 28 # Force content length to zero if missing. 29 content_length = env_table['CONTENT_LENGTH'].to_i 30 31 # Set multipart to false by default. 32 @multipart = false 33 34 # POST and PUT may have params in entity body. If content type is missing 35 # or non-urlencoded, don't read the body or parse parameters: assume it's 36 # binary data. 37 if method == :post || method == :put 38 if boundary = extract_multipart_form_boundary(content_type) 39 @multipart = true 40 @params = read_multipart(boundary, content_length) 41 elsif content_type.blank? || content_type !~ %r{application/x-www-form-urlencoded}i 42 @params = {} 43 end 44 end 45 46 @params ||= CGI.parse(read_params(method, content_length)) 18 @cookies = CGI::Cookie::parse(env_table['HTTP_COOKIE'] || env_table['COOKIE']) 19 @params = {} 47 20 end 48 49 private 50 unless defined?(MULTIPART_FORM_BOUNDARY_RE) 51 MULTIPART_FORM_BOUNDARY_RE = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n #" 52 end 53 54 def extract_multipart_form_boundary(content_type) 55 MULTIPART_FORM_BOUNDARY_RE.match(content_type).to_a.pop 56 end 57 58 if defined? MOD_RUBY 59 def read_query 60 Apache::request.args || '' 61 end 62 else 63 def read_query 64 # fixes CGI querystring parsing for lighttpd 65 env_qs = env_table['QUERY_STRING'] 66 if env_qs.blank? && !(uri = env_table['REQUEST_URI']).blank? 67 uri.split('?', 2)[1] || '' 68 else 69 env_qs || '' 70 end 71 end 72 end 73 74 def read_body(content_length) 75 stdinput.binmode if stdinput.respond_to?(:binmode) 76 content = stdinput.read(content_length) || '' 77 # Fix for Safari Ajax postings that always append \000 78 content.chop! if content[-1] == 0 79 content.gsub!(/&_=$/, '') 80 env_table['RAW_POST_DATA'] = content.freeze 81 end 82 83 def read_params(method, content_length) 84 case method 85 when :get 86 read_query 87 when :post, :put 88 read_body(content_length) 89 when :cmd 90 read_from_cmdline 91 else # :head, :delete, :options, :trace, :connect 92 read_query 93 end 94 end 95 end # module QueryExtension 21 end 96 22 end trunk/actionpack/lib/action_controller/cgi_process.rb
r6743 r6764 48 48 49 49 def query_string 50 if (qs = @cgi.query_string) && !qs.empty? 50 qs = @cgi.query_string 51 if !qs.blank? 51 52 qs 52 53 elsif uri = @env['REQUEST_URI'] 53 parts = uri.split('?') 54 parts.shift 55 parts.join('?') 54 uri.split('?', 2).last 56 55 else 57 56 @env['QUERY_STRING'] || '' … … 70 69 71 70 def query_parameters 72 @query_parameters ||= CGI.parse_query_parameters(query_string)71 @query_parameters ||= self.class.parse_query_parameters(query_string) 73 72 end 74 73 75 74 def request_parameters 76 @request_parameters ||= 77 if ActionController::Base.param_parsers.has_key?(content_type) 78 self.class.parse_formatted_request_parameters(content_type, body.read) 79 else 80 CGI.parse_request_parameters(@cgi.params) 81 end 75 @request_parameters ||= self.class.parse_formatted_request_parameters(body, content_type_with_parameters, content_length, env) 82 76 end 83 77 trunk/actionpack/lib/action_controller/integration.rb
r6759 r6764 307 307 "HTTP_HOST" => host, 308 308 "SERVER_PORT" => https? ? "443" : "80", 309 "HTTPS" => https? ? "on" : "off") 309 "HTTPS" => https? ? "on" : "off") 310 310 ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {}) 311 311 end trunk/actionpack/lib/action_controller/request.rb
r6742 r6764 1 require 'tempfile' 2 require 'stringio' 3 require 'strscan' 4 1 5 module ActionController 2 6 # CgiRequest and TestRequest provide concrete implementations. … … 56 60 end 57 61 62 def content_length 63 @content_length ||= env['CONTENT_LENGTH'].to_i 64 end 65 66 def content_type_with_parameters 67 @content_type_with_parameters ||= env['CONTENT_TYPE'].to_s 68 end 69 58 70 # Determine whether the body of a HTTP call is URL-encoded (default) 59 71 # or matches one of the registered param_parsers. … … 65 77 begin 66 78 # Receive header sans any charset information. 67 content_type = @env['CONTENT_TYPE'].to_s.sub(/\s*\;.*$/, '').strip.downcase79 content_type = content_type_with_parameters.sub(/\s*\;.*$/, '').strip.downcase 68 80 69 81 if x_post_format = @env['HTTP_X_POST_DATA_FORMAT'] … … 298 310 299 311 300 def self.parse_formatted_request_parameters(mime_type, body) 301 case strategy = ActionController::Base.param_parsers[mime_type] 302 when Proc 303 strategy.call(body) 304 when :xml_simple, :xml_node 305 body.blank? ? {} : Hash.from_xml(body).with_indifferent_access 306 when :yaml 307 YAML.load(body) 312 class << self 313 def parse_formatted_request_parameters(body, content_type, content_length, env = {}) 314 content_length = content_length.to_i 315 return {} if content_length.zero? 316 317 content_type, boundary = extract_multipart_boundary(content_type.to_s) 318 return {} if content_type.blank? 319 320 mime_type = Mime::Type.lookup(content_type) 321 strategy = ActionController::Base.param_parsers[mime_type] 322 323 raise [content_type, content_length, mime_type, ActionController::Base.param_parsers].inspect unless strategy 324 325 # Only multipart form parsing expects a stream. 326 if strategy && strategy != :multipart_form 327 body = body.read(content_length) 328 end 329 330 case strategy 331 when Proc 332 strategy.call(body) 333 when :url_encoded_form 334 clean_up_ajax_request_body! body 335 parse_query_parameters(body) 336 when :multipart_form 337 parse_multipart_form_parameters(body, boundary, content_length, env) 338 when :xml_simple, :xml_node 339 body.blank? ? {} : Hash.from_xml(body).with_indifferent_access 340 when :yaml 341 YAML.load(body) 342 else 343 {} 344 end 345 rescue Exception => e # YAML, XML or Ruby code block errors 346 raise 347 { "body" => body, 348 "content_type" => content_type, 349 "content_length" => content_length, 350 "exception" => "#{e.message} (#{e.class})", 351 "backtrace" => e.backtrace } 352 end 353 354 def parse_query_parameters(query_string) 355 return {} if query_string.blank? 356 357 pairs = query_string.split('&').collect do |chunk| 358 next if chunk.empty? 359 key, value = chunk.split('=', 2) 360 next if key.empty? 361 value = value.nil? ? nil : CGI.unescape(value) 362 [ CGI.unescape(key), value ] 363 end.compact 364 365 UrlEncodedPairParser.new(pairs).result 366 end 367 368 def parse_request_parameters(params) 369 parser = UrlEncodedPairParser.new 370 371 params = params.dup 372 until params.empty? 373 for key, value in params 374 if key.blank? 375 params.delete key 376 elsif !key.include?('[') 377 # much faster to test for the most common case first (GET) 378 # and avoid the call to build_deep_hash 379 parser.result[key] = get_typed_value(value[0]) 380 params.delete key 381 elsif value.is_a?(Array) 382 parser.parse(key, get_typed_value(value.shift)) 383 params.delete key if value.empty? 384 else 385 raise TypeError, "Expected array, found #{value.inspect}" 386 end 387 end 388 end 389 390 parser.result 391 end 392 393 def parse_multipart_form_parameters(body, boundary, content_length, env) 394 parse_request_parameters(read_multipart(body, boundary, content_length, env)) 395 end 396 397 private 398 def get_typed_value(value) 399 case value 400 when String 401 value 402 when NilClass 403 '' 404 when Array 405 value.map { |v| get_typed_value(v) } 406 else 407 # Uploaded file provides content type and filename. 408 if value.respond_to?(:content_type) && 409 !value.content_type.blank? && 410 !value.original_filename.blank? 411 unless value.respond_to?(:full_original_filename) 412 class << value 413 alias_method :full_original_filename, :original_filename 414 415 # Take the basename of the upload's original filename. 416 # This handles the full Windows paths given by Internet Explorer 417 # (and perhaps other broken user agents) without affecting 418 # those which give the lone filename. 419 # The Windows regexp is adapted from Perl's File::Basename. 420 def original_filename 421 if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename) 422 md.captures.first 423 else 424 File.basename full_original_filename 425 end 426 end 427 end 428 end 429 430 # Return the same value after overriding original_filename. 431 value 432 433 # Multipart values may have content type, but no filename. 434 elsif value.respond_to?(:read) 435 result = value.read 436 value.rewind 437 result 438 439 # Unknown value, neither string nor multipart. 440 else 441 raise "Unknown form value: #{value.inspect}" 442 end 443 end 444 end 445 446 447 MULTIPART_BOUNDARY = %r|\Amultipart/form-data.*boundary=\"?([^\";,]+)\"?|n 448 449 def extract_multipart_boundary(content_type) 450 if content_type =~ MULTIPART_BOUNDARY 451 ['multipart/form-data', $1.dup] 452 else 453 content_type 454 end 455 end 456 457 def clean_up_ajax_request_body!(body) 458 body.chop! if body[-1] == 0 459 body.gsub!(/&_=$/, '') 460 end 461 462 463 EOL = "\015\012" 464 465 def read_multipart(body, boundary, content_length, env) 466 params = Hash.new([]) 467 boundary = "--" + boundary 468 quoted_boundary = Regexp.quote(boundary, "n") 469 buf = "" 470 bufsize = 10 * 1024 471 boundary_end="" 472 473 # start multipart/form-data 474 body.binmode if defined? body.binmode 475 boundary_size = boundary.size + EOL.size 476 content_length -= boundary_size 477 status = body.read(boundary_size) 478 if nil == status 479 raise EOFError, "no content body" 480 elsif boundary + EOL != status 481 raise EOFError, "bad content body" 482 end 483 484 loop do 485 head = nil 486 content = 487 if 10240 < content_length 488 Tempfile.new("CGI") 489 else 490 StringIO.new 491 end 492 content.binmode if defined? content.binmode 493 494 until head and /#{quoted_boundary}(?:#{EOL}|--)/n.match(buf) 495 496 if (not head) and /#{EOL}#{EOL}/n.match(buf) 497 buf = buf.sub(/\A((?:.|\n)*?#{EOL})#{EOL}/n) do 498 head = $1.dup 499 "" 500 end 501 next 502 end 503 504 if head and ( (EOL + boundary + EOL).size < buf.size ) 505 content.print buf[0 ... (buf.size - (EOL + boundary + EOL).size)] 506 buf[0 ... (buf.size - (EOL + boundary + EOL).size)] = "" 507 end 508 509 c = if bufsize < content_length 510 body.read(bufsize) 511 else 512 body.read(content_length) 513 end 514 if c.nil? || c.empty? 515 raise EOFError, "bad content body" 516 end 517 buf.concat(c) 518 content_length -= c.size 519 end 520 521 buf = buf.sub(/\A((?:.|\n)*?)(?:[\r\n]{1,2})?#{quoted_boundary}([\r\n]{1,2}|--)/n) do 522 content.print $1 523 if "--" == $2 524 content_length = -1 525 end 526 boundary_end = $2.dup 527 "" 528 end 529 530 content.rewind 531 532 /Content-Disposition:.* filename=(?:"((?:\\.|[^\"])*)"|([^;]*))/ni.match(head) 533 filename = ($1 or $2 or "") 534 if /Mac/ni.match(env['HTTP_USER_AGENT']) and 535 /Mozilla/ni.match(env['HTTP_USER_AGENT']) and 536 (not /MSIE/ni.match(env['HTTP_USER_AGENT'])) 537 filename = CGI.unescape(filename) 538 end 539 540 /Content-Type: (.*)/ni.match(head) 541 content_type = ($1 or "") 542 543 (class << content; self; end).class_eval do 544 alias local_path path 545 define_method(:original_filename) {filename.dup.taint} 546 define_method(:content_type) {content_type.dup.taint} 547 end 548 549 /Content-Disposition:.* name="?([^\";]*)"?/ni.match(head) 550 name = $1.dup 551 552 if params.has_key?(name) 553 params[name].push(content) 554 else 555 params[name] = [content] 556 end 557 break if buf.size == 0 558 break if content_length == -1 559 end 560 raise EOFError, "bad boundary end of body part" unless boundary_end=~/--/ 561 562 params 563 end 564 end 565 end 566 567 class UrlEncodedPairParser < StringScanner #:nodoc: 568 attr_reader :top, :parent, :result 569 570 def initialize(pairs = []) 571 super('') 572 @result = {} 573 pairs.each { |key, value| parse(key, value) } 574 end 575 576 KEY_REGEXP = %r{([^\[\]=&]+)} 577 BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]} 578 579 # Parse the query string 580 def parse(key, value) 581 self.string = key 582 @top, @parent = result, nil 583 584 # First scan the bare key 585 key = scan(KEY_REGEXP) or return 586 key = post_key_check(key) 587 588 # Then scan as many nestings as present 589 until eos? 590 r = scan(BRACKETED_KEY_REGEXP) or return 591 key = self[1] 592 key = post_key_check(key) 593 end 594 595 bind(key, value) 596 end 597 598 private 599 # After we see a key, we must look ahead to determine our next action. Cases: 600 # 601 # [] follows the key. Then the value must be an array. 602 # = follows the key. (A value comes next) 603 # & or the end of string follows the key. Then the key is a flag. 604 # otherwise, a hash follows the key. 605 def post_key_check(key) 606 if scan(/\[\]/) # a[b][] indicates that b is an array 607 container(key, Array) 608 nil 609 elsif check(/\[[^\]]/) # a[b] indicates that a is a hash 610 container(key, Hash) 611 nil 612 else # End of key? We do nothing. 613 key 614 end 615 end 616 617 # Add a container to the stack. 618 def container(key, klass) 619 type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass) 620 value = bind(key, klass.new) 621 type_conflict! klass, value unless value.is_a?(klass) 622 push(value) 623 end 624 625 # Push a value onto the 'stack', which is actually only the top 2 items. 626 def push(value) 627 @parent, @top = @top, value 628 end 629 630 # Bind a key (which may be nil for items in an array) to the provided value. 631 def bind(key, value) 632 if top.is_a? Array 633 if key 634 if top[-1].is_a?(Hash) && ! top[-1].key?(key) 635 top[-1][key] = value 636 else 637 top << {key => value}.with_indifferent_access 638 push top.last 639 end 640 else 641 top << value 642 end 643 elsif top.is_a? Hash 644 key = CGI.unescape(key) 645 parent << (@top = {}) if top.key?(key) && parent.is_a?(Array) 646 return top[key] ||= value 308 647 else 309 {} 310 end 311 rescue Exception => e # YAML, XML or Ruby code block errors 312 { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace, 313 "body" => body, "format" => mime_type } 314 end 648 raise ArgumentError, "Don't know what to do: top is #{top.inspect}" 649 end 650 651 return value 652 end 653 654 def type_conflict!(klass, value) 655 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." 656 end 315 657 end 316 658 end trunk/actionpack/test/controller/cgi_test.rb
r6742 r6764 2 2 require 'action_controller/cgi_process' 3 3 4 class CGITest < Test::Unit::TestCase 5 def setup 6 @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" 7 @query_string_with_empty = "action=create_customer&full_name=" 8 @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3" 9 @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does" 10 @query_string_with_multiple_of_same_name = 11 "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3" 12 @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi" 13 @query_string_without_equal = "action" 14 @query_string_with_many_ampersands = 15 "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" 16 @query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save" 17 end 18 19 def test_query_string 20 assert_equal( 21 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, 22 CGI.parse_query_parameters(@query_string) 23 ) 24 end 25 26 def test_deep_query_string 27 expected = {'x' => {'y' => {'z' => '10'}}} 28 assert_equal(expected, CGI.parse_query_parameters('x[y][z]=10')) 29 end 30 31 def test_deep_query_string_with_array 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')) 34 end 35 36 def test_deep_query_string_with_array_of_hash 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')) 39 end 40 41 def test_deep_query_string_with_array_of_hashes_with_one_pair 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]) 45 end 46 47 def test_request_hash_parsing 48 query = { 49 "note[viewers][viewer][][type]" => ["User", "Group"], 50 "note[viewers][viewer][][id]" => ["1", "2"] 51 } 52 53 expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } 54 55 assert_equal(expected, CGI.parse_request_parameters(query)) 56 end 57 58 def test_deep_query_string_with_array_of_hashes_with_multiple_pairs 59 assert_equal( 60 {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, 61 CGI.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') 62 ) 63 end 64 65 def test_query_string_with_nil 66 assert_equal( 67 { "action" => "create_customer", "full_name" => ''}, 68 CGI.parse_query_parameters(@query_string_with_empty) 69 ) 70 end 71 72 def test_query_string_with_array 73 assert_equal( 74 { "action" => "create_customer", "selected" => ["1", "2", "3"]}, 75 CGI.parse_query_parameters(@query_string_with_array) 76 ) 77 end 78 79 def test_query_string_with_amps 80 assert_equal( 81 { "action" => "create_customer", "name" => "Don't & Does"}, 82 CGI.parse_query_parameters(@query_string_with_amps) 83 ) 84 end 85 86 def test_query_string_with_many_equal 87 assert_equal( 88 { "action" => "create_customer", "full_name" => "abc=def=ghi"}, 89 CGI.parse_query_parameters(@query_string_with_many_equal) 90 ) 91 end 92 93 def test_query_string_without_equal 94 assert_equal( 95 { "action" => nil }, 96 CGI.parse_query_parameters(@query_string_without_equal) 97 ) 98 end 99 100 def test_query_string_with_empty_key 101 assert_equal( 102 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" }, 103 CGI.parse_query_parameters(@query_string_with_empty_key) 104 ) 105 end 106 107 def test_query_string_with_many_ampersands 108 assert_equal( 109 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, 110 CGI.parse_query_parameters(@query_string_with_many_ampersands) 111 ) 112 end 113 114 def test_parse_params 115 input = { 116 "customers[boston][first][name]" => [ "David" ], 117 "customers[boston][first][url]" => [ "http://David" ], 118 "customers[boston][second][name]" => [ "Allan" ], 119 "customers[boston][second][url]" => [ "http://Allan" ], 120 "something_else" => [ "blah" ], 121 "something_nil" => [ nil ], 122 "something_empty" => [ "" ], 123 "products[first]" => [ "Apple Computer" ], 124 "products[second]" => [ "Pc" ], 125 "" => [ 'Save' ] 126 } 127 128 expected_output = { 129 "customers" => { 130 "boston" => { 131 "first" => { 132 "name" => "David", 133 "url" => "http://David" 134 }, 135 "second" => { 136 "name" => "Allan", 137 "url" => "http://Allan" 138 } 139 } 140 }, 141 "something_else" => "blah", 142 "something_empty" => "", 143 "something_nil" => "", 144 "products" => { 145 "first" => "Apple Computer", 146 "second" => "Pc" 147 } 148 } 149 150 assert_equal expected_output, CGI.parse_request_parameters(input) 151 end 152 153 def test_parse_params_from_multipart_upload 154 mockup = Struct.new(:content_type, :original_filename, :read, :rewind) 155 file = mockup.new('img/jpeg', 'foo.jpg') 156 ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg') 157 non_file_text_part = mockup.new('text/plain', '', 'abc') 158 159 input = { 160 "something" => [ StringIO.new("") ], 161 "array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]], 162 "mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]], 163 "mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]], 164 "ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]], 165 "products[string]" => [ StringIO.new("Apple Computer") ], 166 "products[file]" => [ file ], 167 "ie_products[string]" => [ StringIO.new("Microsoft") ], 168 "ie_products[file]" => [ ie_file ], 169 "text_part" => [non_file_text_part] 170 } 171 172 expected_output = { 173 "something" => "", 174 "array_of_stringios" => ["One", "Two"], 175 "mixed_types_array" => [ "Three", "NotStringIO" ], 176 "mixed_types_as_checkboxes" => { 177 "strings" => { 178 "nested" => [ file, "String", "StringIO" ] 179 }, 180 }, 181 "ie_mixed_types_as_checkboxes" => { 182 "strings" => { 183 "nested" => [ ie_file, "String", "StringIO" ] 184 }, 185 }, 186 "products" => { 187 "string" => "Apple Computer", 188 "file" => file 189 }, 190 "ie_products" => { 191 "string" => "Microsoft", 192 "file" => ie_file 193 }, 194 "text_part" => "abc" 195 } 196 197 params = CGI.parse_request_parameters(input) 198 assert_equal expected_output, params 199 200 # Lone filenames are preserved. 201 assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename 202 assert_equal 'foo.jpg', params['products']['file'].original_filename 203 204 # But full Windows paths are reduced to their basename. 205 assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename 206 assert_equal 'bar.jpg', params['ie_products']['file'].original_filename 207 end 208 209 def test_parse_params_with_file 210 input = { 211 "customers[boston][first][name]" => [ "David" ], 212 "something_else" => [ "blah" ], 213 "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ] 214 } 215 216 expected_output = { 217 "customers" => { 218 "boston" => { 219 "first" => { 220 "name" => "David" 221 } 222 } 223 }, 224 "something_else" => "blah", 225 "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path, 226 } 227 228 assert_equal expected_output, CGI.parse_request_parameters(input) 229 end 230 231 def test_parse_params_with_array 232 input = { "selected[]" => [ "1", "2", "3" ] } 233 234 expected_output = { "selected" => [ "1", "2", "3" ] } 235 236 assert_equal expected_output, CGI.parse_request_parameters(input) 237 end 238 239 def test_parse_params_with_non_alphanumeric_name 240 input = { "a/b[c]" => %w(d) } 241 expected = { "a/b" => { "c" => "d" }} 242 assert_equal expected, CGI.parse_request_parameters(input) 243 end 244 245 def test_parse_params_with_single_brackets_in_middle 246 input = { "a/b[c]d" => %w(e) } 247 expected = { "a/b" => {} } 248 assert_equal expected, CGI.parse_request_parameters(input) 249 end 250 251 def test_parse_params_with_separated_brackets 252 input = { "a/b@[c]d[e]" => %w(f) } 253 expected = { "a/b@" => { }} 254 assert_equal expected, CGI.parse_request_parameters(input) 255 end 256 257 def test_parse_params_with_separated_brackets_and_array 258 input = { "a/b@[c]d[e][]" => %w(f) } 259 expected = { "a/b@" => { }} 260 assert_equal expected , CGI.parse_request_parameters(input) 261 end 262 263 def test_parse_params_with_unmatched_brackets_and_array 264 input = { "a/b@[c][d[e][]" => %w(f) } 265 expected = { "a/b@" => { "c" => { }}} 266 assert_equal expected, CGI.parse_request_parameters(input) 267 end 268 269 def test_parse_params_with_nil_key 270 input = { nil => nil, "test2" => %w(value1) } 271 expected = { "test2" => "value1" } 272 assert_equal expected, CGI.parse_request_parameters(input) 273 end 274 end 275 276 277 class MultipartCGITest < Test::Unit::TestCase 278 FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart' 279 280 def setup 281 ENV['REQUEST_METHOD'] = 'POST' 282 ENV['CONTENT_LENGTH'] = '0' 283 ENV['CONTENT_TYPE'] = 'multipart/form-data, boundary=AaB03x' 284 end 285 286 def test_single_parameter 287 params = process('single_parameter') 288 assert_equal({ 'foo' => 'bar' }, params) 289 end 290 291 def test_text_file 292 params = process('text_file') 293 assert_equal %w(file foo), params.keys.sort 294 assert_equal 'bar', params['foo'] 295 296 file = params['file'] 297 assert_kind_of StringIO, file 298 assert_equal 'file.txt', file.original_filename 299 assert_equal "text/plain\r", file.content_type 300 assert_equal 'contents', file.read 301 end 302 303 def test_large_text_file 304 params = process('large_text_file') 305 assert_equal %w(file foo), params.keys.sort 306 assert_equal 'bar', params['foo'] 307 308 file = params['file'] 309 assert_kind_of Tempfile, file 310 assert_equal 'file.txt', file.original_filename 311 assert_equal "text/plain\r", file.content_type 312 assert ('a' * 20480) == file.read 313 end 314 315 def test_binary_file 316 params = process('binary_file') 317 assert_equal %w(file flowers foo), params.keys.sort 318 assert_equal 'bar', params['foo'] 319 320 file = params['file'] 321 assert_kind_of StringIO, file 322 assert_equal 'file.txt', file.original_filename 323 assert_equal "text/plain\r", file.content_type 324 assert_equal 'contents', file.read 325 326 file = params['flowers'] 327 assert_kind_of StringIO, file 328 assert_equal 'flowers.jpg', file.original_filename 329 assert_equal "image/jpeg\r", file.content_type 330 assert_equal 19512, file.size 331 #assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read 332 end 333 334 def test_mixed_files 335 params = process('mixed_files') 336 assert_equal %w(files foo), params.keys.sort 337 assert_equal 'bar', params['foo'] 338 339 # Ruby CGI doesn't handle multipart/mixed for us. 340 assert_kind_of String, params['files'] 341 assert_equal 19756, params['files'].size 342 end 343 344 # Rewind readable cgi params so others may reread them (such as CGI::Session 345 # when passing the session id in a multipart form). 346 def test_multipart_param_rewound 347 params = process('text_file') 348 assert_equal 'bar', @cgi.params['foo'][0].read 349 end 350 351 private 352 def process(name) 353 File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| 354 ENV['CONTENT_LENGTH'] = file.stat.size.to_s 355 @cgi = CGI.new('query', file) 356 CGI.parse_request_parameters @cgi.params 357 end 358 end 359 end 360 361 # Ensures that PUT works with multipart as well as POST. 362 class PutMultipartCGITest < MultipartCGITest 363 def setup 364 super 365 ENV['REQUEST_METHOD'] = 'PUT' 366 end 367 end 368 369 370 class CGIRequestTest < Test::Unit::TestCase 4 class CgiRequestTest < Test::Unit::TestCase 371 5 def setup 372 6 @request_hash = {"HTTP_MAX_FORWARDS"=>"10", "SERVER_NAME"=>"glu.ttono.us:8007", "FCGI_ROLE"=>"RESPONDER", "HTTP_X_FORWARDED_HOST"=>"glu.ttono.us", "HTTP_ACCEPT_ENCODING"=>"gzip, deflate", "HTTP_USER_AGENT"=>"Mozilla/5.0 (Macintosh; U; PPC Mac OS X; en) AppleWebKit/312.5.1 (KHTML, like Gecko) Safari/312.3.1", "PATH_INFO"=>"", "HTTP_ACCEPT_LANGUAGE"=>"en", "HTTP_HOST"=>"glu.ttono.us:8007", "SERVER_PROTOCOL"=>"HTTP/1.1", "REDIRECT_URI"=>"/dispatch.fcgi", "SCRIPT_NAME"=>"/dispatch.fcgi", "SERVER_ADDR"=>"207.7.108.53", "REMOTE_ADDR"=>"207.7.108.53", "SERVER_SOFTWARE"=>"lighttpd/1.4.5", "HTTP_COOKIE"=>"_session_id=c84ace84796670c052c6ceb2451fb0f2; is_admin=yes", "HTTP_X_FORWARDED_SERVER"=>"glu.ttono.us", "REQUEST_URI"=>"/admin", "DOCUMENT_ROOT"=>"/home/kevinc/sites/typo/public", "SERVER_PORT"=>"8007", "QUERY_STRING"=>"", "REMOTE_PORT"=>"63137", "GATEWAY_INTERFACE"=>"CGI/1.1", "HTTP_X_FORWARDED_FOR"=>"65.88.180.234", "HTTP_ACCEPT"=>"*/*", "SCRIPT_FILENAME"=>"/home/kevinc/sites/typo/public/dispatch.fcgi", "REDIRECT_STATUS"=>"200", "REQUEST_METHOD"=>"GET"} … … 376 10 @request = ActionController::CgiRequest.new(@fake_cgi) 377 11 end 378 12 379 13 def test_proxy_request 380 14 assert_equal 'glu.ttono.us', @request.host_with_port 381 15 end 382 16 383 17 def test_http_host 384 18 @request_hash.delete "HTTP_X_FORWARDED_HOST" 385 19 @request_hash['HTTP_HOST'] = "rubyonrails.org:8080" 386 20 assert_equal "rubyonrails.org:8080", @request.host_with_port 387 21 388 22 @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org" 389 23 assert_equal "www.secondhost.org", @request.host 390 24 end 391 25 392 26 def test_http_host_with_default_port_overrides_server_port 393 27 @request_hash.delete "HTTP_X_FORWARDED_HOST" … … 413 47 assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"] 414 48 assert_equal ["yes"], cookies["is_admin"] 415 49 416 50 alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]); 417 51 assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"] 418 52 assert_equal ["yes"], alt_cookies["is_admin"] 419 53 end 420 421 def test_unbalanced_query_string_with_array422 assert_equal(423 {'location' => ["1", "2"], 'age_group' => ["2"]},424 CGI.parse_query_parameters("location[]=1&location[]=2&age_group[]=2")425 )426 assert_equal(427 {'location' => ["1", "2"], 'age_group' => ["2"]},428 CGI.parse_request_parameters({'location[]' => ["1", "2"],429 'age_group[]' => ["2"]})430 )431 end432 54 end trunk/actionpack/test/controller/mime_responds_test.rb
r6517 r6764 233 233 end 234 234 235 def test_with_ content_type235 def test_with_atom_content_type 236 236 @request.env["CONTENT_TYPE"] = "application/atom+xml" 237 237 get :made_for_content_type 238 238 assert_equal "ATOM", @response.body 239 239 end 240 241 def test_with_rss_content_type 240 242 @request.env["CONTENT_TYPE"] = "application/rss+xml" 241 243 get :made_for_content_type trunk/actionpack/test/controller/request_test.rb
r6742 r6764 45 45 @request.host = "www.rubyonrails.co.uk" 46 46 assert_equal "rubyonrails.co.uk", @request.domain(2) 47 47 48 48 @request.host = "192.168.1.200" 49 49 assert_nil @request.domain … … 69 69 assert_equal [], @request.subdomains 70 70 end 71 71 72 72 def test_port_string 73 73 @request.port = 80 … … 77 77 assert_equal ":8080", @request.port_string 78 78 end 79 79 80 80 def test_relative_url_root 81 81 @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" … … 84 84 85 85 @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text' 86 86 87 87 @request.env['SCRIPT_NAME'] = nil 88 88 assert_equal "", @request.relative_url_root … … 100 100 @request.relative_url_root = nil 101 101 @request.env['SCRIPT_NAME'] = "/collaboration/hieraki/dispatch.cgi" 102 assert_equal "/collaboration/hieraki", @request.relative_url_root 103 102 assert_equal "/collaboration/hieraki", @request.relative_url_root 103 104 104 # apache/scgi case 105 105 @request.relative_url_root = nil 106 106 @request.env['SCRIPT_NAME'] = "/collaboration/hieraki" 107 assert_equal "/collaboration/hieraki", @request.relative_url_root 108 107 assert_equal "/collaboration/hieraki", @request.relative_url_root 108 109 109 @request.relative_url_root = nil 110 110 @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" … … 112 112 @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki" 113 113 assert_equal "/hieraki", @request.relative_url_root 114 114 115 115 # @env overrides path guess 116 116 @request.relative_url_root = nil … … 120 120 assert_equal "/real_url", @request.relative_url_root 121 121 end 122 122 123 123 def test_request_uri 124 124 @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432' 125 125 126 126 @request.relative_url_root = nil 127 127 @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1" 128 128 assert_equal "/path/of/some/uri?mapped=1", @request.request_uri 129 129 assert_equal "/path/of/some/uri", @request.path 130 130 131 131 @request.relative_url_root = nil 132 132 @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri" … … 148 148 assert_equal "/?m=b", @request.request_uri 149 149 assert_equal "/", @request.path 150 150 151 151 @request.relative_url_root = nil 152 152 @request.set_REQUEST_URI "/" 153 153 @request.env['SCRIPT_NAME'] = "/dispatch.cgi" 154 154 assert_equal "/", @request.request_uri 155 assert_equal "/", @request.path 155 assert_equal "/", @request.path 156 156 157 157 @request.relative_url_root = nil … … 159 159 @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" 160 160 assert_equal "/hieraki/", @request.request_uri 161 assert_equal "/", @request.path 161 assert_equal "/", @request.path 162 162 163 163 @request.relative_url_root = nil … … 166 166 assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri 167 167 assert_equal "/books/edit/2", @request.path 168 168 169 169 # The following tests are for when REQUEST_URI is not supplied (as in IIS) 170 170 @request.relative_url_root = nil … … 239 239 @request.port = 80 240 240 assert_equal "rubyonrails.org", @request.host_with_port 241 241 242 242 @request.host = "rubyonrails.org" 243 243 @request.port = 81 244 244 assert_equal "rubyonrails.org:81", @request.host_with_port 245 245 end 246 246 247 247 def test_server_software 248 248 assert_equal nil, @request.server_software 249 249 250 250 @request.env['SERVER_SOFTWARE'] = 'Apache3.422' 251 251 assert_equal 'apache', @request.server_software 252 252 253 253 @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)' 254 254 assert_equal 'lighttpd', @request.server_software 255 255 end 256 256 257 257 def test_xml_http_request 258 258 assert !@request.xml_http_request? 259 259 assert !@request.xhr? 260 260 261 261 @request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0" 262 262 assert !@request.xml_http_request? 263 263 assert !@request.xhr? 264 264 265 265 @request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" 266 266 assert @request.xml_http_request? … … 302 302 end 303 303 end 304 304 305 305 def test_head_masquarading_as_get 306 306 set_request_method_to :head … … 314 314 assert_equal Mime::XML, @request.format 315 315 end 316 316 317 317 def test_xhtml_format 318 318 @request.instance_eval { @parameters = { :format => 'xhtml' } } 319 319 assert_equal Mime::HTML, @request.format 320 320 end 321 321 322 322 def test_txt_format 323 323 @request.instance_eval { @parameters = { :format => 'txt' } } … … 330 330 assert_equal Mime::JS, @request.format 331 331 end 332 332 333 333 def test_content_type 334 334 @request.env["CONTENT_TYPE"] = "text/html" … … 339 339 assert_equal nil, @request.content_type 340 340 end 341 341 342 342 def test_content_type_xml 343 343 @request.env["CONTENT_TYPE"] = "application/xml" … … 358 358 359 359 360 class RequestParameterParsingTest < Test::Unit::TestCase 361 def test_xml_with_single_file 360 class UrlEncodedRequestParameterParsingTest < Test::Unit::TestCase 361 def setup 362 @query_string = "action=create_customer&full_name=David%20Heinemeier%20Hansson&customerId=1" 363 @query_string_with_empty = "action=create_customer&full_name=" 364 @query_string_with_array = "action=create_customer&selected[]=1&selected[]=2&selected[]=3" 365 @query_string_with_amps = "action=create_customer&name=Don%27t+%26+Does" 366 @query_string_with_multiple_of_same_name = 367 "action=update_order&full_name=Lau%20Taarnskov&products=4&products=2&products=3" 368 @query_string_with_many_equal = "action=create_customer&full_name=abc=def=ghi" 369 @query_string_without_equal = "action" 370 @query_string_with_many_ampersands = 371 "&action=create_customer&&&full_name=David%20Heinemeier%20Hansson" 372 @query_string_with_empty_key = "action=create_customer&full_name=David%20Heinemeier%20Hansson&=Save" 373 end 374 375 def test_query_string 376 assert_equal( 377 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson", "customerId" => "1"}, 378 ActionController::AbstractRequest.parse_query_parameters(@query_string) 379 ) 380 end 381 382 def test_deep_query_string 383 expected = {'x' => {'y' => {'z' => '10'}}} 384 assert_equal(expected, ActionController::AbstractRequest.parse_query_parameters('x[y][z]=10')) 385 end 386 387 def test_deep_query_string_with_array 388 assert_equal({'x' => {'y' => {'z' => ['10']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10')) 389 assert_equal({'x' => {'y' => {'z' => ['10', '5']}}}, ActionController::AbstractRequest.parse_query_parameters('x[y][z][]=10&x[y][z][]=5')) 390 end 391 392 def test_deep_query_string_with_array_of_hash 393 assert_equal({'x' => {'y' => [{'z' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10')) 394 assert_equal({'x' => {'y' => [{'z' => '10', 'w' => '10'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=10')) 395 end 396 397 def test_deep_query_string_with_array_of_hashes_with_one_pair 398 assert_equal({'x' => {'y' => [{'z' => '10'}, {'z' => '20'}]}}, ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')) 399 assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20')["x"]["y"].first["z"]) 400 assert_equal("10", ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][z]=20').with_indifferent_access[:x][:y].first[:z]) 401 end 402 403 def test_deep_query_string_with_array_of_hashes_with_multiple_pairs 404 assert_equal( 405 {'x' => {'y' => [{'z' => '10', 'w' => 'a'}, {'z' => '20', 'w' => 'b'}]}}, 406 ActionController::AbstractRequest.parse_query_parameters('x[y][][z]=10&x[y][][w]=a&x[y][][z]=20&x[y][][w]=b') 407 ) 408 end 409 410 def test_query_string_with_nil 411 assert_equal( 412 { "action" => "create_customer", "full_name" => ''}, 413 ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty) 414 ) 415 end 416 417 def test_query_string_with_array 418 assert_equal( 419 { "action" => "create_customer", "selected" => ["1", "2", "3"]}, 420 ActionController::AbstractRequest.parse_query_parameters(@query_string_with_array) 421 ) 422 end 423 424 def test_query_string_with_amps 425 assert_equal( 426 { "action" => "create_customer", "name" => "Don't & Does"}, 427 ActionController::AbstractRequest.parse_query_parameters(@query_string_with_amps) 428 ) 429 end 430 431 def test_query_string_with_many_equal 432 assert_equal( 433 { "action" => "create_customer", "full_name" => "abc=def=ghi"}, 434 ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_equal) 435 ) 436 end 437 438 def test_query_string_without_equal 439 assert_equal( 440 { "action" => nil }, 441 ActionController::AbstractRequest.parse_query_parameters(@query_string_without_equal) 442 ) 443 end 444 445 def test_query_string_with_empty_key 446 assert_equal( 447 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson" }, 448 ActionController::AbstractRequest.parse_query_parameters(@query_string_with_empty_key) 449 ) 450 end 451 452 def test_query_string_with_many_ampersands 453 assert_equal( 454 { "action" => "create_customer", "full_name" => "David Heinemeier Hansson"}, 455 ActionController::AbstractRequest.parse_query_parameters(@query_string_with_many_ampersands) 456 ) 457 end 458 459 def test_unbalanced_query_string_with_array 460 assert_equal( 461 {'location' => ["1", "2"], 'age_group' => ["2"]}, 462 ActionController::AbstractRequest.parse_query_parameters("location[]=1&location[]=2&age_group[]=2") 463 ) 464 assert_equal( 465 {'location' => ["1", "2"], 'age_group' => ["2"]}, 466 ActionController::AbstractRequest.parse_request_parameters({'location[]' => ["1", "2"], 467 'age_group[]' => ["2"]}) 468 ) 469 end 470 471 472 def test_request_hash_parsing 473 query = { 474 "note[viewers][viewer][][type]" => ["User", "Group"], 475 "note[viewers][viewer][][id]" => ["1", "2"] 476 } 477 478 expected = { "note" => { "viewers"=>{"viewer"=>[{ "id"=>"1", "type"=>"User"}, {"type"=>"Group", "id"=>"2"} ]} } } 479 480 assert_equal(expected, ActionController::AbstractRequest.parse_request_parameters(query)) 481 end 482 483 484 def test_parse_params 485 input = { 486 "customers[boston][first][name]" => [ "David" ], 487 "customers[boston][first][url]" => [ "http://David" ], 488 "customers[boston][second][name]" => [ "Allan" ], 489 "customers[boston][second][url]" => [ "http://Allan" ], 490 "something_else" => [ "blah" ], 491 "something_nil" => [ nil ], 492 "something_empty" => [ "" ], 493 "products[first]" => [ "Apple Computer" ], 494 "products[second]" => [ "Pc" ], 495 "" => [ 'Save' ] 496 } 497 498 expected_output = { 499 "customers" => { 500 "boston" => { 501 "first" => { 502 "name" => "David", 503 "url" => "http://David" 504 }, 505 "second" => { 506 "name" => "Allan", 507 "url" => "http://Allan" 508 } 509 } 510 }, 511 "something_else" => "blah", 512 "something_empty" => "", 513 "something_nil" => "", 514 "products" => { 515 "first" => "Apple Computer", 516 "second" => "Pc" 517 } 518 } 519 520 assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input) 521 end 522 523 def test_parse_params_from_multipart_upload 524 mockup = Struct.new(:content_type, :original_filename, :read, :rewind) 525 file = mockup.new('img/jpeg', 'foo.jpg') 526 ie_file = mockup.new('img/jpeg', 'c:\\Documents and Settings\\foo\\Desktop\\bar.jpg') 527 non_file_text_part = mockup.new('text/plain', '', 'abc') 528 529 input = { 530 "something" => [ StringIO.new("") ], 531 "array_of_stringios" => [[ StringIO.new("One"), StringIO.new("Two") ]], 532 "mixed_types_array" => [[ StringIO.new("Three"), "NotStringIO" ]], 533 "mixed_types_as_checkboxes[strings][nested]" => [[ file, "String", StringIO.new("StringIO")]], 534 "ie_mixed_types_as_checkboxes[strings][nested]" => [[ ie_file, "String", StringIO.new("StringIO")]], 535 "products[string]" => [ StringIO.new("Apple Computer") ], 536 "products[file]" => [ file ], 537 "ie_products[string]" => [ StringIO.new("Microsoft") ], 538 "ie_products[file]" => [ ie_file ], 539 "text_part" => [non_file_text_part] 540 } 541 542 expected_output = { 543 "something" => "", 544 "array_of_stringios" => ["One", "Two"], 545 "mixed_types_array" => [ "Three", "NotStringIO" ], 546 "mixed_types_as_checkboxes" => { 547 "strings" => { 548 "nested" => [ file, "String", "StringIO" ] 549 }, 550 }, 551 "ie_mixed_types_as_checkboxes" => { 552 "strings" => { 553 "nested" => [ ie_file, "String", "StringIO" ] 554 }, 555 }, 556 "products" => { 557 "string" => "Apple Computer", 558 "file" => file 559 }, 560 "ie_products" => { 561 "string" => "Microsoft", 562 "file" => ie_file 563 }, 564 "text_part" => "abc" 565 } 566 567 params = ActionController::AbstractRequest.parse_request_parameters(input) 568 assert_equal expected_output, params 569 570 # Lone filenames are preserved. 571 assert_equal 'foo.jpg', params['mixed_types_as_checkboxes']['strings']['nested'].first.original_filename 572 assert_equal 'foo.jpg', params['products']['file'].original_filename 573 574 # But full Windows paths are reduced to their basename. 575 assert_equal 'bar.jpg', params['ie_mixed_types_as_checkboxes']['strings']['nested'].first.original_filename 576 assert_equal 'bar.jpg', params['ie_products']['file'].original_filename 577 end 578 579 def test_parse_params_with_file 580 input = { 581 "customers[boston][first][name]" => [ "David" ], 582 "something_else" => [ "blah" ], 583 "logo" => [ File.new(File.dirname(__FILE__) + "/cgi_test.rb").path ] 584 } 585 586 expected_output = { 587 "customers" => { 588 "boston" => { 589 "first" => { 590 "name" => "David" 591 } 592 } 593 }, 594 "something_else" => "blah", 595 "logo" => File.new(File.dirname(__FILE__) + "/cgi_test.rb").path, 596 } 597 598 assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input) 599 end 600 601 def test_parse_params_with_array 602 input = { "selected[]" => [ "1", "2", "3" ] } 603 604 expected_output = { "selected" => [ "1", "2", "3" ] } 605 606 assert_equal expected_output, ActionController::AbstractRequest.parse_request_parameters(input) 607 end 608 609 def test_parse_params_with_non_alphanumeric_name 610 input = { "a/b[c]" => %w(d) } 611 expected = { "a/b" => { "c" => "d" }} 612 assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) 613 end 614 615 def test_parse_params_with_single_brackets_in_middle 616 input = { "a/b[c]d" => %w(e) } 617 expected = { "a/b" => {} } 618 assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) 619 end 620 621 def test_parse_params_with_separated_brackets 622 input = { "a/b@[c]d[e]" => %w(f) } 623 expected = { "a/b@" => { }} 624 assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) 625 end 626 627 def test_parse_params_with_separated_brackets_and_array 628 input = { "a/b@[c]d[e][]" => %w(f) } 629 expected = { "a/b@" => { }} 630 assert_equal expected , ActionController::AbstractRequest.parse_request_parameters(input) 631 end 632 633 def test_parse_params_with_unmatched_brackets_and_array 634 input = { "a/b@[c][d[e][]" => %w(f) } 635 expected = { "a/b@" => { "c" => { }}} 636 assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) 637 end 638 639 def test_parse_params_with_nil_key 640 input = { nil => nil, "test2" => %w(value1) } 641 expected = { "test2" => "value1" } 642 assert_equal expected, ActionController::AbstractRequest.parse_request_parameters(input) 643 end 644 end 645 646 647 class MultipartRequestParameterParsingTest < Test::Unit::TestCase 648 FIXTURE_PATH = File.dirname(__FILE__) + '/../fixtures/multipart' 649 650 def test_single_parameter 651 params = process('single_parameter') 652 assert_equal({ 'foo' => 'bar' }, params) 653 end 654 655 def test_text_file 656 params = process('text_file') 657 assert_equal %w(file foo), params.keys.sort 658 assert_equal 'bar', params['foo'] 659 660 file = params['file'] 661 assert_kind_of StringIO, file 662 assert_equal 'file.txt', file.original_filename 663 assert_equal "text/plain\r", file.content_type 664 assert_equal 'contents', file.read 665 end 666 667 def test_large_text_file 668 params = process('large_text_file') 669 assert_equal %w(file foo), params.keys.sort 670 assert_equal 'bar', params['foo'] 671 672 file = params['file'] 673 assert_kind_of Tempfile, file 674 assert_equal 'file.txt', file.original_filename 675 assert_equal "text/plain\r", file.content_type 676 assert ('a' * 20480) == file.read 677 end 678 679 def test_binary_file 680 params = process('binary_file') 681 assert_equal %w(file flowers foo), params.keys.sort 682 assert_equal 'bar', params['foo'] 683 684 file = params['file'] 685 assert_kind_of StringIO, file 686 assert_equal 'file.txt', file.original_filename 687 assert_equal "text/plain\r", file.content_type 688 assert_equal 'contents', file.read 689 690 file = params['flowers'] 691 assert_kind_of StringIO, file 692 assert_equal 'flowers.jpg', file.original_filename 693 assert_equal "image/jpeg\r", file.content_type 694 assert_equal 19512, file.size 695 #assert_equal File.read(File.dirname(__FILE__) + '/../../../activerecord/test/fixtures/flowers.jpg'), file.read 696 end 697 698 def test_mixed_files 699 params = process('mixed_files') 700 assert_equal %w(files foo), params.keys.sort 701 assert_equal 'bar', params['foo'] 702 703 # Ruby CGI doesn't handle multipart/mixed for us. 704 assert_kind_of String, params['files'] 705 assert_equal 19756, params['files'].size 706 end 707 708 private 709 def process(name) 710 File.open(File.join(FIXTURE_PATH, name), 'rb') do |file| 711 content_length = file.stat.size.to_s 712 content_type = 'multipart/form-data, boundary=AaB03x' 713 ActionController::AbstractRequest.parse_formatted_request_parameters(file, content_type, content_length) 714 end 715 end 716 end 717 718 719 class XmlParamsParsingTest < Test::Unit::TestCase 720 def test_single_file 362 721 body = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{Base64.encode64('ABC')}</avatar></person>" 363 722 364 person = ActionController::AbstractRequest.parse_formatted_request_parameters( Mime::XML, body)723 person = ActionController::AbstractRequest.parse_formatted_request_parameters(StringIO.new(body), 'application/xml', body.size) 365 724 366 725 assert_equal "image/jpg", person['person']['avatar'].content_type … … 369 728 end 370 729 371 def test_ xml_with_multiple_files730 def test_multiple_files 372 731 body = <<-end_body 373 732 <person> … … 380 739 end_body 381 740 382 person = ActionController::AbstractRequest.parse_formatted_request_parameters( Mime::XML, body)741 person = ActionController::AbstractRequest.parse_formatted_request_parameters(StringIO.new(body), 'application/xml', body.size) 383 742 384 743 assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type … … 391 750 end 392 751 end 393 trunk/actionpack/test/controller/session/cookie_store_test.rb
r6733 r6764 200 200 ENV['QUERY_STRING'] = '' 201 201 202 $stdin, old_stdin = StringIO.new(''), $stdin 203 yield CGI.new 204 ensure 205 $stdin = old_stdin 202 cgi = CGI.new('query', StringIO.new('')) 203 yield cgi if block_given? 204 cgi 206 205 end 207 206 end trunk/actionpack/test/controller/webservice_test.rb
r6740 r6764 37 37 def setup 38 38 @controller = TestController.new 39 ActionController::Base.param_parsers.clear 40 ActionController::Base.param_parsers[Mime::XML] = :xml_simple 39 @default_param_parsers = ActionController::Base.param_parsers.dup 41 40 end 42 41 42 def teardown 43 ActionController::Base.param_parsers = @default_param_parsers 44 end 45 43 46 def test_check_parameters 44 47 process('GET')