aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb
blob: 1af837c39adf852f76a5b99b22286e3ac0735072 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
require 'cgi'
require 'action_controller/vendor/xml_node'

# Static methods for parsing the query and request parameters that can be used in
# a CGI extension class or testing in isolation.
class CGIMethods #:nodoc:
  class << self
    # Returns a hash with the pairs from the query string. The implicit hash construction that is done in
    # parse_request_params is not done here.
    def parse_query_parameters(query_string)
      parsed_params = {}
  
      query_string.split(/[&;]/).each { |p| 
        # Ignore repeated delimiters.
        next if p.empty?

        k, v = p.split('=',2)
        v = nil if (v && v.empty?)

        k = CGI.unescape(k) if k
        v = CGI.unescape(v) if v

        unless k.include?(?[)
          parsed_params[k] = v
        else
          keys = split_key(k)
          last_key = keys.pop
          last_key = keys.pop if (use_array = last_key.empty?)
          parent = keys.inject(parsed_params) {|h, k| h[k] ||= {}}
          
          if use_array then (parent[last_key] ||= []) << v
          else parent[last_key] = v
          end
        end
      }
  
      parsed_params
    end

    # Returns the request (POST/GET) parameters in a parsed form where pairs such as "customer[address][street]" / 
    # "Somewhere cool!" are translated into a full hash hierarchy, like
    # { "customer" => { "address" => { "street" => "Somewhere cool!" } } }
    def parse_request_parameters(params)
      parsed_params = {}

      for key, value in params
        next unless key
        value = [value] if key =~ /.*\[\]$/
        unless key.include?('[')
          # much faster to test for the most common case first (GET)
          # and avoid the call to build_deep_hash
          parsed_params[key] = get_typed_value(value[0])
        else
          build_deep_hash(get_typed_value(value[0]), parsed_params, get_levels(key))
        end
      end
    
      parsed_params
    end

    def parse_formatted_request_parameters(mime_type, raw_post_data)
      case strategy = ActionController::Base.param_parsers[mime_type]
        when Proc
          strategy.call(raw_post_data)
        when :xml_simple
          raw_post_data.blank? ? {} : Hash.create_from_xml(raw_post_data)
        when :yaml
          YAML.load(raw_post_data)
        when :xml_node
          node = XmlNode.from_xml(raw_post_data)
          { node.node_name => node }
      end
    rescue Object => e
      { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace, 
        "raw_post_data" => raw_post_data, "format" => mime_type }
    end

    private
      # Splits the given key into several pieces. Example keys are 'name', 'person[name]',
      # 'person[name][first]', and 'people[]'. In each instance, an Array instance is returned.
      # 'person[name][first]' produces ['person', 'name', 'first']; 'people[]' produces ['people', '']
      def split_key(key)
        if /^([^\[]+)((?:\[[^\]]*\])+)$/ =~ key
          keys = [$1]
        
          keys.concat($2[1..-2].split(']['))
          keys << '' if key[-2..-1] == '[]' # Have to add it since split will drop empty strings
        
          keys
        else
          [key]
        end
      end
    
      def get_typed_value(value)
        # test most frequent case first
        if value.is_a?(String)
          value
        elsif value.respond_to?(:content_type) && ! value.content_type.blank?
          # Uploaded file
          unless value.respond_to?(:full_original_filename)
            class << value
              alias_method :full_original_filename, :original_filename

              # Take the basename of the upload's original filename.
              # This handles the full Windows paths given by Internet Explorer
              # (and perhaps other broken user agents) without affecting
              # those which give the lone filename.
              # The Windows regexp is adapted from Perl's File::Basename.
              def original_filename
                if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
                  md.captures.first
                else
                  File.basename full_original_filename
                end
              end
            end
          end

          # Return the same value after overriding original_filename.
          value

        elsif value.respond_to?(:read)
          # Value as part of a multipart request
          result = value.read
          value.rewind
          result
        elsif value.class == Array
          value.collect { |v| get_typed_value(v) }
        else
          # other value (neither string nor a multipart request)
          value.to_s
        end
      end
  
      PARAMS_HASH_RE = /^([^\[]+)(\[.*\])?(.)?.*$/
      def get_levels(key)
        all, main, bracketed, trailing = PARAMS_HASH_RE.match(key).to_a
        if main.nil?
          []
        elsif trailing
          [key]
        elsif bracketed
          [main] + bracketed.slice(1...-1).split('][')
        else
          [main]
        end
      end

      def build_deep_hash(value, hash, levels)
        if levels.length == 0
          value
        elsif hash.nil?
          { levels.first => build_deep_hash(value, nil, levels[1..-1]) }
        else
          hash.update({ levels.first => build_deep_hash(value, hash[levels.first], levels[1..-1]) })
        end
      end
  end
end