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.

Files changed (51) hide show
  1. data/EXAMPLES.txt +1 -1
  2. data/GUIDE.txt +11 -12
  3. data/History.txt +24 -0
  4. data/Manifest.txt +2 -1
  5. data/README.txt +1 -1
  6. data/Rakefile +2 -2
  7. data/lib/www/mechanize.rb +43 -11
  8. data/lib/www/mechanize/chain/header_resolver.rb +1 -1
  9. data/lib/www/mechanize/chain/request_resolver.rb +4 -0
  10. data/lib/www/mechanize/chain/response_body_parser.rb +1 -1
  11. data/lib/www/mechanize/chain/response_reader.rb +1 -1
  12. data/lib/www/mechanize/chain/ssl_resolver.rb +1 -1
  13. data/lib/www/mechanize/chain/uri_resolver.rb +21 -9
  14. data/lib/www/mechanize/cookie_jar.rb +11 -6
  15. data/lib/www/mechanize/file_response.rb +2 -0
  16. data/lib/www/mechanize/form.rb +33 -13
  17. data/lib/www/mechanize/form/multi_select_list.rb +2 -2
  18. data/lib/www/mechanize/form/radio_button.rb +1 -1
  19. data/lib/www/mechanize/list.rb +15 -38
  20. data/lib/www/mechanize/page.rb +17 -10
  21. data/lib/www/mechanize/page/link.rb +2 -2
  22. data/lib/www/mechanize/redirect_not_get_or_head_error.rb +20 -0
  23. data/test/helper.rb +4 -0
  24. data/test/htdocs/relative/tc_relative_links.html +1 -0
  25. data/test/servlets.rb +38 -8
  26. data/test/test_checkboxes.rb +14 -15
  27. data/test/test_cookie_class.rb +5 -4
  28. data/test/test_cookie_jar.rb +29 -0
  29. data/test/test_encoded_links.rb +1 -1
  30. data/test/test_errors.rb +1 -1
  31. data/test/test_follow_meta.rb +50 -6
  32. data/test/test_form_as_hash.rb +5 -5
  33. data/test/test_forms.rb +28 -27
  34. data/test/test_links.rb +22 -16
  35. data/test/test_mech.rb +24 -7
  36. data/test/test_multi_select.rb +27 -27
  37. data/test/test_pluggable_parser.rb +4 -4
  38. data/test/test_radiobutton.rb +35 -42
  39. data/test/test_redirect_verb_handling.rb +45 -0
  40. data/test/test_referer.rb +1 -1
  41. data/test/test_relative_links.rb +4 -4
  42. data/test/test_scheme.rb +4 -0
  43. data/test/test_select.rb +15 -15
  44. data/test/test_select_all.rb +1 -1
  45. data/test/test_select_none.rb +1 -1
  46. data/test/test_select_noopts.rb +1 -1
  47. data/test/test_set_fields.rb +4 -4
  48. data/test/test_textarea.rb +13 -13
  49. data/test/test_upload.rb +1 -1
  50. metadata +10 -6
  51. 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('//option').each do |n|
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)
@@ -27,7 +27,7 @@ module WWW
27
27
 
28
28
  private
29
29
  def uncheck_peers
30
- @form.radiobuttons.name(name).each do |b|
30
+ @form.radiobuttons_with(:name => name).each do |b|
31
31
  next if b.value == value
32
32
  b.uncheck
33
33
  end
@@ -1,53 +1,26 @@
1
1
  module WWW
2
2
  class Mechanize
3
- # = Synopsis
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
@@ -33,13 +33,20 @@ module WWW
33
33
  end
34
34
 
35
35
  def title
36
- @title ||= if parser && search('//title').inner_text.length > 0
37
- search('//title').inner_text
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 ||= body && response ? Mechanize.html_parser.parse(body) : nil
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.form(:action => '/post/login.php') do |f|
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{ //a //area }.map do |tag|
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('//form').map do |html_form|
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('//meta').map do |node|
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('//base').map { |node| Base.new(node, @mech, self) }
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('//frame').map { |node| Frame.new(node, @mech, self) }
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('//iframe').map { |node| Frame.new(node, @mech, self) }
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('//img').length > 0
30
+ if (@text.nil? || @text.length == 0) && node.search('img').length > 0
31
31
  @text = ''
32
- node.search('//img').each do |e|
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
  }
@@ -13,6 +13,7 @@
13
13
  [Your user agent does not support frames or is currently configured
14
14
  not to display frames. However, you may visit
15
15
  <A href="foo.html">the related document.</A>]
16
+ <a href="?a=b">just the query string</A>
16
17
  </IFRAME>
17
18
  </FRAMESET>
18
19
  </FRAMESET>
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
- def do_HEAD(req, res)
10
- res.body = "method: HEAD"
11
- end
12
-
13
- def method_missing(method, *args, &block)
14
- super unless method.to_s =~ /^do_([A-Z]*)$/
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>"
@@ -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.checkboxes.name('green').check
12
- assert_equal(true, form.checkboxes.name('green').checked)
13
- assert_equal(false, form.checkboxes.name('red').checked)
14
- assert_equal(false, form.checkboxes.name('blue').checked)
15
- assert_equal(false, form.checkboxes.name('yellow').checked)
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.checkboxes.name('green').length)
42
- form.checkboxes.name('green')[1].check
43
- assert_equal(false, form.checkboxes.name('green')[0].checked)
44
- assert_equal(true, form.checkboxes.name('green')[1].checked)
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.checkboxes.name('green').length)
53
- form.checkboxes.name('green')[0].check
54
- form.checkboxes.name('green')[1].check
55
- assert_equal(true, form.checkboxes.name('green')[0].checked)
56
- assert_equal(true, form.checkboxes.name('green')[1].checked)
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)
@@ -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) { |cookie|
55
- assert_equal(true, cookie.expires < yesterday)
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) { |cookie|
97
- assert_equal(true, cookie.expires.nil?)
97
+ WWW::Mechanize::Cookie.parse(url, cookie) { |c|
98
+ assert_equal(true, c.expires.nil?)
98
99
  }
99
100
  end
100
101
  end
@@ -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
@@ -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('//a').first)
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.forms.name('post_form1').first
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)