tenderlove-mechanize 0.9.3.20090623142847 → 0.9.3.20090911221705

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (165) hide show
  1. data/Manifest.txt +55 -48
  2. data/Rakefile +12 -22
  3. data/lib/mechanize.rb +618 -4
  4. data/lib/mechanize/chain.rb +33 -0
  5. data/lib/mechanize/chain/auth_headers.rb +78 -0
  6. data/lib/mechanize/chain/body_decoding_handler.rb +46 -0
  7. data/lib/mechanize/chain/connection_resolver.rb +76 -0
  8. data/lib/mechanize/chain/custom_headers.rb +21 -0
  9. data/lib/{www/mechanize → mechanize}/chain/handler.rb +1 -1
  10. data/lib/mechanize/chain/header_resolver.rb +51 -0
  11. data/lib/mechanize/chain/parameter_resolver.rb +22 -0
  12. data/lib/{www/mechanize → mechanize}/chain/post_connect_hook.rb +0 -0
  13. data/lib/mechanize/chain/pre_connect_hook.rb +20 -0
  14. data/lib/mechanize/chain/request_resolver.rb +30 -0
  15. data/lib/mechanize/chain/response_body_parser.rb +38 -0
  16. data/lib/mechanize/chain/response_header_handler.rb +48 -0
  17. data/lib/mechanize/chain/response_reader.rb +39 -0
  18. data/lib/mechanize/chain/ssl_resolver.rb +40 -0
  19. data/lib/mechanize/chain/uri_resolver.rb +75 -0
  20. data/lib/mechanize/content_type_error.rb +14 -0
  21. data/lib/mechanize/cookie.rb +70 -0
  22. data/lib/mechanize/cookie_jar.rb +188 -0
  23. data/lib/mechanize/file.rb +71 -0
  24. data/lib/mechanize/file_response.rb +60 -0
  25. data/lib/mechanize/file_saver.rb +37 -0
  26. data/lib/mechanize/form.rb +378 -0
  27. data/lib/mechanize/form/button.rb +9 -0
  28. data/lib/mechanize/form/check_box.rb +11 -0
  29. data/lib/mechanize/form/field.rb +30 -0
  30. data/lib/mechanize/form/file_upload.rb +22 -0
  31. data/lib/mechanize/form/image_button.rb +21 -0
  32. data/lib/mechanize/form/multi_select_list.rb +67 -0
  33. data/lib/mechanize/form/option.rb +49 -0
  34. data/lib/mechanize/form/radio_button.rb +49 -0
  35. data/lib/mechanize/form/select_list.rb +43 -0
  36. data/lib/mechanize/headers.rb +11 -0
  37. data/lib/mechanize/history.rb +65 -0
  38. data/lib/mechanize/inspect.rb +88 -0
  39. data/lib/{www/mechanize → mechanize}/monkey_patch.rb +4 -6
  40. data/lib/mechanize/page.rb +206 -0
  41. data/lib/mechanize/page/base.rb +8 -0
  42. data/lib/mechanize/page/frame.rb +20 -0
  43. data/lib/mechanize/page/image.rb +26 -0
  44. data/lib/mechanize/page/label.rb +20 -0
  45. data/lib/mechanize/page/link.rb +48 -0
  46. data/lib/mechanize/page/meta.rb +50 -0
  47. data/lib/mechanize/pluggable_parsers.rb +101 -0
  48. data/lib/mechanize/redirect_limit_reached_error.rb +16 -0
  49. data/lib/mechanize/redirect_not_get_or_head_error.rb +18 -0
  50. data/lib/mechanize/response_code_error.rb +22 -0
  51. data/lib/mechanize/unsupported_scheme_error.rb +8 -0
  52. data/lib/mechanize/util.rb +67 -0
  53. data/mechanize.gemspec +8 -8
  54. data/test/chain/test_argument_validator.rb +2 -2
  55. data/test/chain/test_auth_headers.rb +2 -2
  56. data/test/chain/test_custom_headers.rb +2 -2
  57. data/test/chain/test_header_resolver.rb +3 -3
  58. data/test/chain/test_parameter_resolver.rb +4 -4
  59. data/test/chain/test_request_resolver.rb +4 -4
  60. data/test/chain/test_response_reader.rb +3 -3
  61. data/test/helper.rb +1 -1
  62. data/test/htdocs/tc_bad_charset.html +9 -0
  63. data/test/htdocs/tc_charset.html +6 -0
  64. data/test/htdocs/test_bad_encoding.html +52 -0
  65. data/test/test_authenticate.rb +3 -3
  66. data/test/test_bad_links.rb +1 -1
  67. data/test/test_blank_form.rb +1 -1
  68. data/test/test_checkboxes.rb +1 -1
  69. data/test/test_content_type.rb +2 -2
  70. data/test/test_cookie_class.rb +12 -12
  71. data/test/test_cookie_jar.rb +13 -13
  72. data/test/test_cookies.rb +1 -1
  73. data/test/test_encoded_links.rb +1 -1
  74. data/test/test_errors.rb +2 -2
  75. data/test/test_follow_meta.rb +3 -3
  76. data/test/test_form_action.rb +1 -1
  77. data/test/test_form_as_hash.rb +1 -1
  78. data/test/test_form_button.rb +2 -2
  79. data/test/test_form_no_inputname.rb +1 -1
  80. data/test/test_forms.rb +1 -1
  81. data/test/test_frames.rb +1 -1
  82. data/test/test_get_headers.rb +1 -1
  83. data/test/test_gzipping.rb +2 -2
  84. data/test/test_hash_api.rb +1 -1
  85. data/test/test_history.rb +7 -7
  86. data/test/test_history_added.rb +1 -1
  87. data/test/test_html_unscape_forms.rb +7 -7
  88. data/test/test_if_modified_since.rb +1 -1
  89. data/test/test_keep_alive.rb +1 -1
  90. data/test/test_links.rb +2 -2
  91. data/test/test_mech.rb +2 -2
  92. data/test/test_mechanize_file.rb +7 -7
  93. data/test/test_meta.rb +2 -2
  94. data/test/test_multi_select.rb +1 -1
  95. data/test/test_no_attributes.rb +1 -1
  96. data/test/test_option.rb +1 -1
  97. data/test/test_page.rb +3 -3
  98. data/test/test_pluggable_parser.rb +14 -14
  99. data/test/test_post_form.rb +1 -1
  100. data/test/test_pretty_print.rb +2 -2
  101. data/test/test_radiobutton.rb +1 -1
  102. data/test/test_redirect_limit_reached.rb +1 -3
  103. data/test/test_redirect_verb_handling.rb +1 -3
  104. data/test/test_referer.rb +1 -1
  105. data/test/test_relative_links.rb +1 -1
  106. data/test/test_request.rb +1 -1
  107. data/test/test_response_code.rb +3 -3
  108. data/test/test_save_file.rb +3 -3
  109. data/test/test_scheme.rb +3 -3
  110. data/test/test_select.rb +2 -2
  111. data/test/test_select_all.rb +1 -1
  112. data/test/test_select_none.rb +1 -1
  113. data/test/test_select_noopts.rb +1 -1
  114. data/test/test_set_fields.rb +1 -1
  115. data/test/test_ssl_server.rb +1 -1
  116. data/test/test_subclass.rb +1 -1
  117. data/test/test_textarea.rb +1 -1
  118. data/test/test_upload.rb +1 -1
  119. data/test/test_verbs.rb +1 -1
  120. metadata +61 -56
  121. data/lib/www/mechanize.rb +0 -619
  122. data/lib/www/mechanize/chain.rb +0 -34
  123. data/lib/www/mechanize/chain/auth_headers.rb +0 -80
  124. data/lib/www/mechanize/chain/body_decoding_handler.rb +0 -48
  125. data/lib/www/mechanize/chain/connection_resolver.rb +0 -78
  126. data/lib/www/mechanize/chain/custom_headers.rb +0 -23
  127. data/lib/www/mechanize/chain/header_resolver.rb +0 -53
  128. data/lib/www/mechanize/chain/parameter_resolver.rb +0 -24
  129. data/lib/www/mechanize/chain/pre_connect_hook.rb +0 -22
  130. data/lib/www/mechanize/chain/request_resolver.rb +0 -32
  131. data/lib/www/mechanize/chain/response_body_parser.rb +0 -40
  132. data/lib/www/mechanize/chain/response_header_handler.rb +0 -50
  133. data/lib/www/mechanize/chain/response_reader.rb +0 -41
  134. data/lib/www/mechanize/chain/ssl_resolver.rb +0 -42
  135. data/lib/www/mechanize/chain/uri_resolver.rb +0 -77
  136. data/lib/www/mechanize/content_type_error.rb +0 -16
  137. data/lib/www/mechanize/cookie.rb +0 -72
  138. data/lib/www/mechanize/cookie_jar.rb +0 -191
  139. data/lib/www/mechanize/file.rb +0 -73
  140. data/lib/www/mechanize/file_response.rb +0 -62
  141. data/lib/www/mechanize/file_saver.rb +0 -39
  142. data/lib/www/mechanize/form.rb +0 -360
  143. data/lib/www/mechanize/form/button.rb +0 -8
  144. data/lib/www/mechanize/form/check_box.rb +0 -13
  145. data/lib/www/mechanize/form/field.rb +0 -28
  146. data/lib/www/mechanize/form/file_upload.rb +0 -24
  147. data/lib/www/mechanize/form/image_button.rb +0 -23
  148. data/lib/www/mechanize/form/multi_select_list.rb +0 -69
  149. data/lib/www/mechanize/form/option.rb +0 -51
  150. data/lib/www/mechanize/form/radio_button.rb +0 -38
  151. data/lib/www/mechanize/form/select_list.rb +0 -45
  152. data/lib/www/mechanize/headers.rb +0 -12
  153. data/lib/www/mechanize/history.rb +0 -67
  154. data/lib/www/mechanize/inspect.rb +0 -90
  155. data/lib/www/mechanize/page.rb +0 -181
  156. data/lib/www/mechanize/page/base.rb +0 -10
  157. data/lib/www/mechanize/page/frame.rb +0 -22
  158. data/lib/www/mechanize/page/link.rb +0 -50
  159. data/lib/www/mechanize/page/meta.rb +0 -51
  160. data/lib/www/mechanize/pluggable_parsers.rb +0 -103
  161. data/lib/www/mechanize/redirect_limit_reached_error.rb +0 -18
  162. data/lib/www/mechanize/redirect_not_get_or_head_error.rb +0 -20
  163. data/lib/www/mechanize/response_code_error.rb +0 -25
  164. data/lib/www/mechanize/unsupported_scheme_error.rb +0 -10
  165. data/lib/www/mechanize/util.rb +0 -76
