mechanize 2.7.4 → 2.8.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.

Files changed (134) hide show
  1. checksums.yaml +5 -5
  2. data/.github/workflows/ci-test.yml +45 -0
  3. data/.yardopts +8 -0
  4. data/{CHANGELOG.rdoc → CHANGELOG.md} +151 -86
  5. data/EXAMPLES.rdoc +1 -24
  6. data/Gemfile +1 -1
  7. data/{LICENSE.rdoc → LICENSE.txt} +4 -0
  8. data/README.md +77 -0
  9. data/Rakefile +18 -3
  10. data/examples/rubygems.rb +2 -2
  11. data/lib/mechanize.rb +3 -2
  12. data/lib/mechanize/chunked_termination_error.rb +1 -0
  13. data/lib/mechanize/content_type_error.rb +1 -0
  14. data/lib/mechanize/cookie.rb +1 -13
  15. data/lib/mechanize/cookie_jar.rb +4 -12
  16. data/lib/mechanize/directory_saver.rb +1 -0
  17. data/lib/mechanize/download.rb +2 -1
  18. data/lib/mechanize/element_matcher.rb +5 -1
  19. data/lib/mechanize/element_not_found_error.rb +1 -0
  20. data/lib/mechanize/file.rb +2 -1
  21. data/lib/mechanize/file_connection.rb +5 -3
  22. data/lib/mechanize/file_request.rb +1 -0
  23. data/lib/mechanize/file_response.rb +4 -1
  24. data/lib/mechanize/file_saver.rb +1 -0
  25. data/lib/mechanize/form.rb +112 -45
  26. data/lib/mechanize/form/button.rb +1 -0
  27. data/lib/mechanize/form/check_box.rb +1 -0
  28. data/lib/mechanize/form/field.rb +47 -0
  29. data/lib/mechanize/form/file_upload.rb +1 -0
  30. data/lib/mechanize/form/hidden.rb +1 -0
  31. data/lib/mechanize/form/image_button.rb +1 -0
  32. data/lib/mechanize/form/keygen.rb +1 -0
  33. data/lib/mechanize/form/multi_select_list.rb +8 -14
  34. data/lib/mechanize/form/option.rb +3 -1
  35. data/lib/mechanize/form/radio_button.rb +1 -0
  36. data/lib/mechanize/form/reset.rb +1 -0
  37. data/lib/mechanize/form/select_list.rb +1 -0
  38. data/lib/mechanize/form/submit.rb +1 -0
  39. data/lib/mechanize/form/text.rb +1 -0
  40. data/lib/mechanize/form/textarea.rb +1 -0
  41. data/lib/mechanize/headers.rb +1 -0
  42. data/lib/mechanize/history.rb +2 -1
  43. data/lib/mechanize/http.rb +1 -0
  44. data/lib/mechanize/http/agent.rb +81 -38
  45. data/lib/mechanize/http/auth_challenge.rb +1 -0
  46. data/lib/mechanize/http/auth_realm.rb +2 -1
  47. data/lib/mechanize/http/auth_store.rb +1 -0
  48. data/lib/mechanize/http/content_disposition_parser.rb +18 -3
  49. data/lib/mechanize/http/www_authenticate_parser.rb +4 -4
  50. data/lib/mechanize/image.rb +1 -0
  51. data/lib/mechanize/page.rb +8 -5
  52. data/lib/mechanize/page/base.rb +1 -0
  53. data/lib/mechanize/page/frame.rb +4 -1
  54. data/lib/mechanize/page/image.rb +1 -0
  55. data/lib/mechanize/page/label.rb +1 -0
  56. data/lib/mechanize/page/link.rb +8 -1
  57. data/lib/mechanize/page/meta_refresh.rb +1 -0
  58. data/lib/mechanize/parser.rb +4 -3
  59. data/lib/mechanize/pluggable_parsers.rb +1 -0
  60. data/lib/mechanize/prependable.rb +1 -0
  61. data/lib/mechanize/redirect_limit_reached_error.rb +1 -0
  62. data/lib/mechanize/redirect_not_get_or_head_error.rb +1 -0
  63. data/lib/mechanize/response_code_error.rb +2 -1
  64. data/lib/mechanize/response_read_error.rb +1 -0
  65. data/lib/mechanize/robots_disallowed_error.rb +1 -0
  66. data/lib/mechanize/test_case.rb +34 -29
  67. data/lib/mechanize/test_case/bad_chunking_servlet.rb +1 -0
  68. data/lib/mechanize/test_case/basic_auth_servlet.rb +1 -0
  69. data/lib/mechanize/test_case/content_type_servlet.rb +1 -0
  70. data/lib/mechanize/test_case/digest_auth_servlet.rb +1 -0
  71. data/lib/mechanize/test_case/file_upload_servlet.rb +1 -0
  72. data/lib/mechanize/test_case/form_servlet.rb +1 -0
  73. data/lib/mechanize/test_case/gzip_servlet.rb +4 -3
  74. data/lib/mechanize/test_case/header_servlet.rb +1 -0
  75. data/lib/mechanize/test_case/http_refresh_servlet.rb +2 -2
  76. data/lib/mechanize/test_case/infinite_redirect_servlet.rb +1 -0
  77. data/lib/mechanize/test_case/infinite_refresh_servlet.rb +2 -2
  78. data/lib/mechanize/test_case/many_cookies_as_string_servlet.rb +1 -0
  79. data/lib/mechanize/test_case/many_cookies_servlet.rb +1 -0
  80. data/lib/mechanize/test_case/modified_since_servlet.rb +1 -0
  81. data/lib/mechanize/test_case/ntlm_servlet.rb +1 -0
  82. data/lib/mechanize/test_case/one_cookie_no_spaces_servlet.rb +1 -0
  83. data/lib/mechanize/test_case/one_cookie_servlet.rb +1 -0
  84. data/lib/mechanize/test_case/quoted_value_cookie_servlet.rb +1 -0
  85. data/lib/mechanize/test_case/redirect_servlet.rb +1 -0
  86. data/lib/mechanize/test_case/referer_servlet.rb +1 -0
  87. data/lib/mechanize/test_case/refresh_with_empty_url.rb +1 -0
  88. data/lib/mechanize/test_case/refresh_without_url.rb +1 -0
  89. data/lib/mechanize/test_case/response_code_servlet.rb +1 -0
  90. data/lib/mechanize/test_case/robots_txt_servlet.rb +15 -0
  91. data/lib/mechanize/test_case/send_cookies_servlet.rb +1 -0
  92. data/lib/mechanize/test_case/server.rb +1 -0
  93. data/lib/mechanize/test_case/servlets.rb +4 -0
  94. data/lib/mechanize/test_case/verb_servlet.rb +5 -6
  95. data/lib/mechanize/unauthorized_error.rb +2 -1
  96. data/lib/mechanize/unsupported_scheme_error.rb +1 -0
  97. data/lib/mechanize/util.rb +5 -3
  98. data/lib/mechanize/version.rb +2 -1
  99. data/lib/mechanize/xml_file.rb +1 -0
  100. data/mechanize.gemspec +39 -31
  101. data/test/htdocs/dir with spaces/foo.html +1 -0
  102. data/test/htdocs/find_link.html +1 -4
  103. data/test/htdocs/tc_links.html +1 -1
  104. data/test/test_mechanize.rb +57 -15
  105. data/test/test_mechanize_cookie.rb +75 -60
  106. data/test/test_mechanize_cookie_jar.rb +112 -59
  107. data/test/test_mechanize_download.rb +13 -1
  108. data/test/test_mechanize_file.rb +10 -0
  109. data/test/test_mechanize_file_connection.rb +21 -3
  110. data/test/test_mechanize_file_response.rb +26 -2
  111. data/test/test_mechanize_form.rb +27 -11
  112. data/test/test_mechanize_form_check_box.rb +10 -0
  113. data/test/test_mechanize_form_encoding.rb +1 -1
  114. data/test/test_mechanize_form_keygen.rb +1 -0
  115. data/test/test_mechanize_form_multi_select_list.rb +5 -1
  116. data/test/test_mechanize_http_agent.rb +116 -8
  117. data/test/test_mechanize_http_auth_challenge.rb +14 -0
  118. data/test/test_mechanize_http_auth_realm.rb +7 -1
  119. data/test/test_mechanize_http_auth_store.rb +37 -0
  120. data/test/test_mechanize_http_content_disposition_parser.rb +35 -1
  121. data/test/test_mechanize_http_www_authenticate_parser.rb +16 -0
  122. data/test/test_mechanize_link.rb +47 -4
  123. data/test/test_mechanize_page.rb +29 -1
  124. data/test/test_mechanize_page_encoding.rb +23 -1
  125. data/test/test_mechanize_page_image.rb +1 -1
  126. data/test/test_mechanize_page_link.rb +3 -3
  127. data/test/test_mechanize_page_meta_refresh.rb +1 -1
  128. data/test/test_mechanize_parser.rb +12 -2
  129. data/test/test_mechanize_util.rb +1 -1
  130. metadata +105 -81
  131. data/.travis.yml +0 -25
  132. data/Manifest.txt +0 -204
  133. data/README.rdoc +0 -77
  134. data/test/htdocs/robots.txt +0 -2
