mechanize 0.8.4 → 0.8.5
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/EXAMPLES.txt +1 -1
- data/GUIDE.txt +11 -12
- data/History.txt +24 -0
- data/Manifest.txt +2 -1
- data/README.txt +1 -1
- data/Rakefile +2 -2
- data/lib/www/mechanize.rb +43 -11
- data/lib/www/mechanize/chain/header_resolver.rb +1 -1
- data/lib/www/mechanize/chain/request_resolver.rb +4 -0
- data/lib/www/mechanize/chain/response_body_parser.rb +1 -1
- data/lib/www/mechanize/chain/response_reader.rb +1 -1
- data/lib/www/mechanize/chain/ssl_resolver.rb +1 -1
- data/lib/www/mechanize/chain/uri_resolver.rb +21 -9
- data/lib/www/mechanize/cookie_jar.rb +11 -6
- data/lib/www/mechanize/file_response.rb +2 -0
- data/lib/www/mechanize/form.rb +33 -13
- data/lib/www/mechanize/form/multi_select_list.rb +2 -2
- data/lib/www/mechanize/form/radio_button.rb +1 -1
- data/lib/www/mechanize/list.rb +15 -38
- data/lib/www/mechanize/page.rb +17 -10
- data/lib/www/mechanize/page/link.rb +2 -2
- data/lib/www/mechanize/redirect_not_get_or_head_error.rb +20 -0
- data/test/helper.rb +4 -0
- data/test/htdocs/relative/tc_relative_links.html +1 -0
- data/test/servlets.rb +38 -8
- data/test/test_checkboxes.rb +14 -15
- data/test/test_cookie_class.rb +5 -4
- data/test/test_cookie_jar.rb +29 -0
- data/test/test_encoded_links.rb +1 -1
- data/test/test_errors.rb +1 -1
- data/test/test_follow_meta.rb +50 -6
- data/test/test_form_as_hash.rb +5 -5
- data/test/test_forms.rb +28 -27
- data/test/test_links.rb +22 -16
- data/test/test_mech.rb +24 -7
- data/test/test_multi_select.rb +27 -27
- data/test/test_pluggable_parser.rb +4 -4
- data/test/test_radiobutton.rb +35 -42
- data/test/test_redirect_verb_handling.rb +45 -0
- data/test/test_referer.rb +1 -1
- data/test/test_relative_links.rb +4 -4
- data/test/test_scheme.rb +4 -0
- data/test/test_select.rb +15 -15
- data/test/test_select_all.rb +1 -1
- data/test/test_select_none.rb +1 -1
- data/test/test_select_noopts.rb +1 -1
- data/test/test_set_fields.rb +4 -4
- data/test/test_textarea.rb +13 -13
- data/test/test_upload.rb +1 -1
- metadata +10 -6
- data/NOTES.txt +0 -318
@@ -17,7 +17,7 @@ module WWW
|
|
17
17
|
@options = WWW::Mechanize::List.new
|
18
18
|
|
19
19
|
# parse
|
20
|
-
node.search('
|
20
|
+
node.search('option').each do |n|
|
21
21
|
option = Option.new(n, self)
|
22
22
|
@options << option
|
23
23
|
end
|
@@ -47,7 +47,7 @@ module WWW
|
|
47
47
|
|
48
48
|
def value=(values)
|
49
49
|
select_none
|
50
|
-
values.each do |value|
|
50
|
+
[values].flatten.each do |value|
|
51
51
|
option = options.find { |o| o.value == value }
|
52
52
|
if option.nil?
|
53
53
|
@value.push(value)
|
data/lib/www/mechanize/list.rb
CHANGED
@@ -1,53 +1,26 @@
|
|
1
1
|
module WWW
|
2
2
|
class Mechanize
|
3
|
-
#
|
4
|
-
# This class provides syntax sugar to help find things within Mechanize.
|
5
|
-
# Most calls in Mechanize that return arrays, like the 'links' method
|
6
|
-
# WWW::Mechanize::Page return a Mechanize::List. This class lets you
|
7
|
-
# find things with a particular attribute on the found class.
|
8
|
-
#
|
9
|
-
# If you have an array with objects that response to the method "name",
|
10
|
-
# and you want to find all objects where name equals 'foo', your code
|
11
|
-
# would look like this:
|
12
|
-
#
|
13
|
-
# list.name('foo') # => Mechanize::List
|
14
|
-
#
|
15
|
-
# == A bit more information
|
16
|
-
# Mechanize::List will iterate through all of the objects it contains,
|
17
|
-
# testing to see if the object will respond to the "name" method. If it
|
18
|
-
# does, it will test to see if calling the name method returns a value
|
19
|
-
# equal to the value passed in.
|
20
|
-
#
|
21
|
-
# Finding the list will return another list, so it is possible to chain
|
22
|
-
# calls with Mechanize::List. For example:
|
23
|
-
#
|
24
|
-
# list.name('foo').href('bar.html')
|
25
|
-
#
|
26
|
-
# This code will find all elements with name 'foo' and href 'bar.html'.
|
27
|
-
# If you call a method with no arguments that List does not know how to
|
28
|
-
# respond to, it will try that method on the first element of the array.
|
29
|
-
# This lets you treat the array like the type of object it contains.
|
30
|
-
# For example, you can click the first element in the array just by
|
31
|
-
# saying:
|
32
|
-
# agent.click page.links
|
33
|
-
# Or click the first link with the text "foo"
|
34
|
-
# agent.click page.links.text('foo')
|
3
|
+
# This class is deprecated and will be removed in Mechanize version 0.9.0
|
35
4
|
class List < Array
|
5
|
+
@@notified = false
|
6
|
+
|
36
7
|
# This method provides syntax sugar so that you can write expressions
|
37
8
|
# like this:
|
38
9
|
# form.fields.with.name('foo').and.href('bar.html')
|
39
10
|
#
|
40
11
|
def with
|
12
|
+
if !@@notified
|
13
|
+
$stderr.puts("This method is deprecated and will be removed in version 0.9.0. Please use: *_with(:#{meth_sym} => #{args.first ? args.first.inspect : 'nil'})")
|
14
|
+
@@notified = true
|
15
|
+
end
|
41
16
|
self
|
42
17
|
end
|
43
18
|
|
44
|
-
# This method will allow the you to set the value of the first element
|
45
|
-
# in the list. For example, finding an input field with name 'foo'
|
46
|
-
# and setting the value to 'bar'.
|
47
|
-
#
|
48
|
-
# form.fields.name('foo').value = 'bar'
|
49
|
-
#
|
50
19
|
def value=(arg)
|
20
|
+
if !@@notified
|
21
|
+
$stderr.puts("This method is deprecated and will be removed in version 0.9.0. Please use: *_with(:#{meth_sym} => #{args.first ? args.first.inspect : 'nil'})")
|
22
|
+
@@notified = true
|
23
|
+
end
|
51
24
|
first().value=(arg)
|
52
25
|
end
|
53
26
|
|
@@ -58,6 +31,10 @@ module WWW
|
|
58
31
|
end
|
59
32
|
|
60
33
|
def method_missing(meth_sym, *args)
|
34
|
+
if !@@notified
|
35
|
+
$stderr.puts("This method is deprecated and will be removed in version 0.9.0. Please use: *_with(:#{meth_sym} => #{args.first ? args.first.inspect : 'nil'})")
|
36
|
+
@@notified = true
|
37
|
+
end
|
61
38
|
if length > 0
|
62
39
|
return first.send(meth_sym) if args.empty?
|
63
40
|
arg = args.first
|
data/lib/www/mechanize/page.rb
CHANGED
@@ -33,13 +33,20 @@ module WWW
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def title
|
36
|
-
@title ||= if parser && search('
|
37
|
-
search('
|
36
|
+
@title ||= if parser && search('title').inner_text.length > 0
|
37
|
+
search('title').inner_text
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
41
41
|
def parser
|
42
|
-
@parser
|
42
|
+
return @parser if @parser
|
43
|
+
|
44
|
+
if body && response
|
45
|
+
html_body = body.length > 0 ? body : '<html></html>'
|
46
|
+
@parser = Mechanize.html_parser.parse(html_body)
|
47
|
+
end
|
48
|
+
|
49
|
+
@parser
|
43
50
|
end
|
44
51
|
alias :root :parser
|
45
52
|
|
@@ -55,7 +62,7 @@ module WWW
|
|
55
62
|
|
56
63
|
# Find a form matching +criteria+.
|
57
64
|
# Example:
|
58
|
-
# page.
|
65
|
+
# page.form_with(:action => '/post/login.php') do |f|
|
59
66
|
# ...
|
60
67
|
# end
|
61
68
|
[:form, :link, :base, :frame, :iframe].each do |type|
|
@@ -80,7 +87,7 @@ module WWW
|
|
80
87
|
|
81
88
|
def links
|
82
89
|
@links ||= WWW::Mechanize::List.new(
|
83
|
-
%w{
|
90
|
+
%w{ a area }.map do |tag|
|
84
91
|
search(tag).map do |node|
|
85
92
|
Link.new(node, @mech, self)
|
86
93
|
end
|
@@ -90,7 +97,7 @@ module WWW
|
|
90
97
|
|
91
98
|
def forms
|
92
99
|
@forms ||= WWW::Mechanize::List.new(
|
93
|
-
search('
|
100
|
+
search('form').map do |html_form|
|
94
101
|
form = Form.new(html_form, @mech, self)
|
95
102
|
form.action ||= @uri.to_s
|
96
103
|
form
|
@@ -100,7 +107,7 @@ module WWW
|
|
100
107
|
|
101
108
|
def meta
|
102
109
|
@meta ||= WWW::Mechanize::List.new(
|
103
|
-
search('
|
110
|
+
search('meta').map do |node|
|
104
111
|
next unless node['http-equiv'] && node['content']
|
105
112
|
(equiv, content) = node['http-equiv'], node['content']
|
106
113
|
if equiv && equiv.downcase == 'refresh'
|
@@ -115,19 +122,19 @@ module WWW
|
|
115
122
|
|
116
123
|
def bases
|
117
124
|
@bases ||= WWW::Mechanize::List.new(
|
118
|
-
search('
|
125
|
+
search('base').map { |node| Base.new(node, @mech, self) }
|
119
126
|
)
|
120
127
|
end
|
121
128
|
|
122
129
|
def frames
|
123
130
|
@frames ||= WWW::Mechanize::List.new(
|
124
|
-
search('
|
131
|
+
search('frame').map { |node| Frame.new(node, @mech, self) }
|
125
132
|
)
|
126
133
|
end
|
127
134
|
|
128
135
|
def iframes
|
129
136
|
@iframes ||= WWW::Mechanize::List.new(
|
130
|
-
search('
|
137
|
+
search('iframe').map { |node| Frame.new(node, @mech, self) }
|
131
138
|
)
|
132
139
|
end
|
133
140
|
end
|
@@ -27,9 +27,9 @@ module WWW
|
|
27
27
|
@attributes = node
|
28
28
|
|
29
29
|
# If there is no text, try to find an image and use it's alt text
|
30
|
-
if (@text.nil? || @text.length == 0) && node.search('
|
30
|
+
if (@text.nil? || @text.length == 0) && node.search('img').length > 0
|
31
31
|
@text = ''
|
32
|
-
node.search('
|
32
|
+
node.search('img').each do |e|
|
33
33
|
@text << ( e['alt'] || '')
|
34
34
|
end
|
35
35
|
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module WWW
|
2
|
+
class Mechanize
|
3
|
+
# Thrown when a POST, PUT, or DELETE request results in a redirect
|
4
|
+
# see RFC 2616 10.3.2, 10.3.3 http://www.ietf.org/rfc/rfc2616.txt
|
5
|
+
class RedirectNotGetOrHeadError < RuntimeError
|
6
|
+
attr_reader :page, :response_code, :verb, :uri
|
7
|
+
def initialize(page, verb)
|
8
|
+
@page = page
|
9
|
+
@verb = verb
|
10
|
+
@uri = page.uri
|
11
|
+
@response_code = page.code
|
12
|
+
end
|
13
|
+
|
14
|
+
def to_s
|
15
|
+
"#{@response_code} redirect received after a #{@verb} request"
|
16
|
+
end
|
17
|
+
alias :inspect :to_s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
data/test/helper.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
$:.unshift(File.expand_path(File.join(File.dirname(__FILE__),'..','lib')))
|
2
3
|
require 'rubygems'
|
3
4
|
require 'mechanize'
|
4
5
|
require 'webrick/httputils'
|
@@ -32,6 +33,7 @@ class Net::HTTP
|
|
32
33
|
'/basic_auth' => BasicAuthServlet,
|
33
34
|
'/form post' => FormTest,
|
34
35
|
'/response_code' => ResponseCodeTest,
|
36
|
+
'/http_refresh' => HttpRefreshTest,
|
35
37
|
'/bad_content_type' => BadContentTypeTest,
|
36
38
|
'/content_type_test' => ContentTypeTest,
|
37
39
|
'/referer' => RefererServlet,
|
@@ -44,6 +46,8 @@ class Net::HTTP
|
|
44
46
|
'/if_modified_since' => ModifiedSinceServlet,
|
45
47
|
'/http_headers' => HeaderServlet,
|
46
48
|
'/infinite_redirect' => InfiniteRedirectTest,
|
49
|
+
'/infinite_refresh' => InfiniteRefreshTest,
|
50
|
+
'/redirect' => RedirectTest,
|
47
51
|
'/digest_auth' => DigestAuthServlet,
|
48
52
|
'/verb' => VerbServlet,
|
49
53
|
}
|
data/test/servlets.rb
CHANGED
@@ -6,14 +6,12 @@ require 'stringio'
|
|
6
6
|
require 'base64'
|
7
7
|
|
8
8
|
class VerbServlet < WEBrick::HTTPServlet::AbstractServlet
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
res = args[1]
|
16
|
-
res.body = "method: #{$1}"
|
9
|
+
%w(HEAD GET POST PUT DELETE).each do |verb|
|
10
|
+
eval(<<-eomethod)
|
11
|
+
def do_#{verb}(req, res)
|
12
|
+
res.body = "method: #{verb}"
|
13
|
+
end
|
14
|
+
eomethod
|
17
15
|
end
|
18
16
|
end
|
19
17
|
|
@@ -157,6 +155,15 @@ class FileUploadTest < WEBrick::HTTPServlet::AbstractServlet
|
|
157
155
|
end
|
158
156
|
end
|
159
157
|
|
158
|
+
class InfiniteRefreshTest < WEBrick::HTTPServlet::AbstractServlet
|
159
|
+
def do_GET(req, res)
|
160
|
+
res['Content-Type'] = req.query['ct'] || "text/html"
|
161
|
+
res.status = req.query['code'] ? req.query['code'].to_i : '302'
|
162
|
+
number = req.query['q'] ? req.query['q'].to_i : 0
|
163
|
+
res['Refresh'] = " 0;url=http://localhost/infinite_refresh?q=#{number + 1}\r\n";
|
164
|
+
end
|
165
|
+
end
|
166
|
+
|
160
167
|
class InfiniteRedirectTest < WEBrick::HTTPServlet::AbstractServlet
|
161
168
|
def do_GET(req, res)
|
162
169
|
res['Content-Type'] = req.query['ct'] || "text/html"
|
@@ -167,6 +174,19 @@ class InfiniteRedirectTest < WEBrick::HTTPServlet::AbstractServlet
|
|
167
174
|
alias :do_POST :do_GET
|
168
175
|
end
|
169
176
|
|
177
|
+
class RedirectTest < WEBrick::HTTPServlet::AbstractServlet
|
178
|
+
def do_GET(req, res)
|
179
|
+
res['Content-Type'] = req.query['ct'] || "text/html"
|
180
|
+
res.status = req.query['code'] ? req.query['code'].to_i : '302'
|
181
|
+
res['Location'] = "/verb"
|
182
|
+
end
|
183
|
+
|
184
|
+
alias :do_POST :do_GET
|
185
|
+
alias :do_HEAD :do_GET
|
186
|
+
alias :do_PUT :do_GET
|
187
|
+
alias :do_DELETE :do_GET
|
188
|
+
end
|
189
|
+
|
170
190
|
class ResponseCodeTest < WEBrick::HTTPServlet::AbstractServlet
|
171
191
|
def do_GET(req, res)
|
172
192
|
res['Content-Type'] = req.query['ct'] || "text/html"
|
@@ -181,6 +201,16 @@ class ResponseCodeTest < WEBrick::HTTPServlet::AbstractServlet
|
|
181
201
|
end
|
182
202
|
end
|
183
203
|
end
|
204
|
+
|
205
|
+
class HttpRefreshTest < WEBrick::HTTPServlet::AbstractServlet
|
206
|
+
def do_GET(req, res)
|
207
|
+
res['Content-Type'] = req.query['ct'] || "text/html"
|
208
|
+
refresh_time = req.query['refresh_time'] || 0
|
209
|
+
refresh_url = req.query['refresh_url'] || '/index.html'
|
210
|
+
res['Refresh'] = " #{refresh_time};url=#{refresh_url}\r\n";
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
184
214
|
class FormTest < WEBrick::HTTPServlet::AbstractServlet
|
185
215
|
def do_GET(req, res)
|
186
216
|
res.body = "<HTML><body>"
|
data/test/test_checkboxes.rb
CHANGED
@@ -8,12 +8,11 @@ class TestCheckBoxes < Test::Unit::TestCase
|
|
8
8
|
|
9
9
|
def test_select_one
|
10
10
|
form = @page.forms.first
|
11
|
-
form.
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
assert_equal(false, form.checkboxes.name('brown').checked)
|
11
|
+
form.checkbox_with(:name => 'green').check
|
12
|
+
assert(form.checkbox_with(:name => 'green').checked)
|
13
|
+
%w{ red blue yellow brown }.each do |color|
|
14
|
+
assert_equal(false, form.checkbox_with(:name => color).checked)
|
15
|
+
end
|
17
16
|
end
|
18
17
|
|
19
18
|
def test_select_all
|
@@ -38,10 +37,10 @@ class TestCheckBoxes < Test::Unit::TestCase
|
|
38
37
|
|
39
38
|
def test_check_one
|
40
39
|
form = @page.forms.first
|
41
|
-
assert_equal(2, form.
|
42
|
-
form.
|
43
|
-
assert_equal(false, form.
|
44
|
-
assert_equal(true, form.
|
40
|
+
assert_equal(2, form.checkboxes_with(:name => 'green').length)
|
41
|
+
form.checkboxes_with(:name => 'green')[1].check
|
42
|
+
assert_equal(false, form.checkboxes_with(:name => 'green')[0].checked)
|
43
|
+
assert_equal(true, form.checkboxes_with(:name => 'green')[1].checked)
|
45
44
|
page = @agent.submit(form)
|
46
45
|
assert_equal(1, page.links.length)
|
47
46
|
assert_equal('green:on', page.links.first.text)
|
@@ -49,11 +48,11 @@ class TestCheckBoxes < Test::Unit::TestCase
|
|
49
48
|
|
50
49
|
def test_check_two
|
51
50
|
form = @page.forms.first
|
52
|
-
assert_equal(2, form.
|
53
|
-
form.
|
54
|
-
form.
|
55
|
-
assert_equal(true, form.
|
56
|
-
assert_equal(true, form.
|
51
|
+
assert_equal(2, form.checkboxes_with(:name => 'green').length)
|
52
|
+
form.checkboxes_with(:name => 'green')[0].check
|
53
|
+
form.checkboxes_with(:name => 'green')[1].check
|
54
|
+
assert_equal(true, form.checkboxes_with(:name => 'green')[0].checked)
|
55
|
+
assert_equal(true, form.checkboxes_with(:name => 'green')[1].checked)
|
57
56
|
page = @agent.submit(form)
|
58
57
|
assert_equal(2, page.links.length)
|
59
58
|
assert_equal('green:on', page.links.first.text)
|
data/test/test_cookie_class.rb
CHANGED
@@ -51,8 +51,9 @@ class CookieClassTest < Test::Unit::TestCase
|
|
51
51
|
dates.each do |date|
|
52
52
|
cookie = "PREF=1; expires=#{date}"
|
53
53
|
silently do
|
54
|
-
WWW::Mechanize::Cookie.parse(url, cookie) { |
|
55
|
-
|
54
|
+
WWW::Mechanize::Cookie.parse(url, cookie) { |c|
|
55
|
+
assert c.expires, "Tried parsing: #{date}"
|
56
|
+
assert_equal(true, c.expires < yesterday)
|
56
57
|
}
|
57
58
|
end
|
58
59
|
end
|
@@ -93,8 +94,8 @@ class CookieClassTest < Test::Unit::TestCase
|
|
93
94
|
silently do
|
94
95
|
dates.each do |date|
|
95
96
|
cookie = "PREF=1; expires=#{date}"
|
96
|
-
WWW::Mechanize::Cookie.parse(url, cookie) { |
|
97
|
-
assert_equal(true,
|
97
|
+
WWW::Mechanize::Cookie.parse(url, cookie) { |c|
|
98
|
+
assert_equal(true, c.expires.nil?)
|
98
99
|
}
|
99
100
|
end
|
100
101
|
end
|
data/test/test_cookie_jar.rb
CHANGED
@@ -311,4 +311,33 @@ class CookieJarTest < Test::Unit::TestCase
|
|
311
311
|
|
312
312
|
FileUtils.rm("cookies.txt")
|
313
313
|
end
|
314
|
+
|
315
|
+
def test_ssl_cookies
|
316
|
+
# thanks to michal "ocher" ochman for reporting the bug responsible for this test.
|
317
|
+
values = { :name => 'Foo',
|
318
|
+
:value => 'Bar',
|
319
|
+
:path => '/login',
|
320
|
+
:expires => nil,
|
321
|
+
:domain => 'rubyforge.org'
|
322
|
+
}
|
323
|
+
values_ssl = { :name => 'Foo',
|
324
|
+
:value => 'Bar',
|
325
|
+
:path => '/login',
|
326
|
+
:expires => nil,
|
327
|
+
:domain => 'rubyforge.org:443'
|
328
|
+
}
|
329
|
+
url = URI.parse('https://rubyforge.org/login')
|
330
|
+
|
331
|
+
jar = WWW::Mechanize::CookieJar.new
|
332
|
+
assert_equal(0, jar.cookies(url).length)
|
333
|
+
|
334
|
+
cookie = cookie_from_hash(values)
|
335
|
+
jar.add(url, cookie)
|
336
|
+
assert_equal(1, jar.cookies(url).length, "did not handle SSL cookie")
|
337
|
+
|
338
|
+
cookie = cookie_from_hash(values_ssl)
|
339
|
+
jar.add(url, cookie)
|
340
|
+
assert_equal(2, jar.cookies(url).length, "did not handle SSL cookie with :443")
|
341
|
+
end
|
342
|
+
|
314
343
|
end
|
data/test/test_encoded_links.rb
CHANGED
@@ -14,7 +14,7 @@ class TestEncodedLinks < Test::Unit::TestCase
|
|
14
14
|
end
|
15
15
|
|
16
16
|
def test_hpricot_link
|
17
|
-
page = @agent.click(@page.search('
|
17
|
+
page = @agent.click(@page.search('a').first)
|
18
18
|
assert_equal("http://localhost/form_post?a=b&b=c", page.uri.to_s)
|
19
19
|
end
|
20
20
|
end
|
data/test/test_errors.rb
CHANGED
@@ -22,7 +22,7 @@ class MechErrorsTest < Test::Unit::TestCase
|
|
22
22
|
|
23
23
|
def test_too_many_radio
|
24
24
|
page = @agent.get("http://localhost/form_test.html")
|
25
|
-
form = page.
|
25
|
+
form = page.form_with(:name => 'post_form1')
|
26
26
|
form.radiobuttons.each { |r| r.checked = true }
|
27
27
|
assert_raise(RuntimeError) {
|
28
28
|
@agent.submit(form)
|