mechanize 2.6.0 → 2.7.0
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.
Potentially problematic release.
This version of mechanize might be problematic. Click here for more details.
- checksums.yaml +4 -4
- data/.travis.yml +16 -0
- data/CHANGELOG.rdoc +14 -0
- data/Manifest.txt +1 -0
- data/README.rdoc +7 -6
- data/Rakefile +3 -0
- data/lib/mechanize.rb +1 -1
- data/lib/mechanize/cookie.rb +51 -222
- data/lib/mechanize/cookie_jar.rb +123 -191
- data/lib/mechanize/download.rb +10 -2
- data/lib/mechanize/file.rb +1 -7
- data/lib/mechanize/form.rb +15 -0
- data/lib/mechanize/form/field.rb +12 -0
- data/lib/mechanize/http/agent.rb +28 -15
- data/lib/mechanize/page/image.rb +3 -3
- data/lib/mechanize/prependable.rb +89 -0
- data/lib/mechanize/test_case.rb +3 -5
- data/test/test_mechanize.rb +5 -9
- data/test/test_mechanize_cookie.rb +4 -4
- data/test/test_mechanize_cookie_jar.rb +5 -5
- data/test/test_mechanize_download.rb +1 -1
- data/test/test_mechanize_form.rb +31 -3
- data/test/test_mechanize_http_agent.rb +38 -24
- data/test/test_mechanize_page_image.rb +1 -0
- data/test/test_mechanize_xml_file.rb +1 -1
- metadata +23 -8
data/lib/mechanize/download.rb
CHANGED
@@ -54,7 +54,17 @@ class Mechanize::Download
|
|
54
54
|
|
55
55
|
def save filename = nil
|
56
56
|
filename = find_free_name filename
|
57
|
+
save! filename
|
58
|
+
end
|
59
|
+
|
60
|
+
alias save_as save
|
57
61
|
|
62
|
+
##
|
63
|
+
# Use this method to save the content of body_io to +filename+.
|
64
|
+
# This method will overwrite any existing filename that exists with the
|
65
|
+
# same name.
|
66
|
+
|
67
|
+
def save! filename = nil
|
58
68
|
dirname = File.dirname filename
|
59
69
|
FileUtils.mkdir_p dirname
|
60
70
|
|
@@ -65,7 +75,5 @@ class Mechanize::Download
|
|
65
75
|
end
|
66
76
|
end
|
67
77
|
|
68
|
-
alias save_as save
|
69
|
-
|
70
78
|
end
|
71
79
|
|
data/lib/mechanize/file.rb
CHANGED
@@ -57,13 +57,7 @@ class Mechanize::File
|
|
57
57
|
|
58
58
|
def save filename = nil
|
59
59
|
filename = find_free_name filename
|
60
|
-
|
61
|
-
dirname = File.dirname filename
|
62
|
-
FileUtils.mkdir_p dirname
|
63
|
-
|
64
|
-
open filename, 'wb' do |f|
|
65
|
-
f.write body
|
66
|
-
end
|
60
|
+
save! filename
|
67
61
|
end
|
68
62
|
|
69
63
|
alias save_as save
|
data/lib/mechanize/form.rb
CHANGED
@@ -252,6 +252,8 @@ class Mechanize::Form
|
|
252
252
|
query = []
|
253
253
|
@mech.log.info("form encoding: #{encoding}") if @mech && @mech.log
|
254
254
|
|
255
|
+
save_hash_field_order
|
256
|
+
|
255
257
|
successful_controls = []
|
256
258
|
|
257
259
|
(fields + checkboxes).reject do |f|
|
@@ -301,6 +303,19 @@ class Mechanize::Form
|
|
301
303
|
query
|
302
304
|
end
|
303
305
|
|
306
|
+
# This method adds an index to all fields that have Hash nodes. This
|
307
|
+
# enables field sorting to maintain order.
|
308
|
+
def save_hash_field_order
|
309
|
+
index = 0
|
310
|
+
|
311
|
+
fields.each do |field|
|
312
|
+
if Hash === field.node
|
313
|
+
field.index = index
|
314
|
+
index += 1
|
315
|
+
end
|
316
|
+
end
|
317
|
+
end
|
318
|
+
|
304
319
|
# This method adds a button to the query. If the form needs to be
|
305
320
|
# submitted with multiple buttons, pass each button to this method.
|
306
321
|
def add_button_to_query(button)
|
data/lib/mechanize/form/field.rb
CHANGED
@@ -19,6 +19,9 @@ class Mechanize::Form::Field
|
|
19
19
|
# This fields value before it's sent through Util.html_unescape.
|
20
20
|
attr_reader :raw_value
|
21
21
|
|
22
|
+
# index is used to maintain order for fields with Hash nodes
|
23
|
+
attr_accessor :index
|
24
|
+
|
22
25
|
def initialize node, value = node['value']
|
23
26
|
@node = node
|
24
27
|
@name = Mechanize::Util.html_unescape(node['name'])
|
@@ -38,8 +41,17 @@ class Mechanize::Form::Field
|
|
38
41
|
|
39
42
|
def <=> other
|
40
43
|
return 0 if self == other
|
44
|
+
|
45
|
+
# If both are hashes, sort by index
|
46
|
+
if Hash === node && Hash === other.node && index
|
47
|
+
return index <=> other.index
|
48
|
+
end
|
49
|
+
|
50
|
+
# Otherwise put Hash based fields at the end
|
41
51
|
return 1 if Hash === node
|
42
52
|
return -1 if Hash === other.node
|
53
|
+
|
54
|
+
# Finally let nokogiri determine sort order
|
43
55
|
node <=> other.node
|
44
56
|
end
|
45
57
|
|
data/lib/mechanize/http/agent.rb
CHANGED
@@ -660,7 +660,16 @@ class Mechanize::HTTP::Agent
|
|
660
660
|
base = nil
|
661
661
|
end
|
662
662
|
|
663
|
-
|
663
|
+
base = referer_uri + (base ? base.uri : referer_uri)
|
664
|
+
|
665
|
+
# Workaround for URI's bug in that it squashes consecutive
|
666
|
+
# slashes. See #304.
|
667
|
+
if uri.path.match(%r{\A(.*?/)(?!/\.\.?(?!/))(/.*)\z}i)
|
668
|
+
uri = URI((base + $1).to_s + $2)
|
669
|
+
else
|
670
|
+
uri = base + uri
|
671
|
+
end
|
672
|
+
|
664
673
|
# Strip initial "/.." bits from the path
|
665
674
|
uri.path.sub!(/^(\/\.\.)+(?=\/)/, '')
|
666
675
|
end
|
@@ -793,7 +802,7 @@ class Mechanize::HTTP::Agent
|
|
793
802
|
return body_io if length.zero?
|
794
803
|
|
795
804
|
out_io = case response['Content-Encoding']
|
796
|
-
when nil, 'none', '7bit' then
|
805
|
+
when nil, 'none', '7bit', "" then
|
797
806
|
body_io
|
798
807
|
when 'deflate' then
|
799
808
|
content_encoding_inflate body_io
|
@@ -840,14 +849,15 @@ class Mechanize::HTTP::Agent
|
|
840
849
|
end
|
841
850
|
|
842
851
|
def save_cookies(uri, set_cookie)
|
843
|
-
|
844
|
-
|
845
|
-
|
846
|
-
log.debug("saved cookie: #{c}")
|
847
|
-
|
848
|
-
|
849
|
-
|
850
|
-
|
852
|
+
return [] if set_cookie.nil?
|
853
|
+
if log = log() # reduce method calls
|
854
|
+
@cookie_jar.parse(set_cookie, uri, :logger => log) { |c|
|
855
|
+
log.debug("saved cookie: #{c}")
|
856
|
+
true
|
857
|
+
}
|
858
|
+
else
|
859
|
+
@cookie_jar.parse(set_cookie, uri)
|
860
|
+
end
|
851
861
|
end
|
852
862
|
|
853
863
|
def response_follow_meta_refresh response, uri, page, redirects
|
@@ -913,7 +923,7 @@ class Mechanize::HTTP::Agent
|
|
913
923
|
body_io.rewind
|
914
924
|
raise Mechanize::ChunkedTerminationError.new(e, response, body_io, uri,
|
915
925
|
@context)
|
916
|
-
rescue Net::HTTP::Persistent::Error => e
|
926
|
+
rescue Net::HTTP::Persistent::Error, Errno::ECONNRESET => e
|
917
927
|
body_io.rewind
|
918
928
|
raise Mechanize::ResponseReadError.new(e, response, body_io, uri,
|
919
929
|
@context)
|
@@ -928,9 +938,12 @@ class Mechanize::HTTP::Agent
|
|
928
938
|
content_length = response.content_length
|
929
939
|
|
930
940
|
unless Net::HTTP::Head === request or Net::HTTPRedirection === response then
|
931
|
-
|
932
|
-
|
933
|
-
|
941
|
+
if content_length and content_length != body_io.length
|
942
|
+
err = EOFError.new("Content-Length (#{content_length}) does not " \
|
943
|
+
"match response body length (#{body_io.length})")
|
944
|
+
raise Mechanize::ResponseReadError.new(err, response, body_io, uri,
|
945
|
+
@context)
|
946
|
+
end
|
934
947
|
end
|
935
948
|
|
936
949
|
body_io
|
@@ -1217,7 +1230,7 @@ class Mechanize::HTTP::Agent
|
|
1217
1230
|
end
|
1218
1231
|
|
1219
1232
|
def reset
|
1220
|
-
@cookie_jar.clear
|
1233
|
+
@cookie_jar.clear
|
1221
1234
|
@history.clear
|
1222
1235
|
end
|
1223
1236
|
|
data/lib/mechanize/page/image.rb
CHANGED
@@ -160,12 +160,12 @@ class Mechanize::Page::Image
|
|
160
160
|
def url
|
161
161
|
if relative? then
|
162
162
|
if page.bases[0] then
|
163
|
-
|
163
|
+
page.bases[0].href + src
|
164
164
|
else
|
165
|
-
page.uri + src
|
165
|
+
page.uri + Mechanize::Util.uri_escape(src)
|
166
166
|
end
|
167
167
|
else
|
168
|
-
URI src
|
168
|
+
URI Mechanize::Util.uri_escape(src)
|
169
169
|
end
|
170
170
|
end
|
171
171
|
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# Fake implementation of prepend(), which does not support overriding
|
2
|
+
# inherited methods nor methods that are formerly overridden by
|
3
|
+
# another invocation of prepend().
|
4
|
+
#
|
5
|
+
# Here's what <Original>.prepend(<Wrapper>) does:
|
6
|
+
#
|
7
|
+
# - Create an anonymous stub module (hereinafter <Stub>) and define
|
8
|
+
# <Stub>#<method> that calls #<method>_without_<Wrapper> for each
|
9
|
+
# instance method of <Wrapper>.
|
10
|
+
#
|
11
|
+
# - Rename <Original>#<method> to #<method>_without_<Wrapper> for each
|
12
|
+
# instance method of <Wrapper>.
|
13
|
+
#
|
14
|
+
# - Include <Wrapper> and <Stub> into <Original> in that order.
|
15
|
+
#
|
16
|
+
# This way, a call of <Original>#<method> is dispatched to
|
17
|
+
# <Wrapper><method>, which may call super which is dispatched to
|
18
|
+
# <Stub>#<method>, which finally calls
|
19
|
+
# <Original>#<method>_without_<Wrapper> which is used to be called
|
20
|
+
# <Original>#<method>.
|
21
|
+
#
|
22
|
+
# Usage:
|
23
|
+
#
|
24
|
+
# class Mechanize
|
25
|
+
# # module with methods that overrides those of X
|
26
|
+
# module Y
|
27
|
+
# end
|
28
|
+
#
|
29
|
+
# unless X.respond_to?(:prepend, true)
|
30
|
+
# require 'mechanize/prependable'
|
31
|
+
# X.extend(Prependable)
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class X
|
35
|
+
# prepend Y
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
module Mechanize::Prependable
|
39
|
+
def prepend(mod)
|
40
|
+
stub = Module.new
|
41
|
+
|
42
|
+
mod_id = (mod.name || 'Module__%d' % mod.object_id).gsub(/::/, '__')
|
43
|
+
|
44
|
+
mod.instance_methods.each { |name|
|
45
|
+
method_defined?(name) or next
|
46
|
+
|
47
|
+
original = instance_method(name)
|
48
|
+
if original.owner != self
|
49
|
+
warn "%s cannot override an inherited method: %s(%s)#%s" % [
|
50
|
+
__method__, self, original.owner, name
|
51
|
+
]
|
52
|
+
next
|
53
|
+
end
|
54
|
+
|
55
|
+
name = name.to_s
|
56
|
+
name_without = name.sub(/(?=[?!=]?\z)/) { '_without_%s' % mod_id }
|
57
|
+
|
58
|
+
arity = original.arity
|
59
|
+
arglist = (
|
60
|
+
if arity >= 0
|
61
|
+
(1..arity).map { |i| 'x%d' % i }
|
62
|
+
else
|
63
|
+
(1..(-arity - 1)).map { |i| 'x%d' % i } << '*a'
|
64
|
+
end << '&b'
|
65
|
+
).join(', ')
|
66
|
+
|
67
|
+
if name.end_with?('=')
|
68
|
+
stub.module_eval %{
|
69
|
+
def #{name}(#{arglist})
|
70
|
+
__send__(:#{name_without}, #{arglist})
|
71
|
+
end
|
72
|
+
}
|
73
|
+
else
|
74
|
+
stub.module_eval %{
|
75
|
+
def #{name}(#{arglist})
|
76
|
+
#{name_without}(#{arglist})
|
77
|
+
end
|
78
|
+
}
|
79
|
+
end
|
80
|
+
module_eval {
|
81
|
+
alias_method name_without, name
|
82
|
+
remove_method name
|
83
|
+
}
|
84
|
+
}
|
85
|
+
|
86
|
+
include(mod, stub)
|
87
|
+
end
|
88
|
+
private :prepend
|
89
|
+
end
|
data/lib/mechanize/test_case.rb
CHANGED
@@ -29,7 +29,7 @@ require 'minitest/autorun'
|
|
29
29
|
#
|
30
30
|
# Which will launch a test server at http://localhost:8000
|
31
31
|
|
32
|
-
class Mechanize::TestCase <
|
32
|
+
class Mechanize::TestCase < Minitest::Test
|
33
33
|
|
34
34
|
TEST_DIR = File.expand_path '../../../test', __FILE__
|
35
35
|
REQUESTS = []
|
@@ -84,9 +84,7 @@ class Mechanize::TestCase < MiniTest::Unit::TestCase
|
|
84
84
|
def cookie_jar str, uri = URI('http://example')
|
85
85
|
jar = Mechanize::CookieJar.new
|
86
86
|
|
87
|
-
|
88
|
-
jar.add uri, cookie
|
89
|
-
end
|
87
|
+
jar.parse str, uri
|
90
88
|
|
91
89
|
jar
|
92
90
|
end
|
@@ -170,7 +168,7 @@ UQIBATANBgkqhkiG9w0BAQUFAANBAAAB////////////////////////////////
|
|
170
168
|
# Creates a Tempfile with +content+ that is immediately unlinked
|
171
169
|
|
172
170
|
def tempfile content
|
173
|
-
body_io = Tempfile.new @
|
171
|
+
body_io = Tempfile.new @NAME
|
174
172
|
body_io.unlink
|
175
173
|
body_io.write content
|
176
174
|
body_io.flush
|
data/test/test_mechanize.rb
CHANGED
@@ -263,10 +263,8 @@ but not <a href="/" rel="me nofollow">this</a>!
|
|
263
263
|
|
264
264
|
def test_cookies
|
265
265
|
uri = URI 'http://example'
|
266
|
-
jar =
|
267
|
-
|
268
|
-
jar.add uri, cookie
|
269
|
-
end
|
266
|
+
jar = HTTP::CookieJar.new
|
267
|
+
jar.parse 'a=b', uri
|
270
268
|
|
271
269
|
@mech.cookie_jar = jar
|
272
270
|
|
@@ -276,7 +274,7 @@ but not <a href="/" rel="me nofollow">this</a>!
|
|
276
274
|
def test_cookie_jar
|
277
275
|
assert_kind_of Mechanize::CookieJar, @mech.cookie_jar
|
278
276
|
|
279
|
-
jar =
|
277
|
+
jar = HTTP::CookieJar.new
|
280
278
|
|
281
279
|
@mech.cookie_jar = jar
|
282
280
|
|
@@ -1031,10 +1029,8 @@ but not <a href="/" rel="me nofollow">this</a>!
|
|
1031
1029
|
|
1032
1030
|
def test_shutdown
|
1033
1031
|
uri = URI 'http://localhost'
|
1034
|
-
jar =
|
1035
|
-
|
1036
|
-
jar.add uri, cookie
|
1037
|
-
end
|
1032
|
+
jar = HTTP::CookieJar.new
|
1033
|
+
jar.parse 'a=b', uri
|
1038
1034
|
|
1039
1035
|
@mech.cookie_jar = jar
|
1040
1036
|
|
@@ -36,7 +36,7 @@ class TestMechanizeCookie < Mechanize::TestCase
|
|
36
36
|
"Fri, 17 Mar 89 4:01:33",
|
37
37
|
"Fri, 17 Mar 89 4:01 GMT",
|
38
38
|
"Mon Jan 16 16:12 PDT 1989",
|
39
|
-
"Mon Jan 16 16:12 +0130 1989",
|
39
|
+
#"Mon Jan 16 16:12 +0130 1989",
|
40
40
|
"6 May 1992 16:41-JST (Wednesday)",
|
41
41
|
#"22-AUG-1993 10:59:12.82",
|
42
42
|
"22-AUG-1993 10:59pm",
|
@@ -45,7 +45,7 @@ class TestMechanizeCookie < Mechanize::TestCase
|
|
45
45
|
#"Friday, August 04, 1995 3:54 PM",
|
46
46
|
#"06/21/95 04:24:34 PM",
|
47
47
|
#"20/06/95 21:07",
|
48
|
-
"95-06-08 19:32:48 EDT",
|
48
|
+
#"95-06-08 19:32:48 EDT",
|
49
49
|
]
|
50
50
|
|
51
51
|
dates.each do |date|
|
@@ -91,7 +91,7 @@ class TestMechanizeCookie < Mechanize::TestCase
|
|
91
91
|
|
92
92
|
Mechanize::Cookie.parse uri, cookie_str do |cookie|
|
93
93
|
assert_equal 'quoted', cookie.name
|
94
|
-
assert_equal '
|
94
|
+
assert_equal 'value', cookie.value
|
95
95
|
end
|
96
96
|
end
|
97
97
|
|
@@ -222,7 +222,7 @@ class TestMechanizeCookie < Mechanize::TestCase
|
|
222
222
|
end
|
223
223
|
|
224
224
|
def test_parse_many
|
225
|
-
url = URI 'http://
|
225
|
+
url = URI 'http://localhost/'
|
226
226
|
cookie_str =
|
227
227
|
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/, " \
|
228
228
|
"name=Aaron; Domain=localhost; Expires=Sun, 06 Nov 2011 00:29:51 GMT; Path=/, " \
|
@@ -123,8 +123,9 @@ class TestMechanizeCookieJar < Mechanize::TestCase
|
|
123
123
|
|
124
124
|
tld_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.org'))
|
125
125
|
@jar.add(url, tld_cookie)
|
126
|
-
|
127
|
-
|
126
|
+
# single dot domain is now treated as no domain
|
127
|
+
# single_dot_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.'))
|
128
|
+
# @jar.add(url, single_dot_cookie)
|
128
129
|
|
129
130
|
assert_equal(0, @jar.cookies(url).length)
|
130
131
|
end
|
@@ -463,8 +464,7 @@ class TestMechanizeCookieJar < Mechanize::TestCase
|
|
463
464
|
@jar.load("cookies.txt", :cookiestxt)
|
464
465
|
end
|
465
466
|
|
466
|
-
assert_equal(
|
467
|
-
assert_nil @jar.cookies(url).first.expires
|
467
|
+
assert_equal(0, @jar.cookies(url).length)
|
468
468
|
end
|
469
469
|
|
470
470
|
def test_save_and_read_expired_cookies
|
@@ -536,7 +536,7 @@ class TestMechanizeCookieJar < Mechanize::TestCase
|
|
536
536
|
#
|
537
537
|
# * Cookies that only match exactly the domain specified must not have a
|
538
538
|
# leading dot, and must have FALSE as the second field.
|
539
|
-
# * Cookies that match subdomains
|
539
|
+
# * Cookies that match subdomains may have a leading dot, and must have
|
540
540
|
# TRUE as the second field.
|
541
541
|
cookies_txt = File.readlines("cookies.txt")
|
542
542
|
assert_equal(1, cookies_txt.grep( /^rubyforge\.org\tFALSE/ ).length)
|
data/test/test_mechanize_form.rb
CHANGED
@@ -8,7 +8,7 @@ class TestMechanizeForm < Mechanize::TestCase
|
|
8
8
|
@uri = URI 'http://example'
|
9
9
|
@page = page @uri
|
10
10
|
|
11
|
-
@form = Mechanize::Form.new node('form', 'name' =>
|
11
|
+
@form = Mechanize::Form.new node('form', 'name' => @NAME), @mech, @page
|
12
12
|
end
|
13
13
|
|
14
14
|
def test_action
|
@@ -25,7 +25,7 @@ class TestMechanizeForm < Mechanize::TestCase
|
|
25
25
|
end
|
26
26
|
|
27
27
|
assert_equal "#{button.inspect} does not belong to the same page " \
|
28
|
-
"as the form \"#{@
|
28
|
+
"as the form \"#{@NAME}\" in #{@uri}",
|
29
29
|
e.message
|
30
30
|
end
|
31
31
|
|
@@ -312,7 +312,7 @@ class TestMechanizeForm < Mechanize::TestCase
|
|
312
312
|
page = html_page '<form><input name="a" value="b"><input name="a"></form>'
|
313
313
|
form = page.forms.first
|
314
314
|
|
315
|
-
form.set_fields :a => ['c', 1]
|
315
|
+
form.set_fields :a => ['c', 1]
|
316
316
|
|
317
317
|
assert_equal 'b', form.fields.first.value
|
318
318
|
assert_equal 'c', form.fields.last.value
|
@@ -935,4 +935,32 @@ class TestMechanizeForm < Mechanize::TestCase
|
|
935
935
|
assert_empty page.links
|
936
936
|
end
|
937
937
|
|
938
|
+
def test_form_built_from_array_post
|
939
|
+
submitted = @mech.post(
|
940
|
+
'http://example/form_post',
|
941
|
+
[
|
942
|
+
%w[order_matters 0],
|
943
|
+
%w[order_matters 1],
|
944
|
+
%w[order_matters 2],
|
945
|
+
%w[mess_it_up asdf]
|
946
|
+
]
|
947
|
+
)
|
948
|
+
|
949
|
+
assert_equal 'order_matters=0&order_matters=1&order_matters=2&mess_it_up=asdf', submitted.parser.at('#query').text
|
950
|
+
end
|
951
|
+
|
952
|
+
def test_form_built_from_hashes_submit
|
953
|
+
uri = URI 'http://example/form_post'
|
954
|
+
page = page uri
|
955
|
+
form = Mechanize::Form.new node('form', 'name' => @NAME, 'method' => 'POST'), @mech, page
|
956
|
+
form.fields << Mechanize::Form::Field.new({'name' => 'order_matters'}, '0')
|
957
|
+
form.fields << Mechanize::Form::Field.new({'name' => 'order_matters'}, '1')
|
958
|
+
form.fields << Mechanize::Form::Field.new({'name' => 'order_matters'}, '2')
|
959
|
+
form.fields << Mechanize::Form::Field.new({'name' => 'mess_it_up'}, 'asdf')
|
960
|
+
|
961
|
+
submitted = @mech.submit form
|
962
|
+
|
963
|
+
assert_equal 'order_matters=0&order_matters=1&order_matters=2&mess_it_up=asdf', submitted.parser.at('#query').text
|
964
|
+
end
|
965
|
+
|
938
966
|
end
|