neocoin-mechanize 2.0.2

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 (174) hide show
  1. data/.autotest +6 -0
  2. data/.gemtest +0 -0
  3. data/CHANGELOG.rdoc +638 -0
  4. data/EXAMPLES.rdoc +187 -0
  5. data/FAQ.rdoc +11 -0
  6. data/GUIDE.rdoc +163 -0
  7. data/LICENSE.rdoc +20 -0
  8. data/Manifest.txt +172 -0
  9. data/README.rdoc +63 -0
  10. data/Rakefile +36 -0
  11. data/examples/flickr_upload.rb +22 -0
  12. data/examples/mech-dump.rb +5 -0
  13. data/examples/proxy_req.rb +7 -0
  14. data/examples/rubyforge.rb +20 -0
  15. data/examples/spider.rb +21 -0
  16. data/lib/mechanize.rb +662 -0
  17. data/lib/mechanize/content_type_error.rb +14 -0
  18. data/lib/mechanize/cookie.rb +85 -0
  19. data/lib/mechanize/cookie_jar.rb +241 -0
  20. data/lib/mechanize/element_matcher.rb +35 -0
  21. data/lib/mechanize/file.rb +80 -0
  22. data/lib/mechanize/file_connection.rb +17 -0
  23. data/lib/mechanize/file_request.rb +26 -0
  24. data/lib/mechanize/file_response.rb +74 -0
  25. data/lib/mechanize/file_saver.rb +37 -0
  26. data/lib/mechanize/form.rb +478 -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 +44 -0
  30. data/lib/mechanize/form/file_upload.rb +23 -0
  31. data/lib/mechanize/form/image_button.rb +20 -0
  32. data/lib/mechanize/form/multi_select_list.rb +83 -0
  33. data/lib/mechanize/form/option.rb +49 -0
  34. data/lib/mechanize/form/radio_button.rb +48 -0
  35. data/lib/mechanize/form/select_list.rb +40 -0
  36. data/lib/mechanize/headers.rb +25 -0
  37. data/lib/mechanize/history.rb +83 -0
  38. data/lib/mechanize/http.rb +3 -0
  39. data/lib/mechanize/http/agent.rb +738 -0
  40. data/lib/mechanize/inspect.rb +88 -0
  41. data/lib/mechanize/monkey_patch.rb +37 -0
  42. data/lib/mechanize/page.rb +408 -0
  43. data/lib/mechanize/page/base.rb +8 -0
  44. data/lib/mechanize/page/frame.rb +27 -0
  45. data/lib/mechanize/page/image.rb +30 -0
  46. data/lib/mechanize/page/label.rb +20 -0
  47. data/lib/mechanize/page/link.rb +82 -0
  48. data/lib/mechanize/page/meta_refresh.rb +56 -0
  49. data/lib/mechanize/pluggable_parsers.rb +101 -0
  50. data/lib/mechanize/redirect_limit_reached_error.rb +16 -0
  51. data/lib/mechanize/redirect_not_get_or_head_error.rb +19 -0
  52. data/lib/mechanize/response_code_error.rb +22 -0
  53. data/lib/mechanize/response_read_error.rb +27 -0
  54. data/lib/mechanize/robots_disallowed_error.rb +29 -0
  55. data/lib/mechanize/unsupported_scheme_error.rb +8 -0
  56. data/lib/mechanize/util.rb +113 -0
  57. data/test/data/htpasswd +1 -0
  58. data/test/data/server.crt +16 -0
  59. data/test/data/server.csr +12 -0
  60. data/test/data/server.key +15 -0
  61. data/test/data/server.pem +15 -0
  62. data/test/helper.rb +175 -0
  63. data/test/htdocs/alt_text.html +10 -0
  64. data/test/htdocs/bad_form_test.html +9 -0
  65. data/test/htdocs/button.jpg +0 -0
  66. data/test/htdocs/canonical_uri.html +9 -0
  67. data/test/htdocs/dir with spaces/foo.html +1 -0
  68. data/test/htdocs/empty_form.html +6 -0
  69. data/test/htdocs/file_upload.html +26 -0
  70. data/test/htdocs/find_link.html +41 -0
  71. data/test/htdocs/form_multi_select.html +16 -0
  72. data/test/htdocs/form_multival.html +37 -0
  73. data/test/htdocs/form_no_action.html +18 -0
  74. data/test/htdocs/form_no_input_name.html +16 -0
  75. data/test/htdocs/form_select.html +16 -0
  76. data/test/htdocs/form_select_all.html +16 -0
  77. data/test/htdocs/form_select_none.html +17 -0
  78. data/test/htdocs/form_select_noopts.html +10 -0
  79. data/test/htdocs/form_set_fields.html +14 -0
  80. data/test/htdocs/form_test.html +188 -0
  81. data/test/htdocs/frame_referer_test.html +10 -0
  82. data/test/htdocs/frame_test.html +30 -0
  83. data/test/htdocs/google.html +13 -0
  84. data/test/htdocs/iframe_test.html +16 -0
  85. data/test/htdocs/index.html +6 -0
  86. data/test/htdocs/link with space.html +5 -0
  87. data/test/htdocs/meta_cookie.html +11 -0
  88. data/test/htdocs/no_title_test.html +6 -0
  89. data/test/htdocs/nofollow.html +9 -0
  90. data/test/htdocs/noindex.html +9 -0
  91. data/test/htdocs/norobots.html +8 -0
  92. data/test/htdocs/rails_3_encoding_hack_form_test.html +27 -0
  93. data/test/htdocs/rel_nofollow.html +8 -0
  94. data/test/htdocs/relative/tc_relative_links.html +21 -0
  95. data/test/htdocs/robots.html +8 -0
  96. data/test/htdocs/robots.txt +2 -0
  97. data/test/htdocs/tc_bad_charset.html +9 -0
  98. data/test/htdocs/tc_bad_links.html +5 -0
  99. data/test/htdocs/tc_base_images.html +10 -0
  100. data/test/htdocs/tc_base_link.html +8 -0
  101. data/test/htdocs/tc_blank_form.html +11 -0
  102. data/test/htdocs/tc_charset.html +6 -0
  103. data/test/htdocs/tc_checkboxes.html +19 -0
  104. data/test/htdocs/tc_encoded_links.html +5 -0
  105. data/test/htdocs/tc_field_precedence.html +11 -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_images.html +8 -0
  109. data/test/htdocs/tc_links.html +18 -0
  110. data/test/htdocs/tc_meta_in_body.html +9 -0
  111. data/test/htdocs/tc_no_attributes.html +16 -0
  112. data/test/htdocs/tc_pretty_print.html +17 -0
  113. data/test/htdocs/tc_radiobuttons.html +17 -0
  114. data/test/htdocs/tc_referer.html +16 -0
  115. data/test/htdocs/tc_relative_links.html +19 -0
  116. data/test/htdocs/tc_textarea.html +23 -0
  117. data/test/htdocs/test_bad_encoding.html +52 -0
  118. data/test/htdocs/test_click.html +11 -0
  119. data/test/htdocs/unusual______.html +5 -0
  120. data/test/servlets.rb +402 -0
  121. data/test/ssl_server.rb +48 -0
  122. data/test/test_cookies.rb +129 -0
  123. data/test/test_form_action.rb +52 -0
  124. data/test/test_form_as_hash.rb +59 -0
  125. data/test/test_form_button.rb +46 -0
  126. data/test/test_frames.rb +34 -0
  127. data/test/test_headers.rb +33 -0
  128. data/test/test_history.rb +118 -0
  129. data/test/test_history_added.rb +16 -0
  130. data/test/test_html_unscape_forms.rb +46 -0
  131. data/test/test_if_modified_since.rb +20 -0
  132. data/test/test_images.rb +19 -0
  133. data/test/test_mechanize.rb +842 -0
  134. data/test/test_mechanize_cookie.rb +345 -0
  135. data/test/test_mechanize_cookie_jar.rb +401 -0
  136. data/test/test_mechanize_file.rb +53 -0
  137. data/test/test_mechanize_file_request.rb +19 -0
  138. data/test/test_mechanize_file_response.rb +21 -0
  139. data/test/test_mechanize_form.rb +576 -0
  140. data/test/test_mechanize_form_check_box.rb +37 -0
  141. data/test/test_mechanize_form_encoding.rb +120 -0
  142. data/test/test_mechanize_form_field.rb +21 -0
  143. data/test/test_mechanize_form_image_button.rb +12 -0
  144. data/test/test_mechanize_form_textarea.rb +51 -0
  145. data/test/test_mechanize_http_agent.rb +697 -0
  146. data/test/test_mechanize_link.rb +84 -0
  147. data/test/test_mechanize_page_encoding.rb +147 -0
  148. data/test/test_mechanize_page_link.rb +382 -0
  149. data/test/test_mechanize_page_meta_refresh.rb +115 -0
  150. data/test/test_mechanize_redirect_not_get_or_head_error.rb +18 -0
  151. data/test/test_mechanize_subclass.rb +22 -0
  152. data/test/test_mechanize_util.rb +92 -0
  153. data/test/test_multi_select.rb +118 -0
  154. data/test/test_no_attributes.rb +13 -0
  155. data/test/test_option.rb +18 -0
  156. data/test/test_pluggable_parser.rb +136 -0
  157. data/test/test_post_form.rb +37 -0
  158. data/test/test_pretty_print.rb +22 -0
  159. data/test/test_radiobutton.rb +75 -0
  160. data/test/test_redirect_limit_reached.rb +39 -0
  161. data/test/test_referer.rb +81 -0
  162. data/test/test_relative_links.rb +40 -0
  163. data/test/test_request.rb +13 -0
  164. data/test/test_response_code.rb +53 -0
  165. data/test/test_robots.rb +72 -0
  166. data/test/test_save_file.rb +48 -0
  167. data/test/test_scheme.rb +48 -0
  168. data/test/test_select.rb +119 -0
  169. data/test/test_select_all.rb +15 -0
  170. data/test/test_select_none.rb +15 -0
  171. data/test/test_select_noopts.rb +18 -0
  172. data/test/test_set_fields.rb +44 -0
  173. data/test/test_ssl_server.rb +20 -0
  174. metadata +354 -0
