mechanize 1.0.1.beta.20110107104205 → 2.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of mechanize might be problematic. Click here for more details.
- data.tar.gz.sig +2 -0
- data/{lib/mechanize/chain/post_connect_hook.rb → .gemtest} +0 -0
- data/CHANGELOG.rdoc +51 -6
- data/EXAMPLES.rdoc +5 -3
- data/GUIDE.rdoc +72 -32
- data/LICENSE.rdoc +20 -340
- data/Manifest.txt +20 -27
- data/README.rdoc +12 -9
- data/Rakefile +5 -2
- data/examples/spider.rb +13 -2
- data/lib/mechanize.rb +545 -267
- data/lib/mechanize/content_type_error.rb +1 -1
- data/lib/mechanize/cookie.rb +72 -65
- data/lib/mechanize/cookie_jar.rb +197 -148
- data/lib/mechanize/element_matcher.rb +35 -0
- data/lib/mechanize/file.rb +3 -1
- data/lib/mechanize/file_connection.rb +17 -0
- data/lib/mechanize/file_request.rb +26 -0
- data/lib/mechanize/file_response.rb +61 -47
- data/lib/mechanize/form.rb +57 -58
- data/lib/mechanize/form/image_button.rb +2 -3
- data/lib/mechanize/form/multi_select_list.rb +71 -55
- data/lib/mechanize/form/select_list.rb +34 -62
- data/lib/mechanize/monkey_patch.rb +13 -11
- data/lib/mechanize/page.rb +277 -270
- data/lib/mechanize/page/image.rb +6 -2
- data/lib/mechanize/redirect_limit_reached_error.rb +1 -1
- data/lib/mechanize/redirect_not_get_or_head_error.rb +1 -1
- data/lib/mechanize/response_code_error.rb +3 -3
- data/lib/mechanize/unsupported_scheme_error.rb +1 -1
- data/lib/mechanize/uri_resolver.rb +82 -0
- data/lib/mechanize/util.rb +76 -60
- data/test/helper.rb +35 -5
- data/test/htdocs/dir with spaces/foo.html +1 -0
- data/test/htdocs/rails_3_encoding_hack_form_test.html +27 -0
- data/test/htdocs/tc_base_images.html +10 -0
- data/test/htdocs/tc_images.html +8 -0
- data/test/htdocs/test_click.html +11 -0
- data/test/servlets.rb +3 -2
- data/test/test_authenticate.rb +5 -5
- data/test/test_errors.rb +8 -8
- data/test/test_follow_meta.rb +4 -4
- data/test/test_form_as_hash.rb +4 -4
- data/test/test_forms.rb +3 -7
- data/test/test_hash_api.rb +2 -2
- data/test/test_headers.rb +1 -1
- data/test/test_images.rb +19 -0
- data/test/test_mech.rb +6 -6
- data/test/test_mechanize.rb +687 -0
- data/test/{test_cookie_class.rb → test_mechanize_cookie.rb} +52 -45
- data/test/test_mechanize_cookie_jar.rb +400 -0
- data/test/test_mechanize_file.rb +7 -1
- data/test/test_mechanize_file_request.rb +19 -0
- data/test/test_mechanize_file_response.rb +21 -0
- data/test/test_mechanize_form_image_button.rb +12 -0
- data/test/test_mechanize_page.rb +165 -0
- data/test/test_mechanize_uri_resolver.rb +29 -0
- data/test/{test_util.rb → test_mechanize_util.rb} +1 -1
- data/test/test_multi_select.rb +12 -0
- data/test/test_post_form.rb +7 -0
- data/test/test_redirect_verb_handling.rb +6 -6
- data/test/test_scheme.rb +0 -7
- data/test/test_verbs.rb +3 -3
- metadata +106 -72
- metadata.gz.sig +0 -0
- data/lib/mechanize/chain.rb +0 -36
- data/lib/mechanize/chain/auth_headers.rb +0 -78
- data/lib/mechanize/chain/body_decoding_handler.rb +0 -50
- data/lib/mechanize/chain/connection_resolver.rb +0 -28
- data/lib/mechanize/chain/custom_headers.rb +0 -21
- data/lib/mechanize/chain/handler.rb +0 -9
- data/lib/mechanize/chain/header_resolver.rb +0 -48
- data/lib/mechanize/chain/parameter_resolver.rb +0 -22
- data/lib/mechanize/chain/pre_connect_hook.rb +0 -20
- data/lib/mechanize/chain/request_resolver.rb +0 -31
- data/lib/mechanize/chain/response_body_parser.rb +0 -36
- data/lib/mechanize/chain/response_header_handler.rb +0 -34
- data/lib/mechanize/chain/response_reader.rb +0 -39
- data/lib/mechanize/chain/ssl_resolver.rb +0 -40
- data/lib/mechanize/chain/uri_resolver.rb +0 -75
- data/test/chain/test_argument_validator.rb +0 -14
- data/test/chain/test_auth_headers.rb +0 -25
- data/test/chain/test_custom_headers.rb +0 -18
- data/test/chain/test_header_resolver.rb +0 -27
- data/test/chain/test_parameter_resolver.rb +0 -35
- data/test/chain/test_request_resolver.rb +0 -29
- data/test/chain/test_response_reader.rb +0 -24
- data/test/test_cookie_jar.rb +0 -324
- data/test/test_page.rb +0 -124
@@ -0,0 +1,35 @@
|
|
1
|
+
module Mechanize::ElementMatcher
|
2
|
+
|
3
|
+
def elements_with singular, plural = "#{singular}s"
|
4
|
+
class_eval <<-CODE
|
5
|
+
def #{plural}_with criteria = {}
|
6
|
+
criteria = if String === criteria then
|
7
|
+
{:name => criteria}
|
8
|
+
else
|
9
|
+
criteria.map do |k, v|
|
10
|
+
k = :dom_id if k.to_sym == :id
|
11
|
+
[k, v]
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
f = #{plural}.find_all do |thing|
|
16
|
+
criteria.all? do |k,v|
|
17
|
+
v === thing.send(k)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
yield f if block_given?
|
21
|
+
f
|
22
|
+
end
|
23
|
+
|
24
|
+
def #{singular}_with criteria = {}
|
25
|
+
f = #{plural}_with(criteria).first
|
26
|
+
yield f if block_given?
|
27
|
+
f
|
28
|
+
end
|
29
|
+
|
30
|
+
alias :#{singular} :#{singular}_with
|
31
|
+
CODE
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
data/lib/mechanize/file.rb
CHANGED
@@ -29,7 +29,9 @@ class Mechanize
|
|
29
29
|
alias :content :body
|
30
30
|
|
31
31
|
def initialize(uri=nil, response=nil, body=nil, code=nil)
|
32
|
-
@uri
|
32
|
+
@uri = uri
|
33
|
+
@body = body
|
34
|
+
@code = code
|
33
35
|
@response = Headers.new
|
34
36
|
|
35
37
|
# Copy the headers in to a hash to prevent memory leaks
|
@@ -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
|
+
|
@@ -1,60 +1,74 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
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
|
8
11
|
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
yield ::File.read(@file_path)
|
15
|
-
end
|
16
|
-
else
|
17
|
-
yield ''
|
12
|
+
if directory?
|
13
|
+
yield dir_body
|
14
|
+
else
|
15
|
+
open @file_path, 'rb' do |io|
|
16
|
+
yield io.read
|
18
17
|
end
|
19
18
|
end
|
19
|
+
end
|
20
20
|
|
21
|
-
|
22
|
-
|
23
|
-
|
21
|
+
def code
|
22
|
+
File.exist?(@file_path) ? 200 : 404
|
23
|
+
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
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
29
|
|
30
|
-
|
30
|
+
def each_header; end
|
31
31
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
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
40
|
|
41
|
-
|
42
|
-
|
41
|
+
def each
|
42
|
+
end
|
43
43
|
|
44
|
-
|
45
|
-
|
46
|
-
|
44
|
+
def get_fields(key)
|
45
|
+
[]
|
46
|
+
end
|
47
47
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
Dir[::File.join(@file_path, '*')].map { |f|
|
52
|
-
"<a href=\"file://#{f}\">#{::File.basename(f)}</a>"
|
53
|
-
}.join("\n") + '</body></html>'
|
54
|
-
end
|
48
|
+
def http_version
|
49
|
+
'0'
|
50
|
+
end
|
55
51
|
|
56
|
-
|
57
|
-
|
58
|
-
|
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)
|
59
72
|
end
|
60
73
|
end
|
74
|
+
|
data/lib/mechanize/form.rb
CHANGED
@@ -1,12 +1,13 @@
|
|
1
|
+
require 'mechanize/element_matcher'
|
1
2
|
require 'mechanize/form/field'
|
2
|
-
require 'mechanize/form/file_upload'
|
3
3
|
require 'mechanize/form/button'
|
4
|
+
require 'mechanize/form/file_upload'
|
4
5
|
require 'mechanize/form/image_button'
|
6
|
+
require 'mechanize/form/multi_select_list'
|
7
|
+
require 'mechanize/form/option'
|
5
8
|
require 'mechanize/form/radio_button'
|
6
9
|
require 'mechanize/form/check_box'
|
7
|
-
require 'mechanize/form/multi_select_list'
|
8
10
|
require 'mechanize/form/select_list'
|
9
|
-
require 'mechanize/form/option'
|
10
11
|
|
11
12
|
class Mechanize
|
12
13
|
# =Synopsis
|
@@ -22,6 +23,9 @@ class Mechanize
|
|
22
23
|
# form['name'] = 'Aaron'
|
23
24
|
# puts form['name']
|
24
25
|
class Form
|
26
|
+
|
27
|
+
extend Mechanize::ElementMatcher
|
28
|
+
|
25
29
|
attr_accessor :method, :action, :name
|
26
30
|
|
27
31
|
attr_reader :fields, :buttons, :file_uploads, :radiobuttons, :checkboxes
|
@@ -47,13 +51,13 @@ class Mechanize
|
|
47
51
|
|
48
52
|
# Returns whether or not the form contains a field with +field_name+
|
49
53
|
def has_field?(field_name)
|
50
|
-
|
54
|
+
fields.find { |f| f.name == field_name }
|
51
55
|
end
|
52
56
|
|
53
57
|
alias :has_key? :has_field?
|
54
58
|
|
55
59
|
def has_value?(value)
|
56
|
-
|
60
|
+
fields.find { |f| f.value == value }
|
57
61
|
end
|
58
62
|
|
59
63
|
def keys; fields.map { |f| f.name }; end
|
@@ -66,11 +70,11 @@ class Mechanize
|
|
66
70
|
def hiddens ; @hiddens ||= fields.select { |f| f.class == Hidden }; end
|
67
71
|
def textareas; @textareas ||= fields.select { |f| f.class == Textarea }; end
|
68
72
|
|
69
|
-
def submit_button?(button_name)
|
70
|
-
def reset_button?(button_name)
|
71
|
-
def text_field?(field_name)
|
72
|
-
def hidden_field?(field_name)
|
73
|
-
def textarea_field?(field_name)
|
73
|
+
def submit_button?(button_name) submits.find{|f| f.name == button_name}; end
|
74
|
+
def reset_button?(button_name) resets.find{|f| f.name == button_name}; end
|
75
|
+
def text_field?(field_name) texts.find{|f| f.name == field_name}; end
|
76
|
+
def hidden_field?(field_name) hiddens.find{|f| f.name == field_name}; end
|
77
|
+
def textarea_field?(field_name) textareas.find{|f| f.name == field_name}; end
|
74
78
|
|
75
79
|
# This method is a shortcut to get form's DOM id.
|
76
80
|
# Common usage:
|
@@ -105,8 +109,8 @@ class Mechanize
|
|
105
109
|
value = nil
|
106
110
|
index = 0
|
107
111
|
[v].flatten.each do |val|
|
108
|
-
index = val.to_i
|
109
|
-
value = val
|
112
|
+
index = val.to_i if value
|
113
|
+
value = val unless value
|
110
114
|
end
|
111
115
|
self.fields_with(:name => k.to_s).[](index).value = value
|
112
116
|
end
|
@@ -128,20 +132,22 @@ class Mechanize
|
|
128
132
|
# form['name'] = 'Aaron'
|
129
133
|
def []=(field_name, value)
|
130
134
|
f = field(field_name)
|
131
|
-
if f
|
132
|
-
add_field!(field_name, value)
|
133
|
-
else
|
135
|
+
if f
|
134
136
|
f.value = value
|
137
|
+
else
|
138
|
+
add_field!(field_name, value)
|
135
139
|
end
|
136
140
|
end
|
137
141
|
|
138
142
|
# Treat form fields like accessors.
|
139
|
-
def method_missing(
|
140
|
-
method =
|
143
|
+
def method_missing(meth, *args)
|
144
|
+
method = meth.to_s.gsub(/=$/, '')
|
145
|
+
|
141
146
|
if field(method)
|
142
147
|
return field(method).value if args.empty?
|
143
148
|
return field(method).value = args[0]
|
144
149
|
end
|
150
|
+
|
145
151
|
super
|
146
152
|
end
|
147
153
|
|
@@ -206,7 +212,8 @@ class Mechanize
|
|
206
212
|
qval = proc_query(f)
|
207
213
|
query.push(*qval)
|
208
214
|
elsif checked.size > 1
|
209
|
-
raise
|
215
|
+
raise Mechanize::Error,
|
216
|
+
"multiple radiobuttons are checked in the same group!"
|
210
217
|
end
|
211
218
|
end
|
212
219
|
|
@@ -228,14 +235,22 @@ class Mechanize
|
|
228
235
|
# multi-part post,
|
229
236
|
def request_data
|
230
237
|
query_params = build_query()
|
238
|
+
|
231
239
|
case @enctype.downcase
|
232
240
|
when /^multipart\/form-data/
|
233
241
|
boundary = rand_string(20)
|
234
242
|
@enctype = "multipart/form-data; boundary=#{boundary}"
|
235
|
-
|
236
|
-
query_params.
|
237
|
-
|
238
|
-
|
243
|
+
|
244
|
+
params = query_params.map do |k,v|
|
245
|
+
param_to_multipart(k, v) if k
|
246
|
+
end.compact
|
247
|
+
|
248
|
+
params.concat @file_uploads.map { |f| file_to_multipart(f) }
|
249
|
+
|
250
|
+
params.map do |part|
|
251
|
+
part.force_encoding('ASCII-8BIT') if part.respond_to? :force_encoding
|
252
|
+
"--#{boundary}\r\n#{part}"
|
253
|
+
end.join('') +
|
239
254
|
"--#{boundary}--\r\n"
|
240
255
|
else
|
241
256
|
Mechanize::Util.build_query_string(query_params)
|
@@ -263,6 +278,8 @@ class Mechanize
|
|
263
278
|
# field.value = 'hello!'
|
264
279
|
# end
|
265
280
|
|
281
|
+
elements_with :field
|
282
|
+
|
266
283
|
##
|
267
284
|
# :method: button_with(criteria)
|
268
285
|
#
|
@@ -279,6 +296,8 @@ class Mechanize
|
|
279
296
|
# button.value = 'hello!'
|
280
297
|
# end
|
281
298
|
|
299
|
+
elements_with :button
|
300
|
+
|
282
301
|
##
|
283
302
|
# :method: file_upload_with(criteria)
|
284
303
|
#
|
@@ -295,6 +314,8 @@ class Mechanize
|
|
295
314
|
# field.value = 'foo!'
|
296
315
|
# end
|
297
316
|
|
317
|
+
elements_with :file_upload
|
318
|
+
|
298
319
|
##
|
299
320
|
# :method: radiobutton_with(criteria)
|
300
321
|
#
|
@@ -311,6 +332,8 @@ class Mechanize
|
|
311
332
|
# field.check
|
312
333
|
# end
|
313
334
|
|
335
|
+
elements_with :radiobutton
|
336
|
+
|
314
337
|
##
|
315
338
|
# :method: checkbox_with(criteria)
|
316
339
|
#
|
@@ -327,36 +350,10 @@ class Mechanize
|
|
327
350
|
# field.check
|
328
351
|
# end
|
329
352
|
|
330
|
-
|
331
|
-
{ :field => :fields,
|
332
|
-
:button => :buttons,
|
333
|
-
:file_upload => :file_uploads,
|
334
|
-
:radiobutton => :radiobuttons,
|
335
|
-
:checkbox => :checkboxes,
|
336
|
-
}.each do |singular,plural|
|
337
|
-
eval(<<-eomethod)
|
338
|
-
def #{plural}_with criteria = {}
|
339
|
-
criteria = {:name => criteria} if String === criteria
|
340
|
-
f = #{plural}.find_all do |thing|
|
341
|
-
criteria.all? do |k,v|
|
342
|
-
k = :dom_id if(k.to_s == "id")
|
343
|
-
v === thing.send(k)
|
344
|
-
end
|
345
|
-
end
|
346
|
-
yield f if block_given?
|
347
|
-
f
|
348
|
-
end
|
349
|
-
|
350
|
-
def #{singular}_with criteria = {}
|
351
|
-
f = #{plural}_with(criteria).first
|
352
|
-
yield f if block_given?
|
353
|
-
f
|
354
|
-
end
|
355
|
-
alias :#{singular} :#{singular}_with
|
356
|
-
eomethod
|
357
|
-
end
|
353
|
+
elements_with :checkbox, :checkboxes
|
358
354
|
|
359
355
|
private
|
356
|
+
|
360
357
|
def parse
|
361
358
|
@fields = []
|
362
359
|
@buttons = []
|
@@ -368,7 +365,7 @@ class Mechanize
|
|
368
365
|
form_node.search('input').each do |node|
|
369
366
|
type = (node['type'] || 'text').downcase
|
370
367
|
name = node['name']
|
371
|
-
next if name.nil? &&
|
368
|
+
next if name.nil? && !%w[submit button image].include?(type)
|
372
369
|
case type
|
373
370
|
when 'radio'
|
374
371
|
@radiobuttons << RadioButton.new(node, self)
|
@@ -397,13 +394,13 @@ class Mechanize
|
|
397
394
|
|
398
395
|
# Find all textarea tags
|
399
396
|
form_node.search('textarea').each do |node|
|
400
|
-
next
|
397
|
+
next unless node['name']
|
401
398
|
@fields << Field.new(node, node.inner_text)
|
402
399
|
end
|
403
400
|
|
404
401
|
# Find all select tags
|
405
402
|
form_node.search('select').each do |node|
|
406
|
-
next
|
403
|
+
next unless node['name']
|
407
404
|
if node.has_attribute? 'multiple'
|
408
405
|
@fields << MultiSelectList.new(node)
|
409
406
|
else
|
@@ -444,13 +441,14 @@ class Mechanize
|
|
444
441
|
"filename=\"#{mime_value_quote(file_name)}\"\r\n" +
|
445
442
|
"Content-Transfer-Encoding: binary\r\n"
|
446
443
|
|
447
|
-
if file.file_data.nil? and
|
448
|
-
file.file_data =
|
449
|
-
file.mime_type =
|
450
|
-
|
444
|
+
if file.file_data.nil? and file.file_name
|
445
|
+
file.file_data = open(file.file_name, "rb") { |f| f.read }
|
446
|
+
file.mime_type =
|
447
|
+
WEBrick::HTTPUtils.mime_type(file.file_name,
|
448
|
+
WEBrick::HTTPUtils::DefaultMimeTypes)
|
451
449
|
end
|
452
450
|
|
453
|
-
if file.mime_type
|
451
|
+
if file.mime_type
|
454
452
|
body << "Content-Type: #{file.mime_type}\r\n"
|
455
453
|
end
|
456
454
|
|
@@ -463,5 +461,6 @@ class Mechanize
|
|
463
461
|
|
464
462
|
body
|
465
463
|
end
|
464
|
+
|
466
465
|
end
|
467
466
|
end
|