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

Changeset 6764

Show
Ignore:
Timestamp:
05/18/07 06:24:50 (3 years ago)
Author:
bitsweat
Message:

Parse url-encoded and multipart requests ourselves instead of delegating to CGI.

Files:

Legend:

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

    r6763 r6764  
    11*SVN* 
     2 
     3* Parse url-encoded and multipart requests ourselves instead of delegating to CGI.  [Jeremy Kemper] 
    24 
    35* 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  
    272272    # 
    273273    #   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 } 
    275277    cattr_accessor :param_parsers 
    276278 
  • trunk/actionpack/lib/action_controller/cgi_ext.rb

    r6740 r6764  
    11require 'action_controller/cgi_ext/stdinput' 
    2 require 'action_controller/cgi_ext/parameters' 
    32require 'action_controller/cgi_ext/query_extension' 
    43require 'action_controller/cgi_ext/cookie' 
     
    76class CGI #:nodoc: 
    87  include ActionController::CgiExt::Stdinput 
    9   include ActionController::CgiExt::Parameters 
    108 
    119  class << self 
  • trunk/actionpack/lib/action_controller/cgi_ext/query_extension.rb

    r6740 r6764  
    66    remove_method :initialize_query 
    77 
    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. 
    129    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' 
    1412 
    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' 
    2016      end 
    2117 
    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 = {} 
    4720    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 
    9622end 
  • trunk/actionpack/lib/action_controller/cgi_process.rb

    r6743 r6764  
    4848 
    4949    def query_string 
    50       if (qs = @cgi.query_string) && !qs.empty? 
     50      qs = @cgi.query_string 
     51      if !qs.blank? 
    5152        qs 
    5253      elsif uri = @env['REQUEST_URI'] 
    53         parts = uri.split('?') 
    54         parts.shift 
    55         parts.join('?') 
     54        uri.split('?', 2).last 
    5655      else 
    5756        @env['QUERY_STRING'] || '' 
     
    7069 
    7170    def query_parameters 
    72       @query_parameters ||= CGI.parse_query_parameters(query_string) 
     71      @query_parameters ||= self.class.parse_query_parameters(query_string) 
    7372    end 
    7473 
    7574    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) 
    8276    end 
    8377 
  • trunk/actionpack/lib/action_controller/integration.rb

    r6759 r6764  
    307307                            "HTTP_HOST"      => host, 
    308308                            "SERVER_PORT"    => https? ? "443" : "80", 
    309                             "HTTPS"          => https? ? "on" : "off")                           
     309                            "HTTPS"          => https? ? "on" : "off") 
    310310          ActionController::UrlRewriter.new(ActionController::CgiRequest.new(cgi), {}) 
    311311        end 
  • trunk/actionpack/lib/action_controller/request.rb

    r6742 r6764  
     1require 'tempfile' 
     2require 'stringio' 
     3require 'strscan' 
     4 
    15module ActionController 
    26  # CgiRequest and TestRequest provide concrete implementations. 
     
    5660    end 
    5761 
     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 
    5870    # Determine whether the body of a HTTP call is URL-encoded (default) 
    5971    # or matches one of the registered param_parsers.  
     
    6577        begin 
    6678          # Receive header sans any charset information. 
    67           content_type = @env['CONTENT_TYPE'].to_s.sub(/\s*\;.*$/, '').strip.downcase 
     79          content_type = content_type_with_parameters.sub(/\s*\;.*$/, '').strip.downcase 
    6880 
    6981          if x_post_format = @env['HTTP_X_POST_DATA_FORMAT'] 
     
    298310 
    299311 
    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 
    308647        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 
    315657  end 
    316658end 
  • trunk/actionpack/test/controller/cgi_test.rb

    r6742 r6764  
    22require 'action_controller/cgi_process' 
    33 
    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 
     4class CgiRequestTest < Test::Unit::TestCase 
    3715  def setup 
    3726    @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"} 
     
    37610    @request = ActionController::CgiRequest.new(@fake_cgi) 
    37711  end 
    378    
     12 
    37913  def test_proxy_request 
    38014    assert_equal 'glu.ttono.us', @request.host_with_port 
    38115  end 
    382    
     16 
    38317  def test_http_host 
    38418    @request_hash.delete "HTTP_X_FORWARDED_HOST" 
    38519    @request_hash['HTTP_HOST'] = "rubyonrails.org:8080" 
    38620    assert_equal "rubyonrails.org:8080", @request.host_with_port 
    387      
     21 
    38822    @request_hash['HTTP_X_FORWARDED_HOST'] = "www.firsthost.org, www.secondhost.org" 
    38923    assert_equal "www.secondhost.org", @request.host 
    39024  end 
    391    
     25 
    39226  def test_http_host_with_default_port_overrides_server_port 
    39327    @request_hash.delete "HTTP_X_FORWARDED_HOST" 
     
    41347    assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], cookies["_session_id"] 
    41448    assert_equal ["yes"], cookies["is_admin"] 
    415      
     49 
    41650    alt_cookies = CGI::Cookie::parse(@alt_cookie_fmt_request_hash["HTTP_COOKIE"]); 
    41751    assert_equal ["c84ace84796670c052c6ceb2451fb0f2"], alt_cookies["_session_id"] 
    41852    assert_equal ["yes"], alt_cookies["is_admin"] 
    41953  end 
    420    
    421   def test_unbalanced_query_string_with_array 
    422    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   end 
    43254end 
  • trunk/actionpack/test/controller/mime_responds_test.rb

    r6517 r6764  
    233233  end 
    234234   
    235   def test_with_content_type 
     235  def test_with_atom_content_type 
    236236    @request.env["CONTENT_TYPE"] = "application/atom+xml" 
    237237    get :made_for_content_type 
    238238    assert_equal "ATOM", @response.body 
    239  
     239  end 
     240 
     241  def test_with_rss_content_type 
    240242    @request.env["CONTENT_TYPE"] = "application/rss+xml" 
    241243    get :made_for_content_type 
  • trunk/actionpack/test/controller/request_test.rb

    r6742 r6764  
    4545    @request.host = "www.rubyonrails.co.uk" 
    4646    assert_equal "rubyonrails.co.uk", @request.domain(2) 
    47      
     47 
    4848    @request.host = "192.168.1.200" 
    4949    assert_nil @request.domain 
     
    6969    assert_equal [], @request.subdomains 
    7070  end 
    71    
     71 
    7272  def test_port_string 
    7373    @request.port = 80 
     
    7777    assert_equal ":8080", @request.port_string 
    7878  end 
    79    
     79 
    8080  def test_relative_url_root 
    8181    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" 
     
    8484 
    8585    @request.env['SERVER_SOFTWARE'] = 'apache/1.2.3 some random text' 
    86        
     86 
    8787    @request.env['SCRIPT_NAME'] = nil 
    8888    assert_equal "", @request.relative_url_root 
     
    100100    @request.relative_url_root = nil 
    101101    @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 
    104104    # apache/scgi case 
    105105    @request.relative_url_root = nil 
    106106    @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 
    109109    @request.relative_url_root = nil 
    110110    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" 
     
    112112    @request.env['RAILS_RELATIVE_URL_ROOT'] = "/hieraki" 
    113113    assert_equal "/hieraki", @request.relative_url_root 
    114      
     114 
    115115    # @env overrides path guess 
    116116    @request.relative_url_root = nil 
     
    120120    assert_equal "/real_url", @request.relative_url_root 
    121121  end 
    122    
     122 
    123123  def test_request_uri 
    124124    @request.env['SERVER_SOFTWARE'] = 'Apache 42.342.3432' 
    125    
     125 
    126126    @request.relative_url_root = nil 
    127127    @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri?mapped=1" 
    128128    assert_equal "/path/of/some/uri?mapped=1", @request.request_uri 
    129129    assert_equal "/path/of/some/uri", @request.path 
    130      
     130 
    131131    @request.relative_url_root = nil 
    132132    @request.set_REQUEST_URI "http://www.rubyonrails.org/path/of/some/uri" 
     
    148148    assert_equal "/?m=b", @request.request_uri 
    149149    assert_equal "/", @request.path 
    150      
     150 
    151151    @request.relative_url_root = nil 
    152152    @request.set_REQUEST_URI "/" 
    153153    @request.env['SCRIPT_NAME'] = "/dispatch.cgi" 
    154154    assert_equal "/", @request.request_uri 
    155     assert_equal "/", @request.path     
     155    assert_equal "/", @request.path 
    156156 
    157157    @request.relative_url_root = nil 
     
    159159    @request.env['SCRIPT_NAME'] = "/hieraki/dispatch.cgi" 
    160160    assert_equal "/hieraki/", @request.request_uri 
    161     assert_equal "/", @request.path     
     161    assert_equal "/", @request.path 
    162162 
    163163    @request.relative_url_root = nil 
     
    166166    assert_equal "/collaboration/hieraki/books/edit/2", @request.request_uri 
    167167    assert_equal "/books/edit/2", @request.path 
    168    
     168 
    169169    # The following tests are for when REQUEST_URI is not supplied (as in IIS) 
    170170    @request.relative_url_root = nil 
     
    239239    @request.port = 80 
    240240    assert_equal "rubyonrails.org", @request.host_with_port 
    241      
     241 
    242242    @request.host = "rubyonrails.org" 
    243243    @request.port = 81 
    244244    assert_equal "rubyonrails.org:81", @request.host_with_port 
    245245  end 
    246    
     246 
    247247  def test_server_software 
    248248    assert_equal nil, @request.server_software 
    249    
     249 
    250250    @request.env['SERVER_SOFTWARE'] = 'Apache3.422' 
    251251    assert_equal 'apache', @request.server_software 
    252      
     252 
    253253    @request.env['SERVER_SOFTWARE'] = 'lighttpd(1.1.4)' 
    254254    assert_equal 'lighttpd', @request.server_software 
    255255  end 
    256    
     256 
    257257  def test_xml_http_request 
    258258    assert !@request.xml_http_request? 
    259259    assert !@request.xhr? 
    260      
     260 
    261261    @request.env['HTTP_X_REQUESTED_WITH'] = "DefinitelyNotAjax1.0" 
    262262    assert !@request.xml_http_request? 
    263263    assert !@request.xhr? 
    264      
     264 
    265265    @request.env['HTTP_X_REQUESTED_WITH'] = "XMLHttpRequest" 
    266266    assert @request.xml_http_request? 
     
    302302    end 
    303303  end 
    304    
     304 
    305305  def test_head_masquarading_as_get 
    306306    set_request_method_to :head 
     
    314314    assert_equal Mime::XML, @request.format 
    315315  end 
    316    
     316 
    317317  def test_xhtml_format 
    318318    @request.instance_eval { @parameters = { :format => 'xhtml' } } 
    319319    assert_equal Mime::HTML, @request.format 
    320320  end 
    321    
     321 
    322322  def test_txt_format 
    323323    @request.instance_eval { @parameters = { :format => 'txt' } } 
     
    330330    assert_equal Mime::JS, @request.format 
    331331  end 
    332    
     332 
    333333  def test_content_type 
    334334    @request.env["CONTENT_TYPE"] = "text/html" 
     
    339339    assert_equal nil, @request.content_type 
    340340  end 
    341    
     341 
    342342  def test_content_type_xml 
    343343    @request.env["CONTENT_TYPE"] = "application/xml" 
     
    358358 
    359359 
    360 class RequestParameterParsingTest < Test::Unit::TestCase 
    361   def test_xml_with_single_file 
     360class 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 
     644end 
     645 
     646 
     647class 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 
     716end 
     717 
     718 
     719class XmlParamsParsingTest < Test::Unit::TestCase 
     720  def test_single_file 
    362721    body = "<person><name>David</name><avatar type='file' name='me.jpg' content_type='image/jpg'>#{Base64.encode64('ABC')}</avatar></person>" 
    363722 
    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
    365724 
    366725    assert_equal "image/jpg", person['person']['avatar'].content_type 
     
    369728  end 
    370729 
    371   def test_xml_with_multiple_files 
     730  def test_multiple_files 
    372731    body = <<-end_body 
    373732      <person> 
     
    380739    end_body 
    381740 
    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
    383742 
    384743    assert_equal "image/jpg", person['person']['avatars']['avatar'].first.content_type 
     
    391750  end 
    392751end 
    393  
  • trunk/actionpack/test/controller/session/cookie_store_test.rb

    r6733 r6764  
    200200      ENV['QUERY_STRING'] = '' 
    201201 
    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 
    206205    end 
    207206end 
  • trunk/actionpack/test/controller/webservice_test.rb

    r6740 r6764  
    3737  def setup 
    3838    @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 
    4140  end 
    42    
     41 
     42  def teardown 
     43    ActionController::Base.param_parsers = @default_param_parsers 
     44  end 
     45 
    4346  def test_check_parameters 
    4447    process('GET')