data/EXAMPLES.rdoc CHANGED
@@ -24,29 +24,6 @@ example, <code>do ... end.submit</code> is the same as <code>{ ...
24
24
  end
25
25
  end
26
26
 
27
- == Rubyforge
28
-
29
- require 'rubygems'
30
- require 'mechanize'
31
-
32
- a = Mechanize.new
33
- a.get('http://rubyforge.org/') do |page|
34
- # Click the login link
35
- login_page = a.click(page.link_with(:text => /Log In/))
36
-
37
- # Submit the login form
38
- my_page = login_page.form_with(:action => '/account/login.php') do |f|
39
- f.form_loginname = ARGV[0]
40
- f.form_pw = ARGV[1]
41
- end.click_button
42
-
43
- my_page.links.each do |link|
44
- text = link.text.strip
45
- next unless text.length > 0
46
- puts text
47
- end
48
- end
49
-
50
27
  == File Upload
51
28
 
52
29
  Upload a file to flickr.
@@ -129,7 +106,7 @@ This example also demonstrates subclassing Mechanize.
129
106
 
130
107
  class TestMech < Mechanize
131
108
  def process
132
- get 'http://rubyforge.org/'
109
+ get 'http://rubygems.org/'
133
110
  search_form = page.forms.first
134
111
  search_form.words = 'WWW'
135
112
  submit search_form
data/Gemfile CHANGED
@@ -1,3 +1,3 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
3
  gemspec
@@ -1,5 +1,9 @@
1
1
  (The MIT License)
2
2
 
3
+ Copyright (c) 2005 by Michael Neumann (mneumann@ntecs.de)
4
+
5
+ Copyright (c) 2006-2021 by Eric Hodel, Akinori MUSHA, Aaron Patterson, Lee Jarvis, Mike Dalessio
6
+
3
7
  Permission is hereby granted, free of charge, to any person obtaining
4
8
  a copy of this software and associated documentation files (the
5
9
  'Software'), to deal in the Software without restriction, including
data/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # Mechanize
2
+
3
+ * https://www.rubydoc.info/gems/mechanize/
4
+ * https://github.com/sparklemotion/mechanize
5
+
6
+ [![Test suite](https://github.com/sparklemotion/mechanize/actions/workflows/ci-test.yml/badge.svg)](https://github.com/sparklemotion/mechanize/actions/workflows/ci-test.yml)
7
+
8
+
9
+ ## Description
10
+
11
+ The Mechanize library is used for automating interaction with websites. Mechanize automatically stores and sends cookies, follows redirects, and can follow links and submit forms. Form fields can be populated and submitted. Mechanize also keeps track of the sites that you have visited as a history.
12
+
13
+
14
+ ## Dependencies
15
+
16
+ * Ruby >= 2.5
17
+ * Gems:
18
+ * `addressable`
19
+ * `domain_name`
20
+ * `http-cookie`
21
+ * `mime-types`
22
+ * `net-http-digest_auth`
23
+ * `net-http-persistent`
24
+ * `nokogiri`
25
+ * `rubyntlm`
26
+ * `webrick`
27
+ * `webrobots`
28
+
29
+
30
+ ## Support:
31
+
32
+ The bug tracker is available here:
33
+
34
+ * https://github.com/sparklemotion/mechanize/issues
35
+
36
+
37
+ ## Examples
38
+
39
+ If you are just starting, check out [GUIDE.rdoc](https://github.com/sparklemotion/mechanize/blob/main/GUIDE.rdoc) or [EXAMPLES.rdoc](https://github.com/sparklemotion/mechanize/blob/main/EXAMPLES.rdoc).
40
+
41
+
42
+ ## Developers
43
+
44
+ Use bundler to install dependencies:
45
+
46
+ ```
47
+ bundle install
48
+ ```
49
+
50
+ Run all tests with:
51
+
52
+ ```
53
+ bundle exec rake test
54
+ ```
55
+
56
+ See also Mechanize::TestCase to read about the built-in testing infrastructure.
57
+
58
+
59
+ ## Authors
60
+
61
+ * Eric Hodel
62
+ * Akinori MUSHA
63
+ * Aaron Patterson
64
+ * Lee Jarvis
65
+ * Mike Dalessio
66
+
67
+
68
+ ## Acknowledgments
69
+
70
+ This library was heavily influenced by its namesake in the Perl world. A big
71
+ thanks goes to [Andy Lester](http://petdance.com), the author of the original Perl module WWW::Mechanize which is available [here](http://search.cpan.org/dist/WWW-Mechanize/). Ruby Mechanize would not be around without you!
72
+
73
+ Thank you to Michael Neumann for starting the Ruby version. Thanks to everyone who's helped out in various ways. Finally, thank you to the people using this library!
74
+
75
+ ## License
76
+
77
+ This library is distributed under the MIT license. Please see [LICENSE.txt](https://github.com/sparklemotion/mechanize/blob/main/LICENSE.txt).
data/Rakefile CHANGED
@@ -23,9 +23,9 @@ task('ssl_cert') do |p|
23
23
  end
24
24
 
25
25
  RDoc::Task.new do |rdoc|
26
- rdoc.main = "README.rdoc"
26
+ rdoc.main = "README.md"
27
27
  rdoc.rdoc_dir = 'doc'
28
- rdoc.rdoc_files.include( "CHANGELOG.rdoc", "EXAMPLES.rdoc", "GUIDE.rdoc", "LICENSE.rdoc", "Manifest.txt", "README.rdoc", "lib/**/*.rb")
28
+ rdoc.rdoc_files.include( "CHANGELOG.md", "EXAMPLES.rdoc", "GUIDE.rdoc", "LICENSE.txt", "README.md", "lib/**/*.rb")
29
29
  end
30
30
 
31
31
  desc "Run tests"
@@ -38,4 +38,19 @@ task publish_docs: %w[rdoc] do
38
38
  sh 'rsync', '-avzO', '--delete', 'doc/', 'docs-push.seattlerb.org:/data/www/docs.seattlerb.org/mechanize/'
39
39
  end
40
40
 
41
- task default: :test
41
+ desc "Run rubocop checks"
42
+ task :rubocop => ["rubocop:security", "rubocop:frozen_string_literals"]
43
+
44
+ namespace "rubocop" do
45
+ desc "Run rubocop security check"
46
+ task :security do
47
+ sh "rubocop lib --only Security"
48
+ end
49
+
50
+ desc "Run rubocop string literals check"
51
+ task :frozen_string_literals do
52
+ sh "rubocop lib --auto-correct-all --only Style/FrozenStringLiteralComment"
53
+ end
54
+ end
55
+
56
+ task default: [:rubocop, :test]
data/examples/rubygems.rb CHANGED
@@ -1,4 +1,4 @@
1
- # This example logs a user in to rubyforge and prints out the body of the
1
+ # This example logs a user in to rubygems and prints out the body of the
2
2
  # page after logging the user in.
3
3
  require 'rubygems'
4
4
  require 'mechanize'
@@ -9,7 +9,7 @@ mech = Mechanize.new
9
9
  mech.log = Logger.new $stderr
10
10
  mech.agent.http.debug_output = $stderr
11
11
 
12
- # Load the rubyforge website
12
+ # Load the rubygems website
13
13
  page = mech.get('https://rubygems.org/')
14
14
  page = mech.click page.link_with(:text => /Sign in/) # Click the login link
15
15
  form = page.forms[1] # Select the first form
data/lib/mechanize.rb CHANGED
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'mechanize/version'
2
3
  require 'fileutils'
3
4
  require 'forwardable'
@@ -175,7 +176,7 @@ class Mechanize
175
176
  # as SSL parameters or proxies:
176
177
  #
177
178
  # agent = Mechanize.new do |a|
178
- # a.proxy_host = 'proxy.example'
179
+ # a.proxy_addr = 'proxy.example'
179
180
  # a.proxy_port = 8080
180
181
  # end
181
182
  #
@@ -396,7 +397,7 @@ class Mechanize
396
397
  io = if io_or_filename.respond_to? :write then
397
398
  io_or_filename
398
399
  else
399
- open io_or_filename, 'wb'
400
+ ::File.open(io_or_filename, 'wb')
400
401
  end
401
402
 
402
403
  case page
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # Raised when Mechanize detects the chunked transfer-encoding may be
3
4
  # incorrectly terminated.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # This error is raised when a pluggable parser tries to parse a content type
3
4
  # that it does not know how to handle. For example if Mechanize::Page were to
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  warn 'mechanize/cookie will be deprecated. Please migrate to the http-cookie APIs.' if $VERBOSE
2
3
 
3
4
  require 'http/cookie'
@@ -50,19 +51,6 @@ class Mechanize
50
51
 
51
52
  Cookie = ::HTTP::Cookie
52
53
 
53
- # Compatibility for Ruby 1.8/1.9
54
- unless Cookie.respond_to?(:prepend, true)
55
- require 'mechanize/prependable'
56
-
57
- class Cookie
58
- extend Prependable
59
-
60
- class << self
61
- extend Prependable
62
- end
63
- end
64
- end
65
-
66
54
  class Cookie
67
55
  prepend CookieIMethods
68
56
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  warn 'mechanize/cookie_jar will be deprecated. Please migrate to the http-cookie APIs.' if $VERBOSE
2
3
 
3
4
  require 'http/cookie_jar'
@@ -65,7 +66,7 @@ class Mechanize
65
66
  class CookieJar < ::HTTP::CookieJar
66
67
  def save(output, *options)
67
68
  output.respond_to?(:write) or
68
- return open(output, 'w') { |io| save(io, *options) }
69
+ return ::File.open(output, 'w') { |io| save(io, *options) }
69
70
 
70
71
  opthash = {
71
72
  :format => :yaml,
@@ -119,7 +120,7 @@ class Mechanize
119
120
 
120
121
  def load(input, *options)
121
122
  input.respond_to?(:write) or
122
- return open(input, 'r') { |io| load(io, *options) }
123
+ return ::File.open(input, 'r') { |io| load(io, *options) }
123
124
 
124
125
  opthash = {
125
126
  :format => :yaml,
@@ -148,7 +149,7 @@ class Mechanize
148
149
  return super(input, opthash) if opthash[:format] != :yaml
149
150
 
150
151
  begin
151
- data = YAML.load(input)
152
+ data = YAML.load(input) # rubocop:disable Security/YAMLLoad
152
153
  rescue ArgumentError
153
154
  @logger.warn "unloadable YAML cookie data discarded" if @logger
154
155
  return self
@@ -175,15 +176,6 @@ class Mechanize
175
176
  end
176
177
  end
177
178
 
178
- # Compatibility for Ruby 1.8/1.9
179
- unless ::HTTP::CookieJar.respond_to?(:prepend, true)
180
- require 'mechanize/prependable'
181
-
182
- class ::HTTP::CookieJar
183
- extend Prependable
184
- end
185
- end
186
-
187
179
  class ::HTTP::CookieJar
188
180
  prepend CookieJarIMethods
189
181
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # Unlike Mechanize::FileSaver, the directory saver places all downloaded files
3
4
  # in a single pre-specified directory.
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # Download is a pluggable parser for downloading files without loading them
3
4
  # into memory first. You may subclass this class to handle content types you
@@ -71,7 +72,7 @@ class Mechanize::Download
71
72
  dirname = File.dirname filename
72
73
  FileUtils.mkdir_p dirname
73
74
 
74
- open filename, 'wb' do |io|
75
+ ::File.open(filename, 'wb')do |io|
75
76
  until @body_io.eof? do
76
77
  io.write @body_io.read 16384
77
78
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  module Mechanize::ElementMatcher
2
3
 
3
4
  def elements_with singular, plural = "#{singular}s"
@@ -15,6 +16,9 @@ module Mechanize::ElementMatcher
15
16
  h[:dom_class] = v
16
17
  when :search, :xpath, :css
17
18
  if v
19
+ if method
20
+ warn "multiple search selectors are given; previous selector (\#{method}: \#{selector.inspect}) is ignored."
21
+ end
18
22
  selector = v
19
23
  method = k
20
24
  end
@@ -26,7 +30,7 @@ module Mechanize::ElementMatcher
26
30
 
27
31
  f = select_#{plural}(selector, method).find_all do |thing|
28
32
  criteria.all? do |k,v|
29
- v === thing.send(k)
33
+ v === thing.__send__(k)
30
34
  end
31
35
  end
32
36
  yield f if block_given?
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # Raised when an an element was not found on the Page
3
4
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # This is the base class for the Pluggable Parsers. If Mechanize cannot find
3
4
  # an appropriate class to use for the content type, this class will be used.
@@ -82,7 +83,7 @@ class Mechanize::File
82
83
  dirname = File.dirname filename
83
84
  FileUtils.mkdir_p dirname
84
85
 
85
- open filename, 'wb' do |f|
86
+ ::File.open(filename, 'wb')do |f|
86
87
  f.write body
87
88
  end
88
89
 
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # Wrapper to make a file URI work like an http URI
3
4
 
@@ -10,8 +11,9 @@ class Mechanize::FileConnection
10
11
  end
11
12
 
12
13
  def request uri, request
13
- yield Mechanize::FileResponse.new Mechanize::Util.uri_unescape uri.path
14
+ file_path = uri.select(:host, :path)
15
+ .select { |part| part && (part.length > 0) }
16
+ .join(":")
17
+ yield Mechanize::FileResponse.new(Mechanize::Util.uri_unescape(file_path))
14
18
  end
15
-
16
19
  end
17
-
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # A wrapper for a file URI that makes a request that works like a
3
4
  # Net::HTTPRequest
@@ -1,8 +1,11 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # Fake response for dealing with file:/// requests
3
4
 
4
5
  class Mechanize::FileResponse
5
6
 
7
+ attr_reader :file_path
8
+
6
9
  def initialize(file_path)
7
10
  @file_path = file_path
8
11
  @uri = nil
@@ -15,7 +18,7 @@ class Mechanize::FileResponse
15
18
  if directory?
16
19
  yield dir_body
17
20
  else
18
- open @file_path, 'rb' do |io|
21
+ ::File.open(@file_path, 'rb') do |io|
19
22
  yield io.read
20
23
  end
21
24
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  ##
2
3
  # This is a pluggable parser that automatically saves every file it
3
4
  # encounters. Unlike Mechanize::DirectorySaver, the file saver saves the
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
  require 'mechanize/element_matcher'
2
3
 
3
4
  # This class encapsulates a form parsed out of an HTML page. Each type of
@@ -16,7 +17,7 @@ require 'mechanize/element_matcher'
16
17
  # puts form['name']
17
18
 
18
19
  class Mechanize::Form
19
-
20
+ extend Forwardable
20
21
  extend Mechanize::ElementMatcher
21
22
 
22
23
  attr_accessor :method, :action, :name
@@ -35,12 +36,13 @@ class Mechanize::Form
35
36
 
36
37
  alias :elements :fields
37
38
 
38
- attr_reader :form_node
39
+ attr_reader :node
40
+ alias form_node node # for backward compatibility
39
41
  attr_reader :page
40
42
 
41
43
  def initialize(node, mech = nil, page = nil)
42
44
  @enctype = node['enctype'] || 'application/x-www-form-urlencoded'
43
- @form_node = node
45
+ @node = node
44
46
  @action = Mechanize::Util.html_unescape(node['action'])
45
47
  @method = (node['method'] || 'GET').upcase
46
48
  @name = node['name']
@@ -55,24 +57,24 @@ class Mechanize::Form
55
57
 
56
58
  # Returns whether or not the form contains a field with +field_name+
57
59
  def has_field?(field_name)
58
- fields.find { |f| f.name == field_name }
60
+ fields.any? { |f| f.name == field_name }
59
61
  end
60
62
 
61
63
  alias :has_key? :has_field?
62
64
 
63
65
  # Returns whether or not the form contains a field with +value+
64
66
  def has_value?(value)
65
- fields.find { |f| f.value == value }
67
+ fields.any? { |f| f.value == value }
66
68
  end
67
69
 
68
70
  # Returns all field names (keys) for this form
69
71
  def keys
70
- fields.map { |f| f.name }
72
+ fields.map(&:name)
71
73
  end
72
74
 
73
75
  # Returns all field values for this form
74
76
  def values
75
- fields.map { |f| f.value }
77
+ fields.map(&:value)
76
78
  end
77
79
 
78
80
  # Returns all buttons of type Submit
@@ -136,7 +138,7 @@ class Mechanize::Form
136
138
  # Note that you can also use +:id+ to get to this method:
137
139
  # page.form_with(:id => "foorm")
138
140
  def dom_id
139
- form_node['id']
141
+ @node['id']
140
142
  end
141
143
 
142
144
  # This method is a shortcut to get form's DOM class.
@@ -144,10 +146,57 @@ class Mechanize::Form
144
146
  # page.form_with(:dom_class => "foorm")
145
147
  # Note that you can also use +:class+ to get to this method:
146
148
  # page.form_with(:class => "foorm")
149
+ # However, attribute values are compared literally as string, so
150
+ # form_with(class: "a") does not match a form with class="a b".
151
+ # Use form_with(css: "form.a") instead.
147
152
  def dom_class
148
- form_node['class']
153
+ @node['class']
149
154
  end
150
155
 
156
+ ##
157
+ # :method: search
158
+ #
159
+ # Shorthand for +node.search+.
160
+ #
161
+ # See Nokogiri::XML::Node#search for details.
162
+
163
+ ##
164
+ # :method: css
165
+ #
166
+ # Shorthand for +node.css+.
167
+ #
168
+ # See also Nokogiri::XML::Node#css for details.
169
+
170
+ ##
171
+ # :method: xpath
172
+ #
173
+ # Shorthand for +node.xpath+.
174
+ #
175
+ # See also Nokogiri::XML::Node#xpath for details.
176
+
177
+ ##
178
+ # :method: at
179
+ #
180
+ # Shorthand for +node.at+.
181
+ #
182
+ # See also Nokogiri::XML::Node#at for details.
183
+
184
+ ##
185
+ # :method: at_css
186
+ #
187
+ # Shorthand for +node.at_css+.
188
+ #
189
+ # See also Nokogiri::XML::Node#at_css for details.
190
+
191
+ ##
192
+ # :method: at_xpath
193
+ #
194
+ # Shorthand for +node.at_xpath+.
195
+ #
196
+ # See also Nokogiri::XML::Node#at_xpath for details.
197
+
198
+ def_delegators :node, :search, :css, :xpath, :at, :at_css, :at_xpath
199
+
151
200
  # Add a field with +field_name+ and +value+
152
201
  def add_field!(field_name, value = nil)
153
202
  fields << Field.new({'name' => field_name}, value)
@@ -207,7 +256,7 @@ class Mechanize::Form
207
256
 
208
257
  # Treat form fields like accessors.
209
258
  def method_missing(meth, *args)
210
- method = meth.to_s.gsub(/=$/, '')
259
+ (method = meth.to_s).chomp!('=')
211
260
 
212
261
  if field(method)
213
262
  return field(method).value if args.empty?
@@ -257,7 +306,7 @@ class Mechanize::Form
257
306
  successful_controls = []
258
307
 
259
308
  (fields + checkboxes).reject do |f|
260
- f.node["disabled"]
309
+ f.node["disabled"] || f.node["name"] == ""
261
310
  end.sort.each do |f|
262
311
  case f
263
312
  when Mechanize::Form::CheckBox
@@ -278,10 +327,10 @@ class Mechanize::Form
278
327
 
279
328
  # take one radio button from each group
280
329
  radio_groups.each_value do |g|
281
- checked = g.select {|f| f.checked}
330
+ checked = g.select(&:checked)
282
331
 
283
332
  if checked.uniq.size > 1 then
284
- values = checked.map { |button| button.value }.join(', ').inspect
333
+ values = checked.map(&:value).join(', ').inspect
285
334
  name = checked.first.name.inspect
286
335
  raise Mechanize::Error,
287
336
  "radiobuttons #{values} are checked in the #{name} group, " \
@@ -319,7 +368,7 @@ class Mechanize::Form
319
368
  # This method adds a button to the query. If the form needs to be
320
369
  # submitted with multiple buttons, pass each button to this method.
321
370
  def add_button_to_query(button)
322
- unless button.node.document == @form_node.document then
371
+ unless button.node.document == @node.document then
323
372
  message =
324
373
  "#{button.inspect} does not belong to the same page as " \
325
374
  "the form #{@name.inspect} in #{@page.uri}"
@@ -337,6 +386,8 @@ class Mechanize::Form
337
386
  @clicked_buttons = []
338
387
  end
339
388
 
389
+ CRLF = "\r\n".freeze
390
+
340
391
  # This method calculates the request data to be sent back to the server
341
392
  # for this form, depending on if this is a regular post, get, or a
342
393
  # multi-part post,
@@ -348,16 +399,23 @@ class Mechanize::Form
348
399
  boundary = rand_string(20)
349
400
  @enctype = "multipart/form-data; boundary=#{boundary}"
350
401
 
351
- params = query_params.map do |k,v|
352
- param_to_multipart(k, v) if k
353
- end.compact
402
+ delimiter = "--#{boundary}\r\n"
354
403
 
355
- params.concat @file_uploads.map { |f| file_to_multipart(f) }
404
+ data = ::String.new
356
405
 
357
- params.map do |part|
358
- "--#{boundary}\r\n#{part.force_encoding(Encoding::ASCII_8BIT)}"
359
- end.join('') +
360
- "--#{boundary}--\r\n"
406
+ query_params.each do |k,v|
407
+ if k
408
+ data << delimiter
409
+ param_to_multipart(k, v, data)
410
+ end
411
+ end
412
+
413
+ @file_uploads.each do |f|
414
+ data << delimiter
415
+ file_to_multipart(f, data)
416
+ end
417
+
418
+ data << "--#{boundary}--\r\n"
361
419
  else
362
420
  Mechanize::Util.build_query_string(query_params)
363
421
  end
@@ -526,7 +584,7 @@ class Mechanize::Form
526
584
  @checkboxes = []
527
585
 
528
586
  # Find all input tags
529
- form_node.search('input').each do |node|
587
+ @node.search('input').each do |node|
530
588
  type = (node['type'] || 'text').downcase
531
589
  name = node['name']
532
590
  next if name.nil? && !%w[submit button image].include?(type)
@@ -557,13 +615,13 @@ class Mechanize::Form
557
615
  end
558
616
 
559
617
  # Find all textarea tags
560
- form_node.search('textarea').each do |node|
618
+ @node.search('textarea').each do |node|
561
619
  next unless node['name']
562
620
  @fields << Textarea.new(node, node.inner_text)
563
621
  end
564
622
 
565
623
  # Find all select tags
566
- form_node.search('select').each do |node|
624
+ @node.search('select').each do |node|
567
625
  next unless node['name']
568
626
  if node.has_attribute? 'multiple'
569
627
  @fields << MultiSelectList.new(node)
@@ -574,61 +632,70 @@ class Mechanize::Form
574
632
 
575
633
  # Find all submit button tags
576
634
  # FIXME: what can I do with the reset buttons?
577
- form_node.search('button').each do |node|
635
+ @node.search('button').each do |node|
578
636
  type = (node['type'] || 'submit').downcase
579
637
  next if type == 'reset'
580
638
  @buttons << Button.new(node)
581
639
  end
582
640
 
583
641
  # Find all keygen tags
584
- form_node.search('keygen').each do |node|
642
+ @node.search('keygen').each do |node|
585
643
  @fields << Keygen.new(node, node['value'] || '')
586
644
  end
587
645
  end
588
646
 
589
647
  def rand_string(len = 10)
590
648
  chars = ("a".."z").to_a + ("A".."Z").to_a
591
- string = ""
649
+ string = ::String.new
592
650
  1.upto(len) { |i| string << chars[rand(chars.size-1)] }
593
651
  string
594
652
  end
595
653
 
596
654
  def mime_value_quote(str)
597
- str.gsub(/(["\r\\])/){|s| '\\' + s}
655
+ str.b.gsub(/(["\r\\])/, '\\\\\1')
598
656
  end
599
657
 
600
- def param_to_multipart(name, value)
601
- return "Content-Disposition: form-data; name=\"" +
602
- "#{mime_value_quote(name)}\"\r\n" +
603
- "\r\n#{value}\r\n"
658
+ def param_to_multipart(name, value, buf = ::String.new)
659
+ buf <<
660
+ "Content-Disposition: form-data; name=\"".freeze <<
661
+ mime_value_quote(name) <<
662
+ "\"\r\n\r\n".freeze <<
663
+ value.b <<
664
+ CRLF
604
665
  end
605
666
 
606
- def file_to_multipart(file)
667
+ def file_to_multipart(file, buf = ::String.new)
607
668
  file_name = file.file_name ? ::File.basename(file.file_name) : ''
608
- body = "Content-Disposition: form-data; name=\"" +
609
- "#{mime_value_quote(file.name)}\"; " +
610
- "filename=\"#{mime_value_quote(file_name)}\"\r\n" +
611
- "Content-Transfer-Encoding: binary\r\n"
669
+
670
+ body = buf <<
671
+ "Content-Disposition: form-data; name=\"".freeze <<
672
+ mime_value_quote(file.name) <<
673
+ "\"; filename=\"".freeze <<
674
+ mime_value_quote(file_name) <<
675
+ "\"\r\nContent-Transfer-Encoding: binary\r\n".freeze
612
676
 
613
677
  if file.file_data.nil? and file.file_name
614
- file.file_data = open(file.file_name, "rb") { |f| f.read }
678
+ file.file_data = File.binread(file.file_name)
615
679
  file.mime_type =
616
680
  WEBrick::HTTPUtils.mime_type(file.file_name,
617
681
  WEBrick::HTTPUtils::DefaultMimeTypes)
618
682
  end
619
683
 
620
684
  if file.mime_type
621
- body << "Content-Type: #{file.mime_type}\r\n"
685
+ body << "Content-Type: ".freeze << file.mime_type << CRLF
622
686
  end
623
687
 
624
- body <<
625
- if file.file_data.respond_to? :read
626
- "\r\n#{file.file_data.read}\r\n"
688
+ body << CRLF
689
+
690
+ if file_data = file.file_data
691
+ if file_data.respond_to? :read
692
+ body << file_data.read.force_encoding(Encoding::ASCII_8BIT)
627
693
  else
628
- "\r\n#{file.file_data}\r\n"
694
+ body << file_data.b
629
695
  end
696
+ end
630
697
 
631
- body
698
+ body << CRLF
632
699
  end
633
700
  end
634
701