knu-mechanize 0.9.3.20090623142847

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