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.

@@ -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
 
@@ -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
@@ -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)
@@ -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
 
@@ -660,7 +660,16 @@ class Mechanize::HTTP::Agent
660
660
  base = nil
661
661
  end
662
662
 
663
- uri = referer_uri + (base ? base.uri : referer_uri) + uri
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
- log = log() # reduce method calls
844
- Mechanize::Cookie.parse(uri, set_cookie, log) { |c|
845
- if @cookie_jar.add(uri, c)
846
- log.debug("saved cookie: #{c}") if log
847
- else
848
- log.debug("rejected cookie: #{c}") if log
849
- end
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
- raise EOFError, "Content-Length (#{content_length}) does not match " \
932
- "response body length (#{body_io.length})" if
933
- content_length and content_length != body_io.length
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
 
@@ -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
- page.bases[0].href + src
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
@@ -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 < MiniTest::Unit::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
- Mechanize::Cookie.parse uri, str do |cookie|
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 @__name__
171
+ body_io = Tempfile.new @NAME
174
172
  body_io.unlink
175
173
  body_io.write content
176
174
  body_io.flush
@@ -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 = Mechanize::CookieJar.new
267
- Mechanize::Cookie.parse uri, 'a=b' do |cookie|
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 = Mechanize::CookieJar.new
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 = Mechanize::CookieJar.new
1035
- Mechanize::Cookie.parse uri, 'a=b' do |cookie|
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 '"value"', cookie.value
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://example/'
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
- single_dot_cookie = Mechanize::Cookie.new(cookie_values(:domain => '.'))
127
- @jar.add(url, single_dot_cookie)
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(1, @jar.cookies(url).length)
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 must have a leading dot, and must have
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)
@@ -33,7 +33,7 @@ class TestMechanizeDownload < Mechanize::TestCase
33
33
 
34
34
  def test_save_tempfile
35
35
  uri = URI.parse 'http://example/foo.html'
36
- Tempfile.open __name__ do |body_io|
36
+ Tempfile.open @NAME do |body_io|
37
37
  body_io.unlink
38
38
  body_io.write '0123456789'
39
39
 
@@ -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' => __name__), @mech, @page
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 \"#{@__name__}\" in #{@uri}",
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