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.
- checksums.yaml +5 -5
- data/.github/workflows/ci-test.yml +45 -0
- data/.yardopts +8 -0
- data/{CHANGELOG.rdoc → CHANGELOG.md} +151 -86
- data/EXAMPLES.rdoc +1 -24
- data/Gemfile +1 -1
- data/{LICENSE.rdoc → LICENSE.txt} +4 -0
- data/README.md +77 -0
- data/Rakefile +18 -3
- data/examples/rubygems.rb +2 -2
- data/lib/mechanize.rb +3 -2
- data/lib/mechanize/chunked_termination_error.rb +1 -0
- data/lib/mechanize/content_type_error.rb +1 -0
- data/lib/mechanize/cookie.rb +1 -13
- data/lib/mechanize/cookie_jar.rb +4 -12
- data/lib/mechanize/directory_saver.rb +1 -0
- data/lib/mechanize/download.rb +2 -1
- data/lib/mechanize/element_matcher.rb +5 -1
- data/lib/mechanize/element_not_found_error.rb +1 -0
- data/lib/mechanize/file.rb +2 -1
- data/lib/mechanize/file_connection.rb +5 -3
- data/lib/mechanize/file_request.rb +1 -0
- data/lib/mechanize/file_response.rb +4 -1
- data/lib/mechanize/file_saver.rb +1 -0
- data/lib/mechanize/form.rb +112 -45
- data/lib/mechanize/form/button.rb +1 -0
- data/lib/mechanize/form/check_box.rb +1 -0
- data/lib/mechanize/form/field.rb +47 -0
- data/lib/mechanize/form/file_upload.rb +1 -0
- data/lib/mechanize/form/hidden.rb +1 -0
- data/lib/mechanize/form/image_button.rb +1 -0
- data/lib/mechanize/form/keygen.rb +1 -0
- data/lib/mechanize/form/multi_select_list.rb +8 -14
- data/lib/mechanize/form/option.rb +3 -1
- data/lib/mechanize/form/radio_button.rb +1 -0
- data/lib/mechanize/form/reset.rb +1 -0
- data/lib/mechanize/form/select_list.rb +1 -0
- data/lib/mechanize/form/submit.rb +1 -0
- data/lib/mechanize/form/text.rb +1 -0
- data/lib/mechanize/form/textarea.rb +1 -0
- data/lib/mechanize/headers.rb +1 -0
- data/lib/mechanize/history.rb +2 -1
- data/lib/mechanize/http.rb +1 -0
- data/lib/mechanize/http/agent.rb +81 -38
- data/lib/mechanize/http/auth_challenge.rb +1 -0
- data/lib/mechanize/http/auth_realm.rb +2 -1
- data/lib/mechanize/http/auth_store.rb +1 -0
- data/lib/mechanize/http/content_disposition_parser.rb +18 -3
- data/lib/mechanize/http/www_authenticate_parser.rb +4 -4
- data/lib/mechanize/image.rb +1 -0
- data/lib/mechanize/page.rb +8 -5
- data/lib/mechanize/page/base.rb +1 -0
- data/lib/mechanize/page/frame.rb +4 -1
- data/lib/mechanize/page/image.rb +1 -0
- data/lib/mechanize/page/label.rb +1 -0
- data/lib/mechanize/page/link.rb +8 -1
- data/lib/mechanize/page/meta_refresh.rb +1 -0
- data/lib/mechanize/parser.rb +4 -3
- data/lib/mechanize/pluggable_parsers.rb +1 -0
- data/lib/mechanize/prependable.rb +1 -0
- data/lib/mechanize/redirect_limit_reached_error.rb +1 -0
- data/lib/mechanize/redirect_not_get_or_head_error.rb +1 -0
- data/lib/mechanize/response_code_error.rb +2 -1
- data/lib/mechanize/response_read_error.rb +1 -0
- data/lib/mechanize/robots_disallowed_error.rb +1 -0
- data/lib/mechanize/test_case.rb +34 -29
- data/lib/mechanize/test_case/bad_chunking_servlet.rb +1 -0
- data/lib/mechanize/test_case/basic_auth_servlet.rb +1 -0
- data/lib/mechanize/test_case/content_type_servlet.rb +1 -0
- data/lib/mechanize/test_case/digest_auth_servlet.rb +1 -0
- data/lib/mechanize/test_case/file_upload_servlet.rb +1 -0
- data/lib/mechanize/test_case/form_servlet.rb +1 -0
- data/lib/mechanize/test_case/gzip_servlet.rb +4 -3
- data/lib/mechanize/test_case/header_servlet.rb +1 -0
- data/lib/mechanize/test_case/http_refresh_servlet.rb +2 -2
- data/lib/mechanize/test_case/infinite_redirect_servlet.rb +1 -0
- data/lib/mechanize/test_case/infinite_refresh_servlet.rb +2 -2
- data/lib/mechanize/test_case/many_cookies_as_string_servlet.rb +1 -0
- data/lib/mechanize/test_case/many_cookies_servlet.rb +1 -0
- data/lib/mechanize/test_case/modified_since_servlet.rb +1 -0
- data/lib/mechanize/test_case/ntlm_servlet.rb +1 -0
- data/lib/mechanize/test_case/one_cookie_no_spaces_servlet.rb +1 -0
- data/lib/mechanize/test_case/one_cookie_servlet.rb +1 -0
- data/lib/mechanize/test_case/quoted_value_cookie_servlet.rb +1 -0
- data/lib/mechanize/test_case/redirect_servlet.rb +1 -0
- data/lib/mechanize/test_case/referer_servlet.rb +1 -0
- data/lib/mechanize/test_case/refresh_with_empty_url.rb +1 -0
- data/lib/mechanize/test_case/refresh_without_url.rb +1 -0
- data/lib/mechanize/test_case/response_code_servlet.rb +1 -0
- data/lib/mechanize/test_case/robots_txt_servlet.rb +15 -0
- data/lib/mechanize/test_case/send_cookies_servlet.rb +1 -0
- data/lib/mechanize/test_case/server.rb +1 -0
- data/lib/mechanize/test_case/servlets.rb +4 -0
- data/lib/mechanize/test_case/verb_servlet.rb +5 -6
- data/lib/mechanize/unauthorized_error.rb +2 -1
- data/lib/mechanize/unsupported_scheme_error.rb +1 -0
- data/lib/mechanize/util.rb +5 -3
- data/lib/mechanize/version.rb +2 -1
- data/lib/mechanize/xml_file.rb +1 -0
- data/mechanize.gemspec +39 -31
- data/test/htdocs/dir with spaces/foo.html +1 -0
- data/test/htdocs/find_link.html +1 -4
- data/test/htdocs/tc_links.html +1 -1
- data/test/test_mechanize.rb +57 -15
- data/test/test_mechanize_cookie.rb +75 -60
- data/test/test_mechanize_cookie_jar.rb +112 -59
- data/test/test_mechanize_download.rb +13 -1
- data/test/test_mechanize_file.rb +10 -0
- data/test/test_mechanize_file_connection.rb +21 -3
- data/test/test_mechanize_file_response.rb +26 -2
- data/test/test_mechanize_form.rb +27 -11
- data/test/test_mechanize_form_check_box.rb +10 -0
- data/test/test_mechanize_form_encoding.rb +1 -1
- data/test/test_mechanize_form_keygen.rb +1 -0
- data/test/test_mechanize_form_multi_select_list.rb +5 -1
- data/test/test_mechanize_http_agent.rb +116 -8
- data/test/test_mechanize_http_auth_challenge.rb +14 -0
- data/test/test_mechanize_http_auth_realm.rb +7 -1
- data/test/test_mechanize_http_auth_store.rb +37 -0
- data/test/test_mechanize_http_content_disposition_parser.rb +35 -1
- data/test/test_mechanize_http_www_authenticate_parser.rb +16 -0
- data/test/test_mechanize_link.rb +47 -4
- data/test/test_mechanize_page.rb +29 -1
- data/test/test_mechanize_page_encoding.rb +23 -1
- data/test/test_mechanize_page_image.rb +1 -1
- data/test/test_mechanize_page_link.rb +3 -3
- data/test/test_mechanize_page_meta_refresh.rb +1 -1
- data/test/test_mechanize_parser.rb +12 -2
- data/test/test_mechanize_util.rb +1 -1
- metadata +105 -81
- data/.travis.yml +0 -25
- data/Manifest.txt +0 -204
- data/README.rdoc +0 -77
- 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://
|
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,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.
|
26
|
+
rdoc.main = "README.md"
|
27
27
|
rdoc.rdoc_dir = 'doc'
|
28
|
-
rdoc.rdoc_files.include( "CHANGELOG.
|
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
|
-
|
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
|
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
|
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.
|
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
|
400
|
+
::File.open(io_or_filename, 'wb')
|
400
401
|
end
|
401
402
|
|
402
403
|
case page
|
data/lib/mechanize/cookie.rb
CHANGED
@@ -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
|
|
data/lib/mechanize/cookie_jar.rb
CHANGED
@@ -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
|
data/lib/mechanize/download.rb
CHANGED
@@ -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
|
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.
|
33
|
+
v === thing.__send__(k)
|
30
34
|
end
|
31
35
|
end
|
32
36
|
yield f if block_given?
|
data/lib/mechanize/file.rb
CHANGED
@@ -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
|
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
|
-
|
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,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
|
21
|
+
::File.open(@file_path, 'rb') do |io|
|
19
22
|
yield io.read
|
20
23
|
end
|
21
24
|
end
|
data/lib/mechanize/file_saver.rb
CHANGED
data/lib/mechanize/form.rb
CHANGED
@@ -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 :
|
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
|
-
@
|
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.
|
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.
|
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
|
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
|
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
|
-
|
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
|
-
|
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.
|
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
|
330
|
+
checked = g.select(&:checked)
|
282
331
|
|
283
332
|
if checked.uniq.size > 1 then
|
284
|
-
values = checked.map
|
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 == @
|
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
|
-
|
352
|
-
param_to_multipart(k, v) if k
|
353
|
-
end.compact
|
402
|
+
delimiter = "--#{boundary}\r\n"
|
354
403
|
|
355
|
-
|
404
|
+
data = ::String.new
|
356
405
|
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
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\\])
|
655
|
+
str.b.gsub(/(["\r\\])/, '\\\\\1')
|
598
656
|
end
|
599
657
|
|
600
|
-
def param_to_multipart(name, value)
|
601
|
-
|
602
|
-
"
|
603
|
-
|
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
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
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 =
|
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:
|
685
|
+
body << "Content-Type: ".freeze << file.mime_type << CRLF
|
622
686
|
end
|
623
687
|
|
624
|
-
body <<
|
625
|
-
|
626
|
-
|
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
|
-
|
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
|
|