@@ -0,0 +1,17 @@
1
+ ##
2
+ # Wrapper to make a file URI work like an http URI
3
+
4
+ class Mechanize::FileConnection
5
+
6
+ @instance = nil
7
+
8
+ def self.new *a
9
+ @instance ||= super
10
+ end
11
+
12
+ def request uri, request
13
+ yield Mechanize::FileResponse.new Mechanize::Util.uri_unescape uri.path
14
+ end
15
+
16
+ end
17
+
@@ -0,0 +1,26 @@
1
+ ##
2
+ # A wrapper for a file URI that makes a request that works like a
3
+ # Net::HTTPRequest
4
+
5
+ class Mechanize::FileRequest
6
+
7
+ attr_accessor :uri
8
+
9
+ def initialize uri
10
+ @uri = uri
11
+ end
12
+
13
+ def add_field *a
14
+ end
15
+
16
+ alias []= add_field
17
+
18
+ def path
19
+ @uri.path
20
+ end
21
+
22
+ def each_header
23
+ end
24
+
25
+ end
26
+
@@ -0,0 +1,74 @@
1
+ ##
2
+ # Fake response for dealing with file:/// requests
3
+
4
+ class Mechanize::FileResponse
5
+ def initialize(file_path)
6
+ @file_path = file_path
7
+ end
8
+
9
+ def read_body
10
+ raise Mechanize::ResponseCodeError, self unless File.exist? @file_path
11
+
12
+ if directory?
13
+ yield dir_body
14
+ else
15
+ open @file_path, 'rb' do |io|
16
+ yield io.read
17
+ end
18
+ end
19
+ end
20
+
21
+ def code
22
+ File.exist?(@file_path) ? 200 : 404
23
+ end
24
+
25
+ def content_length
26
+ return dir_body.length if directory?
27
+ File.exist?(@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
+ def http_version
49
+ '0'
50
+ end
51
+
52
+ def message
53
+ File.exist?(@file_path) ? 'OK' : 'Not Found'
54
+ end
55
+
56
+ private
57
+
58
+ def dir_body
59
+ body = %w[<html><body>]
60
+ body.concat Dir[File.join(@file_path, '*')].map { |f|
61
+ "<a href=\"file://#{f}\">#{File.basename(f)}</a>"
62
+ }
63
+ body << %w[</body></html>]
64
+
65
+ body = body.join "\n"
66
+ body.force_encoding Encoding::BINARY if body.respond_to? :force_encoding
67
+ body
68
+ end
69
+
70
+ def directory?
71
+ File.directory?(@file_path)
72
+ end
73
+ end
74
+
@@ -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,478 @@
1
+ require 'mechanize/element_matcher'
2
+ require 'mechanize/form/field'
3
+ require 'mechanize/form/button'
4
+ require 'mechanize/form/file_upload'
5
+ require 'mechanize/form/image_button'
6
+ require 'mechanize/form/multi_select_list'
7
+ require 'mechanize/form/option'
8
+ require 'mechanize/form/radio_button'
9
+ require 'mechanize/form/check_box'
10
+ require 'mechanize/form/select_list'
11
+
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 # => 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
+
27
+ extend Mechanize::ElementMatcher
28
+
29
+ attr_accessor :method, :action, :name
30
+
31
+ attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
32
+
33
+ # Content-Type for form data (i.e. application/x-www-form-urlencoded)
34
+ attr_accessor :enctype
35
+
36
+ # Character encoding of form data (i.e. UTF-8)
37
+ attr_accessor :encoding
38
+
39
+ # When true, character encoding errors will never be never raised on form
40
+ # submission. Default is false
41
+ attr_accessor :ignore_encoding_error
42
+
43
+ alias :elements :fields
44
+
45
+ attr_reader :form_node
46
+ attr_reader :page
47
+
48
+ def initialize(node, mech=nil, page=nil)
49
+ @enctype = node['enctype'] || 'application/x-www-form-urlencoded'
50
+ @form_node = node
51
+ @action = Util.html_unescape(node['action'])
52
+ @method = (node['method'] || 'GET').upcase
53
+ @name = node['name']
54
+ @clicked_buttons = []
55
+ @page = page
56
+ @mech = mech
57
+
58
+ @encoding = node['accept-charset'] || (page && page.encoding) || nil
59
+ @ignore_encoding_error = false
60
+ parse
61
+ end
62
+
63
+ # Returns whether or not the form contains a field with +field_name+
64
+ def has_field?(field_name)
65
+ fields.find { |f| f.name == field_name }
66
+ end
67
+
68
+ alias :has_key? :has_field?
69
+
70
+ def has_value?(value)
71
+ fields.find { |f| f.value == value }
72
+ end
73
+
74
+ def keys; fields.map { |f| f.name }; end
75
+
76
+ def values; fields.map { |f| f.value }; end
77
+
78
+ def submits ; @submits ||= buttons.select { |f| f.class == Submit }; end
79
+ def resets ; @resets ||= buttons.select { |f| f.class == Reset }; end
80
+ def texts ; @texts ||= fields.select { |f| f.class == Text }; end
81
+ def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
82
+ def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
83
+
84
+ def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
85
+ def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
86
+ def text_field?(field_name) texts.find{|f| f.name == field_name}; end
87
+ def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
88
+ def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
89
+
90
+ # This method is a shortcut to get form's DOM id.
91
+ # Common usage:
92
+ # page.form_with(:dom_id => "foorm")
93
+ # Note that you can also use +:id+ to get to this method:
94
+ # page.form_with(:id => "foorm")
95
+ def dom_id
96
+ form_node['id']
97
+ end
98
+
99
+ # Add a field with +field_name+ and +value+
100
+ def add_field!(field_name, value = nil)
101
+ fields << Field.new({'name' => field_name}, value)
102
+ end
103
+
104
+ # This method sets multiple fields on the form. It takes a list of field
105
+ # name, value pairs. If there is more than one field found with the
106
+ # same name, this method will set the first one found. If you want to
107
+ # set the value of a duplicate field, use a value which is a Hash with
108
+ # the key as the index in to the form. The index
109
+ # is zero based. For example, to set the second field named 'foo', you
110
+ # could do the following:
111
+ # form.set_fields( :foo => { 1 => 'bar' } )
112
+ def set_fields(fields = {})
113
+ fields.each do |k,v|
114
+ case v
115
+ when Hash
116
+ v.each do |index, value|
117
+ self.fields_with(:name => k.to_s).[](index).value = value
118
+ end
119
+ else
120
+ value = nil
121
+ index = 0
122
+ [v].flatten.each do |val|
123
+ index = val.to_i if value
124
+ value = val unless value
125
+ end
126
+ self.fields_with(:name => k.to_s).[](index).value = value
127
+ end
128
+ end
129
+ end
130
+
131
+ # Fetch the value of the first input field with the name passed in
132
+ # ==Example
133
+ # Fetch the value set in the input field 'name'
134
+ # puts form['name']
135
+ def [](field_name)
136
+ f = field(field_name)
137
+ f && f.value
138
+ end
139
+
140
+ # Set the value of the first input field with the name passed in
141
+ # ==Example
142
+ # Set the value in the input field 'name' to "Aaron"
143
+ # form['name'] = 'Aaron'
144
+ def []=(field_name, value)
145
+ f = field(field_name)
146
+ if f
147
+ f.value = value
148
+ else
149
+ add_field!(field_name, value)
150
+ end
151
+ end
152
+
153
+ # Treat form fields like accessors.
154
+ def method_missing(meth, *args)
155
+ method = meth.to_s.gsub(/=$/, '')
156
+
157
+ if field(method)
158
+ return field(method).value if args.empty?
159
+ return field(method).value = args[0]
160
+ end
161
+
162
+ super
163
+ end
164
+
165
+ # Submit this form with the button passed in
166
+ def submit button=nil, headers = {}
167
+ @mech.submit(self, button, headers)
168
+ end
169
+
170
+ # Submit form using +button+. Defaults
171
+ # to the first button.
172
+ def click_button(button = buttons.first)
173
+ submit(button)
174
+ end
175
+
176
+ # This method is sub-method of build_query.
177
+ # It converts charset of query value of fields into expected one.
178
+ def proc_query(field)
179
+ return unless field.query_value
180
+ field.query_value.map{|(name, val)|
181
+ [from_native_charset(name), from_native_charset(val.to_s)]
182
+ }
183
+ end
184
+ private :proc_query
185
+
186
+ def from_native_charset str
187
+ Util.from_native_charset(str, encoding, @ignore_encoding_error, @mech && @mech.log)
188
+ end
189
+ private :from_native_charset
190
+
191
+ # This method builds an array of arrays that represent the query
192
+ # parameters to be used with this form. The return value can then
193
+ # be used to create a query string for this form.
194
+ def build_query(buttons = [])
195
+ query = []
196
+ @mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
197
+
198
+ (fields + checkboxes).sort.each do |f|
199
+ case f
200
+ when Form::CheckBox
201
+ if f.checked
202
+ qval = proc_query(f)
203
+ query.push(*qval)
204
+ end
205
+ when Form::Field
206
+ qval = proc_query(f)
207
+ query.push(*qval)
208
+ end
209
+ end
210
+
211
+ radio_groups = {}
212
+ radiobuttons.each do |f|
213
+ fname = from_native_charset(f.name)
214
+ radio_groups[fname] ||= []
215
+ radio_groups[fname] << f
216
+ end
217
+
218
+ # take one radio button from each group
219
+ radio_groups.each_value do |g|
220
+ checked = g.select {|f| f.checked}
221
+
222
+ if checked.size == 1
223
+ f = checked.first
224
+ qval = proc_query(f)
225
+ query.push(*qval)
226
+ elsif checked.size > 1
227
+ raise Mechanize::Error,
228
+ "multiple radiobuttons are checked in the same group!"
229
+ end
230
+ end
231
+
232
+ @clicked_buttons.each { |b|
233
+ qval = proc_query(b)
234
+ query.push(*qval)
235
+ }
236
+ query
237
+ end
238
+
239
+ # This method adds a button to the query. If the form needs to be
240
+ # submitted with multiple buttons, pass each button to this method.
241
+ def add_button_to_query(button)
242
+ @clicked_buttons << button
243
+ end
244
+
245
+ # This method calculates the request data to be sent back to the server
246
+ # for this form, depending on if this is a regular post, get, or a
247
+ # multi-part post,
248
+ def request_data
249
+ query_params = build_query()
250
+
251
+ case @enctype.downcase
252
+ when /^multipart\/form-data/
253
+ boundary = rand_string(20)
254
+ @enctype = "multipart/form-data; boundary=#{boundary}"
255
+
256
+ params = query_params.map do |k,v|
257
+ param_to_multipart(k, v) if k
258
+ end.compact
259
+
260
+ params.concat @file_uploads.map { |f| file_to_multipart(f) }
261
+
262
+ params.map do |part|
263
+ part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
264
+ "--#{boundary}\r\n#{part}"
265
+ end.join('') +
266
+ "--#{boundary}--\r\n"
267
+ else
268
+ Mechanize::Util.build_query_string(query_params)
269
+ end
270
+ end
271
+
272
+ # Removes all fields with name +field_name+.
273
+ def delete_field!(field_name)
274
+ @fields.delete_if{ |f| f.name == field_name}
275
+ end
276
+
277
+ ##
278
+ # :method: field_with(criteria)
279
+ #
280
+ # Find one field that matches +criteria+
281
+ # Example:
282
+ # form.field_with(:id => "exact_field_id").value = 'hello'
283
+
284
+ ##
285
+ # :method: fields_with(criteria)
286
+ #
287
+ # Find all fields that match +criteria+
288
+ # Example:
289
+ # form.fields_with(:value => /foo/).each do |field|
290
+ # field.value = 'hello!'
291
+ # end
292
+
293
+ elements_with :field
294
+
295
+ ##
296
+ # :method: button_with(criteria)
297
+ #
298
+ # Find one button that matches +criteria+
299
+ # Example:
300
+ # form.button_with(:value => /submit/).value = 'hello'
301
+
302
+ ##
303
+ # :method: buttons_with(criteria)
304
+ #
305
+ # Find all buttons that match +criteria+
306
+ # Example:
307
+ # form.buttons_with(:value => /submit/).each do |button|
308
+ # button.value = 'hello!'
309
+ # end
310
+
311
+ elements_with :button
312
+
313
+ ##
314
+ # :method: file_upload_with(criteria)
315
+ #
316
+ # Find one file upload field that matches +criteria+
317
+ # Example:
318
+ # form.file_upload_with(:file_name => /picture/).value = 'foo'
319
+
320
+ ##
321
+ # :method: file_uploads_with(criteria)
322
+ #
323
+ # Find all file upload fields that match +criteria+
324
+ # Example:
325
+ # form.file_uploads_with(:file_name => /picutre/).each do |field|
326
+ # field.value = 'foo!'
327
+ # end
328
+
329
+ elements_with :file_upload
330
+
331
+ ##
332
+ # :method: radiobutton_with(criteria)
333
+ #
334
+ # Find one radio button that matches +criteria+
335
+ # Example:
336
+ # form.radiobutton_with(:name => /woo/).check
337
+
338
+ ##
339
+ # :method: radiobuttons_with(criteria)
340
+ #
341
+ # Find all radio buttons that match +criteria+
342
+ # Example:
343
+ # form.radiobuttons_with(:name => /woo/).each do |field|
344
+ # field.check
345
+ # end
346
+
347
+ elements_with :radiobutton
348
+
349
+ ##
350
+ # :method: checkbox_with(criteria)
351
+ #
352
+ # Find one checkbox that matches +criteria+
353
+ # Example:
354
+ # form.checkbox_with(:name => /woo/).check
355
+
356
+ ##
357
+ # :method: checkboxes_with(criteria)
358
+ #
359
+ # Find all checkboxes that match +criteria+
360
+ # Example:
361
+ # form.checkboxes_with(:name => /woo/).each do |field|
362
+ # field.check
363
+ # end
364
+
365
+ elements_with :checkbox, :checkboxes
366
+
367
+ private
368
+
369
+ def parse
370
+ @fields = []
371
+ @buttons = []
372
+ @file_uploads = []
373
+ @radiobuttons = []
374
+ @checkboxes = []
375
+
376
+ # Find all input tags
377
+ form_node.search('input').each do |node|
378
+ type = (node['type'] || 'text').downcase
379
+ name = node['name']
380
+ next if name.nil? && !%w[submit button image].include?(type)
381
+ case type
382
+ when 'radio'
383
+ @radiobuttons << RadioButton.new(node, self)
384
+ when 'checkbox'
385
+ @checkboxes << CheckBox.new(node, self)
386
+ when 'file'
387
+ @file_uploads << FileUpload.new(node, nil)
388
+ when 'submit'
389
+ @buttons << Submit.new(node)
390
+ when 'button'
391
+ @buttons << Button.new(node)
392
+ when 'reset'
393
+ @buttons << Reset.new(node)
394
+ when 'image'
395
+ @buttons << ImageButton.new(node)
396
+ when 'hidden'
397
+ @fields << Hidden.new(node, node['value'] || '')
398
+ when 'text'
399
+ @fields << Text.new(node, node['value'] || '')
400
+ when 'textarea'
401
+ @fields << Textarea.new(node, node['value'] || '')
402
+ else
403
+ @fields << Field.new(node, node['value'] || '')
404
+ end
405
+ end
406
+
407
+ # Find all textarea tags
408
+ form_node.search('textarea').each do |node|
409
+ next unless node['name']
410
+ @fields << Textarea.new(node, node.inner_text)
411
+ end
412
+
413
+ # Find all select tags
414
+ form_node.search('select').each do |node|
415
+ next unless node['name']
416
+ if node.has_attribute? 'multiple'
417
+ @fields << MultiSelectList.new(node)
418
+ else
419
+ @fields << SelectList.new(node)
420
+ end
421
+ end
422
+
423
+ # Find all submit button tags
424
+ # FIXME: what can I do with the reset buttons?
425
+ form_node.search('button').each do |node|
426
+ type = (node['type'] || 'submit').downcase
427
+ next if type == 'reset'
428
+ @buttons << Button.new(node)
429
+ end
430
+ end
431
+
432
+ def rand_string(len = 10)
433
+ chars = ("a".."z").to_a + ("A".."Z").to_a
434
+ string = ""
435
+ 1.upto(len) { |i| string << chars[rand(chars.size-1)] }
436
+ string
437
+ end
438
+
439
+ def mime_value_quote(str)
440
+ str.gsub(/(["\r\\])/){|s| '\\' + s}
441
+ end
442
+
443
+ def param_to_multipart(name, value)
444
+ return "Content-Disposition: form-data; name=\"" +
445
+ "#{mime_value_quote(name)}\"\r\n" +
446
+ "\r\n#{value}\r\n"
447
+ end
448
+
449
+ def file_to_multipart(file)
450
+ file_name = file.file_name ? ::File.basename(file.file_name) : ''
451
+ body = "Content-Disposition: form-data; name=\"" +
452
+ "#{mime_value_quote(file.name)}\"; " +
453
+ "filename=\"#{mime_value_quote(file_name)}\"\r\n" +
454
+ "Content-Transfer-Encoding: binary\r\n"
455
+
456
+ if file.file_data.nil? and file.file_name
457
+ file.file_data = open(file.file_name, "rb") { |f| f.read }
458
+ file.mime_type =
459
+ WEBrick::HTTPUtils.mime_type(file.file_name,
460
+ WEBrick::HTTPUtils::DefaultMimeTypes)
461
+ end
462
+
463
+ if file.mime_type
464
+ body << "Content-Type: #{file.mime_type}\r\n"
465
+ end
466
+
467
+ body <<
468
+ if file.file_data.respond_to? :read
469
+ "\r\n#{file.file_data.read}\r\n"
470
+ else
471
+ "\r\n#{file.file_data}\r\n"
472
+ end
473
+
474
+ body
475
+ end
476
+
477
+ end
478
+ end