@@ -0,0 +1,60 @@
1
+ class Mechanize
2
+ ###
3
+ # Fake response for dealing with file:/// requests
4
+ class FileResponse
5
+ def initialize(file_path)
6
+ @file_path = file_path
7
+ end
8
+
9
+ def read_body
10
+ if ::File.exists?(@file_path)
11
+ if directory?
12
+ yield dir_body
13
+ else
14
+ yield ::File.read(@file_path)
15
+ end
16
+ else
17
+ yield ''
18
+ end
19
+ end
20
+
21
+ def code
22
+ ::File.exists?(@file_path) ? 200 : 400
23
+ end
24
+
25
+ def content_length
26
+ return dir_body.length if directory?
27
+ ::File.exists?(@file_path) ? ::File.stat(@file_path).size : 0
28
+ end
29
+
30
+ def each_header; end
31
+
32
+ def [](key)
33
+ return nil unless key.downcase == 'content-type'
34
+ return 'text/html' if directory?
35
+ return 'text/html' if ['.html', '.xhtml'].any? { |extn|
36
+ @file_path =~ /#{extn}$/
37
+ }
38
+ nil
39
+ end
40
+
41
+ def each
42
+ end
43
+
44
+ def get_fields(key)
45
+ []
46
+ end
47
+
48
+ private
49
+ def dir_body
50
+ '<html><body>' +
51
+ Dir[::File.join(@file_path, '*')].map { |f|
52
+ "<a href=\"file://#{f}\">#{::File.basename(f)}</a>"
53
+ }.join("\n") + '</body></html>'
54
+ end
55
+
56
+ def directory?
57
+ ::File.directory?(@file_path)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,37 @@
1
+ class Mechanize
2
+ # = Synopsis
3
+ # This is a pluggable parser that automatically saves every file
4
+ # it encounters. It saves the files as a tree, reflecting the
5
+ # host and file path.
6
+ #
7
+ # == Example to save all PDF's
8
+ # require 'rubygems'
9
+ # require 'mechanize'
10
+ #
11
+ # agent = Mechanize.new
12
+ # agent.pluggable_parser.pdf = Mechanize::FileSaver
13
+ # agent.get('http://example.com/foo.pdf')
14
+ #
15
+ class FileSaver < File
16
+ attr_reader :filename
17
+
18
+ def initialize(uri=nil, response=nil, body=nil, code=nil)
19
+ super(uri, response, body, code)
20
+ path = uri.path.empty? ? 'index.html' : uri.path.gsub(/^[\/]*/, '')
21
+ path += 'index.html' if path =~ /\/$/
22
+
23
+ split_path = path.split(/\//)
24
+ filename = split_path.length > 0 ? split_path.pop : 'index.html'
25
+ joined_path = split_path.join(::File::SEPARATOR)
26
+ path = if joined_path.empty?
27
+ uri.host
28
+ else
29
+ "#{uri.host}#{::File::SEPARATOR}#{joined_path}"
30
+ end
31
+
32
+ @filename = "#{path}#{::File::SEPARATOR}#{filename}"
33
+ FileUtils.mkdir_p(path)
34
+ save_as(@filename)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,378 @@
1
+ require 'mechanize/form/field'
2
+ require 'mechanize/form/file_upload'
3
+ require 'mechanize/form/button'
4
+ require 'mechanize/form/image_button'
5
+ require 'mechanize/form/radio_button'
6
+ require 'mechanize/form/check_box'
7
+ require 'mechanize/form/multi_select_list'
8
+ require 'mechanize/form/select_list'
9
+ require 'mechanize/form/option'
10
+
11
+ class Mechanize
12
+ # =Synopsis
13
+ # This class encapsulates a form parsed out of an HTML page. Each type
14
+ # of input fields available in a form can be accessed through this object.
15
+ # See GlobalForm for more methods.
16
+ #
17
+ # ==Example
18
+ # Find a form and print out its fields
19
+ # form = page.forms.first # => Mechanize::Form
20
+ # form.fields.each { |f| puts f.name }
21
+ # Set the input field 'name' to "Aaron"
22
+ # form['name'] = 'Aaron'
23
+ # puts form['name']
24
+ class Form
25
+ attr_accessor :method, :action, :name
26
+
27
+ attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
28
+ attr_accessor :enctype
29
+
30
+ alias :elements :fields
31
+
32
+ attr_reader :form_node
33
+ attr_reader :page
34
+
35
+ def initialize(node, mech=nil, page=nil)
36
+ @enctype = node['enctype'] || 'application/x-www-form-urlencoded'
37
+ @form_node = node
38
+ @action = Util.html_unescape(node['action'])
39
+ @method = (node['method'] || 'GET').upcase
40
+ @name = node['name']
41
+ @clicked_buttons = []
42
+ @page = page
43
+ @mech = mech
44
+
45
+ parse
46
+ end
47
+
48
+ # Returns whether or not the form contains a field with +field_name+
49
+ def has_field?(field_name)
50
+ ! fields.find { |f| f.name.eql? field_name }.nil?
51
+ end
52
+
53
+ alias :has_key? :has_field?
54
+
55
+ def has_value?(value)
56
+ ! fields.find { |f| f.value.eql? value }.nil?
57
+ end
58
+
59
+ def keys; fields.map { |f| f.name }; end
60
+
61
+ def values; fields.map { |f| f.value }; end
62
+
63
+ def submits ; @submits ||= buttons.select { |f| f.class == Submit }; end
64
+ def resets ; @resets ||= buttons.select { |f| f.class == Reset }; end
65
+ def texts ; @texts ||= fields.select { |f| f.class == Text }; end
66
+ def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
67
+ def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
68
+
69
+ def submit_button?(button_name) !! submits.find{|f| f.name == button_name}; end
70
+ def reset_button?(button_name) !! resets.find{|f| f.name == button_name}; end
71
+ def text_field?(field_name) !! texts.find{|f| f.name == field_name}; end
72
+ def hidden_field?(field_name) !! hiddens.find{|f| f.name == field_name}; end
73
+ def textarea_field?(field_name) !!textareas.find{|f| f.name == field_name}; end
74
+
75
+ # Add a field with +field_name+ and +value+
76
+ def add_field!(field_name, value = nil)
77
+ fields << Field.new(field_name, value)
78
+ end
79
+
80
+ # This method sets multiple fields on the form. It takes a list of field
81
+ # name, value pairs. If there is more than one field found with the
82
+ # same name, this method will set the first one found. If you want to
83
+ # set the value of a duplicate field, use a value which is a Hash with
84
+ # the key as the index in to the form. The index
85
+ # is zero based. For example, to set the second field named 'foo', you
86
+ # could do the following:
87
+ # form.set_fields( :foo => { 1 => 'bar' } )
88
+ def set_fields(fields = {})
89
+ fields.each do |k,v|
90
+ case v
91
+ when Hash
92
+ v.each do |index, value|
93
+ self.fields_with(:name => k.to_s).[](index).value = value
94
+ end
95
+ else
96
+ value = nil
97
+ index = 0
98
+ [v].flatten.each do |val|
99
+ index = val.to_i unless value.nil?
100
+ value = val if value.nil?
101
+ end
102
+ self.fields_with(:name => k.to_s).[](index).value = value
103
+ end
104
+ end
105
+ end
106
+
107
+ # Fetch the value of the first input field with the name passed in
108
+ # ==Example
109
+ # Fetch the value set in the input field 'name'
110
+ # puts form['name']
111
+ def [](field_name)
112
+ f = field(field_name)
113
+ f && f.value
114
+ end
115
+
116
+ # Set the value of the first input field with the name passed in
117
+ # ==Example
118
+ # Set the value in the input field 'name' to "Aaron"
119
+ # form['name'] = 'Aaron'
120
+ def []=(field_name, value)
121
+ f = field(field_name)
122
+ if f.nil?
123
+ add_field!(field_name, value)
124
+ else
125
+ f.value = value
126
+ end
127
+ end
128
+
129
+ # Treat form fields like accessors.
130
+ def method_missing(id,*args)
131
+ method = id.to_s.gsub(/=$/, '')
132
+ if field(method)
133
+ return field(method).value if args.empty?
134
+ return field(method).value = args[0]
135
+ end
136
+ super
137
+ end
138
+
139
+ # Submit this form with the button passed in
140
+ def submit button=nil, headers = {}
141
+ @mech.submit(self, button, headers)
142
+ end
143
+
144
+ # Submit form using +button+. Defaults
145
+ # to the first button.
146
+ def click_button(button = buttons.first)
147
+ submit(button)
148
+ end
149
+
150
+ # This method is sub-method of build_query.
151
+ # It converts charset of query value of fields into expected one.
152
+ def proc_query(field)
153
+ return unless field.query_value
154
+ field.query_value.map{|(name, val)|
155
+ [from_native_charset(name), from_native_charset(val.to_s)]
156
+ }
157
+ end
158
+ private :proc_query
159
+
160
+ def from_native_charset(str, enc=nil)
161
+ if page
162
+ enc ||= page.encoding
163
+ Util.from_native_charset(str,enc) rescue str
164
+ else
165
+ str
166
+ end
167
+ end
168
+ private :from_native_charset
169
+
170
+ # This method builds an array of arrays that represent the query
171
+ # parameters to be used with this form. The return value can then
172
+ # be used to create a query string for this form.
173
+ def build_query(buttons = [])
174
+ query = []
175
+
176
+ fields().each do |f|
177
+ qval = proc_query(f)
178
+ query.push(*qval)
179
+ end
180
+
181
+ checkboxes().each do |f|
182
+ if f.checked
183
+ qval = proc_query(f)
184
+ query.push(*qval)
185
+ end
186
+ end
187
+
188
+ radio_groups = {}
189
+ radiobuttons().each do |f|
190
+ fname = from_native_charset(f.name)
191
+ radio_groups[fname] ||= []
192
+ radio_groups[fname] << f
193
+ end
194
+
195
+ # take one radio button from each group
196
+ radio_groups.each_value do |g|
197
+ checked = g.select {|f| f.checked}
198
+
199
+ if checked.size == 1
200
+ f = checked.first
201
+ qval = proc_query(f)
202
+ query.push(*qval)
203
+ elsif checked.size > 1
204
+ raise "multiple radiobuttons are checked in the same group!"
205
+ end
206
+ end
207
+
208
+ @clicked_buttons.each { |b|
209
+ qval = proc_query(b)
210
+ query.push(*qval)
211
+ }
212
+ query
213
+ end
214
+
215
+ # This method adds a button to the query. If the form needs to be
216
+ # submitted with multiple buttons, pass each button to this method.
217
+ def add_button_to_query(button)
218
+ @clicked_buttons << button
219
+ end
220
+
221
+ # This method calculates the request data to be sent back to the server
222
+ # for this form, depending on if this is a regular post, get, or a
223
+ # multi-part post,
224
+ def request_data
225
+ query_params = build_query()
226
+ case @enctype.downcase
227
+ when /^multipart\/form-data/
228
+ boundary = rand_string(20)
229
+ @enctype = "multipart/form-data; boundary=#{boundary}"
230
+ params = []
231
+ query_params.each { |k,v| params << param_to_multipart(k, v) unless k.nil? }
232
+ @file_uploads.each { |f| params << file_to_multipart(f) }
233
+ params.collect { |p| "--#{boundary}\r\n#{p}" }.join('') +
234
+ "--#{boundary}--\r\n"
235
+ else
236
+ Mechanize::Util.build_query_string(query_params)
237
+ end
238
+ end
239
+
240
+ # Removes all fields with name +field_name+.
241
+ def delete_field!(field_name)
242
+ @fields.delete_if{ |f| f.name == field_name}
243
+ end
244
+
245
+ { :field => :fields,
246
+ :button => :buttons,
247
+ :file_upload => :file_uploads,
248
+ :radiobutton => :radiobuttons,
249
+ :checkbox => :checkboxes,
250
+ }.each do |singular,plural|
251
+ eval(<<-eomethod)
252
+ def #{plural}_with criteria = {}
253
+ criteria = {:name => criteria} if String === criteria
254
+ f = #{plural}.find_all do |thing|
255
+ criteria.all? { |k,v| v === thing.send(k) }
256
+ end
257
+ yield f if block_given?
258
+ f
259
+ end
260
+
261
+ def #{singular}_with criteria = {}
262
+ f = #{plural}_with(criteria).first
263
+ yield f if block_given?
264
+ f
265
+ end
266
+ alias :#{singular} :#{singular}_with
267
+ eomethod
268
+ end
269
+
270
+ private
271
+ def parse
272
+ @fields = []
273
+ @buttons = []
274
+ @file_uploads = []
275
+ @radiobuttons = []
276
+ @checkboxes = []
277
+
278
+ # Find all input tags
279
+ form_node.search('input').each do |node|
280
+ type = (node['type'] || 'text').downcase
281
+ name = node['name']
282
+ next if name.nil? && !(type == 'submit' || type =='button')
283
+ case type
284
+ when 'radio'
285
+ @radiobuttons << RadioButton.new(node['name'], node['value'], !!node['checked'], self, node)
286
+ when 'checkbox'
287
+ @checkboxes << CheckBox.new(node['name'], node['value'], !!node['checked'], self, node)
288
+ when 'file'
289
+ @file_uploads << FileUpload.new(node['name'], nil)
290
+ when 'submit'
291
+ @buttons << Submit.new(node['name'], node['value'])
292
+ when 'button'
293
+ @buttons << Button.new(node['name'], node['value'])
294
+ when 'reset'
295
+ @buttons << Reset.new(node['name'], node['value'])
296
+ when 'image'
297
+ @buttons << ImageButton.new(node['name'], node['value'])
298
+ when 'hidden'
299
+ @fields << Hidden.new(node['name'], node['value'] || '')
300
+ when 'text'
301
+ @fields << Text.new(node['name'], node['value'] || '')
302
+ when 'textarea'
303
+ @fields << Textarea.new(node['name'], node['value'] || '')
304
+ else
305
+ @fields << Field.new(node['name'], node['value'] || '')
306
+ end
307
+ end
308
+
309
+ # Find all textarea tags
310
+ form_node.search('textarea').each do |node|
311
+ next if node['name'].nil?
312
+ @fields << Field.new(node['name'], node.inner_text)
313
+ end
314
+
315
+ # Find all select tags
316
+ form_node.search('select').each do |node|
317
+ next if node['name'].nil?
318
+ if node.has_attribute? 'multiple'
319
+ @fields << MultiSelectList.new(node['name'], node)
320
+ else
321
+ @fields << SelectList.new(node['name'], node)
322
+ end
323
+ end
324
+
325
+ # Find all submit button tags
326
+ # FIXME: what can I do with the reset buttons?
327
+ form_node.search('button').each do |node|
328
+ type = (node['type'] || 'submit').downcase
329
+ next if type == 'reset'
330
+ @buttons << Button.new(node['name'], node['value'])
331
+ end
332
+ end
333
+
334
+ def rand_string(len = 10)
335
+ chars = ("a".."z").to_a + ("A".."Z").to_a
336
+ string = ""
337
+ 1.upto(len) { |i| string << chars[rand(chars.size-1)] }
338
+ string
339
+ end
340
+
341
+ def mime_value_quote(str)
342
+ str.gsub(/(["\r\\])/){|s| '\\' + s}
343
+ end
344
+
345
+ def param_to_multipart(name, value)
346
+ return "Content-Disposition: form-data; name=\"" +
347
+ "#{mime_value_quote(name)}\"\r\n" +
348
+ "\r\n#{value}\r\n"
349
+ end
350
+
351
+ def file_to_multipart(file)
352
+ file_name = file.file_name ? ::File.basename(file.file_name) : ''
353
+ body = "Content-Disposition: form-data; name=\"" +
354
+ "#{mime_value_quote(file.name)}\"; " +
355
+ "filename=\"#{mime_value_quote(file_name)}\"\r\n" +
356
+ "Content-Transfer-Encoding: binary\r\n"
357
+
358
+ if file.file_data.nil? and ! file.file_name.nil?
359
+ file.file_data = ::File.open(file.file_name, "rb") { |f| f.read }
360
+ file.mime_type = WEBrick::HTTPUtils.mime_type(file.file_name,
361
+ WEBrick::HTTPUtils::DefaultMimeTypes)
362
+ end
363
+
364
+ if file.mime_type != nil
365
+ body << "Content-Type: #{file.mime_type}\r\n"
366
+ end
367
+
368
+ body <<
369
+ if file.file_data.respond_to? :read
370
+ "\r\n#{file.file_data.read}\r\n"
371
+ else
372
+ "\r\n#{file.file_data}\r\n"
373
+ end
374
+
375
+ body
376
+ end
377
+ end
378
+ end