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

root/branches/1-2-stable/actionpack/lib/action_controller/cgi_ext/cgi_methods.rb

Revision 5980, 6.8 kB (checked in by david, 2 years ago)

Keep the irrelevant stuff out with :nodoc:

  • Property svn:executable set to *
Line 
1 require 'cgi'
2 require 'action_controller/vendor/xml_node'
3 require 'strscan'
4
5 # Static methods for parsing the query and request parameters that can be used in
6 # a CGI extension class or testing in isolation.
7 class CGIMethods #:nodoc:
8   class << self
9     # DEPRECATED: Use parse_form_encoded_parameters
10     def parse_query_parameters(query_string)
11       pairs = query_string.split('&').collect do |chunk|
12         next if chunk.empty?
13         key, value = chunk.split('=', 2)
14         next if key.empty?
15         value = (value.nil? || value.empty?) ? nil : CGI.unescape(value)
16         [ CGI.unescape(key), value ]
17       end.compact
18
19       FormEncodedPairParser.new(pairs).result
20     end
21
22     # DEPRECATED: Use parse_form_encoded_parameters
23     def parse_request_parameters(params)
24       parser = FormEncodedPairParser.new
25
26       params = params.dup
27       until params.empty?
28         for key, value in params
29           if key.blank?
30             params.delete key
31           elsif !key.include?('[')
32             # much faster to test for the most common case first (GET)
33             # and avoid the call to build_deep_hash
34             parser.result[key] = get_typed_value(value[0])
35             params.delete key
36           elsif value.is_a?(Array)
37             parser.parse(key, get_typed_value(value.shift))
38             params.delete key if value.empty?
39           else
40             raise TypeError, "Expected array, found #{value.inspect}"
41           end
42         end
43       end
44    
45       parser.result
46     end
47
48     def parse_formatted_request_parameters(mime_type, raw_post_data)
49       case strategy = ActionController::Base.param_parsers[mime_type]
50         when Proc
51           strategy.call(raw_post_data)
52         when :xml_simple
53           raw_post_data.blank? ? {} : Hash.from_xml(raw_post_data)
54         when :yaml
55           YAML.load(raw_post_data)
56         when :xml_node
57           node = XmlNode.from_xml(raw_post_data)
58           { node.node_name => node }
59       end
60     rescue Exception => e # YAML, XML or Ruby code block errors
61       { "exception" => "#{e.message} (#{e.class})", "backtrace" => e.backtrace,
62         "raw_post_data" => raw_post_data, "format" => mime_type }
63     end
64
65     private
66       def get_typed_value(value)
67         case value
68           when String
69             value
70           when NilClass
71             ''
72           when Array
73             value.map { |v| get_typed_value(v) }
74           else
75             # Uploaded file provides content type and filename.
76             if value.respond_to?(:content_type) &&
77                   !value.content_type.blank? &&
78                   !value.original_filename.blank?
79               unless value.respond_to?(:full_original_filename)
80                 class << value
81                   alias_method :full_original_filename, :original_filename
82
83                   # Take the basename of the upload's original filename.
84                   # This handles the full Windows paths given by Internet Explorer
85                   # (and perhaps other broken user agents) without affecting
86                   # those which give the lone filename.
87                   # The Windows regexp is adapted from Perl's File::Basename.
88                   def original_filename
89                     if md = /^(?:.*[:\\\/])?(.*)/m.match(full_original_filename)
90                       md.captures.first
91                     else
92                       File.basename full_original_filename
93                     end
94                   end
95                 end
96               end
97
98               # Return the same value after overriding original_filename.
99               value
100
101             # Multipart values may have content type, but no filename.
102             elsif value.respond_to?(:read)
103               result = value.read
104               value.rewind
105               result
106
107             # Unknown value, neither string nor multipart.
108             else
109               raise "Unknown form value: #{value.inspect}"
110             end
111         end
112       end
113   end
114
115   class FormEncodedPairParser < StringScanner #:nodoc:
116     attr_reader :top, :parent, :result
117
118     def initialize(pairs = [])
119       super('')
120       @result = {}
121       pairs.each { |key, value| parse(key, value) }
122     end
123      
124     KEY_REGEXP = %r{([^\[\]=&]+)}
125     BRACKETED_KEY_REGEXP = %r{\[([^\[\]=&]+)\]}
126    
127     # Parse the query string
128     def parse(key, value)
129       self.string = key
130       @top, @parent = result, nil
131      
132       # First scan the bare key
133       key = scan(KEY_REGEXP) or return
134       key = post_key_check(key)
135            
136       # Then scan as many nestings as present
137       until eos?
138         r = scan(BRACKETED_KEY_REGEXP) or return
139         key = self[1]
140         key = post_key_check(key)
141       end
142  
143       bind(key, value)
144     end
145
146     private
147       # After we see a key, we must look ahead to determine our next action. Cases:
148       #
149       #   [] follows the key. Then the value must be an array.
150       #   = follows the key. (A value comes next)
151       #   & or the end of string follows the key. Then the key is a flag.
152       #   otherwise, a hash follows the key.
153       def post_key_check(key)
154         if scan(/\[\]/) # a[b][] indicates that b is an array
155           container(key, Array)
156           nil
157         elsif check(/\[[^\]]/) # a[b] indicates that a is a hash
158           container(key, Hash)
159           nil
160         else # End of key? We do nothing.
161           key
162         end
163       end
164    
165       # Add a container to the stack.
166       #
167       def container(key, klass)
168         type_conflict! klass, top[key] if top.is_a?(Hash) && top.key?(key) && ! top[key].is_a?(klass)
169         value = bind(key, klass.new)
170         type_conflict! klass, value unless value.is_a?(klass)
171         push(value)
172       end
173    
174       # Push a value onto the 'stack', which is actually only the top 2 items.
175       def push(value)
176         @parent, @top = @top, value
177       end
178    
179       # Bind a key (which may be nil for items in an array) to the provided value.
180       def bind(key, value)
181         if top.is_a? Array
182           if key
183             if top[-1].is_a?(Hash) && ! top[-1].key?(key)
184               top[-1][key] = value
185             else
186               top << {key => value}.with_indifferent_access
187               push top.last
188             end
189           else
190             top << value
191           end
192         elsif top.is_a? Hash
193           key = CGI.unescape(key)
194           parent << (@top = {}) if top.key?(key) && parent.is_a?(Array)
195           return top[key] ||= value
196         else
197           raise ArgumentError, "Don't know what to do: top is #{top.inspect}"
198         end
199
200         return value
201       end
202      
203       def type_conflict!(klass, value)
204         raise TypeError,
205           "Conflicting types for parameter containers. " +
206           "Expected an instance of #{klass}, but found an instance of #{value.class}. " +
207           "This can be caused by passing Array and Hash based paramters qs[]=value&qs[key]=value. "
208       end
209      
210     end
211 end
Note: See TracBrowser for help on using the browser.